refactor(pilot-chat): stream native ai sdk reasoning parts
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
MeQueryDocument,
|
||||
} from "~~/graphql/generated";
|
||||
import { Chat as AiChat } from "@ai-sdk/vue";
|
||||
import { DefaultChatTransport, isTextUIPart, type DataUIPart, type UIMessage } from "ai";
|
||||
import { DefaultChatTransport, isReasoningUIPart, isTextUIPart, type UIMessage } from "ai";
|
||||
import { isVoiceCaptureSupported, transcribeAudioBlob } from "~/composables/useVoiceTranscription";
|
||||
|
||||
import type { Contact } from "~/composables/useContacts";
|
||||
@@ -90,30 +90,8 @@ export type ChatConversation = {
|
||||
lastMessageText?: string | null;
|
||||
};
|
||||
|
||||
type PilotDataTypes = {
|
||||
"agent-log": {
|
||||
requestId: string;
|
||||
at: string;
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
|
||||
type PilotUiMessage = UIMessage<unknown, PilotDataTypes>;
|
||||
|
||||
function safeTrim(value: unknown) { return String(value ?? "").trim(); }
|
||||
|
||||
function parsePilotAgentLog(part: DataUIPart<PilotDataTypes>) {
|
||||
if (part.type !== "data-agent-log") return null;
|
||||
|
||||
const data = part.data as Partial<PilotDataTypes["agent-log"]> | null | undefined;
|
||||
const text = safeTrim(data?.text);
|
||||
if (!text) return null;
|
||||
|
||||
return {
|
||||
text,
|
||||
at: safeTrim(data?.at) || new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
type PilotUiMessage = UIMessage;
|
||||
|
||||
export function usePilotChat(opts: {
|
||||
apolloAuthReady: ComputedRef<boolean>;
|
||||
@@ -218,11 +196,6 @@ export function usePilotChat(opts: {
|
||||
transport: new DefaultChatTransport({
|
||||
api: "/api/pilot-chat",
|
||||
}),
|
||||
onData: (part) => {
|
||||
const log = parsePilotAgentLog(part);
|
||||
if (!log) return;
|
||||
pilotLiveLogs.value = [...pilotLiveLogs.value, { id: `${Date.now()}-${Math.random()}`, text: log.text, at: log.at }];
|
||||
},
|
||||
onFinish: async () => {
|
||||
pilotSending.value = false;
|
||||
livePilotUserText.value = "";
|
||||
@@ -274,6 +247,16 @@ export function usePilotChat(opts: {
|
||||
|
||||
const textPart = latestAssistant.parts.find(isTextUIPart);
|
||||
livePilotAssistantText.value = textPart?.text ?? "";
|
||||
|
||||
// Use native AI SDK reasoning parts for live "thinking" output.
|
||||
pilotLiveLogs.value = latestAssistant.parts
|
||||
.filter(isReasoningUIPart)
|
||||
.map((part, index) => ({
|
||||
id: `${latestAssistant.id}-reasoning-${index}`,
|
||||
text: safeTrim(part.text),
|
||||
at: new Date().toISOString(),
|
||||
}))
|
||||
.filter((log) => Boolean(log.text));
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -309,10 +292,15 @@ export function usePilotChat(opts: {
|
||||
// Pilot ↔ UIMessage bridge
|
||||
// ---------------------------------------------------------------------------
|
||||
function pilotToUiMessage(message: PilotMessage): PilotUiMessage {
|
||||
const reasoningParts = (message.thinking ?? [])
|
||||
.map((item) => safeTrim(item))
|
||||
.filter(Boolean)
|
||||
.map((text) => ({ type: "reasoning" as const, text, state: "done" as const }));
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
parts: [{ type: "text", text: message.text }],
|
||||
parts: [...reasoningParts, { type: "text", text: message.text }],
|
||||
metadata: {
|
||||
createdAt: message.createdAt ?? null,
|
||||
},
|
||||
@@ -702,6 +690,7 @@ export function usePilotChat(opts: {
|
||||
refetchChatConversations,
|
||||
// realtime pilot trace handlers (called from useCrmRealtime)
|
||||
handleRealtimePilotTrace(log: { text: string; at: string }) {
|
||||
if (pilotSending.value) return;
|
||||
const text = String(log.text ?? "").trim();
|
||||
if (!text) return;
|
||||
// Mark as sending so the UI shows live-log panel
|
||||
|
||||
Reference in New Issue
Block a user