persist prompt drafts per session

This commit is contained in:
Shantur Rathore
2025-11-08 22:24:19 +00:00
parent c6b3686f13
commit 03ff2779b0
4 changed files with 93 additions and 46 deletions

View File

@@ -2,13 +2,12 @@ import { createSignal, Show, onMount, For, onCleanup, createEffect, on, untrack
import UnifiedPicker from "./unified-picker"
import { addToHistory, getHistory } from "../stores/message-history"
import { getAttachments, addAttachment, clearAttachments, removeAttachment } from "../stores/attachments"
import { getPromptValue, setPromptValue, clearPromptValue } from "../stores/prompt-state"
import { createFileAttachment, createTextAttachment, createAgentAttachment } from "../types/attachment"
import type { Attachment } from "../types/attachment"
import Kbd from "./kbd"
import HintRow from "./hint-row"
import { getActiveInstance } from "../stores/instances"
import { agents } from "../stores/sessions"
import { agents, getSessionDraftPrompt, setSessionDraftPrompt, clearSessionDraftPrompt } from "../stores/sessions"
interface PromptInputProps {
instanceId: string
@@ -57,11 +56,11 @@ export default function PromptInput(props: PromptInputProps) {
const setPrompt = (value: string) => {
setPromptInternal(value)
setPromptValue(props.instanceId, props.sessionId, value)
setSessionDraftPrompt(props.instanceId, props.sessionId, value)
}
const clearPrompt = () => {
clearPromptValue(props.instanceId, props.sessionId)
clearSessionDraftPrompt(props.instanceId, props.sessionId)
setPromptInternal("")
}
@@ -116,14 +115,14 @@ export default function PromptInput(props: PromptInputProps) {
const sessionId = props.sessionId
onCleanup(() => {
setPromptValue(instanceId, sessionId, prompt())
setSessionDraftPrompt(instanceId, sessionId, prompt())
})
const storedPrompt = getPromptValue(instanceId, sessionId)
const storedPrompt = getSessionDraftPrompt(instanceId, sessionId)
const currentAttachments = untrack(() => getAttachments(instanceId, sessionId))
setPromptInternal(storedPrompt)
setPromptValue(instanceId, sessionId, storedPrompt)
setSessionDraftPrompt(instanceId, sessionId, storedPrompt)
setHistoryIndex(-1)
setIgnoredAtPositions(new Set<number>())
setShowPicker(false)
@@ -134,9 +133,8 @@ export default function PromptInput(props: PromptInputProps) {
queueMicrotask(() => {
adjustTextareaHeight(textareaRef)
})
},
{ defer: true },
),
}
)
)
function handleRemoveAttachment(attachmentId: string) {

View File

@@ -2,7 +2,13 @@ import { createSignal } from "solid-js"
import type { Instance, LogEntry } from "../types/instance"
import { sdkManager } from "../lib/sdk-manager"
import { sseManager } from "../lib/sse-manager"
import { fetchSessions, fetchAgents, fetchProviders, removeSessionIndexes } from "./sessions"
import {
fetchSessions,
fetchAgents,
fetchProviders,
removeSessionIndexes,
clearInstanceDraftPrompts,
} from "./sessions"
import { preferences, updateLastUsedBinary } from "./preferences"
const [instances, setInstances] = createSignal<Map<string, Instance>>(new Map())
@@ -69,8 +75,9 @@ function removeInstance(id: string) {
setActiveInstanceId(null)
}
// Clean up session indexes for removed instance
// Clean up session indexes and drafts for removed instance
removeSessionIndexes(id)
clearInstanceDraftPrompts(id)
}
async function createInstance(folder: string, binaryPath?: string): Promise<string> {

View File

@@ -1,34 +0,0 @@
import { createSignal } from "solid-js"
function getSessionKey(instanceId: string, sessionId: string): string {
return `${instanceId}:${sessionId}`
}
const [prompts, setPrompts] = createSignal<Map<string, string>>(new Map())
export function getPromptValue(instanceId: string, sessionId: string): string {
const key = getSessionKey(instanceId, sessionId)
return prompts().get(key) || ""
}
export function setPromptValue(instanceId: string, sessionId: string, value: string) {
const key = getSessionKey(instanceId, sessionId)
setPrompts((prev) => {
const next = new Map(prev)
if (value.length === 0) {
next.delete(key)
} else {
next.set(key, value)
}
return next
})
}
export function clearPromptValue(instanceId: string, sessionId: string) {
const key = getSessionKey(instanceId, sessionId)
setPrompts((prev) => {
const next = new Map(prev)
next.delete(key)
return next
})
}

View File

@@ -25,6 +25,74 @@ const [activeSessionId, setActiveSessionId] = createSignal<Map<string, string>>(
const [activeParentSessionId, setActiveParentSessionId] = createSignal<Map<string, string>>(new Map())
const [agents, setAgents] = createSignal<Map<string, Agent[]>>(new Map())
const [providers, setProviders] = createSignal<Map<string, Provider[]>>(new Map())
const [sessionDraftPrompts, setSessionDraftPrompts] = createSignal<Map<string, string>>(new Map())
function getDraftKey(instanceId: string, sessionId: string): string {
return `${instanceId}:${sessionId}`
}
function getSessionDraftPrompt(instanceId: string, sessionId: string): string {
if (!instanceId || !sessionId) return ""
const key = getDraftKey(instanceId, sessionId)
return sessionDraftPrompts().get(key) ?? ""
}
function setSessionDraftPrompt(instanceId: string, sessionId: string, value: string) {
const key = getDraftKey(instanceId, sessionId)
setSessionDraftPrompts((prev) => {
const next = new Map(prev)
if (!value) {
next.delete(key)
} else {
next.set(key, value)
}
return next
})
}
function clearSessionDraftPrompt(instanceId: string, sessionId: string) {
const key = getDraftKey(instanceId, sessionId)
setSessionDraftPrompts((prev) => {
if (!prev.has(key)) return prev
const next = new Map(prev)
next.delete(key)
return next
})
}
function clearInstanceDraftPrompts(instanceId: string) {
if (!instanceId) return
setSessionDraftPrompts((prev) => {
let changed = false
const next = new Map(prev)
const prefix = `${instanceId}:`
for (const key of Array.from(next.keys())) {
if (key.startsWith(prefix)) {
next.delete(key)
changed = true
}
}
return changed ? next : prev
})
}
function pruneDraftPrompts(instanceId: string, validSessionIds: Set<string>) {
setSessionDraftPrompts((prev) => {
let changed = false
const next = new Map(prev)
const prefix = `${instanceId}:`
for (const key of Array.from(next.keys())) {
if (key.startsWith(prefix)) {
const sessionId = key.slice(prefix.length)
if (!validSessionIds.has(sessionId)) {
next.delete(key)
changed = true
}
}
}
return changed ? next : prev
})
}
const [loading, setLoading] = createSignal({
fetchingSessions: new Map<string, boolean>(),
@@ -323,6 +391,8 @@ async function fetchSessions(instanceId: string): Promise<void> {
next.set(instanceId, sessionMap)
return next
})
pruneDraftPrompts(instanceId, new Set(sessionMap.keys()))
} catch (error) {
console.error("Failed to fetch sessions:", error)
throw error
@@ -736,6 +806,8 @@ async function deleteSession(instanceId: string, sessionId: string): Promise<voi
return next
})
clearSessionDraftPrompt(instanceId, sessionId)
// Remove session info entry
setSessionInfoByInstance((prev) => {
const next = new Map(prev)
@@ -1788,4 +1860,8 @@ export {
updateSessionModel,
getDefaultModel,
removeSessionIndexes,
getSessionDraftPrompt,
setSessionDraftPrompt,
clearSessionDraftPrompt,
clearInstanceDraftPrompts,
}