feat(chat): add guest chat mode for unauthenticated users
Some checks failed
ci/woodpecker/push/ci Pipeline failed

- Add POST /api/chat/guest endpoint (no auth required)
- Add proxyGuestChat() method using configurable LLM endpoint
- Add streamGuestChat() function to frontend chat API
- Modify useChat to fall back to guest mode on auth errors (403/401)
- Remove !user check from ChatInput disabled prop
- Configure guest LLM via env vars: GUEST_LLM_URL, GUEST_LLM_API_KEY, GUEST_LLM_MODEL
- Default guest LLM: http://10.1.1.42:11434/v1 (Ollama) with llama3.2 model
This commit is contained in:
2026-03-03 11:16:23 -06:00
parent b1baa70e00
commit c45cec3bba
5 changed files with 326 additions and 9 deletions

View File

@@ -7,6 +7,7 @@ import { useState, useCallback, useRef } from "react";
import {
sendChatMessage,
streamChatMessage,
streamGuestChat,
type ChatMessage as ApiChatMessage,
} from "@/lib/api/chat";
import { createConversation, updateConversation, getIdea, type Idea } from "@/lib/api/ideas";
@@ -278,10 +279,69 @@ export function useChat(options: UseChatOptions = {}): UseChatReturn {
return;
}
// Streaming failed — fall back to non-streaming
console.warn("Streaming failed, falling back to non-streaming", {
error: err instanceof Error ? err : new Error(String(err)),
});
// Streaming failed - check if auth error, try guest mode
const isAuthError = err instanceof Error &&
(err.message.includes("403") || err.message.includes("401") ||
err.message.includes("auth") || err.message.includes("Forbidden"));
if (isAuthError) {
console.warn("Auth failed, trying guest chat mode");
// Try guest chat streaming
try {
await new Promise<void>((guestResolve, guestReject) => {
let hasReceivedData = false;
streamGuestChat(
request,
(chunk: string) => {
if (!hasReceivedData) {
hasReceivedData = true;
setIsLoading(false);
setIsStreaming(true);
setMessages((prev) => {
const updated = [...prev, { ...placeholderMessage }];
messagesRef.current = updated;
return updated;
});
}
setMessages((prev) => {
const updated = prev.map((msg) =>
msg.id === assistantMessageId ? { ...msg, content: msg.content + chunk } : msg
);
messagesRef.current = updated;
return updated;
});
},
() => {
streamingSucceeded = true;
setIsStreaming(false);
guestResolve();
},
(guestErr: Error) => {
guestReject(guestErr);
},
controller.signal
);
});
} catch (guestErr: unknown) {
// Guest also failed
setMessages((prev) => {
const withoutPlaceholder = prev.filter((m) => m.id !== assistantMessageId);
messagesRef.current = withoutPlaceholder;
return withoutPlaceholder;
});
const errorMsg = guestErr instanceof Error ? guestErr.message : "Chat unavailable";
setError(`Unable to connect to chat: ${errorMsg}`);
setIsLoading(false);
return;
}
} else {
// Streaming failed — fall back to non-streaming
console.warn("Streaming failed, falling back to non-streaming", {
error: err instanceof Error ? err : new Error(String(err)),
});
setMessages((prev) => {
const withoutPlaceholder = prev.filter((m) => m.id !== assistantMessageId);