diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 4db5508bf..24ae16a31 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -117,6 +117,7 @@ function createThrottledValue(getValue: () => string) { createEffect(() => { const next = getValue() const now = Date.now() + const remaining = TEXT_RENDER_THROTTLE_MS - (now - last) if (remaining <= 0) { if (timeout) { @@ -250,6 +251,126 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo { } const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"]) +const HIDDEN_TOOLS = new Set(["todowrite", "todoread"]) + +function list(value: T[] | undefined | null, fallback: T[]) { + if (Array.isArray(value)) return value + return fallback +} + +function renderable(part: PartType) { + if (part.type === "tool") { + if (HIDDEN_TOOLS.has(part.tool)) return false + if (part.tool === "question") return part.state.status !== "pending" && part.state.status !== "running" + return true + } + if (part.type === "text") return !!part.text?.trim() + if (part.type === "reasoning") return !!part.text?.trim() + return !!PART_MAPPING[part.type] +} + +export function AssistantParts(props: { + messages: AssistantMessage[] + showAssistantCopyPartID?: string | null + working?: boolean +}) { + const data = useData() + const emptyParts: PartType[] = [] + + const grouped = createMemo(() => { + const keys: string[] = [] + const items: Record< + string, + { type: "part"; part: PartType; message: AssistantMessage } | { type: "context"; parts: ToolPart[] } + > = {} + const push = ( + key: string, + item: { type: "part"; part: PartType; message: AssistantMessage } | { type: "context"; parts: ToolPart[] }, + ) => { + keys.push(key) + items[key] = item + } + + const parts = props.messages.flatMap((message) => + list(data.store.part?.[message.id], emptyParts) + .filter(renderable) + .map((part) => ({ message, part })), + ) + + let start = -1 + + const flush = (end: number) => { + if (start < 0) return + const first = parts[start] + const last = parts[end] + if (!first || !last) { + start = -1 + return + } + push(`context:${first.part.id}`, { + type: "context", + parts: parts + .slice(start, end + 1) + .map((x) => x.part) + .filter((part): part is ToolPart => isContextGroupTool(part)), + }) + start = -1 + } + + parts.forEach((item, index) => { + if (isContextGroupTool(item.part)) { + if (start < 0) start = index + return + } + + flush(index - 1) + push(`part:${item.message.id}:${item.part.id}`, { type: "part", part: item.part, message: item.message }) + }) + + flush(parts.length - 1) + + return { keys, items } + }) + + const last = createMemo(() => grouped().keys.at(-1)) + + return ( + + {(key) => { + const item = createMemo(() => grouped().items[key]) + const ctx = createMemo(() => { + const value = item() + if (!value) return + if (value.type !== "context") return + return value + }) + const part = createMemo(() => { + const value = item() + if (!value) return + if (value.type !== "part") return + return value + }) + const tail = createMemo(() => last() === key) + return ( + <> + + {(entry) => } + + + {(entry) => ( + + )} + + + ) + }} + + ) +} function isContextGroupTool(part: PartType): part is ToolPart { return part.type === "tool" && CONTEXT_GROUP_TOOLS.has(part.tool) @@ -390,6 +511,8 @@ export function AssistantMessageDisplay(props: { } parts.forEach((part, index) => { + if (!renderable(part)) return + if (isContextGroupTool(part)) { if (start < 0) start = index return @@ -408,31 +531,43 @@ export function AssistantMessageDisplay(props: { {(key) => { const item = createMemo(() => grouped().items[key]) + const ctx = createMemo(() => { + const value = item() + if (!value) return + if (value.type !== "context") return + return value + }) + const part = createMemo(() => { + const value = item() + if (!value) return + if (value.type !== "part") return + return value + }) return ( - - {(value) => { - const entry = value() - if (entry.type === "context") return - return ( + <> + {(entry) => } + + {(entry) => ( - ) - }} - + )} + + ) }} ) } -function ContextToolGroup(props: { parts: ToolPart[] }) { +function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { const i18n = useI18n() const [open, setOpen] = createSignal(false) - const pending = createMemo(() => - props.parts.some((part) => part.state.status === "pending" || part.state.status === "running"), + const pending = createMemo( + () => + !!props.busy || props.parts.some((part) => part.state.status === "pending" || part.state.status === "running"), ) const summary = createMemo(() => contextToolSummary(props.parts)) const details = createMemo(() => summary().join(", ")) @@ -445,7 +580,7 @@ function ContextToolGroup(props: { parts: ToolPart[] }) { when={pending()} fallback={ - Gathered context + {i18n.t("ui.sessionTurn.status.gatheredContext")} {details()} diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index a99cc8d03..e4c0a2273 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -6,7 +6,7 @@ import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createMemo, createSignal, For, ParentProps, Show } from "solid-js" import { Dynamic } from "solid-js/web" -import { Message } from "./message-part" +import { AssistantParts, Message } from "./message-part" import { Card } from "./card" import { Collapsible } from "./collapsible" import { DiffChanges } from "./diff-changes" @@ -91,13 +91,6 @@ function visible(part: PartType) { return false } -function AssistantMessageItem(props: { message: AssistantMessage; showAssistantCopyPartID?: string | null }) { - const data = useData() - const emptyParts: PartType[] = [] - const msgParts = createMemo(() => list(data.store.part?.[props.message.id], emptyParts)) - return -} - export function SessionTurn( props: ParentProps<{ sessionID: string @@ -237,8 +230,7 @@ export function SessionTurn( const working = createMemo(() => status().type !== "idle" && isLastUserMessage()) const assistantCopyPartID = createMemo(() => { - if (!isLastUserMessage()) return null - if (status().type !== "idle") return null + if (working()) return null return showAssistantCopyPartID() ?? null }) const assistantVisible = createMemo(() => @@ -281,14 +273,11 @@ export function SessionTurn( 0}>
- - {(assistantMessage) => ( - - )} - +
0}> diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 6f8781ebf..aa30bbd57 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "تفويض العمل", "ui.sessionTurn.status.planning": "تخطيط الخطوات التالية", - "ui.sessionTurn.status.gatheringContext": "جمع السياق", + "ui.sessionTurn.status.gatheringContext": "استكشاف...", + "ui.sessionTurn.status.gatheredContext": "تم الاستكشاف", "ui.sessionTurn.status.searchingCodebase": "البحث في قاعدة التعليمات البرمجية", "ui.sessionTurn.status.searchingWeb": "البحث في الويب", "ui.sessionTurn.status.makingEdits": "إجراء تعديلات", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 89eb391f9..ca34e4a04 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegando trabalho", "ui.sessionTurn.status.planning": "Planejando próximos passos", - "ui.sessionTurn.status.gatheringContext": "Coletando contexto", + "ui.sessionTurn.status.gatheringContext": "Explorando...", + "ui.sessionTurn.status.gatheredContext": "Explorado", "ui.sessionTurn.status.searchingCodebase": "Pesquisando no código", "ui.sessionTurn.status.searchingWeb": "Pesquisando na web", "ui.sessionTurn.status.makingEdits": "Fazendo edições", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index f1163758a..5b9a2443b 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -37,7 +37,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegiranje posla", "ui.sessionTurn.status.planning": "Planiranje sljedećih koraka", - "ui.sessionTurn.status.gatheringContext": "Prikupljanje konteksta", + "ui.sessionTurn.status.gatheringContext": "Istraživanje...", + "ui.sessionTurn.status.gatheredContext": "Istraženo", "ui.sessionTurn.status.searchingCodebase": "Pretraživanje baze koda", "ui.sessionTurn.status.searchingWeb": "Pretraživanje weba", "ui.sessionTurn.status.makingEdits": "Pravljenje izmjena", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 8b42c1b80..4abdce547 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -32,7 +32,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegerer arbejde", "ui.sessionTurn.status.planning": "Planlægger næste trin", - "ui.sessionTurn.status.gatheringContext": "Indsamler kontekst", + "ui.sessionTurn.status.gatheringContext": "Udforsker...", + "ui.sessionTurn.status.gatheredContext": "Udforsket", "ui.sessionTurn.status.searchingCodebase": "Søger i koden", "ui.sessionTurn.status.searchingWeb": "Søger på nettet", "ui.sessionTurn.status.makingEdits": "Laver ændringer", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index c805b1e01..a2427332e 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -36,7 +36,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Arbeit delegieren", "ui.sessionTurn.status.planning": "Nächste Schritte planen", - "ui.sessionTurn.status.gatheringContext": "Kontext sammeln", + "ui.sessionTurn.status.gatheringContext": "Erkunden...", + "ui.sessionTurn.status.gatheredContext": "Erkundet", "ui.sessionTurn.status.searchingCodebase": "Codebasis durchsuchen", "ui.sessionTurn.status.searchingWeb": "Web durchsuchen", "ui.sessionTurn.status.makingEdits": "Änderungen vornehmen", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 4d707404a..72d35dbd7 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegating work", "ui.sessionTurn.status.planning": "Planning next steps", - "ui.sessionTurn.status.gatheringContext": "Gathering context", + "ui.sessionTurn.status.gatheringContext": "Exploring...", + "ui.sessionTurn.status.gatheredContext": "Explored", "ui.sessionTurn.status.searchingCodebase": "Searching the codebase", "ui.sessionTurn.status.searchingWeb": "Searching the web", "ui.sessionTurn.status.makingEdits": "Making edits", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 54e786762..7179c6345 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegando trabajo", "ui.sessionTurn.status.planning": "Planificando siguientes pasos", - "ui.sessionTurn.status.gatheringContext": "Recopilando contexto", + "ui.sessionTurn.status.gatheringContext": "Explorando...", + "ui.sessionTurn.status.gatheredContext": "Explorado", "ui.sessionTurn.status.searchingCodebase": "Buscando en la base de código", "ui.sessionTurn.status.searchingWeb": "Buscando en la web", "ui.sessionTurn.status.makingEdits": "Realizando ediciones", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 36551e5f4..6885f4c58 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Délégation du travail", "ui.sessionTurn.status.planning": "Planification des prochaines étapes", - "ui.sessionTurn.status.gatheringContext": "Collecte du contexte", + "ui.sessionTurn.status.gatheringContext": "Exploration...", + "ui.sessionTurn.status.gatheredContext": "Exploré", "ui.sessionTurn.status.searchingCodebase": "Recherche dans la base de code", "ui.sessionTurn.status.searchingWeb": "Recherche sur le web", "ui.sessionTurn.status.makingEdits": "Application des modifications", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 039b998d5..7d357c57e 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -32,7 +32,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "作業を委任中", "ui.sessionTurn.status.planning": "次のステップを計画中", - "ui.sessionTurn.status.gatheringContext": "コンテキストを収集中", + "ui.sessionTurn.status.gatheringContext": "探索中...", + "ui.sessionTurn.status.gatheredContext": "探索済み", "ui.sessionTurn.status.searchingCodebase": "コードベースを検索中", "ui.sessionTurn.status.searchingWeb": "ウェブを検索中", "ui.sessionTurn.status.makingEdits": "編集を実行中", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index d77cdfbb2..7bfea2819 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "작업 위임 중", "ui.sessionTurn.status.planning": "다음 단계 계획 중", - "ui.sessionTurn.status.gatheringContext": "컨텍스트 수집 중", + "ui.sessionTurn.status.gatheringContext": "탐색 중...", + "ui.sessionTurn.status.gatheredContext": "탐색됨", "ui.sessionTurn.status.searchingCodebase": "코드베이스 검색 중", "ui.sessionTurn.status.searchingWeb": "웹 검색 중", "ui.sessionTurn.status.makingEdits": "편집 수행 중", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 29b988c96..caea05827 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -36,7 +36,8 @@ export const dict: Record = { "ui.sessionTurn.status.delegating": "Delegerer arbeid", "ui.sessionTurn.status.planning": "Planlegger neste trinn", - "ui.sessionTurn.status.gatheringContext": "Samler inn kontekst", + "ui.sessionTurn.status.gatheringContext": "Utforsker...", + "ui.sessionTurn.status.gatheredContext": "Utforsket", "ui.sessionTurn.status.searchingCodebase": "Søker i kodebasen", "ui.sessionTurn.status.searchingWeb": "Søker på nettet", "ui.sessionTurn.status.makingEdits": "Gjør endringer", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 2dfcd272e..9d1a860bb 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -32,7 +32,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegowanie pracy", "ui.sessionTurn.status.planning": "Planowanie kolejnych kroków", - "ui.sessionTurn.status.gatheringContext": "Zbieranie kontekstu", + "ui.sessionTurn.status.gatheringContext": "Eksplorowanie...", + "ui.sessionTurn.status.gatheredContext": "Wyeksplorowano", "ui.sessionTurn.status.searchingCodebase": "Przeszukiwanie bazy kodu", "ui.sessionTurn.status.searchingWeb": "Przeszukiwanie sieci", "ui.sessionTurn.status.makingEdits": "Wprowadzanie zmian", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 9494b7605..1c363da2a 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -32,7 +32,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "Делегирование работы", "ui.sessionTurn.status.planning": "Планирование следующих шагов", - "ui.sessionTurn.status.gatheringContext": "Сбор контекста", + "ui.sessionTurn.status.gatheringContext": "Исследование...", + "ui.sessionTurn.status.gatheredContext": "Исследовано", "ui.sessionTurn.status.searchingCodebase": "Поиск в кодовой базе", "ui.sessionTurn.status.searchingWeb": "Поиск в интернете", "ui.sessionTurn.status.makingEdits": "Внесение изменений", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 8758d5534..ef8517af2 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -33,7 +33,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "มอบหมายงาน", "ui.sessionTurn.status.planning": "วางแผนขั้นตอนถัดไป", - "ui.sessionTurn.status.gatheringContext": "รวบรวมบริบท", + "ui.sessionTurn.status.gatheringContext": "กำลังสำรวจ...", + "ui.sessionTurn.status.gatheredContext": "สำรวจแล้ว", "ui.sessionTurn.status.searchingCodebase": "กำลังค้นหาโค้ดเบส", "ui.sessionTurn.status.searchingWeb": "กำลังค้นหาบนเว็บ", "ui.sessionTurn.status.makingEdits": "กำลังแก้ไข", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index fc9c6d68e..2c3bcf1d8 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -37,7 +37,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "正在委派工作", "ui.sessionTurn.status.planning": "正在规划下一步", - "ui.sessionTurn.status.gatheringContext": "正在收集上下文", + "ui.sessionTurn.status.gatheringContext": "正在探索...", + "ui.sessionTurn.status.gatheredContext": "已探索", "ui.sessionTurn.status.searchingCodebase": "正在搜索代码库", "ui.sessionTurn.status.searchingWeb": "正在搜索网页", "ui.sessionTurn.status.makingEdits": "正在修改", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 248519d4f..00cbbd79d 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -37,7 +37,8 @@ export const dict = { "ui.sessionTurn.status.delegating": "正在委派工作", "ui.sessionTurn.status.planning": "正在規劃下一步", - "ui.sessionTurn.status.gatheringContext": "正在收集上下文", + "ui.sessionTurn.status.gatheringContext": "正在探索...", + "ui.sessionTurn.status.gatheredContext": "已探索", "ui.sessionTurn.status.searchingCodebase": "正在搜尋程式碼庫", "ui.sessionTurn.status.searchingWeb": "正在搜尋網頁", "ui.sessionTurn.status.makingEdits": "正在修改",