diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 0d2718efbda..5ef68cc5c57 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1099,7 +1099,6 @@ export default function Page() { anchor={anchor} onRegisterMessage={scrollSpy.register} onUnregisterMessage={scrollSpy.unregister} - lastUserMessageID={lastUserMessage()?.id} /> diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 8215f31bade..d2de720a312 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -105,7 +105,6 @@ export function MessageTimeline(props: { anchor: (id: string) => string onRegisterMessage: (el: HTMLDivElement, id: string) => void onUnregisterMessage: (id: string) => void - lastUserMessageID?: string }) { let touchGesture: number | undefined @@ -601,7 +600,6 @@ export function MessageTimeline(props: { +
+ + + {i18n.t("ui.messagePart.compaction")} + + +
+ + ) +} + PART_MAPPING["text"] = function TextPartDisplay(props) { const data = useData() const i18n = useI18n() diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 9639e6635a7..4af87b36171 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -37,6 +37,12 @@ max-width: 100%; } + [data-slot="session-turn-compaction"] { + width: 100%; + min-width: 0; + align-self: stretch; + } + [data-slot="session-turn-thinking"] { display: flex; align-items: center; diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index bd4f2843aa5..e329b117007 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 { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { Dynamic } from "solid-js/web" -import { AssistantParts, Message, PART_MAPPING } from "./message-part" +import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part" import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" @@ -139,7 +139,6 @@ export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string - lastUserMessageID?: string showReasoningSummaries?: boolean shellToolDefaultOpen?: boolean editToolDefaultOpen?: boolean @@ -187,18 +186,18 @@ export function SessionTurn( return msg }) - const lastUserMessageID = createMemo(() => { - if (props.lastUserMessageID) return props.lastUserMessageID - + const pending = createMemo(() => { const messages = allMessages() ?? emptyMessages - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - if (msg?.role === "user") return msg.id - } - return undefined + return messages.findLast( + (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", + ) + }) + const active = createMemo(() => { + const msg = message() + const item = pending() + if (!msg || !item) return false + return item.parentID === msg.id }) - - const isLastUserMessage = createMemo(() => props.messageID === lastUserMessageID()) const parts = createMemo(() => { const msg = message() @@ -206,6 +205,8 @@ export function SessionTurn( return list(data.store.part?.[msg.id], emptyParts) }) + const compaction = createMemo(() => parts().find((part) => part.type === "compaction")) + const diffs = createMemo(() => { const files = message()?.summary?.diffs if (!files?.length) return emptyDiffs @@ -285,7 +286,7 @@ export function SessionTurn( }) const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle) - const working = createMemo(() => status().type !== "idle" && isLastUserMessage()) + const working = createMemo(() => status().type !== "idle" && active()) const showReasoningSummaries = createMemo(() => props.showReasoningSummaries ?? true) const assistantCopyPartID = createMemo(() => { @@ -365,6 +366,13 @@ export function SessionTurn(
+ + {(part) => ( +
+ +
+ )} +
0}>
- + 0 && !working()}>
diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 1f4b30aa7d6..afd046d7e10 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -60,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "النظر في الخطوات التالية", "ui.messagePart.questions.dismissed": "تم رفض الأسئلة", + "ui.messagePart.compaction": "تم ضغط السجل", "ui.messagePart.context.read.one": "{{count}} قراءة", "ui.messagePart.context.read.other": "{{count}} قراءات", "ui.messagePart.context.search.one": "{{count}} بحث", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index b08a7f57f7c..9b7a1d1d4d9 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -60,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Considerando próximos passos", "ui.messagePart.questions.dismissed": "Perguntas descartadas", + "ui.messagePart.compaction": "Histórico compactado", "ui.messagePart.context.read.one": "{{count}} leitura", "ui.messagePart.context.read.other": "{{count}} leituras", "ui.messagePart.context.search.one": "{{count}} pesquisa", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 9f2eb7cd2e6..7d31289e102 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -64,6 +64,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Razmatranje sljedećih koraka", "ui.messagePart.questions.dismissed": "Pitanja odbačena", + "ui.messagePart.compaction": "Historija sažeta", "ui.messagePart.context.read.one": "{{count}} čitanje", "ui.messagePart.context.read.other": "{{count}} čitanja", "ui.messagePart.context.search.one": "{{count}} pretraga", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 26c7db0c50b..3cd0328a10e 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -59,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Overvejer næste skridt", "ui.messagePart.questions.dismissed": "Spørgsmål afvist", + "ui.messagePart.compaction": "Historik komprimeret", "ui.messagePart.context.read.one": "{{count}} læsning", "ui.messagePart.context.read.other": "{{count}} læsninger", "ui.messagePart.context.search.one": "{{count}} søgning", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index 467fa4e2e9a..384ebd33824 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -65,6 +65,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Nächste Schritte erwägen", "ui.messagePart.questions.dismissed": "Fragen verworfen", + "ui.messagePart.compaction": "Verlauf komprimiert", "ui.messagePart.context.read.one": "{{count}} Lesevorgang", "ui.messagePart.context.read.other": "{{count}} Lesevorgänge", "ui.messagePart.context.search.one": "{{count}} Suche", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 60e169dfa00..a78474dafcd 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -66,6 +66,7 @@ export const dict: Record = { "ui.messagePart.option.typeOwnAnswer": "Type your own answer", "ui.messagePart.review.title": "Review your answers", "ui.messagePart.questions.dismissed": "Questions dismissed", + "ui.messagePart.compaction": "History compacted", "ui.messagePart.context.read.one": "{{count}} read", "ui.messagePart.context.read.other": "{{count}} reads", "ui.messagePart.context.search.one": "{{count}} search", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 0a06f0d9d9f..c5b7d60cef3 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -60,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Considerando siguientes pasos", "ui.messagePart.questions.dismissed": "Preguntas descartadas", + "ui.messagePart.compaction": "Historial compactado", "ui.messagePart.context.read.one": "{{count}} lectura", "ui.messagePart.context.read.other": "{{count}} lecturas", "ui.messagePart.context.search.one": "{{count}} búsqueda", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 86d277308e6..de1005ec300 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -60,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Examen des prochaines étapes", "ui.messagePart.questions.dismissed": "Questions ignorées", + "ui.messagePart.compaction": "Historique compacté", "ui.messagePart.context.read.one": "{{count}} lecture", "ui.messagePart.context.read.other": "{{count}} lectures", "ui.messagePart.context.search.one": "{{count}} recherche", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index ca8c48efe0c..e9e1fff2f86 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -59,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "次のステップを検討中", "ui.messagePart.questions.dismissed": "質問をスキップしました", + "ui.messagePart.compaction": "履歴を圧縮しました", "ui.messagePart.context.read.one": "{{count}} 件の読み取り", "ui.messagePart.context.read.other": "{{count}} 件の読み取り", "ui.messagePart.context.search.one": "{{count}} 件の検索", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index 226de0d22fa..0280cc24ec6 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -60,6 +60,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "다음 단계 고려 중", "ui.messagePart.questions.dismissed": "질문 무시됨", + "ui.messagePart.compaction": "기록이 압축됨", "ui.messagePart.context.read.one": "{{count}}개 읽음", "ui.messagePart.context.read.other": "{{count}}개 읽음", "ui.messagePart.context.search.one": "{{count}}개 검색", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 5243a61b8ba..ca7db5d758e 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -63,6 +63,7 @@ export const dict: Record = { "ui.sessionTurn.status.consideringNextSteps": "Vurderer neste trinn", "ui.messagePart.questions.dismissed": "Spørsmål avvist", + "ui.messagePart.compaction": "Historikk komprimert", "ui.messagePart.context.read.one": "{{count}} lest", "ui.messagePart.context.read.other": "{{count}} lest", "ui.messagePart.context.search.one": "{{count}} søk", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 339694c263d..ccc46a7f150 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -59,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Rozważanie kolejnych kroków", "ui.messagePart.questions.dismissed": "Pytania odrzucone", + "ui.messagePart.compaction": "Historia skompaktowana", "ui.messagePart.context.read.one": "{{count}} odczyt", "ui.messagePart.context.read.other": "{{count}} odczyty", "ui.messagePart.context.search.one": "{{count}} wyszukiwanie", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index ca3ce8e0544..9e9d6722fba 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -59,6 +59,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Рассмотрение следующих шагов", "ui.messagePart.questions.dismissed": "Вопросы отклонены", + "ui.messagePart.compaction": "История сжата", "ui.messagePart.context.read.one": "{{count}} чтение", "ui.messagePart.context.read.other": "{{count}} чтений", "ui.messagePart.context.search.one": "{{count}} поиск", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 2043bbdc8be..c82acd22535 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -61,6 +61,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "พิจารณาขั้นตอนถัดไป", "ui.messagePart.questions.dismissed": "ละทิ้งคำถามแล้ว", + "ui.messagePart.compaction": "ประวัติถูกบีบอัด", "ui.messagePart.context.read.one": "อ่าน {{count}} รายการ", "ui.messagePart.context.read.other": "อ่าน {{count}} รายการ", "ui.messagePart.context.search.one": "ค้นหา {{count}} รายการ", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index da660034b58..766dcb852a8 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -56,6 +56,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "Sonraki adımlar değerlendiriliyor", "ui.messagePart.questions.dismissed": "Sorular reddedildi", + "ui.messagePart.compaction": "Geçmiş sıkıştırıldı", "ui.messagePart.context.read.one": "{{count}} okuma", "ui.messagePart.context.read.other": "{{count}} okuma", "ui.messagePart.context.search.one": "{{count}} arama", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 78006482ae6..98f1f037742 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -64,6 +64,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "正在考虑下一步", "ui.messagePart.questions.dismissed": "问题已忽略", + "ui.messagePart.compaction": "历史已压缩", "ui.messagePart.context.read.one": "{{count}} 次读取", "ui.messagePart.context.read.other": "{{count}} 次读取", "ui.messagePart.context.search.one": "{{count}} 次搜索", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 044697bf690..6f1b1d380e6 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -64,6 +64,7 @@ export const dict = { "ui.sessionTurn.status.consideringNextSteps": "正在考慮下一步", "ui.messagePart.questions.dismissed": "問題已略過", + "ui.messagePart.compaction": "歷史已壓縮", "ui.messagePart.context.read.one": "{{count}} 次讀取", "ui.messagePart.context.read.other": "{{count}} 次讀取", "ui.messagePart.context.search.one": "{{count}} 次搜尋",