From 32f0cfad561e56bc55f379e232d8721683f6a754 Mon Sep 17 00:00:00 2001 From: Developing-Gamer Date: Tue, 14 Jul 2026 11:14:27 -0700 Subject: [PATCH] Revert "Replace AI query chat hook with table filter chat hook." This reverts commit b62fcf82970e4952e384181e34a90d3c23ad5bf2. --- ...chat.test.ts => use-ai-query-chat.test.ts} | 2 +- ...le-filter-chat.ts => use-ai-query-chat.ts} | 83 ++++++------------- 2 files changed, 26 insertions(+), 59 deletions(-) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/{use-ai-table-filter-chat.test.ts => use-ai-query-chat.test.ts} (97%) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/{use-ai-table-filter-chat.ts => use-ai-query-chat.ts} (62%) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-table-filter-chat.test.ts b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.test.ts similarity index 97% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-table-filter-chat.test.ts rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.test.ts index a785379fd..f941edc63 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-table-filter-chat.test.ts +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.test.ts @@ -1,6 +1,6 @@ import type { ThreadMessage } from "@assistant-ui/react"; import { describe, expect, it } from "vitest"; -import { extractLatestQuery } from "./use-ai-table-filter-chat"; +import { extractLatestQuery } from "./use-ai-query-chat"; const fixture = (messages: Array<{ id: string, diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-table-filter-chat.ts b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts similarity index 62% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-table-filter-chat.ts rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts index 8513caf16..7f45a5d67 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-table-filter-chat.ts +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/use-ai-query-chat.ts @@ -1,8 +1,8 @@ "use client"; -import { createAnalyticsTableFilterChatAdapter } from "@/components/vibe-coding"; +import { createAnalyticsQueryChatAdapter } from "@/components/vibe-coding"; import { getPublicEnvVar } from "@/lib/env"; -import { useLocalThreadRuntime, type ThreadMessage, type ToolCallContentPart } from "@assistant-ui/react"; +import { useLocalThreadRuntime, type AssistantRuntime, type ThreadMessage, type ToolCallContentPart } from "@assistant-ui/react"; import { useUser } from "@hexclave/next"; import { throwErr } from "@hexclave/shared/dist/utils/errors"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -58,50 +58,20 @@ export function extractLatestQuery(messages: readonly ThreadMessage[]): { return null; } -/** - * Extracts the trailing text of the last assistant message, so the UI can - * surface what the AI said when it answered without committing a query - * (e.g. "that's an aggregation, which this search can't display"). - */ -function extractLastAssistantText(messages: readonly ThreadMessage[]): string | null { - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i]!; - if (msg.role !== "assistant") continue; - for (let j = msg.content.length - 1; j >= 0; j--) { - const part = msg.content[j]!; - if (part.type === "text" && part.text.trim().length > 0) { - return part.text.trim(); - } - } - } - return null; -} - -export type AiTableFilterChat = { +export type AiQueryChat = { + runtime: AssistantRuntime, messages: readonly ThreadMessage[], isResponding: boolean, error: Error | null, sendMessage: (input: { text: string }) => void, clearMessages: () => void, - /** The last query the AI committed via the queryAnalytics tool (unvalidated). */ + stop: () => void, latestQuery: string | null, - /** - * Set when the last run finished WITHOUT committing a new query — holds the - * assistant's text reply (or null if there was none / a query was - * committed). Cleared on the next send/clear or via `dismissAssistantNote`. - */ - assistantNote: string | null, - dismissAssistantNote: () => void, + queryGeneration: number, + rewindToQuery: (query: string) => void, }; -/** - * AI chat thread backing the analytics table search bar's AI fallback. Uses - * the constrained `filter-analytics-table` system prompt scoped to the given - * table, so the AI only produces `SELECT * FROM WHERE ...` row - * filters (callers still validate the shape before applying — see - * `getValidatedTableFilterQuery`). - */ -export function useAiTableFilterChat(tableName: string): AiTableFilterChat { +export function useAiQueryChat(): AiQueryChat { const currentUser = useUser(); const projectId = useProjectId(); const backendBaseUrl = @@ -112,14 +82,13 @@ export function useAiTableFilterChat(tableName: string): AiTableFilterChat { const [error, setError] = useState(null); const adapter = useMemo( - () => createAnalyticsTableFilterChatAdapter( + () => createAnalyticsQueryChatAdapter( backendBaseUrl, currentUser ?? undefined, projectId, - tableName, setError, ), - [backendBaseUrl, currentUser, projectId, tableName], + [backendBaseUrl, currentUser, projectId], ); const runtime = useLocalThreadRuntime(adapter, { maxSteps: 1 }); @@ -149,7 +118,6 @@ export function useAiTableFilterChat(tableName: string): AiTableFilterChat { query: string, generation: number, } | null>(null); - const [assistantNote, setAssistantNote] = useState(null); const wasRespondingRef = useRef(false); const lastCommittedGenRef = useRef(0); @@ -159,18 +127,10 @@ export function useAiTableFilterChat(tableName: string): AiTableFilterChat { if (!justFinished) return; const latest = extractLatestQuery(messages); - if (latest == null || latest.toolCallIndex <= lastCommittedGenRef.current) { - // The run ended without committing a new query — surface the - // assistant's text (if any) so callers can show WHY nothing changed. - // This must live here (not in a consuming component) because child - // effects run before this one; a child comparing commit state on the - // same falling edge would race the commit below. - setAssistantNote(extractLastAssistantText(messages)); - return; - } + if (latest == null) return; + if (latest.toolCallIndex <= lastCommittedGenRef.current) return; lastCommittedGenRef.current = latest.toolCallIndex; setCommitted({ query: latest.query, generation: latest.toolCallIndex }); - setAssistantNote(null); }, [isResponding, messages]); useEffect(() => { @@ -183,7 +143,6 @@ export function useAiTableFilterChat(tableName: string): AiTableFilterChat { const sendMessage = useCallback( ({ text }: { text: string }) => { setError(null); - setAssistantNote(null); runtime.thread.append({ role: "user", content: [{ type: "text", text }], @@ -194,22 +153,30 @@ export function useAiTableFilterChat(tableName: string): AiTableFilterChat { const clearMessages = useCallback(() => { setError(null); - setAssistantNote(null); runtime.thread.import({ messages: [], headId: null }); }, [runtime]); - const dismissAssistantNote = useCallback(() => { - setAssistantNote(null); + const stop = useCallback(() => { + runtime.thread.cancelRun(); + }, [runtime]); + + const rewindToQuery = useCallback((query: string) => { + setCommitted((prev) => ({ + query, + generation: (prev?.generation ?? 0) + 1, + })); }, []); return { + runtime, messages, isResponding, error, sendMessage, clearMessages, + stop, latestQuery: committed?.query ?? null, - assistantNote, - dismissAssistantNote, + queryGeneration: committed?.generation ?? 0, + rewindToQuery, }; }