iris-chatbot 0.2.5 → 2.0.0
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/package.json +1 -1
- package/template/= +0 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/src/app/api/chat/route.ts +4 -3
- package/template/src/app/api/local-sync/route.ts +20 -9
- package/template/src/app/globals.css +32 -32
- package/template/src/app/page.tsx +10 -9
- package/template/src/components/ChatView.tsx +16 -2
- package/template/src/components/Composer.tsx +13 -4
- package/template/src/components/MessageCard.tsx +15 -7
- package/template/src/components/SettingsModal.tsx +11 -8
- package/template/src/components/Sidebar.tsx +0 -4
- package/template/src/components/TopBar.tsx +20 -16
- package/template/src/lib/memory.ts +133 -2
- package/template/src/lib/tooling/tools/music.ts +16 -5
- package/template/src/lib/tooling/tools/schedule.ts +813 -43
|
@@ -340,6 +340,123 @@ function extractAutoCandidates(params: {
|
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
const calendarPatterns: RegExp[] = [
|
|
344
|
+
/\buse\s+(?:the\s+)?([a-zA-Z0-9_\s-]+?)\s*(?:calendar)?\s*[.!?]?\s*$/i,
|
|
345
|
+
/\b(?:default\s+)?calendar\s+is\s+([a-zA-Z0-9_\s-]+)/i,
|
|
346
|
+
/\balways\s+use\s+([a-zA-Z0-9_\s-]+)\s+for\s+calendar/i,
|
|
347
|
+
];
|
|
348
|
+
for (const pattern of calendarPatterns) {
|
|
349
|
+
const m = text.match(pattern);
|
|
350
|
+
if (m?.[1]) {
|
|
351
|
+
const value = trimSentence(m[1]);
|
|
352
|
+
if (value.length >= 1 && value.length <= 80) {
|
|
353
|
+
const candidate = createCandidate({
|
|
354
|
+
kind: "preference",
|
|
355
|
+
scope,
|
|
356
|
+
conversationId,
|
|
357
|
+
key: "default calendar",
|
|
358
|
+
value,
|
|
359
|
+
source: "auto",
|
|
360
|
+
confidence: 0.75,
|
|
361
|
+
});
|
|
362
|
+
if (candidate) {
|
|
363
|
+
candidates.push(candidate);
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const timezonePatterns: RegExp[] = [
|
|
371
|
+
/\btime\s*zone\s+is\s+([a-zA-Z\s]+?)(?:\s+time)?[.!?]?\s*$/i,
|
|
372
|
+
/\btimezone\s+is\s+([a-zA-Z\s]+?)(?:\s+time)?[.!?]?\s*$/i,
|
|
373
|
+
/\bI'?m\s+in\s+([a-zA-Z\s]+?)(?:\s+time)?[.!?]?\s*$/i,
|
|
374
|
+
/\buse\s+([a-zA-Z\s]+?)\s+time[.!?]?\s*$/i,
|
|
375
|
+
];
|
|
376
|
+
for (const pattern of timezonePatterns) {
|
|
377
|
+
const m = text.match(pattern);
|
|
378
|
+
if (m?.[1]) {
|
|
379
|
+
const value = trimSentence(m[1]);
|
|
380
|
+
if (value.length >= 2 && value.length <= 60) {
|
|
381
|
+
const candidate = createCandidate({
|
|
382
|
+
kind: "preference",
|
|
383
|
+
scope,
|
|
384
|
+
conversationId,
|
|
385
|
+
key: "timezone",
|
|
386
|
+
value,
|
|
387
|
+
source: "auto",
|
|
388
|
+
confidence: 0.75,
|
|
389
|
+
});
|
|
390
|
+
if (candidate) {
|
|
391
|
+
candidates.push(candidate);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return candidates;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const CALENDAR_QUESTION_PATTERN = /\b(which|what)\s+calendar\b|calendar\s+(?:should|to)\s+use|which\s+calendar\s+should/i;
|
|
402
|
+
const TIMEZONE_QUESTION_PATTERN = /\b(which|what)\s+time\s*zone|timezone|time\s+zone\s+should|which\s+timezone/i;
|
|
403
|
+
|
|
404
|
+
function extractContextAwareCandidates(params: {
|
|
405
|
+
text: string;
|
|
406
|
+
lastAssistantMessageContent: string;
|
|
407
|
+
scope: MemoryScope;
|
|
408
|
+
conversationId: string;
|
|
409
|
+
}): CapturedMemoryCandidate[] {
|
|
410
|
+
const { text, lastAssistantMessageContent, scope, conversationId } = params;
|
|
411
|
+
const trimmed = trimSentence(text);
|
|
412
|
+
if (!trimmed || trimmed.length > 120) {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
const last = lastAssistantMessageContent.trim();
|
|
416
|
+
if (!last) {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const candidates: CapturedMemoryCandidate[] = [];
|
|
420
|
+
|
|
421
|
+
const isCalendarQuestion = CALENDAR_QUESTION_PATTERN.test(last);
|
|
422
|
+
const isTimezoneQuestion = TIMEZONE_QUESTION_PATTERN.test(last);
|
|
423
|
+
|
|
424
|
+
if (isCalendarQuestion && !isTimezoneQuestion) {
|
|
425
|
+
const value = trimmed.split(/[.!?\n]/)[0]?.trim() ?? trimmed;
|
|
426
|
+
if (value.length >= 1 && value.length <= 80) {
|
|
427
|
+
const candidate = createCandidate({
|
|
428
|
+
kind: "preference",
|
|
429
|
+
scope,
|
|
430
|
+
conversationId,
|
|
431
|
+
key: "default calendar",
|
|
432
|
+
value,
|
|
433
|
+
source: "auto",
|
|
434
|
+
confidence: 0.7,
|
|
435
|
+
});
|
|
436
|
+
if (candidate) {
|
|
437
|
+
candidates.push(candidate);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (isTimezoneQuestion) {
|
|
443
|
+
const value = trimmed.split(/[.!?\n]/)[0]?.trim() ?? trimmed;
|
|
444
|
+
if (value.length >= 2 && value.length <= 60) {
|
|
445
|
+
const candidate = createCandidate({
|
|
446
|
+
kind: "preference",
|
|
447
|
+
scope,
|
|
448
|
+
conversationId,
|
|
449
|
+
key: "timezone",
|
|
450
|
+
value,
|
|
451
|
+
source: "auto",
|
|
452
|
+
confidence: 0.7,
|
|
453
|
+
});
|
|
454
|
+
if (candidate) {
|
|
455
|
+
candidates.push(candidate);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
343
460
|
return candidates;
|
|
344
461
|
}
|
|
345
462
|
|
|
@@ -422,6 +539,7 @@ export async function captureMemoriesFromUserTurn(params: {
|
|
|
422
539
|
text: string;
|
|
423
540
|
conversationId: string;
|
|
424
541
|
settingsMemory?: MemorySettings | null;
|
|
542
|
+
lastAssistantMessageContent?: string;
|
|
425
543
|
}): Promise<void> {
|
|
426
544
|
const rawText = params.text?.trim();
|
|
427
545
|
if (!rawText) {
|
|
@@ -449,8 +567,21 @@ export async function captureMemoriesFromUserTurn(params: {
|
|
|
449
567
|
scope,
|
|
450
568
|
conversationId: params.conversationId,
|
|
451
569
|
});
|
|
452
|
-
|
|
453
|
-
|
|
570
|
+
const contextAwareCandidates =
|
|
571
|
+
params.lastAssistantMessageContent != null && params.lastAssistantMessageContent.trim() !== ""
|
|
572
|
+
? extractContextAwareCandidates({
|
|
573
|
+
text: rawText,
|
|
574
|
+
lastAssistantMessageContent: params.lastAssistantMessageContent,
|
|
575
|
+
scope,
|
|
576
|
+
conversationId: params.conversationId,
|
|
577
|
+
})
|
|
578
|
+
: [];
|
|
579
|
+
|
|
580
|
+
const candidates = dedupeCandidates([
|
|
581
|
+
...explicitCandidates,
|
|
582
|
+
...autoCandidates,
|
|
583
|
+
...contextAwareCandidates,
|
|
584
|
+
]);
|
|
454
585
|
if (candidates.length === 0) {
|
|
455
586
|
return;
|
|
456
587
|
}
|
|
@@ -247,7 +247,7 @@ function rankPlaylistCandidates(params: {
|
|
|
247
247
|
const rawScore = textMatchScore(candidate.name, cleanedRaw);
|
|
248
248
|
const exactBonus =
|
|
249
249
|
normalizeLoose(candidate.name) === normalizeLoose(cleanedQuery) ||
|
|
250
|
-
|
|
250
|
+
normalizeLoose(candidate.name) === normalizeLoose(cleanedRaw)
|
|
251
251
|
? 30
|
|
252
252
|
: 0;
|
|
253
253
|
const playlistBonus = params.playlistRequested ? 20 : 0;
|
|
@@ -668,7 +668,9 @@ async function runMusicPlay(input: unknown, context: ToolExecutionContext) {
|
|
|
668
668
|
title,
|
|
669
669
|
artist,
|
|
670
670
|
});
|
|
671
|
+
console.log("[music_play] Search queries:", searchQueries, "title:", title, "artist:", artist, "query:", query);
|
|
671
672
|
const primaryLibraryCandidates = await searchLibraryTracks(searchQueries, context.signal);
|
|
673
|
+
console.log("[music_play] Primary candidates found:", primaryLibraryCandidates.length);
|
|
672
674
|
let libraryCandidates = primaryLibraryCandidates;
|
|
673
675
|
if (libraryCandidates.length === 0 || enforceTitleMatch || enforceArtistMatch) {
|
|
674
676
|
try {
|
|
@@ -928,13 +930,22 @@ async function runMusicNowPlaying(_: unknown, context: ToolExecutionContext) {
|
|
|
928
930
|
export const musicTools: ToolDefinition[] = [
|
|
929
931
|
{
|
|
930
932
|
name: "music_play",
|
|
931
|
-
description: "Play Apple Music or
|
|
933
|
+
description: "Play a song, album, or playlist from Apple Music. Use 'query' for general searches like song names, artist names, or any combination.",
|
|
932
934
|
inputSchema: {
|
|
933
935
|
type: "object",
|
|
934
936
|
properties: {
|
|
935
|
-
query: {
|
|
936
|
-
|
|
937
|
-
|
|
937
|
+
query: {
|
|
938
|
+
type: "string",
|
|
939
|
+
description: "The search query - can be a song name, artist name, album, or combination like 'song by artist'. This is the primary search parameter."
|
|
940
|
+
},
|
|
941
|
+
title: {
|
|
942
|
+
type: "string",
|
|
943
|
+
description: "Optional: specific track name if known exactly"
|
|
944
|
+
},
|
|
945
|
+
artist: {
|
|
946
|
+
type: "string",
|
|
947
|
+
description: "Optional: artist name to filter results"
|
|
948
|
+
},
|
|
938
949
|
source: { type: "string", enum: ["apple_music", "local_library"] },
|
|
939
950
|
},
|
|
940
951
|
additionalProperties: false,
|