Spaces:
Running
Running
Update app.js
Browse files
app.js
CHANGED
|
@@ -53,12 +53,18 @@ async function initDB() {
|
|
| 53 |
console.log('✅ Connected to Supabase');
|
| 54 |
if (dbChats) {
|
| 55 |
dbChats.forEach(c => {
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
});
|
| 58 |
console.log(`✅ Hydrated ${dbChats.length} chats from DB.`);
|
| 59 |
}
|
| 60 |
|
| 61 |
-
// Sync to DB every 15 seconds
|
| 62 |
setInterval(async () => {
|
| 63 |
if (dirtyChats.size === 0) return;
|
| 64 |
const toSync = Array.from(dirtyChats);
|
|
@@ -71,6 +77,8 @@ async function initDB() {
|
|
| 71 |
id: chat.id,
|
| 72 |
title: chat.title,
|
| 73 |
totalTokens: chat.totalTokens,
|
|
|
|
|
|
|
| 74 |
messages: chat.messages,
|
| 75 |
updatedAt: chat.updatedAt
|
| 76 |
};
|
|
@@ -97,7 +105,12 @@ initDB();
|
|
| 97 |
// Get all chats (Sidebar)
|
| 98 |
app.get('/api/chats', (req, res) => {
|
| 99 |
const chatsList = Object.values(memoryChats).map(c => ({
|
| 100 |
-
id: c.id,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
})).sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
| 102 |
res.json(chatsList);
|
| 103 |
});
|
|
@@ -116,6 +129,8 @@ app.post('/api/chats', (req, res) => {
|
|
| 116 |
id: newId,
|
| 117 |
title: "New Chat",
|
| 118 |
totalTokens: 0,
|
|
|
|
|
|
|
| 119 |
messages:[],
|
| 120 |
isGenerating: false,
|
| 121 |
updatedAt: new Date().toISOString()
|
|
@@ -124,6 +139,20 @@ app.post('/api/chats', (req, res) => {
|
|
| 124 |
res.json(memoryChats[newId]);
|
| 125 |
});
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
// Delete chat permanently
|
| 128 |
app.delete('/api/chats/:id', async (req, res) => {
|
| 129 |
const { id } = req.params;
|
|
@@ -141,12 +170,12 @@ app.post('/api/chats/:id/stream', async (req, res) => {
|
|
| 141 |
|
| 142 |
if (!memoryChats[id]) return res.status(404).send("Chat not found");
|
| 143 |
|
| 144 |
-
// Title generation
|
| 145 |
-
if (memoryChats[id].messages.length === 0) {
|
| 146 |
memoryChats[id].title = prompt.substring(0, 30) + (prompt.length > 30 ? '...' : '');
|
| 147 |
}
|
| 148 |
|
| 149 |
-
// 1. SAVE TO MEMORY
|
| 150 |
memoryChats[id].messages.push({ role: "user", content: prompt });
|
| 151 |
|
| 152 |
const aiMessage = { role: "assistant", content: "", reasoning: "" };
|
|
@@ -160,7 +189,9 @@ app.post('/api/chats/:id/stream', async (req, res) => {
|
|
| 160 |
res.setHeader('X-Accel-Buffering', 'no');
|
| 161 |
res.flushHeaders();
|
| 162 |
|
| 163 |
-
let
|
|
|
|
|
|
|
| 164 |
|
| 165 |
try {
|
| 166 |
const bedrockModelId = getBedrockModelId(model);
|
|
@@ -175,7 +206,7 @@ app.post('/api/chats/:id/stream', async (req, res) => {
|
|
| 175 |
contentBlock = [...imageBlocks, ...contentBlock];
|
| 176 |
}
|
| 177 |
|
| 178 |
-
// Map history for Bedrock
|
| 179 |
const historicalMessages = memoryChats[id].messages.slice(0, -2).map(m => ({
|
| 180 |
role: m.role, content:[{ text: m.content }]
|
| 181 |
}));
|
|
@@ -207,16 +238,25 @@ app.post('/api/chats/:id/stream', async (req, res) => {
|
|
| 207 |
}
|
| 208 |
}
|
| 209 |
if (chunk.metadata && chunk.metadata.usage) {
|
| 210 |
-
|
|
|
|
|
|
|
| 211 |
}
|
| 212 |
}
|
| 213 |
|
| 214 |
-
// Clean up & finalize memory
|
| 215 |
-
memoryChats[id].
|
|
|
|
|
|
|
| 216 |
memoryChats[id].isGenerating = false;
|
| 217 |
dirtyChats.add(id);
|
| 218 |
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
res.end();
|
| 221 |
|
| 222 |
} catch (err) {
|
|
|
|
| 53 |
console.log('✅ Connected to Supabase');
|
| 54 |
if (dbChats) {
|
| 55 |
dbChats.forEach(c => {
|
| 56 |
+
// Ensure legacy chats don't crash if they lack the new columns
|
| 57 |
+
memoryChats[c.id] = {
|
| 58 |
+
...c,
|
| 59 |
+
inputTokens: c.inputTokens || 0,
|
| 60 |
+
outputTokens: c.outputTokens || 0,
|
| 61 |
+
isGenerating: false
|
| 62 |
+
};
|
| 63 |
});
|
| 64 |
console.log(`✅ Hydrated ${dbChats.length} chats from DB.`);
|
| 65 |
}
|
| 66 |
|
| 67 |
+
// Sync to DB every 15 seconds
|
| 68 |
setInterval(async () => {
|
| 69 |
if (dirtyChats.size === 0) return;
|
| 70 |
const toSync = Array.from(dirtyChats);
|
|
|
|
| 77 |
id: chat.id,
|
| 78 |
title: chat.title,
|
| 79 |
totalTokens: chat.totalTokens,
|
| 80 |
+
inputTokens: chat.inputTokens,
|
| 81 |
+
outputTokens: chat.outputTokens,
|
| 82 |
messages: chat.messages,
|
| 83 |
updatedAt: chat.updatedAt
|
| 84 |
};
|
|
|
|
| 105 |
// Get all chats (Sidebar)
|
| 106 |
app.get('/api/chats', (req, res) => {
|
| 107 |
const chatsList = Object.values(memoryChats).map(c => ({
|
| 108 |
+
id: c.id,
|
| 109 |
+
title: c.title,
|
| 110 |
+
totalTokens: c.totalTokens,
|
| 111 |
+
inputTokens: c.inputTokens,
|
| 112 |
+
outputTokens: c.outputTokens,
|
| 113 |
+
updatedAt: c.updatedAt
|
| 114 |
})).sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
| 115 |
res.json(chatsList);
|
| 116 |
});
|
|
|
|
| 129 |
id: newId,
|
| 130 |
title: "New Chat",
|
| 131 |
totalTokens: 0,
|
| 132 |
+
inputTokens: 0,
|
| 133 |
+
outputTokens: 0,
|
| 134 |
messages:[],
|
| 135 |
isGenerating: false,
|
| 136 |
updatedAt: new Date().toISOString()
|
|
|
|
| 139 |
res.json(memoryChats[newId]);
|
| 140 |
});
|
| 141 |
|
| 142 |
+
// Update chat title manually
|
| 143 |
+
app.put('/api/chats/:id/title', (req, res) => {
|
| 144 |
+
const { id } = req.params;
|
| 145 |
+
const { title } = req.body;
|
| 146 |
+
|
| 147 |
+
if (!memoryChats[id]) return res.status(404).json({ error: "Chat not found" });
|
| 148 |
+
if (!title || typeof title !== 'string') return res.status(400).json({ error: "Invalid title" });
|
| 149 |
+
|
| 150 |
+
memoryChats[id].title = title.trim();
|
| 151 |
+
dirtyChats.add(id);
|
| 152 |
+
|
| 153 |
+
res.json({ success: true, title: memoryChats[id].title });
|
| 154 |
+
});
|
| 155 |
+
|
| 156 |
// Delete chat permanently
|
| 157 |
app.delete('/api/chats/:id', async (req, res) => {
|
| 158 |
const { id } = req.params;
|
|
|
|
| 170 |
|
| 171 |
if (!memoryChats[id]) return res.status(404).send("Chat not found");
|
| 172 |
|
| 173 |
+
// Title generation ONLY if it hasn't been manually renamed from defaults
|
| 174 |
+
if (memoryChats[id].messages.length === 0 && memoryChats[id].title === "New Chat") {
|
| 175 |
memoryChats[id].title = prompt.substring(0, 30) + (prompt.length > 30 ? '...' : '');
|
| 176 |
}
|
| 177 |
|
| 178 |
+
// 1. SAVE TO MEMORY
|
| 179 |
memoryChats[id].messages.push({ role: "user", content: prompt });
|
| 180 |
|
| 181 |
const aiMessage = { role: "assistant", content: "", reasoning: "" };
|
|
|
|
| 189 |
res.setHeader('X-Accel-Buffering', 'no');
|
| 190 |
res.flushHeaders();
|
| 191 |
|
| 192 |
+
let streamInputTokens = 0;
|
| 193 |
+
let streamOutputTokens = 0;
|
| 194 |
+
let streamTotalTokens = 0;
|
| 195 |
|
| 196 |
try {
|
| 197 |
const bedrockModelId = getBedrockModelId(model);
|
|
|
|
| 206 |
contentBlock = [...imageBlocks, ...contentBlock];
|
| 207 |
}
|
| 208 |
|
| 209 |
+
// Map history for Bedrock
|
| 210 |
const historicalMessages = memoryChats[id].messages.slice(0, -2).map(m => ({
|
| 211 |
role: m.role, content:[{ text: m.content }]
|
| 212 |
}));
|
|
|
|
| 238 |
}
|
| 239 |
}
|
| 240 |
if (chunk.metadata && chunk.metadata.usage) {
|
| 241 |
+
streamInputTokens = chunk.metadata.usage.inputTokens || 0;
|
| 242 |
+
streamOutputTokens = chunk.metadata.usage.outputTokens || 0;
|
| 243 |
+
streamTotalTokens = streamInputTokens + streamOutputTokens;
|
| 244 |
}
|
| 245 |
}
|
| 246 |
|
| 247 |
+
// Clean up & finalize memory with granular token usage
|
| 248 |
+
memoryChats[id].inputTokens += streamInputTokens;
|
| 249 |
+
memoryChats[id].outputTokens += streamOutputTokens;
|
| 250 |
+
memoryChats[id].totalTokens += streamTotalTokens;
|
| 251 |
memoryChats[id].isGenerating = false;
|
| 252 |
dirtyChats.add(id);
|
| 253 |
|
| 254 |
+
// Send granular usage to frontend
|
| 255 |
+
res.write(`__USAGE__${JSON.stringify({
|
| 256 |
+
inputTokens: streamInputTokens,
|
| 257 |
+
outputTokens: streamOutputTokens,
|
| 258 |
+
totalTokens: streamTotalTokens
|
| 259 |
+
})}`);
|
| 260 |
res.end();
|
| 261 |
|
| 262 |
} catch (err) {
|