Spaces:
Sleeping
Sleeping
fix: stable message IDs, list markers, node runtime logging, date prompt
Browse files- .gitignore +1 -0
- src/app/api/chat/route.ts +45 -2
- src/app/globals.css +9 -1
- src/components/chat.tsx +10 -2
.gitignore
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
node_modules
|
| 2 |
.next
|
| 3 |
.gradio
|
|
|
|
|
|
| 1 |
node_modules
|
| 2 |
.next
|
| 3 |
.gradio
|
| 4 |
+
chat-logs.jsonl
|
src/app/api/chat/route.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
import { NextRequest } from "next/server";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
const BASE_URL = process.env.BASE_URL || "https://llama.gptbox.dev/v1";
|
| 4 |
const API_KEY = process.env.OPENAI_API_KEY || "";
|
|
@@ -63,6 +67,17 @@ async function executeWebSearch(query: string): Promise<string> {
|
|
| 63 |
}
|
| 64 |
}
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
// Helper to stream a single completion request
|
| 67 |
async function streamCompletion(
|
| 68 |
currentMessages: Array<{ role: string; content: string | null; tool_calls?: unknown; tool_call_id?: string }>,
|
|
@@ -169,10 +184,19 @@ export async function POST(req: NextRequest) {
|
|
| 169 |
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
|
| 170 |
};
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
try {
|
|
|
|
| 173 |
const systemMessage = {
|
| 174 |
role: "system",
|
| 175 |
-
content:
|
| 176 |
};
|
| 177 |
|
| 178 |
const tools = searchEnabled ? [WEB_SEARCH_TOOL] : undefined;
|
|
@@ -183,7 +207,17 @@ export async function POST(req: NextRequest) {
|
|
| 183 |
|
| 184 |
// Loop for tool calls - model reasons, calls tool, then we send results back
|
| 185 |
for (let round = 0; round < 5; round++) {
|
| 186 |
-
const { content, toolCalls } = await streamCompletion(currentMessages, tools, send);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
// If no tool calls, we're done
|
| 189 |
if (toolCalls.length === 0) {
|
|
@@ -241,11 +275,20 @@ export async function POST(req: NextRequest) {
|
|
| 241 |
send({ type: "done" });
|
| 242 |
} catch (error) {
|
| 243 |
console.error("Chat error:", error);
|
|
|
|
| 244 |
send({
|
| 245 |
type: "error",
|
| 246 |
message: error instanceof Error ? error.message : "Unknown error",
|
| 247 |
});
|
| 248 |
} finally {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
| 250 |
controller.close();
|
| 251 |
}
|
|
|
|
| 1 |
import { NextRequest } from "next/server";
|
| 2 |
+
import { promises as fs } from "fs";
|
| 3 |
+
import path from "path";
|
| 4 |
+
|
| 5 |
+
export const runtime = "nodejs";
|
| 6 |
|
| 7 |
const BASE_URL = process.env.BASE_URL || "https://llama.gptbox.dev/v1";
|
| 8 |
const API_KEY = process.env.OPENAI_API_KEY || "";
|
|
|
|
| 67 |
}
|
| 68 |
}
|
| 69 |
|
| 70 |
+
const LOG_FILE = process.env.CHAT_LOG_PATH || path.join(process.cwd(), "chat-logs.jsonl");
|
| 71 |
+
|
| 72 |
+
async function appendChatLog(entry: unknown) {
|
| 73 |
+
try {
|
| 74 |
+
const line = JSON.stringify(entry) + "\n";
|
| 75 |
+
await fs.appendFile(LOG_FILE, line, "utf8");
|
| 76 |
+
} catch (error) {
|
| 77 |
+
console.error("Failed to write chat log:", error);
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
// Helper to stream a single completion request
|
| 82 |
async function streamCompletion(
|
| 83 |
currentMessages: Array<{ role: string; content: string | null; tool_calls?: unknown; tool_call_id?: string }>,
|
|
|
|
| 184 |
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
|
| 185 |
};
|
| 186 |
|
| 187 |
+
const timestamp = new Date().toISOString();
|
| 188 |
+
const logRounds: Array<{
|
| 189 |
+
content: string;
|
| 190 |
+
reasoning: string;
|
| 191 |
+
toolCalls: Array<{ id: string; name: string; args: string }>;
|
| 192 |
+
}> = [];
|
| 193 |
+
let logError: string | null = null;
|
| 194 |
+
|
| 195 |
try {
|
| 196 |
+
const today = new Date().toISOString().slice(0, 10);
|
| 197 |
const systemMessage = {
|
| 198 |
role: "system",
|
| 199 |
+
content: `You are a helpful AI assistant. Today's date is ${today}.`,
|
| 200 |
};
|
| 201 |
|
| 202 |
const tools = searchEnabled ? [WEB_SEARCH_TOOL] : undefined;
|
|
|
|
| 207 |
|
| 208 |
// Loop for tool calls - model reasons, calls tool, then we send results back
|
| 209 |
for (let round = 0; round < 5; round++) {
|
| 210 |
+
const { content, reasoning, toolCalls } = await streamCompletion(currentMessages, tools, send);
|
| 211 |
+
|
| 212 |
+
logRounds.push({
|
| 213 |
+
content,
|
| 214 |
+
reasoning,
|
| 215 |
+
toolCalls: toolCalls.map((tc) => ({
|
| 216 |
+
id: tc.id,
|
| 217 |
+
name: tc.function.name,
|
| 218 |
+
args: tc.function.arguments,
|
| 219 |
+
})),
|
| 220 |
+
});
|
| 221 |
|
| 222 |
// If no tool calls, we're done
|
| 223 |
if (toolCalls.length === 0) {
|
|
|
|
| 275 |
send({ type: "done" });
|
| 276 |
} catch (error) {
|
| 277 |
console.error("Chat error:", error);
|
| 278 |
+
logError = error instanceof Error ? error.message : String(error);
|
| 279 |
send({
|
| 280 |
type: "error",
|
| 281 |
message: error instanceof Error ? error.message : "Unknown error",
|
| 282 |
});
|
| 283 |
} finally {
|
| 284 |
+
// Fire-and-forget logging of raw chat history for debugging
|
| 285 |
+
void appendChatLog({
|
| 286 |
+
timestamp,
|
| 287 |
+
searchEnabled,
|
| 288 |
+
messages,
|
| 289 |
+
rounds: logRounds,
|
| 290 |
+
error: logError,
|
| 291 |
+
});
|
| 292 |
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
| 293 |
controller.close();
|
| 294 |
}
|
src/app/globals.css
CHANGED
|
@@ -213,6 +213,15 @@ body {
|
|
| 213 |
.markdown-content ol {
|
| 214 |
margin: 0.75em 0;
|
| 215 |
padding-left: 1.5em;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
}
|
| 217 |
|
| 218 |
.markdown-content li {
|
|
@@ -344,7 +353,6 @@ body {
|
|
| 344 |
font-size: 0.875em;
|
| 345 |
color: var(--muted-foreground);
|
| 346 |
line-height: 1.6;
|
| 347 |
-
white-space: pre-wrap;
|
| 348 |
border-top: 1px solid var(--border);
|
| 349 |
background: var(--background);
|
| 350 |
}
|
|
|
|
| 213 |
.markdown-content ol {
|
| 214 |
margin: 0.75em 0;
|
| 215 |
padding-left: 1.5em;
|
| 216 |
+
list-style-position: outside;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
.markdown-content ul {
|
| 220 |
+
list-style-type: disc;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.markdown-content ol {
|
| 224 |
+
list-style-type: decimal;
|
| 225 |
}
|
| 226 |
|
| 227 |
.markdown-content li {
|
|
|
|
| 353 |
font-size: 0.875em;
|
| 354 |
color: var(--muted-foreground);
|
| 355 |
line-height: 1.6;
|
|
|
|
| 356 |
border-top: 1px solid var(--border);
|
| 357 |
background: var(--background);
|
| 358 |
}
|
src/components/chat.tsx
CHANGED
|
@@ -127,6 +127,14 @@ export function Chat() {
|
|
| 127 |
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 128 |
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
const scrollToBottom = () => {
|
| 131 |
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
| 132 |
};
|
|
@@ -148,7 +156,7 @@ export function Chat() {
|
|
| 148 |
if (!input.trim() || isLoading) return;
|
| 149 |
|
| 150 |
const userMessage: Message = {
|
| 151 |
-
id:
|
| 152 |
role: "user",
|
| 153 |
content: input.trim(),
|
| 154 |
steps: [],
|
|
@@ -159,7 +167,7 @@ export function Chat() {
|
|
| 159 |
setIsLoading(true);
|
| 160 |
|
| 161 |
const assistantMessage: Message = {
|
| 162 |
-
id: (
|
| 163 |
role: "assistant",
|
| 164 |
content: "",
|
| 165 |
steps: [],
|
|
|
|
| 127 |
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 128 |
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
| 129 |
|
| 130 |
+
const makeId = () => {
|
| 131 |
+
try {
|
| 132 |
+
return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
| 133 |
+
} catch {
|
| 134 |
+
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
| 135 |
+
}
|
| 136 |
+
};
|
| 137 |
+
|
| 138 |
const scrollToBottom = () => {
|
| 139 |
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
| 140 |
};
|
|
|
|
| 156 |
if (!input.trim() || isLoading) return;
|
| 157 |
|
| 158 |
const userMessage: Message = {
|
| 159 |
+
id: makeId(),
|
| 160 |
role: "user",
|
| 161 |
content: input.trim(),
|
| 162 |
steps: [],
|
|
|
|
| 167 |
setIsLoading(true);
|
| 168 |
|
| 169 |
const assistantMessage: Message = {
|
| 170 |
+
id: makeId(),
|
| 171 |
role: "assistant",
|
| 172 |
content: "",
|
| 173 |
steps: [],
|