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),
|
getChatLines(selectedId),
|
||||||
])
|
])
|
||||||
if (!alive) return
|
if (!alive) return
|
||||||
setCfg(c)
|
setCfg(c as Record<string, string>)
|
||||||
setMessages(parseChat(lines))
|
setMessages(parseChat(lines))
|
||||||
} catch {
|
} catch {
|
||||||
// session may have been removed
|
// session may have been removed
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
const renderedHtml = useMemo(() =>
|
const renderedHtml = useMemo(() =>
|
||||||
messages.map(msg =>
|
messages.map(msg =>
|
||||||
(msg.role === 'user' || msg.role === 'assistant')
|
(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]
|
[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
|
id: string
|
||||||
state: string
|
state: string
|
||||||
cwd: string
|
|
||||||
backend: string
|
|
||||||
model: 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[]> {
|
// Compat alias for components still using the old name.
|
||||||
const r = await fetch('/s/idx')
|
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 []
|
if (!r.ok) return []
|
||||||
const data = await r.json()
|
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>> {
|
export async function getSessionCfg(id: string): Promise<SessionConfig> {
|
||||||
const r = await fetch(`/s/${id}/cfg`)
|
const r = await fetch(`/sessions/${id}/config`)
|
||||||
if (!r.ok) throw new Error(`cfg ${r.status}`)
|
if (!r.ok) throw new Error(`config ${r.status}`)
|
||||||
const data = await r.json()
|
const data = await r.json()
|
||||||
return (data && typeof data === 'object' && !Array.isArray(data))
|
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[]> {
|
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 []
|
if (!r.ok) return []
|
||||||
const text = await r.text()
|
const text = await r.text()
|
||||||
if (!text.trim()) return []
|
if (!text.trim()) return []
|
||||||
return text.trimEnd().split('\n')
|
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> {
|
export async function sendPrompt(id: string, prompt: string): Promise<void> {
|
||||||
await fetch(`/s/${id}/prompt`, {
|
await fetch(`/sessions/${id}/submit`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ prompt }),
|
body: JSON.stringify({ prompt }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendCtl(id: string, ctl: string): Promise<void> {
|
export async function interruptSession(id: string): Promise<void> {
|
||||||
await fetch(`/s/${id}/ctl`, {
|
await fetch(`/sessions/${id}/interrupt`, { method: 'POST' })
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ ctl }),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSession(params: {
|
export async function createSession(params: {
|
||||||
name?: string
|
cwd?: string
|
||||||
cwd: string
|
backend?: string
|
||||||
backend: string
|
model?: string
|
||||||
model: string
|
agent?: string
|
||||||
agent: string
|
}): Promise<string | null> {
|
||||||
[key: string]: string | undefined
|
const r = await fetch('/sessions', {
|
||||||
}): Promise<void> {
|
|
||||||
await fetch('/s/new', {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(params),
|
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'
|
const MODELS_CACHE_KEY = 'ollie:models'
|
||||||
|
|
||||||
export function loadModelsCache(): Record<string, string[]> {
|
export function loadModelsCache(): Record<string, string[]> {
|
||||||
|
|
@ -74,57 +163,17 @@ export function loadModelsCache(): Record<string, string[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchModels(): Promise<Record<string, string[]>> {
|
export async function fetchModels(): Promise<Record<string, string[]>> {
|
||||||
const r = await fetch('/models')
|
// In dbus mode we don't have a global models endpoint.
|
||||||
if (!r.ok) return loadModelsCache()
|
// Return cached data; individual session model lists are fetched via getModels().
|
||||||
const data: {backend: string, models: string[]}[] = await r.json()
|
return loadModelsCache()
|
||||||
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[]) : []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAgents(): Promise<string[]> {
|
// --- 9P browsing stubs (not available in dbus mode) ---
|
||||||
const r = await fetch('/a/')
|
|
||||||
if (!r.ok) return []
|
export async function browse9p(_path: string): Promise<any> {
|
||||||
const data = await r.json()
|
return null
|
||||||
if (!Array.isArray(data)) return []
|
|
||||||
return data
|
|
||||||
.filter((e: any) => e.name?.endsWith('.json'))
|
|
||||||
.map((e: any) => e.name.replace(/\.json$/, ''))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function killSession(id: string): Promise<void> {
|
export async function write9p(_path: string, _content: string): Promise<boolean> {
|
||||||
await fetch(`/s/${id}`, { method: 'DELETE' })
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
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 }),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,10 @@ export default defineConfig({
|
||||||
base: './',
|
base: './',
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/s/': 'http://localhost:8080',
|
'/sessions': 'http://localhost:8011',
|
||||||
'/a/': 'http://localhost:8080',
|
'/backends': 'http://localhost:8011',
|
||||||
'/m/': 'http://localhost:8080',
|
'/agents': 'http://localhost:8011',
|
||||||
'/t/': 'http://localhost:8080',
|
'/openapi.json': 'http://localhost:8011',
|
||||||
'/sk/': 'http://localhost:8080',
|
|
||||||
'/backends': 'http://localhost:8080',
|
|
||||||
'/models': 'http://localhost:8080',
|
|
||||||
'/open': 'http://localhost:8080',
|
|
||||||
'/openapi.json': 'http://localhost:8080',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Reference in New Issue