|
|
@@ -1925,122 +1925,6 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-function AssistantMessage(props: { message: SessionMessageAssistant; last: boolean }) {
|
|
|
- const ctx = use()
|
|
|
- const local = useLocal()
|
|
|
- const theme = useTheme("elevated")
|
|
|
- const model = createMemo(
|
|
|
- () =>
|
|
|
- ctx
|
|
|
- .models()
|
|
|
- .find((model) => model.providerID === props.message.model.providerID && model.id === props.message.model.id)
|
|
|
- ?.name ?? `${props.message.model.providerID}/${props.message.model.id}`,
|
|
|
- )
|
|
|
-
|
|
|
- const final = createMemo(() => {
|
|
|
- return props.message.finish && !["tool-calls", "unknown"].includes(props.message.finish)
|
|
|
- })
|
|
|
-
|
|
|
- const duration = createMemo(() => {
|
|
|
- if (!final()) return 0
|
|
|
- if (!props.message.time.completed) return 0
|
|
|
- return props.message.time.completed - props.message.time.created
|
|
|
- })
|
|
|
-
|
|
|
- const exploration = createMemo(() => {
|
|
|
- const grouped = new Map<string, { first: boolean; parts: SessionMessageAssistantTool[]; active: boolean }>()
|
|
|
- if (!ctx.groupExploration()) return grouped
|
|
|
- const runs = props.message.content
|
|
|
- .map((part) =>
|
|
|
- part.type === "tool" &&
|
|
|
- ["read", "glob", "grep"].includes(toolDisplay(part.name)) &&
|
|
|
- part.state.status !== "streaming"
|
|
|
- ? part
|
|
|
- : undefined,
|
|
|
- )
|
|
|
- .reduce<SessionMessageAssistantTool[][]>(
|
|
|
- (runs, part) => {
|
|
|
- if (part) runs[runs.length - 1].push(part)
|
|
|
- if (!part && runs[runs.length - 1].length) runs.push([])
|
|
|
- return runs
|
|
|
- },
|
|
|
- [[]],
|
|
|
- )
|
|
|
- .filter((run) => run.length > 0)
|
|
|
- for (const run of runs) {
|
|
|
- const summary = {
|
|
|
- parts: run,
|
|
|
- active: false,
|
|
|
- }
|
|
|
- run.forEach((part, index) => grouped.set(part.id, { ...summary, first: index === 0 }))
|
|
|
- }
|
|
|
- return grouped
|
|
|
- })
|
|
|
-
|
|
|
- return (
|
|
|
- <>
|
|
|
- <For each={props.message.content}>
|
|
|
- {(content, index) => (
|
|
|
- <Switch>
|
|
|
- <Match when={content.type === "text"}>
|
|
|
- <TextPart
|
|
|
- part={content as SessionMessageAssistantText}
|
|
|
- last={index() === props.message.content.length - 1}
|
|
|
- />
|
|
|
- </Match>
|
|
|
- <Match when={content.type === "reasoning"}>
|
|
|
- <ReasoningPart
|
|
|
- part={content as SessionMessageAssistantReasoning}
|
|
|
- message={props.message}
|
|
|
- last={index() === props.message.content.length - 1}
|
|
|
- />
|
|
|
- </Match>
|
|
|
- <Match when={content.type === "tool"}>
|
|
|
- <Show when={exploration().get((content as SessionMessageAssistantTool).id)?.first !== false}>
|
|
|
- <Show
|
|
|
- when={exploration().get((content as SessionMessageAssistantTool).id)}
|
|
|
- fallback={<ToolPart part={content as SessionMessageAssistantTool} />}
|
|
|
- >
|
|
|
- {(summary) => <ExplorationSummary {...summary()} />}
|
|
|
- </Show>
|
|
|
- </Show>
|
|
|
- </Match>
|
|
|
- </Switch>
|
|
|
- )}
|
|
|
- </For>
|
|
|
- <Show when={props.message.error}>
|
|
|
- <box
|
|
|
- border={["left"]}
|
|
|
- paddingTop={1}
|
|
|
- paddingBottom={1}
|
|
|
- paddingLeft={2}
|
|
|
- backgroundColor={theme.background.default}
|
|
|
- customBorderChars={SplitBorder.customBorderChars}
|
|
|
- borderColor={theme.text.feedback.error.default}
|
|
|
- >
|
|
|
- <text fg={theme.text.subdued}>{errorMessage(props.message.error)}</text>
|
|
|
- </box>
|
|
|
- </Show>
|
|
|
- <AssistantRetry retry={props.message.retry} />
|
|
|
- <Switch>
|
|
|
- <Match when={props.last || final() || props.message.error}>
|
|
|
- <box paddingLeft={3}>
|
|
|
- <text>
|
|
|
- <span style={{ fg: props.message.error ? theme.text.subdued : local.agent.color(props.message.agent) }}>
|
|
|
- {Locale.titlecase(props.message.agent)}
|
|
|
- </span>
|
|
|
- <span style={{ fg: theme.text.subdued }}> · {model()}</span>
|
|
|
- <Show when={duration()}>
|
|
|
- <span style={{ fg: theme.text.subdued }}> · {Locale.duration(duration())}</span>
|
|
|
- </Show>
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- </Match>
|
|
|
- </Switch>
|
|
|
- </>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
function AssistantRetry(props: { retry: SessionMessageAssistant["retry"] }) {
|
|
|
const theme = useTheme()
|
|
|
return (
|
|
|
@@ -2056,40 +1940,6 @@ function AssistantRetry(props: { retry: SessionMessageAssistant["retry"] }) {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-function ExplorationSummary(props: { parts: SessionMessageAssistantTool[]; active: boolean }) {
|
|
|
- const theme = useTheme()
|
|
|
- const pathFormatter = usePathFormatter()
|
|
|
- const label = (part: SessionMessageAssistantTool) => {
|
|
|
- const input = typeof part.state.input === "string" ? {} : part.state.input
|
|
|
- const tool = toolDisplay(part.name)
|
|
|
- if (tool === "read") return `Read ${pathFormatter.format(stringValue(input.path))}`
|
|
|
- if (tool === "glob") return `Glob "${stringValue(input.pattern)}"`
|
|
|
- return `Grep "${stringValue(input.pattern)}"`
|
|
|
- }
|
|
|
- return (
|
|
|
- <box flexDirection="column">
|
|
|
- <InlineToolRow
|
|
|
- icon="✱"
|
|
|
- color={theme.text.subdued}
|
|
|
- complete={!props.active}
|
|
|
- pending="Exploring"
|
|
|
- spinner={props.active}
|
|
|
- >
|
|
|
- {props.active ? "Exploring" : "Explored"}
|
|
|
- </InlineToolRow>
|
|
|
- <For each={props.parts}>
|
|
|
- {(part, index) => (
|
|
|
- <box paddingLeft={5}>
|
|
|
- <text fg={part.state.status === "error" ? theme.text.feedback.error.default : theme.text.subdued}>
|
|
|
- {index() === props.parts.length - 1 ? "└" : "├"} {label(part)}
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- )}
|
|
|
- </For>
|
|
|
- </box>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
const INLINE_TOOL_ICON_WIDTH = 2
|
|
|
|
|
|
function ReasoningPart(props: {
|