Remove plan from chat payload and UI
This commit is contained in:
117
Frontend/app.vue
117
Frontend/app.vue
@@ -5,7 +5,6 @@ import chatMessagesQuery from "./graphql/operations/chat-messages.graphql?raw";
|
|||||||
import dashboardQuery from "./graphql/operations/dashboard.graphql?raw";
|
import dashboardQuery from "./graphql/operations/dashboard.graphql?raw";
|
||||||
import loginMutation from "./graphql/operations/login.graphql?raw";
|
import loginMutation from "./graphql/operations/login.graphql?raw";
|
||||||
import logoutMutation from "./graphql/operations/logout.graphql?raw";
|
import logoutMutation from "./graphql/operations/logout.graphql?raw";
|
||||||
import sendPilotMessageMutation from "./graphql/operations/send-pilot-message.graphql?raw";
|
|
||||||
import logPilotNoteMutation from "./graphql/operations/log-pilot-note.graphql?raw";
|
import logPilotNoteMutation from "./graphql/operations/log-pilot-note.graphql?raw";
|
||||||
import createCalendarEventMutation from "./graphql/operations/create-calendar-event.graphql?raw";
|
import createCalendarEventMutation from "./graphql/operations/create-calendar-event.graphql?raw";
|
||||||
import createCommunicationMutation from "./graphql/operations/create-communication.graphql?raw";
|
import createCommunicationMutation from "./graphql/operations/create-communication.graphql?raw";
|
||||||
@@ -15,6 +14,8 @@ import createChatConversationMutation from "./graphql/operations/create-chat-con
|
|||||||
import selectChatConversationMutation from "./graphql/operations/select-chat-conversation.graphql?raw";
|
import selectChatConversationMutation from "./graphql/operations/select-chat-conversation.graphql?raw";
|
||||||
import confirmLatestChangeSetMutation from "./graphql/operations/confirm-latest-change-set.graphql?raw";
|
import confirmLatestChangeSetMutation from "./graphql/operations/confirm-latest-change-set.graphql?raw";
|
||||||
import rollbackLatestChangeSetMutation from "./graphql/operations/rollback-latest-change-set.graphql?raw";
|
import rollbackLatestChangeSetMutation from "./graphql/operations/rollback-latest-change-set.graphql?raw";
|
||||||
|
import { Chat as AiChat } from "@ai-sdk/vue";
|
||||||
|
import { DefaultChatTransport, isTextUIPart, type UIMessage } from "ai";
|
||||||
type TabId = "communications" | "documents";
|
type TabId = "communications" | "documents";
|
||||||
type CalendarView = "day" | "week" | "month" | "year" | "agenda";
|
type CalendarView = "day" | "week" | "month" | "year" | "agenda";
|
||||||
type SortMode = "name" | "lastContact";
|
type SortMode = "name" | "lastContact";
|
||||||
@@ -182,7 +183,6 @@ type PilotMessage = {
|
|||||||
id: string;
|
id: string;
|
||||||
role: "user" | "assistant" | "system";
|
role: "user" | "assistant" | "system";
|
||||||
text: string;
|
text: string;
|
||||||
plan?: string[] | null;
|
|
||||||
thinking?: string[] | null;
|
thinking?: string[] | null;
|
||||||
tools?: string[] | null;
|
tools?: string[] | null;
|
||||||
toolRuns?: Array<{
|
toolRuns?: Array<{
|
||||||
@@ -203,6 +203,7 @@ type PilotMessage = {
|
|||||||
after: string;
|
after: string;
|
||||||
}> | null;
|
}> | null;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
|
_live?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ChatConversation = {
|
type ChatConversation = {
|
||||||
@@ -217,6 +218,25 @@ type ChatConversation = {
|
|||||||
const pilotMessages = ref<PilotMessage[]>([]);
|
const pilotMessages = ref<PilotMessage[]>([]);
|
||||||
const pilotInput = ref("");
|
const pilotInput = ref("");
|
||||||
const pilotSending = ref(false);
|
const pilotSending = ref(false);
|
||||||
|
const livePilotUserText = ref("");
|
||||||
|
const livePilotAssistantText = ref("");
|
||||||
|
const pilotChat = new AiChat<UIMessage>({
|
||||||
|
transport: new DefaultChatTransport({
|
||||||
|
api: "/api/pilot-chat",
|
||||||
|
}),
|
||||||
|
onFinish: async () => {
|
||||||
|
livePilotUserText.value = "";
|
||||||
|
livePilotAssistantText.value = "";
|
||||||
|
await Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
if (livePilotUserText.value) {
|
||||||
|
pilotInput.value = livePilotUserText.value;
|
||||||
|
}
|
||||||
|
livePilotUserText.value = "";
|
||||||
|
livePilotAssistantText.value = "";
|
||||||
|
},
|
||||||
|
});
|
||||||
const authMe = ref<{
|
const authMe = ref<{
|
||||||
user: { id: string; phone: string; name: string };
|
user: { id: string; phone: string; name: string };
|
||||||
team: { id: string; name: string };
|
team: { id: string; name: string };
|
||||||
@@ -264,6 +284,47 @@ function formatPilotStamp(iso?: string) {
|
|||||||
}).format(new Date(iso));
|
}).format(new Date(iso));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pilotToUiMessage(message: PilotMessage): UIMessage {
|
||||||
|
return {
|
||||||
|
id: message.id,
|
||||||
|
role: message.role,
|
||||||
|
parts: [{ type: "text", text: message.text }],
|
||||||
|
metadata: {
|
||||||
|
createdAt: message.createdAt ?? null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncPilotChatFromHistory(messages: PilotMessage[]) {
|
||||||
|
pilotChat.messages = messages.map(pilotToUiMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedPilotMessages = computed<PilotMessage[]>(() => {
|
||||||
|
const items = [...pilotMessages.value];
|
||||||
|
|
||||||
|
if (livePilotUserText.value) {
|
||||||
|
items.push({
|
||||||
|
id: "pilot-live-user",
|
||||||
|
role: "user",
|
||||||
|
text: livePilotUserText.value,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
_live: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (livePilotAssistantText.value) {
|
||||||
|
items.push({
|
||||||
|
id: "pilot-live-assistant",
|
||||||
|
role: "assistant",
|
||||||
|
text: livePilotAssistantText.value,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
_live: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
async function gqlFetch<TData>(query: string, variables?: Record<string, unknown>) {
|
async function gqlFetch<TData>(query: string, variables?: Record<string, unknown>) {
|
||||||
const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", {
|
const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -284,6 +345,7 @@ async function gqlFetch<TData>(query: string, variables?: Record<string, unknown
|
|||||||
async function loadPilotMessages() {
|
async function loadPilotMessages() {
|
||||||
const data = await gqlFetch<{ chatMessages: PilotMessage[] }>(chatMessagesQuery);
|
const data = await gqlFetch<{ chatMessages: PilotMessage[] }>(chatMessagesQuery);
|
||||||
pilotMessages.value = data.chatMessages ?? [];
|
pilotMessages.value = data.chatMessages ?? [];
|
||||||
|
syncPilotChatFromHistory(pilotMessages.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadChatConversations() {
|
async function loadChatConversations() {
|
||||||
@@ -352,6 +414,9 @@ async function logout() {
|
|||||||
await gqlFetch<{ logout: { ok: boolean } }>(logoutMutation);
|
await gqlFetch<{ logout: { ok: boolean } }>(logoutMutation);
|
||||||
authMe.value = null;
|
authMe.value = null;
|
||||||
pilotMessages.value = [];
|
pilotMessages.value = [];
|
||||||
|
livePilotUserText.value = "";
|
||||||
|
livePilotAssistantText.value = "";
|
||||||
|
pilotChat.messages = [];
|
||||||
chatConversations.value = [];
|
chatConversations.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,21 +459,39 @@ async function sendPilotMessage() {
|
|||||||
|
|
||||||
pilotSending.value = true;
|
pilotSending.value = true;
|
||||||
pilotInput.value = "";
|
pilotInput.value = "";
|
||||||
await loadPilotMessages().catch(() => {});
|
livePilotUserText.value = text;
|
||||||
const pollId = setInterval(() => {
|
livePilotAssistantText.value = "";
|
||||||
loadPilotMessages().catch(() => {});
|
|
||||||
}, 450);
|
|
||||||
try {
|
try {
|
||||||
await gqlFetch<{ sendPilotMessage: { ok: boolean } }>(sendPilotMessageMutation, { text });
|
await pilotChat.sendMessage({ text });
|
||||||
await Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
|
|
||||||
} catch {
|
} catch {
|
||||||
pilotInput.value = text;
|
pilotInput.value = text;
|
||||||
} finally {
|
} finally {
|
||||||
clearInterval(pollId);
|
const latestAssistant = [...pilotChat.messages]
|
||||||
|
.reverse()
|
||||||
|
.find((message) => message.role === "assistant");
|
||||||
|
|
||||||
|
if (latestAssistant) {
|
||||||
|
const textPart = latestAssistant.parts.find(isTextUIPart);
|
||||||
|
livePilotAssistantText.value = textPart?.text ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
livePilotUserText.value = "";
|
||||||
|
livePilotAssistantText.value = "";
|
||||||
pilotSending.value = false;
|
pilotSending.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (!pilotSending.value) return;
|
||||||
|
const latestAssistant = [...pilotChat.messages]
|
||||||
|
.reverse()
|
||||||
|
.find((message) => message.role === "assistant");
|
||||||
|
if (!latestAssistant) return;
|
||||||
|
|
||||||
|
const textPart = latestAssistant.parts.find(isTextUIPart);
|
||||||
|
livePilotAssistantText.value = textPart?.text ?? "";
|
||||||
|
});
|
||||||
|
|
||||||
const changePanelOpen = ref(true);
|
const changePanelOpen = ref(true);
|
||||||
const changeActionBusy = ref(false);
|
const changeActionBusy = ref(false);
|
||||||
|
|
||||||
@@ -1343,7 +1426,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
|||||||
|
|
||||||
<div class="pilot-timeline min-h-0 flex-1 overflow-y-auto">
|
<div class="pilot-timeline min-h-0 flex-1 overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-for="message in pilotMessages"
|
v-for="message in renderedPilotMessages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
class="pilot-row"
|
class="pilot-row"
|
||||||
>
|
>
|
||||||
@@ -1362,16 +1445,9 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="message.role !== 'user' && ((message.plan && message.plan.length) || (message.thinking && message.thinking.length) || (message.toolRuns && message.toolRuns.length) || (message.tools && message.tools.length))"
|
v-if="!message._live && message.role !== 'user' && ((message.thinking && message.thinking.length) || (message.toolRuns && message.toolRuns.length) || (message.tools && message.tools.length))"
|
||||||
class="pilot-debug mt-2"
|
class="pilot-debug mt-2"
|
||||||
>
|
>
|
||||||
<div v-if="message.plan && message.plan.length" class="pilot-debug-block">
|
|
||||||
<p class="pilot-debug-title">Plan</p>
|
|
||||||
<ol class="pilot-debug-list">
|
|
||||||
<li v-for="(step, idx) in message.plan" :key="`plan-${message.id}-${idx}`">{{ step }}</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="message.thinking && message.thinking.length" class="pilot-debug-block">
|
<div v-if="message.thinking && message.thinking.length" class="pilot-debug-block">
|
||||||
<p class="pilot-debug-title">Trace</p>
|
<p class="pilot-debug-title">Trace</p>
|
||||||
<ol class="pilot-debug-list">
|
<ol class="pilot-debug-list">
|
||||||
@@ -1469,13 +1545,13 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="min-h-0 bg-base-100">
|
<main class="min-h-0 bg-base-100">
|
||||||
<div class="flex h-full min-h-0 flex-col pb-20 md:pb-24">
|
<div class="flex h-full min-h-0 flex-col pb-16 md:pb-0">
|
||||||
<div class="workspace-topbar border-b border-base-300 px-3 py-2 md:px-4">
|
<div class="workspace-topbar border-b border-base-300 px-3 py-2 md:px-4">
|
||||||
<div class="ml-auto flex items-center gap-2">
|
<div class="ml-auto flex items-center gap-2">
|
||||||
<button class="btn btn-sm btn-outline" @click="logout">Logout</button>
|
<button class="btn btn-sm btn-outline" @click="logout">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-h-0 flex-1 p-3 md:p-4">
|
<div class="min-h-0 flex-1 px-3 pt-3 pb-0 md:px-4 md:pt-4 md:pb-0">
|
||||||
<section v-if="selectedTab === 'communications' && peopleLeftMode === 'calendar'" class="flex h-full min-h-0 flex-col gap-3">
|
<section v-if="selectedTab === 'communications' && peopleLeftMode === 'calendar'" class="flex h-full min-h-0 flex-col gap-3">
|
||||||
<div class="mb-1 flex justify-end">
|
<div class="mb-1 flex justify-end">
|
||||||
<div class="join">
|
<div class="join">
|
||||||
@@ -2286,6 +2362,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
|||||||
v-model="selectedWorkspaceContact.description"
|
v-model="selectedWorkspaceContact.description"
|
||||||
:room="`crm-contact-${selectedWorkspaceContact.id}`"
|
:room="`crm-contact-${selectedWorkspaceContact.id}`"
|
||||||
placeholder="Contact summary..."
|
placeholder="Contact summary..."
|
||||||
|
:plain="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ query ChatMessagesQuery {
|
|||||||
id
|
id
|
||||||
role
|
role
|
||||||
text
|
text
|
||||||
plan
|
|
||||||
thinking
|
thinking
|
||||||
tools
|
tools
|
||||||
toolRuns {
|
toolRuns {
|
||||||
|
|||||||
@@ -859,7 +859,6 @@ export async function runLangGraphCrmAgentFor(input: {
|
|||||||
tools: [crmTool],
|
tools: [crmTool],
|
||||||
responseFormat: z.object({
|
responseFormat: z.object({
|
||||||
answer: z.string().describe("Final assistant answer for the user."),
|
answer: z.string().describe("Final assistant answer for the user."),
|
||||||
plan: z.array(z.string()).min(1).max(10).describe("Short plan (3-8 steps)."),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -924,7 +923,7 @@ export async function runLangGraphCrmAgentFor(input: {
|
|||||||
return String(msg?.type ?? msg?.role ?? msg?.constructor?.name ?? "");
|
return String(msg?.type ?? msg?.role ?? msg?.constructor?.name ?? "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const structured = res?.structuredResponse as { answer?: string; plan?: string[] } | undefined;
|
const structured = res?.structuredResponse as { answer?: string } | undefined;
|
||||||
const fallbackText = (() => {
|
const fallbackText = (() => {
|
||||||
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
||||||
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
||||||
@@ -945,7 +944,7 @@ export async function runLangGraphCrmAgentFor(input: {
|
|||||||
if (!text) {
|
if (!text) {
|
||||||
throw new Error("Model returned empty response");
|
throw new Error("Model returned empty response");
|
||||||
}
|
}
|
||||||
const plan = Array.isArray(structured?.plan) ? structured.plan : [];
|
const plan: string[] = [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text,
|
text,
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ async function getChatMessages(auth: AuthContext | null) {
|
|||||||
id: m.id,
|
id: m.id,
|
||||||
role: m.role === "USER" ? "user" : m.role === "ASSISTANT" ? "assistant" : "system",
|
role: m.role === "USER" ? "user" : m.role === "ASSISTANT" ? "assistant" : "system",
|
||||||
text: m.text,
|
text: m.text,
|
||||||
plan: Array.isArray(debug.steps) ? (debug.steps as string[]) : [],
|
|
||||||
thinking: Array.isArray(debug.thinking) ? (debug.thinking as string[]) : [],
|
thinking: Array.isArray(debug.thinking) ? (debug.thinking as string[]) : [],
|
||||||
tools: Array.isArray(debug.tools) ? (debug.tools as string[]) : [],
|
tools: Array.isArray(debug.tools) ? (debug.tools as string[]) : [],
|
||||||
toolRuns: Array.isArray(debug.toolRuns)
|
toolRuns: Array.isArray(debug.toolRuns)
|
||||||
@@ -619,7 +618,6 @@ async function sendPilotMessage(auth: AuthContext | null, textInput: string) {
|
|||||||
authorUserId: null,
|
authorUserId: null,
|
||||||
role: "SYSTEM",
|
role: "SYSTEM",
|
||||||
text: event.text,
|
text: event.text,
|
||||||
plan: [],
|
|
||||||
thinking: [],
|
thinking: [],
|
||||||
tools: event.toolRun ? [event.toolRun.name] : [],
|
tools: event.toolRun ? [event.toolRun.name] : [],
|
||||||
toolRuns: event.toolRun ? [event.toolRun] : [],
|
toolRuns: event.toolRun ? [event.toolRun] : [],
|
||||||
@@ -636,7 +634,6 @@ async function sendPilotMessage(auth: AuthContext | null, textInput: string) {
|
|||||||
authorUserId: null,
|
authorUserId: null,
|
||||||
role: "ASSISTANT",
|
role: "ASSISTANT",
|
||||||
text: reply.text,
|
text: reply.text,
|
||||||
plan: reply.plan,
|
|
||||||
thinking: reply.thinking ?? [],
|
thinking: reply.thinking ?? [],
|
||||||
tools: reply.tools,
|
tools: reply.tools,
|
||||||
toolRuns: reply.toolRuns ?? [],
|
toolRuns: reply.toolRuns ?? [],
|
||||||
@@ -657,7 +654,6 @@ async function logPilotNote(auth: AuthContext | null, textInput: string) {
|
|||||||
authorUserId: null,
|
authorUserId: null,
|
||||||
role: "ASSISTANT",
|
role: "ASSISTANT",
|
||||||
text,
|
text,
|
||||||
plan: [],
|
|
||||||
thinking: [],
|
thinking: [],
|
||||||
tools: [],
|
tools: [],
|
||||||
toolRuns: [],
|
toolRuns: [],
|
||||||
@@ -747,7 +743,6 @@ export const crmGraphqlSchema = buildSchema(`
|
|||||||
id: ID!
|
id: ID!
|
||||||
role: String!
|
role: String!
|
||||||
text: String!
|
text: String!
|
||||||
plan: [String!]!
|
|
||||||
thinking: [String!]!
|
thinking: [String!]!
|
||||||
tools: [String!]!
|
tools: [String!]!
|
||||||
toolRuns: [PilotToolRun!]!
|
toolRuns: [PilotToolRun!]!
|
||||||
|
|||||||
Reference in New Issue
Block a user