Buckets:
| import { Component, createMemo } from "solid-js" | |
| import { useNavigate, useParams } from "@solidjs/router" | |
| import { useSync } from "@/context/sync" | |
| import { useSDK } from "@/context/sdk" | |
| import { usePrompt } from "@/context/prompt" | |
| import { useDialog } from "@opencode-ai/ui/context/dialog" | |
| import { Dialog } from "@opencode-ai/ui/dialog" | |
| import { List } from "@opencode-ai/ui/list" | |
| import { showToast } from "@/utils/toast" | |
| import { extractPromptFromParts } from "@/utils/prompt" | |
| import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client" | |
| import { base64Encode } from "@opencode-ai/core/util/encode" | |
| import { useLanguage } from "@/context/language" | |
| interface ForkableMessage { | |
| id: string | |
| text: string | |
| time: string | |
| } | |
| function formatTime(date: Date): string { | |
| return date.toLocaleTimeString(undefined, { timeStyle: "short" }) | |
| } | |
| export const DialogFork: Component = () => { | |
| const params = useParams() | |
| const navigate = useNavigate() | |
| const sync = useSync() | |
| const sdk = useSDK() | |
| const prompt = usePrompt() | |
| const dialog = useDialog() | |
| const language = useLanguage() | |
| const messages = createMemo((): ForkableMessage[] => { | |
| const sessionID = params.id | |
| if (!sessionID) return [] | |
| const msgs = sync.data.message[sessionID] ?? [] | |
| const result: ForkableMessage[] = [] | |
| for (const message of msgs) { | |
| if (message.role !== "user") continue | |
| const parts = sync.data.part[message.id] ?? [] | |
| const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored) | |
| if (!textPart) continue | |
| result.push({ | |
| id: message.id, | |
| text: textPart.text.replace(/\n/g, " ").slice(0, 200), | |
| time: formatTime(new Date(message.time.created)), | |
| }) | |
| } | |
| return result.reverse() | |
| }) | |
| const handleSelect = (item: ForkableMessage | undefined) => { | |
| if (!item) return | |
| const sessionID = params.id | |
| if (!sessionID) return | |
| const parts = sync.data.part[item.id] ?? [] | |
| const restored = extractPromptFromParts(parts, { | |
| directory: sdk.directory, | |
| attachmentName: language.t("common.attachment"), | |
| }) | |
| const dir = base64Encode(sdk.directory) | |
| sdk.client.session | |
| .fork({ sessionID, messageID: item.id }) | |
| .then((forked) => { | |
| if (!forked.data) { | |
| showToast({ title: language.t("common.requestFailed") }) | |
| return | |
| } | |
| dialog.close() | |
| prompt.set(restored, undefined, { dir, id: forked.data.id }) | |
| navigate(`/${dir}/session/${forked.data.id}`) | |
| }) | |
| .catch((err: unknown) => { | |
| const message = err instanceof Error ? err.message : String(err) | |
| showToast({ title: language.t("common.requestFailed"), description: message }) | |
| }) | |
| } | |
| return ( | |
| <Dialog title={language.t("command.session.fork")}> | |
| <List | |
| class="flex-1 px-3 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0" | |
| search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }} | |
| emptyMessage={language.t("dialog.fork.empty")} | |
| key={(x) => x.id} | |
| items={messages} | |
| filterKeys={["text"]} | |
| onSelect={handleSelect} | |
| > | |
| {(item) => ( | |
| <div class="w-full flex items-center gap-2"> | |
| <span class="truncate flex-1 min-w-0 text-left font-normal">{item.text}</span> | |
| <span class="text-text-weak shrink-0 font-normal">{item.time}</span> | |
| </div> | |
| )} | |
| </List> | |
| </Dialog> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 3.53 kB
- Xet hash:
- 1b2444236a9ab560f4b5bcc4a04de29a6407075fca155314bc60ef218e508b26
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.