feat: plan 18

This commit is contained in:
thanhnv
2026-07-10 11:28:14 +09:00
parent fafccc47ad
commit d882a9dc23
15 changed files with 934 additions and 25 deletions
@@ -139,9 +139,28 @@ export interface ChatAnswer {
artifact_scan?: { ok: boolean; output: string };
tool_output_scan?: { ok: boolean; output: string };
};
synthesis?: {
mode: 'deterministic' | 'model' | string;
reason?: string;
provider?: string;
model?: string;
class?: string;
input_tokens?: number;
output_tokens?: number;
cost_source?: string;
};
actor: SettingsActor;
}
export type ChatStreamPhase = Partial<ChatAnswer> & {
phase?: 'draft' | 'final';
answer: string;
decision: string;
certified: boolean;
mode: string;
sources: ChatSource[];
};
export interface ChatAction {
id: string;
label: string;
@@ -219,6 +238,44 @@ export const api = {
post<{ proposal: any; applied: any; audit_verify: { ok: boolean; output: string } }>('approvals/decide', body, actorHeaders(actor)),
askChat: (actor: SettingsActor, body: { message: string; chatId?: string; agentId?: string; skillId?: string; delegationLevel?: number }) =>
post<ChatAnswer>('chat/ask', body, actorHeaders(actor)),
askChatStream: async (
actor: SettingsActor,
body: { message: string; chatId?: string; agentId?: string; skillId?: string; delegationLevel?: number },
onPhase: (phase: ChatStreamPhase) => void,
): Promise<void> => {
const base = import.meta.env.VITE_API_BASE_URL ?? '/api/v1';
const resp = await fetch(`${base}/chat/ask/stream`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...actorHeaders(actor) },
body: JSON.stringify(body),
});
if (!resp.ok || !resp.body) {
throw new Error(`stream failed: ${resp.status}`);
}
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buf = '';
const flush = (line: string) => {
const trimmed = line.trim();
if (!trimmed) return;
try {
onPhase(JSON.parse(trimmed) as ChatStreamPhase);
} catch {
/* ignore partial/non-JSON chunk */
}
};
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
let idx: number;
while ((idx = buf.indexOf('\n')) >= 0) {
flush(buf.slice(0, idx));
buf = buf.slice(idx + 1);
}
}
flush(buf);
},
verifyChatAudit: () => get<{ ok: boolean; output: string }>('chat/audit/verify'),
replayChat: (chatId = '', turnId = '', tenant = '') => get<ChatReplay>(`chat/replay?chatId=${encodeURIComponent(chatId)}&turnId=${encodeURIComponent(turnId)}&tenant=${encodeURIComponent(tenant)}`),
chatActions: (actor: SettingsActor) => getWithHeaders<{ success: boolean; actions: ChatAction[] }>('chat/actions', actorHeaders(actor)),