Add dedicated quick question API
TestFlight / Build and upload (push) Successful in 1m42s

This commit is contained in:
2026-08-27 19:31:23 -07:00
parent 5f9fc82b86
commit 922172fc60
13 changed files with 269 additions and 44 deletions
+6 -5
View File
@@ -43,6 +43,7 @@ import {
getSearch,
listWorkspaceItems,
runCompletionStream,
runQuickQuestionStream,
runSearchStream,
suggestChatTitle,
updateChatTitle,
@@ -79,6 +80,7 @@ import {
resolveSidebarSelectionAfterRefresh,
type SidebarSelection,
} from "@/lib/sidebar-selection";
import { buildQuickQuestionRequest } from "@/lib/quick-question";
import { cn } from "@/lib/utils";
type DraftSelectionKind = "chat" | "search";
@@ -3225,13 +3227,12 @@ export default function App() {
let streamErrorMessage: string | null = null;
try {
await runCompletionStream(
{
persist: false,
await runQuickQuestionStream(
buildQuickQuestionRequest({
provider: quickProvider,
model: selectedModel,
messages: [{ role: "user", content }],
},
content,
}),
{
onToolCall: (payload) => {
setQuickQuestionMessages((current) => {
+36 -13
View File
@@ -191,6 +191,14 @@ type CompletionStreamHandlers = {
onError?: (payload: { message: string }) => void;
};
function dispatchCompletionStreamEvent(handlers: CompletionStreamHandlers, eventName: string, payload: any) {
if (eventName === "meta") handlers.onMeta?.(payload);
else if (eventName === "tool_call") handlers.onToolCall?.(payload);
else if (eventName === "delta") handlers.onDelta?.(payload);
else if (eventName === "done") handlers.onDone?.(payload);
else if (eventName === "error") handlers.onError?.(payload);
}
type CreateChatRequest = {
title?: string;
provider?: Provider;
@@ -627,13 +635,34 @@ export async function runCompletionStream(
signal: options?.signal,
});
await readSseStream(response, (eventName, payload) => {
if (eventName === "meta") handlers.onMeta?.(payload);
else if (eventName === "tool_call") handlers.onToolCall?.(payload);
else if (eventName === "delta") handlers.onDelta?.(payload);
else if (eventName === "done") handlers.onDone?.(payload);
else if (eventName === "error") handlers.onError?.(payload);
await readSseStream(response, (eventName, payload) => dispatchCompletionStreamEvent(handlers, eventName, payload));
}
export async function runQuickQuestionStream(
body: {
provider: Provider;
model: string;
question: string;
},
handlers: CompletionStreamHandlers,
options?: { signal?: AbortSignal }
) {
const headers = new Headers({
Accept: "text/event-stream",
"Content-Type": "application/json",
});
if (authToken) {
headers.set("Authorization", `Bearer ${authToken}`);
}
const response = await fetch(`${API_BASE_URL}/v1/quick-questions/stream`, {
method: "POST",
headers,
body: JSON.stringify(body),
signal: options?.signal,
});
await readSseStream(response, (eventName, payload) => dispatchCompletionStreamEvent(handlers, eventName, payload));
}
export async function attachCompletionStream(chatId: string, handlers: CompletionStreamHandlers, options?: { signal?: AbortSignal }) {
@@ -650,11 +679,5 @@ export async function attachCompletionStream(chatId: string, handlers: Completio
signal: options?.signal,
});
await readSseStream(response, (eventName, payload) => {
if (eventName === "meta") handlers.onMeta?.(payload);
else if (eventName === "tool_call") handlers.onToolCall?.(payload);
else if (eventName === "delta") handlers.onDelta?.(payload);
else if (eventName === "done") handlers.onDone?.(payload);
else if (eventName === "error") handlers.onError?.(payload);
});
await readSseStream(response, (eventName, payload) => dispatchCompletionStreamEvent(handlers, eventName, payload));
}
+17
View File
@@ -0,0 +1,17 @@
import type { Provider } from "./api";
export function buildQuickQuestionRequest({
provider,
model,
content,
}: {
provider: Provider;
model: string;
content: string;
}) {
return {
provider,
model,
question: content,
};
}
+18
View File
@@ -0,0 +1,18 @@
import assert from "node:assert/strict";
import test from "node:test";
import { buildQuickQuestionRequest } from "../src/lib/quick-question.ts";
test("quick question requests use the dedicated server endpoint shape", () => {
const request = buildQuickQuestionRequest({
provider: "openai",
model: "gpt-4.1-mini",
content: "How do I reset my password?",
});
assert.deepEqual(request, {
provider: "openai",
model: "gpt-4.1-mini",
question: "How do I reset my password?",
});
assert.equal("additionalSystemPrompt" in request, false);
});
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/pwa.ts","./src/root-router.tsx","./src/vite-env.d.ts","./src/components/sybil-character.tsx","./src/components/auth/auth-screen.tsx","./src/components/chat/chat-attachment-list.tsx","./src/components/chat/chat-composer.tsx","./src/components/chat/chat-messages-panel.tsx","./src/components/markdown/markdown-content.tsx","./src/components/search/search-results-panel.tsx","./src/components/ui/button.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/separator.tsx","./src/components/ui/textarea.tsx","./src/hooks/use-session-auth.ts","./src/lib/api.ts","./src/lib/chat-forking.ts","./src/lib/chat-model-selection.ts","./src/lib/sidebar-selection.ts","./src/lib/utils.ts","./src/pages/search-route-page.tsx"],"version":"5.9.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/pwa.ts","./src/root-router.tsx","./src/vite-env.d.ts","./src/components/sybil-character.tsx","./src/components/auth/auth-screen.tsx","./src/components/chat/chat-attachment-list.tsx","./src/components/chat/chat-composer.tsx","./src/components/chat/chat-messages-panel.tsx","./src/components/markdown/markdown-content.tsx","./src/components/search/search-results-panel.tsx","./src/components/ui/button.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/separator.tsx","./src/components/ui/textarea.tsx","./src/hooks/use-session-auth.ts","./src/lib/api.ts","./src/lib/chat-forking.ts","./src/lib/chat-model-selection.ts","./src/lib/quick-question.ts","./src/lib/sidebar-selection.ts","./src/lib/utils.ts","./src/pages/search-route-page.tsx"],"version":"5.9.3"}