diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 78d574d8a..553849de3 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -1,12 +1,24 @@ [data-component="assistant-message"] { content-visibility: auto; width: 100%; + /* min-width:0 — a flex column won't shrink below its content's intrinsic + width without it, so a long unbroken-ish line (URLs, "overlapping" running + into the next word) pushed the message past the viewport and clipped on the + right. The container chain already sets this; the message column + its + text parts were the gap. */ + min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; } +[data-component="assistant-message"] [data-component="text-part"], +[data-component="assistant-message"] [data-slot="text-part-body"] { + min-width: 0; + max-width: 100%; +} + [data-component="user-message"] { font-family: var(--font-family-sans); font-size: var(--font-size-base); diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index cd4d42705..41320c33b 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1,4 +1,4 @@ -import { AmicoSpinner } from "../amicode/spinner" +import { AmicoSpinner, AmicoMark } from "../amicode/spinner" import { Component, createEffect, @@ -604,89 +604,114 @@ export function AssistantParts(props: { const last = createMemo(() => grouped().at(-1)?.key) - return ( - - {(entryAccessor) => { - const entryType = createMemo(() => entryAccessor().type) + // amicode: does this turn already carry an amicode_* tool card? Those cards + // (card.tsx) render their OWN AMICO signature, so a turn-level one would + // double up. The turn-level signature exists precisely for the OTHER case — + // plain prose replies, which otherwise show no Amico identity at all. + const hasAmicodeCard = createMemo(() => + props.messages.some((message) => + list(data.store.part?.[message.id], emptyParts).some( + (p) => p.type === "tool" && /^amicode_/.test((p as ToolPart).tool ?? ""), + ), + ), + ) - return ( - - - {(() => { - const parts = createMemo( - () => { + return ( + <> + {/* amicode: the AMICO signature heads assistant turns so the brand + identity doesn't vanish in plain chat (it only lived on interview + receipt cards before). Suppressed when a turn already has an + amicode_* card — that card brings its own signature. Kate's restyle + (amc-sig/amc-wordmark) is reused untouched. */} + 0 && !hasAmicodeCard()}> + + + AMICO + + + + {(entryAccessor) => { + const entryType = createMemo(() => entryAccessor().type) + + return ( + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() + if (entry.type !== "context") return emptyTools + return entry.refs + .map((ref) => part().get(ref.messageID)?.get(ref.partID)) + .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + const busy = createMemo(() => props.working && last() === entryAccessor().key) + + return ( + 0}> + + + ) + })()} + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() + if (entry.type !== "shell") return emptyTools + return entry.refs + .map((ref) => part().get(ref.messageID)?.get(ref.partID)) + .filter((part): part is ToolPart => !!part && isShellGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + const busy = createMemo(() => props.working && last() === entryAccessor().key) + + return ( + 0}> + + + ) + })()} + + + {(() => { + const message = createMemo(() => { const entry = entryAccessor() - if (entry.type !== "context") return emptyTools - return entry.refs - .map((ref) => part().get(ref.messageID)?.get(ref.partID)) - .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) - }, - emptyTools, - { equals: same }, - ) - const busy = createMemo(() => props.working && last() === entryAccessor().key) - - return ( - 0}> - - - ) - })()} - - - {(() => { - const parts = createMemo( - () => { + if (entry.type !== "part") return + return msgs().get(entry.ref.messageID) + }) + const item = createMemo(() => { const entry = entryAccessor() - if (entry.type !== "shell") return emptyTools - return entry.refs - .map((ref) => part().get(ref.messageID)?.get(ref.partID)) - .filter((part): part is ToolPart => !!part && isShellGroupTool(part)) - }, - emptyTools, - { equals: same }, - ) - const busy = createMemo(() => props.working && last() === entryAccessor().key) - - return ( - 0}> - - - ) - })()} - - - {(() => { - const message = createMemo(() => { - const entry = entryAccessor() - if (entry.type !== "part") return - return msgs().get(entry.ref.messageID) - }) - const item = createMemo(() => { - const entry = entryAccessor() - if (entry.type !== "part") return - return part().get(entry.ref.messageID)?.get(entry.ref.partID) - }) - - return ( - - - + if (entry.type !== "part") return + return part().get(entry.ref.messageID)?.get(entry.ref.partID) + }) + + return ( + + + + - - ) - })()} - - - ) - }} - + ) + })()} + + + ) + }} + + ) }