fix(web): 来源小字按消息角色区分语义——用户'来自',她'发送至'
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- SourceLabel 加 role: user 消息显示'来自 <deviceId>'(她收到,来自主人端), assistant 消息显示'发送至 <deviceId>'(她回复到的端)
- 修复: 之前她发的消息也标'来自 web:chrome:...' 语义错误,不是她来自用户设备
- 4 处调用点传 message.role; 前端 build 绿
This commit is contained in:
Aska committed 2026-09-07 15:07:06 +08:00
1 parent 1b0b5bf99c
commit aeb9fea64c
1 file changed
+9 -6
+9 -6
View File
@@ -120,11 +120,14 @@ function statusLabel(status: ToolStatus): string {
* concrete client the message came from, e.g. "来自 web:chrome:a1b2c3". Rendered * concrete client the message came from, e.g. "来自 web:chrome:a1b2c3". Rendered
* only when a deviceId is present, so legacy/unlabelled messages stay clean. The * only when a deviceId is present, so legacy/unlabelled messages stay clean. The
* Web client defaults to this label; other clients decide themselves. */ * Web client defaults to this label; other clients decide themselves. */
function SourceLabel({ deviceId }: { deviceId?: string }): React.ReactNode { function SourceLabel({ deviceId, role }: { deviceId?: string; role?: string }): React.ReactNode {
if (!deviceId) return null; if (!deviceId) return null;
// A user message is received FROM a client; an assistant reply goes TO the
// client that prompted it. Semantic direction differs — "来自" vs "发送至".
const dir = role === "assistant" ? "发送至" : "来自";
return ( return (
<div className="text-[10px] leading-tight text-[var(--mu-text-lo)]"> <div className="text-[10px] leading-tight text-[var(--mu-text-lo)]">
来自 {deviceId} {dir} {deviceId}
</div> </div>
); );
} }
@@ -213,7 +216,7 @@ function AssistantParaBubble({
{full && message.mood ? ( {full && message.mood ? (
<div className="mb-1 text-xs text-[var(--mu-text-lo)]">心情 · {message.mood}</div> <div className="mb-1 text-xs text-[var(--mu-text-lo)]">心情 · {message.mood}</div>
) : null} ) : null}
{full ? <SourceLabel deviceId={message.deviceId} /> : null} {full ? <SourceLabel deviceId={message.deviceId} role={message.role} /> : null}
{full ? <WakePath active={active} /> : null} {full ? <WakePath active={active} /> : null}
{full ? <ThinkingBlock text={message.thinking} streaming={message.streaming} /> : null} {full ? <ThinkingBlock text={message.thinking} streaming={message.streaming} /> : null}
{full ? message.tools?.map((t, i) => <ToolCard key={i} tool={t} />) : null} {full ? message.tools?.map((t, i) => <ToolCard key={i} tool={t} />) : null}
@@ -245,7 +248,7 @@ function UserParaBubble({
) : null} ) : null}
<span className="whitespace-pre-wrap">{renderContent(text)}</span> <span className="whitespace-pre-wrap">{renderContent(text)}</span>
</div> </div>
{full ? <SourceLabel deviceId={message.deviceId} /> : null} {full ? <SourceLabel deviceId={message.deviceId} role={message.role} /> : null}
</div> </div>
); );
} }
@@ -319,7 +322,7 @@ function UserBubble({ message, mode }: { message: ChatMessage; mode: Mode }): Re
) : null} ) : null}
{body} {body}
</div> </div>
<SourceLabel deviceId={message.deviceId} /> <SourceLabel deviceId={message.deviceId} role={message.role} />
<SenderLabel name={message.senderName} /> <SenderLabel name={message.senderName} />
</div> </div>
); );
@@ -345,7 +348,7 @@ function AssistantBubble({
{message.mood ? ( {message.mood ? (
<div className="mb-1 text-xs text-[var(--mu-text-lo)]">心情 · {message.mood}</div> <div className="mb-1 text-xs text-[var(--mu-text-lo)]">心情 · {message.mood}</div>
) : null} ) : null}
<SourceLabel deviceId={message.deviceId} /> <SourceLabel deviceId={message.deviceId} role={message.role} />
<WakePath active={active} /> <WakePath active={active} />
<ThinkingBlock text={message.thinking} streaming={message.streaming} /> <ThinkingBlock text={message.thinking} streaming={message.streaming} />
{message.tools?.map((t, i) => <ToolCard key={i} tool={t} />)} {message.tools?.map((t, i) => <ToolCard key={i} tool={t} />)}