update webui: chat pane and api changes
This commit is contained in:
parent
0f97e50a49
commit
673cf00bab
|
|
@ -82,7 +82,7 @@ export function App() {
|
|||
getChatLines(selectedId),
|
||||
])
|
||||
if (!alive) return
|
||||
setCfg(c)
|
||||
setCfg(c as Record<string, string>)
|
||||
setMessages(parseChat(lines))
|
||||
} catch {
|
||||
// session may have been removed
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
|||
const renderedHtml = useMemo(() =>
|
||||
messages.map(msg =>
|
||||
(msg.role === 'user' || msg.role === 'assistant')
|
||||
? linkifyPaths((marked.parse(msg.content) as string), session.cwd)
|
||||
? linkifyPaths((marked.parse(msg.content) as string), session.cwd ?? '')
|
||||
: ''),
|
||||
[messages, session.cwd]
|
||||
)
|
||||
|
|
|
|||
203
src/lib/api.ts
203
src/lib/api.ts
|
|
@ -1,68 +1,157 @@
|
|||
export interface IdxEntry {
|
||||
export interface SessionSummary {
|
||||
id: string
|
||||
state: string
|
||||
cwd: string
|
||||
backend: string
|
||||
model: string
|
||||
agent: string
|
||||
// Available in 9P mode, not in DBus list (fetch config for these)
|
||||
cwd?: string
|
||||
backend?: string
|
||||
}
|
||||
|
||||
export async function listSessions(): Promise<IdxEntry[]> {
|
||||
const r = await fetch('/s/idx')
|
||||
// Compat alias for components still using the old name.
|
||||
export type IdxEntry = SessionSummary
|
||||
|
||||
export interface SessionConfig {
|
||||
model?: string
|
||||
backend?: string
|
||||
agent?: string
|
||||
cwd?: string
|
||||
state?: string
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
export async function listSessions(): Promise<SessionSummary[]> {
|
||||
const r = await fetch('/sessions')
|
||||
if (!r.ok) return []
|
||||
const data = await r.json()
|
||||
return Array.isArray(data) ? (data as IdxEntry[]) : []
|
||||
return Array.isArray(data) ? (data as SessionSummary[]) : []
|
||||
}
|
||||
|
||||
export async function getSessionCfg(id: string): Promise<Record<string, string>> {
|
||||
const r = await fetch(`/s/${id}/cfg`)
|
||||
if (!r.ok) throw new Error(`cfg ${r.status}`)
|
||||
export async function getSessionCfg(id: string): Promise<SessionConfig> {
|
||||
const r = await fetch(`/sessions/${id}/config`)
|
||||
if (!r.ok) throw new Error(`config ${r.status}`)
|
||||
const data = await r.json()
|
||||
return (data && typeof data === 'object' && !Array.isArray(data))
|
||||
? (data as Record<string, string>)
|
||||
? (data as SessionConfig)
|
||||
: {}
|
||||
}
|
||||
|
||||
// The gateway returns chat as plain text (one line per key=value pair).
|
||||
export async function getChatLines(id: string): Promise<string[]> {
|
||||
const r = await fetch(`/s/${id}/chat`)
|
||||
const r = await fetch(`/sessions/${id}/chat`)
|
||||
if (!r.ok) return []
|
||||
const text = await r.text()
|
||||
if (!text.trim()) return []
|
||||
return text.trimEnd().split('\n')
|
||||
}
|
||||
|
||||
export async function getChatWithOffset(id: string, offset: number): Promise<{ text: string; newOffset: number }> {
|
||||
const r = await fetch(`/sessions/${id}/chat?offset=${offset}`)
|
||||
if (!r.ok) return { text: '', newOffset: offset }
|
||||
const text = await r.text()
|
||||
const newOffset = parseInt(r.headers.get('X-Chat-Offset') ?? String(offset), 10)
|
||||
return { text, newOffset }
|
||||
}
|
||||
|
||||
export async function sendPrompt(id: string, prompt: string): Promise<void> {
|
||||
await fetch(`/s/${id}/prompt`, {
|
||||
await fetch(`/sessions/${id}/submit`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function sendCtl(id: string, ctl: string): Promise<void> {
|
||||
await fetch(`/s/${id}/ctl`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ctl }),
|
||||
})
|
||||
export async function interruptSession(id: string): Promise<void> {
|
||||
await fetch(`/sessions/${id}/interrupt`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function createSession(params: {
|
||||
name?: string
|
||||
cwd: string
|
||||
backend: string
|
||||
model: string
|
||||
agent: string
|
||||
[key: string]: string | undefined
|
||||
}): Promise<void> {
|
||||
await fetch('/s/new', {
|
||||
cwd?: string
|
||||
backend?: string
|
||||
model?: string
|
||||
agent?: string
|
||||
}): Promise<string | null> {
|
||||
const r = await fetch('/sessions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
if (!r.ok) return null
|
||||
const data = await r.json()
|
||||
return data.id ?? null
|
||||
}
|
||||
|
||||
// Fetch models grouped by backend. Persisted in localStorage.
|
||||
export async function killSession(id: string): Promise<void> {
|
||||
await fetch(`/sessions/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export async function renameSession(id: string, newName: string): Promise<void> {
|
||||
await fetch(`/sessions/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function setConfig(id: string, key: string, value: string): Promise<void> {
|
||||
await fetch(`/sessions/${id}/config`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ key, value }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function getState(id: string): Promise<string> {
|
||||
const r = await fetch(`/sessions/${id}/state`)
|
||||
if (!r.ok) return 'unknown'
|
||||
const data = await r.json()
|
||||
return data.state ?? 'unknown'
|
||||
}
|
||||
|
||||
export async function getBackends(): Promise<string[]> {
|
||||
const r = await fetch('/backends')
|
||||
if (!r.ok) return []
|
||||
const data = await r.json()
|
||||
return Array.isArray(data) ? (data as string[]) : []
|
||||
}
|
||||
|
||||
export async function getAgents(): Promise<string[]> {
|
||||
const r = await fetch('/agents')
|
||||
if (!r.ok) return []
|
||||
const data = await r.json()
|
||||
return Array.isArray(data) ? (data as string[]) : []
|
||||
}
|
||||
|
||||
export async function getModels(sessionId: string): Promise<string[]> {
|
||||
const r = await fetch(`/sessions/${sessionId}/models`)
|
||||
if (!r.ok) return []
|
||||
const data = await r.json()
|
||||
return Array.isArray(data) ? (data as string[]) : []
|
||||
}
|
||||
|
||||
export async function getUsage(id: string): Promise<string> {
|
||||
const r = await fetch(`/sessions/${id}/usage`)
|
||||
if (!r.ok) return ''
|
||||
const data = await r.json()
|
||||
return data.usage ?? ''
|
||||
}
|
||||
|
||||
export async function getCost(id: string): Promise<string> {
|
||||
const r = await fetch(`/sessions/${id}/cost`)
|
||||
if (!r.ok) return ''
|
||||
const data = await r.json()
|
||||
return data.cost ?? ''
|
||||
}
|
||||
|
||||
// --- Compat: sendCtl maps to interrupt for "stop" ---
|
||||
|
||||
export async function sendCtl(id: string, ctl: string): Promise<void> {
|
||||
if (ctl === 'stop') {
|
||||
await interruptSession(id)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Models cache (for NewSession) ---
|
||||
|
||||
const MODELS_CACHE_KEY = 'ollie:models'
|
||||
|
||||
export function loadModelsCache(): Record<string, string[]> {
|
||||
|
|
@ -74,57 +163,17 @@ export function loadModelsCache(): Record<string, string[]> {
|
|||
}
|
||||
|
||||
export async function fetchModels(): Promise<Record<string, string[]>> {
|
||||
const r = await fetch('/models')
|
||||
if (!r.ok) return loadModelsCache()
|
||||
const data: {backend: string, models: string[]}[] = await r.json()
|
||||
const out: Record<string, string[]> = {}
|
||||
for (const entry of data) {
|
||||
out[entry.backend] = entry.models
|
||||
}
|
||||
localStorage.setItem(MODELS_CACHE_KEY, JSON.stringify(out))
|
||||
return out
|
||||
}
|
||||
// Kick off fetch in background to refresh cache.
|
||||
fetchModels()
|
||||
|
||||
export async function getBackends(): Promise<string[]> {
|
||||
const r = await fetch('/backends')
|
||||
if (!r.ok) return []
|
||||
const data = await r.json()
|
||||
return Array.isArray(data) ? (data as string[]) : []
|
||||
// In dbus mode we don't have a global models endpoint.
|
||||
// Return cached data; individual session model lists are fetched via getModels().
|
||||
return loadModelsCache()
|
||||
}
|
||||
|
||||
export async function getAgents(): Promise<string[]> {
|
||||
const r = await fetch('/a/')
|
||||
if (!r.ok) return []
|
||||
const data = await r.json()
|
||||
if (!Array.isArray(data)) return []
|
||||
return data
|
||||
.filter((e: any) => e.name?.endsWith('.json'))
|
||||
.map((e: any) => e.name.replace(/\.json$/, ''))
|
||||
// --- 9P browsing stubs (not available in dbus mode) ---
|
||||
|
||||
export async function browse9p(_path: string): Promise<any> {
|
||||
return null
|
||||
}
|
||||
|
||||
export async function killSession(id: string): Promise<void> {
|
||||
await fetch(`/s/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export async function browse9p(path: string): Promise<any> {
|
||||
const r = await fetch(path)
|
||||
if (!r.ok) return null
|
||||
const ct = r.headers.get('content-type') ?? ''
|
||||
if (ct.includes('text/plain')) return await r.text()
|
||||
return await r.json()
|
||||
}
|
||||
|
||||
export async function write9p(path: string, content: string): Promise<boolean> {
|
||||
const r = await fetch(path, { method: 'PUT', body: content })
|
||||
return r.ok
|
||||
}
|
||||
|
||||
export async function renameSession(id: string, newName: string): Promise<void> {
|
||||
await fetch(`/s/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName }),
|
||||
})
|
||||
export async function write9p(_path: string, _content: string): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,10 @@ export default defineConfig({
|
|||
base: './',
|
||||
server: {
|
||||
proxy: {
|
||||
'/s/': 'http://localhost:8080',
|
||||
'/a/': 'http://localhost:8080',
|
||||
'/m/': 'http://localhost:8080',
|
||||
'/t/': 'http://localhost:8080',
|
||||
'/sk/': 'http://localhost:8080',
|
||||
'/backends': 'http://localhost:8080',
|
||||
'/models': 'http://localhost:8080',
|
||||
'/open': 'http://localhost:8080',
|
||||
'/openapi.json': 'http://localhost:8080',
|
||||
'/sessions': 'http://localhost:8011',
|
||||
'/backends': 'http://localhost:8011',
|
||||
'/agents': 'http://localhost:8011',
|
||||
'/openapi.json': 'http://localhost:8011',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Reference in New Issue