Improve SSR auth bootstrap and align GraphQL communication fields

This commit is contained in:
Ruslan Bakiev
2026-02-19 14:45:31 +07:00
parent da23741b08
commit 808b918894
3 changed files with 49 additions and 14 deletions

View File

@@ -486,8 +486,10 @@ const renderedPilotMessages = computed<PilotMessage[]>(() => {
});
async function gqlFetch<TData>(query: string, variables?: Record<string, unknown>) {
const headers = process.server ? useRequestHeaders(["cookie"]) : undefined;
const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", {
method: "POST",
headers,
body: { query, variables },
});
@@ -531,6 +533,26 @@ async function loadMe() {
authMe.value = data.me;
}
const authResolved = ref(false);
async function bootstrapSession() {
try {
await loadMe();
if (!authMe.value) {
pilotMessages.value = [];
chatConversations.value = [];
return;
}
await Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
} catch {
authMe.value = null;
pilotMessages.value = [];
chatConversations.value = [];
} finally {
authResolved.value = true;
}
}
async function createNewChatConversation() {
if (chatCreating.value) return;
chatThreadPickerOpen.value = false;
@@ -1118,6 +1140,10 @@ async function rollbackLatestChangeSet() {
}
}
if (process.server) {
await bootstrapSession();
}
onMounted(() => {
pilotHeaderText.value = pilotHeaderPhrases[Math.floor(Math.random() * pilotHeaderPhrases.length)] ?? "Every step moves you forward";
pilotMicSupported.value =
@@ -1125,12 +1151,14 @@ onMounted(() => {
typeof MediaRecorder !== "undefined" &&
Boolean(navigator.mediaDevices?.getUserMedia);
loadMe()
.then(() => {
startPilotBackgroundPolling();
return Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
})
.catch(() => {});
if (!authResolved.value) {
void bootstrapSession().finally(() => {
if (authMe.value) startPilotBackgroundPolling();
});
return;
}
if (authMe.value) startPilotBackgroundPolling();
});
onBeforeUnmount(() => {
@@ -1764,7 +1792,7 @@ const latestPinnedItem = computed(() => selectedCommPinnedStream.value[0] ?? nul
const latestPinnedLabel = computed(() => {
if (!latestPinnedItem.value) return "No pinned items yet";
if (latestPinnedItem.value.kind === "pin") return latestPinnedItem.value.text;
if (latestPinnedItem.value.kind === "pin") return stripPinnedPrefix(latestPinnedItem.value.text);
return `${latestPinnedItem.value.event.title} · ${formatDay(latestPinnedItem.value.event.start)}`;
});
@@ -1772,6 +1800,10 @@ function normalizePinText(value: string) {
return String(value ?? "").replace(/\s+/g, " ").trim();
}
function stripPinnedPrefix(value: string) {
return String(value ?? "").replace(/^\s*(закреплено|pinned)\s*:\s*/i, "").trim();
}
function isPinnedText(contact: string, value: string) {
const contactName = String(contact ?? "").trim();
const text = normalizePinText(value);
@@ -1781,7 +1813,7 @@ function isPinnedText(contact: string, value: string) {
function entryPinText(entry: any): string {
if (!entry) return "";
if (entry.kind === "pin") return normalizePinText(entry.text ?? "");
if (entry.kind === "pin") return normalizePinText(stripPinnedPrefix(entry.text ?? ""));
if (entry.kind === "recommendation") return normalizePinText(entry.card?.text ?? "");
if (entry.kind === "event" || entry.kind === "eventAlert" || entry.kind === "eventLog") {
return normalizePinText(entry.event?.note || entry.event?.title || "");
@@ -2353,7 +2385,11 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
<template>
<div class="h-[100dvh] overflow-hidden bg-base-200/35">
<div v-if="!authMe" class="flex h-full items-center justify-center px-3">
<div v-if="!authResolved" class="flex h-full items-center justify-center">
<span class="loading loading-spinner loading-md text-base-content/70" />
</div>
<div v-else-if="!authMe" class="flex h-full items-center justify-center px-3">
<div class="card w-full max-w-sm border border-base-300 bg-base-100 shadow-sm">
<div class="card-body p-5">
<h1 class="text-lg font-semibold">Login</h1>
@@ -3246,7 +3282,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
>
<div v-if="entry.kind === 'pin'" class="flex justify-center">
<article class="w-full max-w-[460px] rounded-xl border border-base-300 bg-base-100 p-3">
<p class="text-sm text-base-content/85">{{ entry.text }}</p>
<p class="text-sm text-base-content/85">{{ stripPinnedPrefix(entry.text) }}</p>
</article>
</div>

View File

@@ -314,8 +314,8 @@ async function main() {
contactId: c.id,
text:
idx % 3 === 0
? "Закреплено: уточнить владельца ERP, владельца данных и целевой квартал запуска."
: "Закреплено: держать коммуникацию вокруг одного KPI и следующего шага.",
? "Уточнить владельца ERP, владельца данных и целевой квартал запуска."
: "Держать коммуникацию вокруг одного KPI и следующего шага.",
})),
});

View File

@@ -388,7 +388,7 @@ async function getDashboard(auth: AuthContext | null) {
kind: m.kind === "CALL" ? "call" : "message",
direction: m.direction === "IN" ? "in" : "out",
text: m.content,
audioUrl: m.audioUrl ?? "",
audioUrl: "",
duration: m.durationSec ? new Date(m.durationSec * 1000).toISOString().slice(14, 19) : "",
transcript: Array.isArray(m.transcriptJson) ? ((m.transcriptJson as any) as string[]) : [],
}));
@@ -542,7 +542,6 @@ async function createCommunication(auth: AuthContext | null, input: {
direction: input?.direction === "in" ? "IN" : "OUT",
channel: toDbChannel(input?.channel ?? "Phone") as any,
content: (input?.text ?? "").trim(),
audioUrl: (input?.audioUrl ?? "").trim() || null,
durationSec: typeof input?.durationSec === "number" ? input.durationSec : null,
transcriptJson: Array.isArray(input?.transcript) ? input.transcript : undefined,
occurredAt,