From 18105ff6db13929566061fe4bc9416c8e3cdf4fd Mon Sep 17 00:00:00 2001 From: claude_code Date: Sun, 21 Jun 2026 05:01:07 +0300 Subject: [PATCH] feat(share-ai): label public chat with the assistant identity name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The anonymous public-share "Ask AI" chat labeled every assistant turn with the generic "AI agent" even when an Assistant identity (agent role) was configured. Surface the configured identity name instead, falling back to "AI agent" when no identity is set. - server: AiSettingsService.resolvePublicShareAssistantName resolves the configured role's name (null when unset/missing/disabled), mirroring PublicShareChatService.resolveShareRole; ShareController returns it as aiAssistantName on /shares/page-info (only when the assistant is on). - client: thread aiAssistantName -> ShareAiWidget -> MessageList -> MessageItem/TypingIndicator via an optional assistantName prop; the internal chat omits it and keeps showing "AI agent". - i18n: add "{{name}} is typing…" (en-US, ru-RU) for the typing line. Co-Authored-By: Claude Opus 4.8 --- .../public/locales/en-US/translation.json | 1 + .../public/locales/ru-RU/translation.json | 1 + .../ai-chat/components/message-item.tsx | 8 ++++- .../ai-chat/components/message-list.tsx | 10 ++++++- .../ai-chat/components/typing-indicator.tsx | 29 +++++++++++++------ .../share/components/share-ai-widget.tsx | 9 +++++- .../src/features/share/types/share.types.ts | 4 +++ apps/client/src/pages/share/shared-page.tsx | 6 +++- 8 files changed, 55 insertions(+), 13 deletions(-) diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 7d4dbc79..3330f641 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -1143,6 +1143,7 @@ "Current context size": "Current context size", "AI agent": "AI agent", "AI agent is typing…": "AI agent is typing…", + "{{name}} is typing…": "{{name}} is typing…", "Send": "Send", "Stop": "Stop", "Chat menu": "Chat menu", diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 414e75b8..233ced69 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -666,6 +666,7 @@ "AI search": "Поиск ИИ", "AI Answer": "Ответ ИИ", "Ask AI": "Спросить ИИ", + "{{name}} is typing…": "{{name}} печатает…", "AI is thinking...": "ИИ обрабатывает запрос...", "Thinking": "Думаю", "Ask a question...": "Задайте вопрос...", diff --git a/apps/client/src/features/ai-chat/components/message-item.tsx b/apps/client/src/features/ai-chat/components/message-item.tsx index e8709d5c..a0c37089 100644 --- a/apps/client/src/features/ai-chat/components/message-item.tsx +++ b/apps/client/src/features/ai-chat/components/message-item.tsx @@ -22,6 +22,11 @@ interface MessageItemProps { * UUIDs/routes in the assistant's markdown don't leak as clickable links. */ neutralizeInternalLinks?: boolean; + /** + * Display name for the dimmed assistant label. Defaults to "AI agent" when + * absent; the public share passes the configured identity (agent role) name. + */ + assistantName?: string; } /** @@ -40,6 +45,7 @@ export default function MessageItem({ message, showCitations = true, neutralizeInternalLinks = false, + assistantName, }: MessageItemProps) { const { t } = useTranslation(); const isUser = message.role === "user"; @@ -61,7 +67,7 @@ export default function MessageItem({ return ( - {t("AI agent")} + {assistantName?.trim() || t("AI agent")} {message.parts.map((part, index) => { if (part.type === "text") { diff --git a/apps/client/src/features/ai-chat/components/message-list.tsx b/apps/client/src/features/ai-chat/components/message-list.tsx index 3d9c5024..b4b4101d 100644 --- a/apps/client/src/features/ai-chat/components/message-list.tsx +++ b/apps/client/src/features/ai-chat/components/message-list.tsx @@ -30,6 +30,12 @@ interface MessageListProps { * UUIDs/routes don't leak as clickable links to anonymous readers. */ neutralizeInternalLinks?: boolean; + /** + * Display name for the assistant's dimmed row label and typing indicator. + * Defaults to "AI agent" when absent. The public share passes the configured + * identity (agent role) name; the internal chat omits it. + */ + assistantName?: string; } // Distance (px) from the bottom within which the viewport still counts as @@ -67,6 +73,7 @@ export default function MessageList({ emptyState, showCitations = true, neutralizeInternalLinks = false, + assistantName, }: MessageListProps) { const { t } = useTranslation(); const viewportRef = useRef(null); @@ -148,9 +155,10 @@ export default function MessageList({ message={message} showCitations={showCitations} neutralizeInternalLinks={neutralizeInternalLinks} + assistantName={assistantName} /> ))} - {typing && } + {typing && } ); diff --git a/apps/client/src/features/ai-chat/components/typing-indicator.tsx b/apps/client/src/features/ai-chat/components/typing-indicator.tsx index 443fe1e1..ba6c6db0 100644 --- a/apps/client/src/features/ai-chat/components/typing-indicator.tsx +++ b/apps/client/src/features/ai-chat/components/typing-indicator.tsx @@ -2,22 +2,33 @@ import { Box, Group, Text } from "@mantine/core"; import { useTranslation } from "react-i18next"; import classes from "@/features/ai-chat/components/ai-chat.module.css"; +interface TypingIndicatorProps { + /** + * Display name for the dimmed label and the "… is typing…" line. Defaults to + * "AI agent" when absent; the public share passes the configured identity + * (agent role) name. + */ + assistantName?: string; +} + /** - * Live "AI agent is typing…" placeholder shown while a turn is in flight but the - * latest assistant message has no visible content yet (no rendered text/tool - * parts). It covers the gap between sending and the first streamed token, and is - * replaced by the real assistant message once content starts arriving. + * Live "… is typing…" placeholder shown while a turn is in flight but the latest + * assistant message has no visible content yet (no rendered text/tool parts). It + * covers the gap between sending and the first streamed token, and is replaced by + * the real assistant message once content starts arriving. * - * Mirrors the assistant row layout in MessageItem (the dimmed "AI agent" label), - * so it reads as the assistant's bubble taking shape. + * Mirrors the assistant row layout in MessageItem (the dimmed label), so it reads + * as the assistant's bubble taking shape. The label and typing line use the + * configured identity name when provided, otherwise the generic "AI agent". */ -export default function TypingIndicator() { +export default function TypingIndicator({ assistantName }: TypingIndicatorProps) { const { t } = useTranslation(); + const name = assistantName?.trim(); return ( - {t("AI agent")} + {name || t("AI agent")} diff --git a/apps/client/src/features/share/components/share-ai-widget.tsx b/apps/client/src/features/share/components/share-ai-widget.tsx index 5212e2c4..a75ad5fe 100644 --- a/apps/client/src/features/share/components/share-ai-widget.tsx +++ b/apps/client/src/features/share/components/share-ai-widget.tsx @@ -27,6 +27,8 @@ interface ShareAiWidgetProps { shareId: string; /** The page the reader currently has open (context for "this page"). */ pageId: string; + /** Display name of the configured assistant identity; falls back to 'AI agent' when absent. */ + assistantName?: string; } /** @@ -47,7 +49,11 @@ interface ShareAiWidgetProps { * links (so internal UUIDs/auth-gated routes in the answer don't leak as * clickable links), and a documentation-focused empty state. */ -export default function ShareAiWidget({ shareId, pageId }: ShareAiWidgetProps) { +export default function ShareAiWidget({ + shareId, + pageId, + assistantName, +}: ShareAiWidgetProps) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [input, setInput] = useState(""); @@ -152,6 +158,7 @@ export default function ShareAiWidget({ shareId, pageId }: ShareAiWidgetProps) { + )} );