| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- 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 { extractPromptFromParts } from "@/utils/prompt"
- import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
- import { base64Encode } from "@opencode-ai/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"),
- })
- dialog.close()
- sdk.client.session.fork({ sessionID, messageID: item.id }).then((forked) => {
- if (!forked.data) return
- navigate(`/${base64Encode(sdk.directory)}/session/${forked.data.id}`)
- requestAnimationFrame(() => {
- prompt.set(restored)
- })
- })
- }
- return (
- <Dialog title={language.t("command.session.fork")}>
- <List
- class="flex-1 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" style={{ "font-weight": "400" }}>
- {item.text}
- </span>
- <span class="text-text-weak shrink-0" style={{ "font-weight": "400" }}>
- {item.time}
- </span>
- </div>
- )}
- </List>
- </Dialog>
- )
- }
|