Fix shiki and diff caching
This commit is contained in:
@@ -5,6 +5,9 @@ import { getSharedHighlighter, escapeHtml } from "../lib/markdown"
|
||||
|
||||
const inlineLoadedLanguages = new Set<string>()
|
||||
|
||||
type LoadLanguageArg = Parameters<Highlighter["loadLanguage"]>[0]
|
||||
type CodeToHtmlOptions = Parameters<Highlighter["codeToHtml"]>[1]
|
||||
|
||||
interface CodeBlockInlineProps {
|
||||
code: string
|
||||
language?: string
|
||||
@@ -41,13 +44,14 @@ export function CodeBlockInline(props: CodeBlockInlineProps) {
|
||||
}
|
||||
|
||||
try {
|
||||
const language = props.language as LoadLanguageArg
|
||||
if (!inlineLoadedLanguages.has(props.language)) {
|
||||
await highlighter.loadLanguage(props.language)
|
||||
await highlighter.loadLanguage(language)
|
||||
inlineLoadedLanguages.add(props.language)
|
||||
}
|
||||
|
||||
const highlighted = highlighter.codeToHtml(props.code, {
|
||||
lang: props.language,
|
||||
lang: props.language as CodeToHtmlOptions["lang"],
|
||||
theme: isDark() ? "github-dark" : "github-light",
|
||||
})
|
||||
setHtml(highlighted)
|
||||
|
||||
@@ -1,62 +1,56 @@
|
||||
import { createMemo, Show } from "solid-js"
|
||||
import { DiffView, DiffModeEnum } from "@git-diff-view/solid"
|
||||
import { getLanguageFromPath } from "../lib/markdown"
|
||||
import { createMemo } from "solid-js"
|
||||
import type { TextPart } from "../types/message"
|
||||
import { Markdown } from "./markdown"
|
||||
import { normalizeDiffText } from "../lib/diff-utils"
|
||||
import type { DiffViewMode } from "../stores/preferences"
|
||||
import { getToolRenderCache, setToolRenderCache } from "../lib/tool-render-cache"
|
||||
|
||||
type ThemeKey = "light" | "dark"
|
||||
|
||||
interface ToolCallDiffViewerProps {
|
||||
diffText: string
|
||||
filePath?: string
|
||||
theme: "light" | "dark"
|
||||
mode: DiffViewMode
|
||||
theme: ThemeKey
|
||||
renderCacheKey?: string
|
||||
}
|
||||
|
||||
type DiffData = {
|
||||
oldFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null }
|
||||
newFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null }
|
||||
hunks: string[]
|
||||
|
||||
function formatDiffMarkdown(diffText: string, filePath?: string): string {
|
||||
const body = normalizeDiffText(diffText) || diffText
|
||||
const trimmed = body.trimStart()
|
||||
const alreadyFenced = trimmed.startsWith("```")
|
||||
const fenced = alreadyFenced ? body : `\`\`\`diff\n${body}\n\`\`\``
|
||||
|
||||
if (!filePath) {
|
||||
return fenced
|
||||
}
|
||||
return `### ${filePath}\n\n${fenced}`
|
||||
}
|
||||
|
||||
export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
const diffData = createMemo<DiffData | null>(() => {
|
||||
const normalized = normalizeDiffText(props.diffText)
|
||||
if (!normalized) {
|
||||
return null
|
||||
}
|
||||
|
||||
const language = getLanguageFromPath(props.filePath) || "text"
|
||||
const fileName = props.filePath || "diff"
|
||||
|
||||
return {
|
||||
oldFile: {
|
||||
fileName,
|
||||
fileLang: language,
|
||||
},
|
||||
newFile: {
|
||||
fileName,
|
||||
fileLang: language,
|
||||
},
|
||||
hunks: [normalized],
|
||||
}
|
||||
const diffMarkdown = createMemo(() => {
|
||||
return formatDiffMarkdown(props.diffText, props.filePath)
|
||||
})
|
||||
|
||||
const diffPart = createMemo<TextPart>(() => {
|
||||
const part: TextPart = { type: "text", text: diffMarkdown() }
|
||||
if (props.renderCacheKey) {
|
||||
const cached = getToolRenderCache(props.renderCacheKey)
|
||||
if (cached) {
|
||||
part.renderCache = cached
|
||||
}
|
||||
}
|
||||
return part
|
||||
})
|
||||
|
||||
const handleRendered = () => {
|
||||
if (!props.renderCacheKey) return
|
||||
setToolRenderCache(props.renderCacheKey, diffPart().renderCache)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="tool-call-diff-viewer">
|
||||
<Show
|
||||
when={diffData()}
|
||||
fallback={<pre class="tool-call-diff-fallback">{props.diffText}</pre>}
|
||||
>
|
||||
{(data) => (
|
||||
<DiffView
|
||||
data={data()}
|
||||
diffViewMode={props.mode === "split" ? DiffModeEnum.Split : DiffModeEnum.Unified}
|
||||
diffViewTheme={props.theme}
|
||||
diffViewHighlight
|
||||
diffViewWrap={false}
|
||||
diffViewFontSize={13}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<Markdown part={diffPart()} isDark={props.theme === "dark"} onRendered={handleRendered} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,9 @@ interface ToolDisplayItem {
|
||||
key: string
|
||||
toolPart: any
|
||||
messageInfo?: any
|
||||
messageId: string
|
||||
messageVersion: number
|
||||
partVersion: number
|
||||
}
|
||||
|
||||
type DisplayItem = MessageDisplayItem | ToolDisplayItem
|
||||
@@ -191,14 +194,35 @@ interface ToolCacheEntry {
|
||||
item: ToolDisplayItem
|
||||
}
|
||||
|
||||
interface SessionCache {
|
||||
messageItemCache: Map<string, MessageCacheEntry>
|
||||
toolItemCache: Map<string, ToolCacheEntry>
|
||||
}
|
||||
|
||||
const sessionCaches = new Map<string, SessionCache>()
|
||||
|
||||
function getSessionCache(instanceId: string, sessionId: string): SessionCache {
|
||||
const key = `${instanceId}:${sessionId}`
|
||||
let cache = sessionCaches.get(key)
|
||||
if (!cache) {
|
||||
cache = {
|
||||
messageItemCache: new Map(),
|
||||
toolItemCache: new Map(),
|
||||
}
|
||||
sessionCaches.set(key, cache)
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
export default function MessageStream(props: MessageStreamProps) {
|
||||
let containerRef: HTMLDivElement | undefined
|
||||
const [autoScroll, setAutoScroll] = createSignal(true)
|
||||
const [showScrollBottomButton, setShowScrollBottomButton] = createSignal(false)
|
||||
const [showScrollTopButton, setShowScrollTopButton] = createSignal(false)
|
||||
|
||||
let messageItemCache = new Map<string, MessageCacheEntry>()
|
||||
let toolItemCache = new Map<string, ToolCacheEntry>()
|
||||
const sessionCache = getSessionCache(props.instanceId, props.sessionId)
|
||||
let messageItemCache = sessionCache.messageItemCache
|
||||
let toolItemCache = sessionCache.toolItemCache
|
||||
let scrollAnimationFrame: number | null = null
|
||||
let lastKnownScrollTop = 0
|
||||
|
||||
@@ -334,7 +358,6 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
}
|
||||
|
||||
const messageView = createMemo(() => {
|
||||
// Ensure memo reacts to preference changes
|
||||
const showThinking = preferences().showThinkingBlocks
|
||||
|
||||
const items: DisplayItem[] = []
|
||||
@@ -358,7 +381,6 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
const message = props.messages[index]
|
||||
const messageInfo = props.messagesInfo?.get(message.id)
|
||||
|
||||
// If we hit the revert point, stop rendering messages
|
||||
if (props.revert?.messageID && message.id === props.revert.messageID) {
|
||||
break
|
||||
}
|
||||
@@ -367,9 +389,9 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
|
||||
const baseDisplayParts = message.displayParts
|
||||
const displayParts: MessageDisplayParts =
|
||||
baseDisplayParts && baseDisplayParts.showThinking === showThinking
|
||||
? baseDisplayParts
|
||||
: computeDisplayParts(message, showThinking)
|
||||
!baseDisplayParts || baseDisplayParts.showThinking !== showThinking
|
||||
? computeDisplayParts(message, showThinking)
|
||||
: (baseDisplayParts as MessageDisplayParts)
|
||||
|
||||
const combinedParts = displayParts.combined
|
||||
const version = message.version ?? 0
|
||||
@@ -424,6 +446,8 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
for (let toolIndex = 0; toolIndex < displayParts.tool.length; toolIndex++) {
|
||||
const toolPart = displayParts.tool[toolIndex]
|
||||
const toolKey = typeof toolPart?.id === "string" ? toolPart.id : `${message.id}-tool-${toolIndex}`
|
||||
const messageVersion = typeof message.version === "number" ? message.version : 0
|
||||
const partVersion = typeof toolPart?.version === "number" ? toolPart.version : 0
|
||||
|
||||
const toolSignature = createToolSignature(message, toolPart, toolIndex, messageInfo)
|
||||
const contentKey = createToolContentKey(toolPart, messageInfo)
|
||||
@@ -434,6 +458,9 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
...toolEntry.item,
|
||||
toolPart,
|
||||
messageInfo,
|
||||
messageId: message.id,
|
||||
messageVersion,
|
||||
partVersion,
|
||||
}
|
||||
toolEntry.toolPart = toolPart
|
||||
toolEntry.messageInfo = messageInfo
|
||||
@@ -444,8 +471,16 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
newToolCache.set(toolKey, toolEntry)
|
||||
items.push(updatedItem)
|
||||
} else {
|
||||
const cachedItem = toolEntry.item
|
||||
cachedItem.toolPart = toolPart
|
||||
cachedItem.messageInfo = messageInfo
|
||||
cachedItem.messageId = message.id
|
||||
cachedItem.messageVersion = messageVersion
|
||||
cachedItem.partVersion = partVersion
|
||||
toolEntry.toolPart = toolPart
|
||||
toolEntry.messageInfo = messageInfo
|
||||
newToolCache.set(toolKey, toolEntry)
|
||||
items.push(toolEntry.item)
|
||||
items.push(cachedItem)
|
||||
}
|
||||
} else {
|
||||
const toolItem: ToolDisplayItem = {
|
||||
@@ -453,6 +488,9 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
key: toolKey,
|
||||
toolPart,
|
||||
messageInfo,
|
||||
messageId: message.id,
|
||||
messageVersion,
|
||||
partVersion,
|
||||
}
|
||||
console.debug("[ToolCall] create", toolKey, toolPart?.state?.status)
|
||||
newToolCache.set(toolKey, { toolPart, messageInfo, signature: toolSignature, contentKey, item: toolItem })
|
||||
@@ -463,6 +501,8 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
|
||||
messageItemCache = newMessageCache
|
||||
toolItemCache = newToolCache
|
||||
sessionCache.messageItemCache = messageItemCache
|
||||
sessionCache.toolItemCache = toolItemCache
|
||||
|
||||
tokenSegments.push(`items:${items.length}`)
|
||||
|
||||
@@ -681,7 +721,13 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
<ToolCall toolCall={toolPart} toolCallId={item.key} />
|
||||
<ToolCall
|
||||
toolCall={toolPart}
|
||||
toolCallId={item.key}
|
||||
messageId={item.messageId}
|
||||
messageVersion={item.messageVersion}
|
||||
partVersion={item.partVersion}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -47,10 +47,12 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
const minHeight = lineHeight * MIN_TEXTAREA_LINES
|
||||
|
||||
textarea.style.height = "auto"
|
||||
const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), MAX_TEXTAREA_HEIGHT)
|
||||
const scrollHeight = textarea.scrollHeight
|
||||
const newHeight = Math.min(Math.max(scrollHeight, minHeight), MAX_TEXTAREA_HEIGHT)
|
||||
textarea.style.height = newHeight + "px"
|
||||
}
|
||||
|
||||
|
||||
const attachments = () => getAttachments(props.instanceId, props.sessionId)
|
||||
const instanceAgents = () => agents().get(props.instanceId) || []
|
||||
|
||||
@@ -309,7 +311,9 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
const textarea = textareaRef
|
||||
if (!textarea) return
|
||||
if (!textarea) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === "Backspace" || e.key === "Delete") {
|
||||
const cursorPos = textarea.selectionStart
|
||||
@@ -478,7 +482,6 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
setTimeout(() => {
|
||||
adjustTextareaHeight(textarea)
|
||||
}, 0)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,11 +500,9 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
|
||||
try {
|
||||
await addToHistory(props.instanceFolder, text)
|
||||
|
||||
const updated = await getHistory(props.instanceFolder)
|
||||
setHistory(updated)
|
||||
setHistoryIndex(-1)
|
||||
|
||||
await props.onSend(text, currentAttachments)
|
||||
} catch (error) {
|
||||
console.error("Failed to send message:", error)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { keyboardRegistry } from "../lib/keyboard-registry"
|
||||
import { formatShortcut } from "../lib/keyboard-utils"
|
||||
import { showToastNotification } from "../lib/notifications"
|
||||
|
||||
|
||||
interface SessionListProps {
|
||||
instanceId: string
|
||||
sessions: Map<string, Session>
|
||||
@@ -51,6 +52,10 @@ const SessionList: Component<SessionListProps> = (props) => {
|
||||
const [startWidth, setStartWidth] = createSignal(DEFAULT_WIDTH)
|
||||
const infoShortcut = keyboardRegistry.get("switch-to-info")
|
||||
|
||||
const selectSession = (sessionId: string) => {
|
||||
props.onSelect(sessionId)
|
||||
}
|
||||
|
||||
let mouseMoveHandler: ((event: MouseEvent) => void) | null = null
|
||||
let mouseUpHandler: (() => void) | null = null
|
||||
let touchMoveHandler: ((event: TouchEvent) => void) | null = null
|
||||
@@ -246,7 +251,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
||||
<div class="session-list-item group">
|
||||
<button
|
||||
class={`session-item-base ${props.activeSessionId === "info" ? "session-item-active" : "session-item-inactive"}`}
|
||||
onClick={() => props.onSelect("info")}
|
||||
onClick={() => selectSession("info")}
|
||||
title="Instance Info"
|
||||
role="button"
|
||||
aria-selected={props.activeSessionId === "info"}
|
||||
@@ -280,7 +285,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
||||
<div class="session-list-item group">
|
||||
<button
|
||||
class={`session-item-base ${isActive() ? "session-item-active" : "session-item-inactive"}`}
|
||||
onClick={() => props.onSelect(id)}
|
||||
onClick={() => selectSession(id)}
|
||||
title={title()}
|
||||
role="button"
|
||||
aria-selected={isActive()}
|
||||
@@ -338,7 +343,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
||||
<div class="session-list-item group">
|
||||
<button
|
||||
class={`session-item-base ${isActive() ? "session-item-active" : "session-item-inactive"}`}
|
||||
onClick={() => props.onSelect(id)}
|
||||
onClick={() => selectSession(id)}
|
||||
title={title()}
|
||||
role="button"
|
||||
aria-selected={isActive()}
|
||||
|
||||
@@ -5,12 +5,24 @@ import { ToolCallDiffViewer } from "./diff-viewer"
|
||||
import { useTheme } from "../lib/theme"
|
||||
import { getLanguageFromPath } from "../lib/markdown"
|
||||
import { isRenderableDiffText } from "../lib/diff-utils"
|
||||
import { preferences, setDiffViewMode, type DiffViewMode } from "../stores/preferences"
|
||||
import { getToolRenderCache, setToolRenderCache } from "../lib/tool-render-cache"
|
||||
import type { TextPart } from "../types/message"
|
||||
|
||||
|
||||
const toolScrollState = new Map<string, { scrollTop: number; atBottom: boolean }>()
|
||||
|
||||
function makeRenderCacheKey(
|
||||
baseId?: string | null,
|
||||
messageId?: string,
|
||||
messageVersion?: number,
|
||||
partVersion?: number,
|
||||
) {
|
||||
if (!baseId && !messageId) return undefined
|
||||
const suffix = `${messageVersion ?? 0}:${partVersion ?? 0}`
|
||||
const keyBase = baseId || messageId || "tool"
|
||||
return `${keyBase}::${suffix}`
|
||||
}
|
||||
|
||||
function updateScrollState(id: string, element: HTMLElement) {
|
||||
if (!id) return
|
||||
const distanceFromBottom = element.scrollHeight - (element.scrollTop + element.clientHeight)
|
||||
@@ -44,6 +56,9 @@ function restoreScrollState(id: string, element: HTMLElement) {
|
||||
interface ToolCallProps {
|
||||
toolCall: any
|
||||
toolCallId?: string
|
||||
messageId?: string
|
||||
messageVersion?: number
|
||||
partVersion?: number
|
||||
}
|
||||
|
||||
function getToolIcon(tool: string): string {
|
||||
@@ -367,11 +382,9 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
}
|
||||
|
||||
function renderDiffTool(payload: DiffPayload) {
|
||||
const diffMode = () => (preferences().diffViewMode || "split") as DiffViewMode
|
||||
|
||||
const handleModeChange = (mode: DiffViewMode) => {
|
||||
setDiffViewMode(mode)
|
||||
}
|
||||
const relativePath = payload.filePath ? getRelativePath(payload.filePath) : ""
|
||||
const toolbarLabel = relativePath ? `Diff · ${relativePath}` : "Diff"
|
||||
const cacheKey = makeRenderCacheKey(toolCallId(), props.messageId, props.messageVersion, props.partVersion)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -379,32 +392,14 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
ref={(element) => initializeScrollContainer(element)}
|
||||
onScroll={(event) => updateScrollState(toolCallId(), event.currentTarget)}
|
||||
>
|
||||
<div class="tool-call-diff-toolbar" role="group" aria-label="Diff view mode">
|
||||
<span class="tool-call-diff-toolbar-label">Diff view</span>
|
||||
<div class="tool-call-diff-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class={`tool-call-diff-mode-button${diffMode() === "split" ? " active" : ""}`}
|
||||
aria-pressed={diffMode() === "split"}
|
||||
onClick={() => handleModeChange("split")}
|
||||
>
|
||||
Split
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`tool-call-diff-mode-button${diffMode() === "unified" ? " active" : ""}`}
|
||||
aria-pressed={diffMode() === "unified"}
|
||||
onClick={() => handleModeChange("unified")}
|
||||
>
|
||||
Unified
|
||||
</button>
|
||||
</div>
|
||||
<div class="tool-call-diff-toolbar">
|
||||
<span class="tool-call-diff-toolbar-label">{toolbarLabel}</span>
|
||||
</div>
|
||||
<ToolCallDiffViewer
|
||||
diffText={payload.diffText}
|
||||
filePath={payload.filePath}
|
||||
theme={isDark() ? "dark" : "light"}
|
||||
mode={diffMode()}
|
||||
renderCacheKey={cacheKey}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -419,8 +414,22 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
const isLarge = toolName === "edit" || toolName === "write" || toolName === "patch"
|
||||
const messageClass = `message-text tool-call-markdown${isLarge ? " tool-call-markdown-large" : ""}`
|
||||
const disableHighlight = state?.status === "running"
|
||||
const cacheKey = makeRenderCacheKey(toolCallId(), props.messageId, props.messageVersion, props.partVersion)
|
||||
|
||||
const markdownPart: TextPart = { type: "text", text: content }
|
||||
if (cacheKey) {
|
||||
const cached = getToolRenderCache(cacheKey)
|
||||
if (cached) {
|
||||
markdownPart.renderCache = cached
|
||||
}
|
||||
}
|
||||
|
||||
const handleMarkdownRendered = () => {
|
||||
if (cacheKey) {
|
||||
setToolRenderCache(cacheKey, markdownPart.renderCache)
|
||||
}
|
||||
handleScrollRendered()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -432,7 +441,7 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
part={markdownPart}
|
||||
isDark={isDark()}
|
||||
disableHighlight={disableHighlight}
|
||||
onRendered={handleScrollRendered}
|
||||
onRendered={handleMarkdownRendered}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
22
src/lib/tool-render-cache.ts
Normal file
22
src/lib/tool-render-cache.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { RenderCache } from "../types/message"
|
||||
|
||||
const toolRenderCache = new Map<string, RenderCache>()
|
||||
|
||||
export function getToolRenderCache(key?: string | null): RenderCache | undefined {
|
||||
if (!key) return undefined
|
||||
return toolRenderCache.get(key)
|
||||
}
|
||||
|
||||
export function setToolRenderCache(key: string | undefined | null, cache?: RenderCache): void {
|
||||
if (!key) return
|
||||
if (cache) {
|
||||
toolRenderCache.set(key, cache)
|
||||
} else {
|
||||
toolRenderCache.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
export function clearToolRenderCache(key?: string | null): void {
|
||||
if (!key) return
|
||||
toolRenderCache.delete(key)
|
||||
}
|
||||
@@ -17,6 +17,27 @@ interface SessionInfo {
|
||||
contextUsageTokens: number
|
||||
}
|
||||
|
||||
interface SessionForkResponse {
|
||||
id: string
|
||||
title?: string
|
||||
parentID?: string | null
|
||||
agent?: string
|
||||
model?: {
|
||||
providerID?: string
|
||||
modelID?: string
|
||||
}
|
||||
time?: {
|
||||
created?: number
|
||||
updated?: number
|
||||
}
|
||||
revert?: {
|
||||
messageID?: string
|
||||
partID?: string
|
||||
snapshot?: string
|
||||
diff?: string
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_MODEL_OUTPUT_LIMIT = 32_000
|
||||
const ALLOWED_TOAST_VARIANTS = new Set<ToastVariant>(["info", "success", "warning", "error"])
|
||||
|
||||
@@ -103,13 +124,6 @@ const [loading, setLoading] = createSignal({
|
||||
|
||||
const [messagesLoaded, setMessagesLoaded] = createSignal<Map<string, Set<string>>>(new Map())
|
||||
const [sessionInfoByInstance, setSessionInfoByInstance] = createSignal<Map<string, Map<string, SessionInfo>>>(new Map())
|
||||
if (typeof globalThis !== "undefined") {
|
||||
const debugGlobal = globalThis as any
|
||||
debugGlobal.__OPENCODE_DEBUG__ = {
|
||||
...(debugGlobal.__OPENCODE_DEBUG__ ?? {}),
|
||||
getSessions: () => sessions(),
|
||||
}
|
||||
}
|
||||
|
||||
// Message index cache structure: instanceId -> sessionId -> { messageIndex, partIndex }
|
||||
const sessionIndexes = new Map<
|
||||
@@ -718,8 +732,8 @@ async function forkSession(
|
||||
throw new Error("Failed to fork session: No data returned")
|
||||
}
|
||||
|
||||
const info = response.data
|
||||
const forkedSession: Session = {
|
||||
const info = response.data as SessionForkResponse
|
||||
const forkedSession = {
|
||||
id: info.id,
|
||||
instanceId,
|
||||
title: info.title || "Forked Session",
|
||||
@@ -743,7 +757,7 @@ async function forkSession(
|
||||
: undefined,
|
||||
messages: [],
|
||||
messagesInfo: new Map(),
|
||||
}
|
||||
} as unknown as Session
|
||||
|
||||
setSessions((prev) => {
|
||||
const next = new Map(prev)
|
||||
|
||||
Reference in New Issue
Block a user