fix: handle PG INTERVAL type, robust compact LLM output + feedback
INTERVAL handling
- pg_value_to_json now decodes PG INTERVAL via PgInterval and renders
it psql-style: `1 year 2 mons 3 days 04:05:06`. Previously
AVG(timestamp - timestamp) and similar interval-returning queries
showed `<unsupported type: INTERVAL>` in chat results.
- 7 unit tests covering zero, days-only, mixed, negative, microsecond
fraction, and the singular/plural unit rules.
Compact reliability
- Sharper system prompt: explicitly instructs plain text starting with
`-`, no JSON, no fences, no field names. qwen3-coder is heavily
trained on the agent JSON protocol and was sometimes returning
`{"action":"final","text":"..."}` even for the compact prompt.
- New clean_summary helper strips ``` fences (with or without lang
identifier) and extracts the underlying string from a JSON envelope
if the model still wraps the answer (looks for text/summary/content/
answer/output keys). 6 unit tests.
- Frontend useChat.compact: success/no-op/error toasts via sonner so
the user sees what happened. "Nothing to compact" appears when there
is no older history beyond the last user turn (previously silent).
Verification: cargo test --lib 66 pass (+13), tsc clean, vitest 20
pass.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { chatCompact, chatSend } from "@/lib/tauri";
|
||||
import { useAppStore } from "@/stores/app-store";
|
||||
import type { ChatMessage } from "@/types";
|
||||
@@ -24,22 +25,40 @@ export function useChat(tabId: string, connectionId: string) {
|
||||
|
||||
const compact = useCallback(async (): Promise<boolean> => {
|
||||
const state = useAppStore.getState();
|
||||
if (state.chatPending[tabId]) return false;
|
||||
if (state.chatPending[tabId]) {
|
||||
toast.message("Wait for the agent to finish first.");
|
||||
return false;
|
||||
}
|
||||
const history = state.chatThreads[tabId] ?? [];
|
||||
if (history.length === 0) return false;
|
||||
if (history.length === 0) {
|
||||
toast.message("Nothing to compact yet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const beforeCount = history.length;
|
||||
setChatPending(tabId, true);
|
||||
try {
|
||||
const turn = await chatCompact(connectionId, history);
|
||||
const afterCount = turn.messages.length;
|
||||
// Backend returns the same thread untouched when there's nothing older
|
||||
// than the last user turn; surface that instead of silently no-op.
|
||||
if (afterCount >= beforeCount) {
|
||||
toast.message("Nothing to compact (no older history beyond the last question).");
|
||||
return false;
|
||||
}
|
||||
replaceChatThread(tabId, turn.messages);
|
||||
setChatUsage(tabId, turn.usage);
|
||||
const removed = beforeCount - afterCount + 1; // +1: original older replaced by single summary
|
||||
toast.success(`Compacted ${removed} earlier message${removed === 1 ? "" : "s"}.`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
const text = `Compact failed: ${String(err)}`;
|
||||
toast.error("Compact failed", { description: String(err) });
|
||||
appendChatMessages(tabId, [
|
||||
{
|
||||
id: newId("err"),
|
||||
role: "assistant",
|
||||
text: `Compact failed: ${String(err)}`,
|
||||
text,
|
||||
created_at: Date.now(),
|
||||
},
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user