Remove plan from chat payload and UI

This commit is contained in:
Ruslan Bakiev
2026-02-19 06:31:55 +07:00
parent 6156aa482c
commit 39dcfc97c4
4 changed files with 99 additions and 29 deletions

View File

@@ -5,7 +5,6 @@ import chatMessagesQuery from "./graphql/operations/chat-messages.graphql?raw";
import dashboardQuery from "./graphql/operations/dashboard.graphql?raw";
import loginMutation from "./graphql/operations/login.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 createCalendarEventMutation from "./graphql/operations/create-calendar-event.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 confirmLatestChangeSetMutation from "./graphql/operations/confirm-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 CalendarView = "day" | "week" | "month" | "year" | "agenda";
type SortMode = "name" | "lastContact";
@@ -182,7 +183,6 @@ type PilotMessage = {
id: string;
role: "user" | "assistant" | "system";
text: string;
plan?: string[] | null;
thinking?: string[] | null;
tools?: string[] | null;
toolRuns?: Array<{
@@ -203,6 +203,7 @@ type PilotMessage = {
after: string;
}> | null;
createdAt?: string;
_live?: boolean;
};
type ChatConversation = {
@@ -217,6 +218,25 @@ type ChatConversation = {
const pilotMessages = ref<PilotMessage[]>([]);
const pilotInput = ref("");
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<{
user: { id: string; phone: string; name: string };
team: { id: string; name: string };
@@ -264,6 +284,47 @@ function formatPilotStamp(iso?: string) {
}).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>) {
const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", {
method: "POST",
@@ -284,6 +345,7 @@ async function gqlFetch<TData>(query: string, variables?: Record<string, unknown
async function loadPilotMessages() {
const data = await gqlFetch<{ chatMessages: PilotMessage[] }>(chatMessagesQuery);
pilotMessages.value = data.chatMessages ?? [];
syncPilotChatFromHistory(pilotMessages.value);
}
async function loadChatConversations() {
@@ -352,6 +414,9 @@ async function logout() {
await gqlFetch<{ logout: { ok: boolean } }>(logoutMutation);
authMe.value = null;
pilotMessages.value = [];
livePilotUserText.value = "";
livePilotAssistantText.value = "";
pilotChat.messages = [];
chatConversations.value = [];
}
@@ -394,21 +459,39 @@ async function sendPilotMessage() {
pilotSending.value = true;
pilotInput.value = "";
await loadPilotMessages().catch(() => {});
const pollId = setInterval(() => {
loadPilotMessages().catch(() => {});
}, 450);
livePilotUserText.value = text;
livePilotAssistantText.value = "";
try {
await gqlFetch<{ sendPilotMessage: { ok: boolean } }>(sendPilotMessageMutation, { text });
await Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
await pilotChat.sendMessage({ text });
} catch {
pilotInput.value = text;
} 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;
}
}
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 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
v-for="message in pilotMessages"
v-for="message in renderedPilotMessages"
:key="message.id"
class="pilot-row"
>
@@ -1362,16 +1445,9 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
</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"
>
<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">
<p class="pilot-debug-title">Trace</p>
<ol class="pilot-debug-list">
@@ -1469,13 +1545,13 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
</aside>
<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="ml-auto flex items-center gap-2">
<button class="btn btn-sm btn-outline" @click="logout">Logout</button>
</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">
<div class="mb-1 flex justify-end">
<div class="join">
@@ -2286,6 +2362,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
v-model="selectedWorkspaceContact.description"
:room="`crm-contact-${selectedWorkspaceContact.id}`"
placeholder="Contact summary..."
:plain="true"
/>
</div>
</template>