claude-code-templates 1.24.15 → 1.24.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/chats-mobile.js
CHANGED
|
@@ -173,6 +173,244 @@ class ChatsMobile {
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
+
// API to get unique working directories from conversations
|
|
177
|
+
this.app.get('/api/directories', (req, res) => {
|
|
178
|
+
try {
|
|
179
|
+
// Extract unique directories from conversations
|
|
180
|
+
const directories = new Set();
|
|
181
|
+
|
|
182
|
+
this.data.conversations.forEach(conv => {
|
|
183
|
+
if (conv.project && conv.project.trim()) {
|
|
184
|
+
directories.add(conv.project);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Convert to array and sort alphabetically
|
|
189
|
+
const sortedDirectories = Array.from(directories).sort((a, b) =>
|
|
190
|
+
a.toLowerCase().localeCompare(b.toLowerCase())
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
res.json({
|
|
194
|
+
directories: sortedDirectories,
|
|
195
|
+
count: sortedDirectories.length,
|
|
196
|
+
timestamp: new Date().toISOString()
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Error getting directories:', error);
|
|
200
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// API to search conversations with advanced filters
|
|
205
|
+
this.app.post('/api/search', async (req, res) => {
|
|
206
|
+
try {
|
|
207
|
+
const { query, workingDirectory, dateFrom, dateTo, contentSearch } = req.body;
|
|
208
|
+
|
|
209
|
+
let results = [...this.data.conversations];
|
|
210
|
+
|
|
211
|
+
// Filter by working directory (project)
|
|
212
|
+
if (workingDirectory && workingDirectory.trim()) {
|
|
213
|
+
results = results.filter(conv => {
|
|
214
|
+
if (!conv.project) return false;
|
|
215
|
+
return conv.project.toLowerCase().includes(workingDirectory.toLowerCase());
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Filter by date range
|
|
220
|
+
if (dateFrom) {
|
|
221
|
+
const fromDate = new Date(dateFrom);
|
|
222
|
+
results = results.filter(conv => new Date(conv.created) >= fromDate);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (dateTo) {
|
|
226
|
+
const toDate = new Date(dateTo);
|
|
227
|
+
toDate.setHours(23, 59, 59, 999); // Include entire day
|
|
228
|
+
results = results.filter(conv => new Date(conv.created) <= toDate);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Filter by conversation metadata (filename, id)
|
|
232
|
+
if (query && query.trim()) {
|
|
233
|
+
const searchTerm = query.toLowerCase();
|
|
234
|
+
results = results.filter(conv =>
|
|
235
|
+
conv.filename.toLowerCase().includes(searchTerm) ||
|
|
236
|
+
conv.id.toLowerCase().includes(searchTerm) ||
|
|
237
|
+
(conv.project && conv.project.toLowerCase().includes(searchTerm))
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Search within message content
|
|
242
|
+
if (contentSearch && contentSearch.trim()) {
|
|
243
|
+
const contentTerm = contentSearch.toLowerCase();
|
|
244
|
+
const matchingConversations = [];
|
|
245
|
+
|
|
246
|
+
for (const conv of results) {
|
|
247
|
+
try {
|
|
248
|
+
const messages = await this.conversationAnalyzer.getParsedConversation(conv.filePath);
|
|
249
|
+
|
|
250
|
+
// Search in message content
|
|
251
|
+
const hasMatch = messages.some(msg => {
|
|
252
|
+
// Search in text content
|
|
253
|
+
if (typeof msg.content === 'string') {
|
|
254
|
+
return msg.content.toLowerCase().includes(contentTerm);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Search in array content (tool use, text blocks)
|
|
258
|
+
if (Array.isArray(msg.content)) {
|
|
259
|
+
return msg.content.some(block => {
|
|
260
|
+
if (block.type === 'text' && block.text) {
|
|
261
|
+
return block.text.toLowerCase().includes(contentTerm);
|
|
262
|
+
}
|
|
263
|
+
if (block.type === 'tool_use' && block.name) {
|
|
264
|
+
return block.name.toLowerCase().includes(contentTerm);
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return false;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (hasMatch) {
|
|
274
|
+
matchingConversations.push(conv);
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// Skip conversations that can't be parsed
|
|
278
|
+
this.log('warn', `Error searching in conversation ${conv.id}:`, error.message);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
results = matchingConversations;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Sort by last modified (most recent first)
|
|
286
|
+
results.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
|
|
287
|
+
|
|
288
|
+
res.json({
|
|
289
|
+
results: results,
|
|
290
|
+
count: results.length,
|
|
291
|
+
filters: {
|
|
292
|
+
query,
|
|
293
|
+
workingDirectory,
|
|
294
|
+
dateFrom,
|
|
295
|
+
dateTo,
|
|
296
|
+
contentSearch
|
|
297
|
+
},
|
|
298
|
+
timestamp: new Date().toISOString()
|
|
299
|
+
});
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error('Error searching conversations:', error);
|
|
302
|
+
res.status(500).json({ error: 'Internal server error', message: error.message });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// API to search within a specific conversation
|
|
307
|
+
this.app.post('/api/conversations/:id/search', async (req, res) => {
|
|
308
|
+
try {
|
|
309
|
+
const conversationId = req.params.id;
|
|
310
|
+
const { query } = req.body;
|
|
311
|
+
const conversation = this.data.conversations.find(conv => conv.id === conversationId);
|
|
312
|
+
|
|
313
|
+
if (!conversation) {
|
|
314
|
+
return res.status(404).json({ error: 'Conversation not found' });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!query || !query.trim()) {
|
|
318
|
+
return res.json({
|
|
319
|
+
matches: [],
|
|
320
|
+
totalMatches: 0,
|
|
321
|
+
conversationId: conversationId
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Get all messages from the conversation
|
|
326
|
+
const allMessages = await this.conversationAnalyzer.getParsedConversation(conversation.filePath);
|
|
327
|
+
const searchTerm = query.toLowerCase();
|
|
328
|
+
const matches = [];
|
|
329
|
+
|
|
330
|
+
// Search through all messages
|
|
331
|
+
allMessages.forEach((msg, index) => {
|
|
332
|
+
let messageText = '';
|
|
333
|
+
let allText = [];
|
|
334
|
+
|
|
335
|
+
// Extract text from message content
|
|
336
|
+
if (typeof msg.content === 'string') {
|
|
337
|
+
allText.push(msg.content);
|
|
338
|
+
} else if (Array.isArray(msg.content)) {
|
|
339
|
+
msg.content.forEach(block => {
|
|
340
|
+
if (block.type === 'text' && block.text) {
|
|
341
|
+
allText.push(block.text);
|
|
342
|
+
}
|
|
343
|
+
// Also search in tool_use content
|
|
344
|
+
if (block.type === 'tool_use') {
|
|
345
|
+
if (block.name) allText.push(block.name);
|
|
346
|
+
if (block.input) {
|
|
347
|
+
allText.push(JSON.stringify(block.input));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// IMPORTANT: Also search in tool results (this is where code blocks appear!)
|
|
354
|
+
if (msg.toolResults && Array.isArray(msg.toolResults)) {
|
|
355
|
+
msg.toolResults.forEach(toolResult => {
|
|
356
|
+
if (toolResult.content) {
|
|
357
|
+
if (typeof toolResult.content === 'string') {
|
|
358
|
+
allText.push(toolResult.content);
|
|
359
|
+
} else if (Array.isArray(toolResult.content)) {
|
|
360
|
+
toolResult.content.forEach(block => {
|
|
361
|
+
if (block.type === 'text' && block.text) {
|
|
362
|
+
allText.push(block.text);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Combine all text
|
|
371
|
+
messageText = allText.join(' ');
|
|
372
|
+
|
|
373
|
+
// Search in the combined text
|
|
374
|
+
if (messageText.toLowerCase().includes(searchTerm)) {
|
|
375
|
+
// Find all positions of the search term in this message
|
|
376
|
+
const lowerText = messageText.toLowerCase();
|
|
377
|
+
let position = 0;
|
|
378
|
+
let matchCount = 0;
|
|
379
|
+
|
|
380
|
+
while ((position = lowerText.indexOf(searchTerm, position)) !== -1) {
|
|
381
|
+
matchCount++;
|
|
382
|
+
position += searchTerm.length;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
matches.push({
|
|
386
|
+
messageIndex: index,
|
|
387
|
+
messageId: msg.id,
|
|
388
|
+
role: msg.role,
|
|
389
|
+
timestamp: msg.timestamp,
|
|
390
|
+
preview: this.getMessagePreview(messageText, searchTerm),
|
|
391
|
+
matchCount: matchCount
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
console.log(`🔍 Search in conversation ${conversationId}:`, {
|
|
397
|
+
query: query,
|
|
398
|
+
messagesWithMatches: matches.length,
|
|
399
|
+
totalOccurrences: matches.reduce((sum, m) => sum + m.matchCount, 0)
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
res.json({
|
|
403
|
+
matches: matches,
|
|
404
|
+
totalMatches: matches.length,
|
|
405
|
+
conversationId: conversationId,
|
|
406
|
+
query: query
|
|
407
|
+
});
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error('Error searching in conversation:', error);
|
|
410
|
+
res.status(500).json({ error: 'Internal server error', message: error.message });
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
176
414
|
// API to get specific conversation messages (with pagination support)
|
|
177
415
|
this.app.get('/api/conversations/:id/messages', async (req, res) => {
|
|
178
416
|
try {
|
|
@@ -418,6 +656,26 @@ class ChatsMobile {
|
|
|
418
656
|
console.log(chalk.gray('🔧 WebSocket server setup prepared'));
|
|
419
657
|
}
|
|
420
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Helper function to get message preview with context
|
|
661
|
+
*/
|
|
662
|
+
getMessagePreview(text, searchTerm, contextLength = 100) {
|
|
663
|
+
const lowerText = text.toLowerCase();
|
|
664
|
+
const lowerTerm = searchTerm.toLowerCase();
|
|
665
|
+
const position = lowerText.indexOf(lowerTerm);
|
|
666
|
+
|
|
667
|
+
if (position === -1) return text.substring(0, contextLength);
|
|
668
|
+
|
|
669
|
+
const start = Math.max(0, position - contextLength / 2);
|
|
670
|
+
const end = Math.min(text.length, position + searchTerm.length + contextLength / 2);
|
|
671
|
+
|
|
672
|
+
let preview = text.substring(start, end);
|
|
673
|
+
if (start > 0) preview = '...' + preview;
|
|
674
|
+
if (end < text.length) preview = preview + '...';
|
|
675
|
+
|
|
676
|
+
return preview;
|
|
677
|
+
}
|
|
678
|
+
|
|
421
679
|
/**
|
|
422
680
|
* Load initial conversation data
|
|
423
681
|
*/
|