feat(chat): stamp each user message with the persona that sent it - #384
Merged
Conversation
A user message now records `personaId` + `personaName` at send time, and the chat renders the persona it was sent as instead of whichever one is active: - `ChatNotifier._sendMessage` stamps the effective persona onto the message. - `ChatMessageMapper` resolves the stored id against a live roster (`ChatBridgeController.setPersonaRoster`, fed from `personaListProvider`): an existing persona contributes its current name and avatar, so renaming it renames its own past messages; a deleted one leaves the message with the name stored on it and `avatarFallback`, which drops the avatar to its letter. - The renderer stamps `data-avatar-pinned` on such a message and `setIdentity` skips pinned avatars, so switching persona no longer re-faces the history. - Messages sent before this change carry no persona and keep following the active identity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018SXpk5JW6Ext697GbCmZvr
- `exportChatAsJsonl` writes `msg.personaName` into the SillyTavern `name` field of a user message, falling back to the caller's `userName` only for messages that carry no persona. - `ChatActionsService.exportChat` passes the chat's effective persona as that fallback, so legacy messages are no longer all named the literal "User". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018SXpk5JW6Ext697GbCmZvr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A user message now records the persona it was sent as — the id and the name — and the chat renders that persona instead of whichever one happens to be active. Switching persona no longer re-faces the whole history, and a message from a deleted persona keeps its name while its avatar drops to the initial letter.
Chat rendering
ChatNotifier._sendMessagestamps the effective persona (personaId+personaName) onto every sent user message. Both fields already existed onChatMessage; nothing wrote them.ChatMessageMapper.toMapresolves the storedpersonaIdagainst a live roster (ChatMessageMapperContext.personasById): a persona that still exists contributes its current name and avatar, so renaming a persona renames the messages it sent; one that is gone (or has no picture) yieldsavatarFallback: true, and the message falls back to the name stored on it plus a letter avatar.ChatBridgeController.setPersonaRosterbuilds that roster once per change, resolving each avatar path to a WebView URL there rather than per message.message_renderer.jsstampsdata-avatar-pinnedon a message that names its own persona, and_refreshIdentityDom(bridge/chat_bridge_controller.jsand thebridge.legacy.jssnapshot) skips pinned avatars — an identity push must not hand a message somebody else's face.ChatWebViewBuildListeners._listenPersonaRosterre-pushes the roster and re-renders (scroll preserved) when a persona is renamed, deleted, or given a new avatar;ChatWebViewInitializerseeds the roster before the firstsetMessagesso a chat never opens with letter avatars first.Chat export
exportChatAsJsonlwritesmsg.personaNameinto the SillyTavernnamefield of a user message, falling back to the caller'suserNameonly for messages that carry none.ChatActionsService.exportChatpasses the chat's effective persona as that fallback, so legacy messages are no longer all named the literalUser.Docs
docs/rules/message-rendering.mdgains the rule the pin exists to protect, so a later refactor does not re-clobber avatars on identity refresh.Verification
Run with Flutter 3.44.9 (SDK installed in the session):
flutter analyze --no-fatal-infos --no-fatal-warnings— no errors; the 7 remaining infos/warnings are pre-existing and untouched by this diff.flutter test— 3782 tests, all passing (after generatinglocale_keys.g.dartthe way CI does).cd test/webview_js && npx playwright test— 73 passing in headless Chromium, including the 4 new cases inspecs/message_persona.spec.js.New tests:
test/chat_message_persona_test.dart(mapper: live persona, rename, deletion, avatar-less persona, legacy message, assistant message),test/chat_export_persona_name_test.dart(export naming),test/webview_js/specs/message_persona.spec.js(rendered DOM), plus two contract assertions intest/webview_assets_test.dart.Not run: the app itself — no runtime/hot-reload check was possible in this environment.
🤖 Generated with Claude Code
https://claude.ai/code/session_018SXpk5JW6Ext697GbCmZvr
Generated by Claude Code