diff --git a/ui/desktop/src/acp/sessions.ts b/ui/desktop/src/acp/sessions.ts index 773409711..d8e322a86 100644 --- a/ui/desktop/src/acp/sessions.ts +++ b/ui/desktop/src/acp/sessions.ts @@ -40,6 +40,7 @@ export interface SessionListItem { modelId?: string; userSetName?: boolean; hasRecipe?: boolean; + sessionType?: Session['session_type']; } export interface SessionListPage { @@ -128,6 +129,7 @@ function sessionInfoToListItem(s: SessionInfo): SessionListItem { modelId: meta.modelId, userSetName: meta.userSetName, hasRecipe: meta.hasRecipe, + sessionType: meta.sessionType, }; } diff --git a/ui/desktop/src/components/sessions/SessionListView.tsx b/ui/desktop/src/components/sessions/SessionListView.tsx index 118e53230..6a98308ef 100644 --- a/ui/desktop/src/components/sessions/SessionListView.tsx +++ b/ui/desktop/src/components/sessions/SessionListView.tsx @@ -14,6 +14,9 @@ import { LoaderCircle, ExternalLink, Copy, + ChevronDown, + ChevronRight, + Clock, } from 'lucide-react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; @@ -163,6 +166,14 @@ const i18n = defineMessages({ 'Anyone with this link can fetch and decrypt the session. Treat it like a secret.', }, close: { id: 'sessions.close', defaultMessage: 'Close' }, + scheduledJobs: { + id: 'sessions.scheduledJobs', + defaultMessage: 'Scheduled Jobs', + }, + scheduledJobsCount: { + id: 'sessions.scheduledJobsCount', + defaultMessage: '{count} {count, plural, one {job} other {jobs}}', + }, }); interface EditSessionModalProps { @@ -314,6 +325,7 @@ const SessionListView: React.FC = React.memo(({ onSelectSe const [error, setError] = useState(null); const [visibleGroupsCount, setVisibleGroupsCount] = useState(15); + const [isScheduledExpanded, setIsScheduledExpanded] = useState(false); // Edit modal state const [showEditModal, setShowEditModal] = useState(false); @@ -343,9 +355,20 @@ const SessionListView: React.FC = React.memo(({ onSelectSe const fileInputRef = useRef(null); + const memoizedAllDateGroups = useMemo(() => { + if (sessions.length > 0) { + return groupSessionsByDate(sessions); + } + return []; + }, [sessions]); + + const activeDateGroups = useMemo(() => { + return debouncedSearchTerm.length > 0 ? memoizedAllDateGroups : dateGroups; + }, [debouncedSearchTerm, memoizedAllDateGroups, dateGroups]); + const visibleDateGroups = useMemo(() => { - return dateGroups.slice(0, visibleGroupsCount); - }, [dateGroups, visibleGroupsCount]); + return activeDateGroups.slice(0, visibleGroupsCount); + }, [activeDateGroups, visibleGroupsCount]); const previousSearchTermRef = useRef(''); useEffect(() => { @@ -354,11 +377,11 @@ const SessionListView: React.FC = React.memo(({ onSelectSe previousSearchTermRef.current = debouncedSearchTerm; if (isSearching) { - setVisibleGroupsCount(dateGroups.length); + setVisibleGroupsCount(memoizedAllDateGroups.length); } else if (wasSearching) { setVisibleGroupsCount(15); } - }, [debouncedSearchTerm, dateGroups.length]); + }, [debouncedSearchTerm, memoizedAllDateGroups.length]); const loadRemainingSessionPages = useCallback( async (initialCursor: string, loadId: number, keyword?: string) => { @@ -437,11 +460,11 @@ const SessionListView: React.FC = React.memo(({ onSelectSe if (scrollHeight - scrollTop - clientHeight >= threshold) return; - if (visibleGroupsCount < dateGroups.length) { - setVisibleGroupsCount((prev) => Math.min(prev + 5, dateGroups.length)); + if (visibleGroupsCount < activeDateGroups.length) { + setVisibleGroupsCount((prev) => Math.min(prev + 5, activeDateGroups.length)); } }, - [visibleGroupsCount, dateGroups.length] + [visibleGroupsCount, activeDateGroups.length] ); useEffect(() => { @@ -474,13 +497,32 @@ const SessionListView: React.FC = React.memo(({ onSelectSe return () => void 0; }, [isLoading, showSkeleton]); - // Memoize date groups calculation to prevent unnecessary recalculations + const { humanSessions, scheduledSessions } = useMemo(() => { + const human: SessionListItem[] = []; + const scheduled: SessionListItem[] = []; + for (const s of sessions) { + if (s.sessionType === 'scheduled') { + scheduled.push(s); + } else { + human.push(s); + } + } + return { humanSessions: human, scheduledSessions: scheduled }; + }, [sessions]); + const memoizedDateGroups = useMemo(() => { - if (sessions.length > 0) { - return groupSessionsByDate(sessions); + if (humanSessions.length > 0) { + return groupSessionsByDate(humanSessions); } return []; - }, [sessions]); + }, [humanSessions]); + + const memoizedScheduledDateGroups = useMemo(() => { + if (scheduledSessions.length > 0) { + return groupSessionsByDate(scheduledSessions); + } + return []; + }, [scheduledSessions]); // Update date groups when filtered sessions change useEffect(() => { @@ -972,6 +1014,61 @@ const SessionListView: React.FC = React.memo(({ onSelectSe ))} + {!debouncedSearchTerm && + visibleGroupsCount >= activeDateGroups.length && + memoizedScheduledDateGroups.length > 0 && ( +
+ + + {isScheduledExpanded && ( +
+ {memoizedScheduledDateGroups.map((group) => ( +
+
+

{group.label}

+
+
+ {group.sessions.map((session) => ( + + ))} +
+
+ ))} +
+ )} +
+ )} + {isPrefetchingSessions && (
diff --git a/ui/desktop/src/i18n/messages/de.json b/ui/desktop/src/i18n/messages/de.json index ed5a93054..860bcbcb8 100644 --- a/ui/desktop/src/i18n/messages/de.json +++ b/ui/desktop/src/i18n/messages/de.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Wird gespeichert..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Keine passenden Sitzungen gefunden" }, diff --git a/ui/desktop/src/i18n/messages/en.json b/ui/desktop/src/i18n/messages/en.json index 39acbd8c3..22c5d3606 100644 --- a/ui/desktop/src/i18n/messages/en.json +++ b/ui/desktop/src/i18n/messages/en.json @@ -3869,6 +3869,12 @@ "sessions.saving": { "defaultMessage": "Saving..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "No matching sessions found" }, diff --git a/ui/desktop/src/i18n/messages/es.json b/ui/desktop/src/i18n/messages/es.json index c8bd5a44c..1c47a0ccb 100644 --- a/ui/desktop/src/i18n/messages/es.json +++ b/ui/desktop/src/i18n/messages/es.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Guardando..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "No se encontraron sesiones que coincidan" }, diff --git a/ui/desktop/src/i18n/messages/fr.json b/ui/desktop/src/i18n/messages/fr.json index b3b9e742a..8cb2e2bd7 100644 --- a/ui/desktop/src/i18n/messages/fr.json +++ b/ui/desktop/src/i18n/messages/fr.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Enregistrement..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Aucune session correspondante trouvée" }, diff --git a/ui/desktop/src/i18n/messages/hi.json b/ui/desktop/src/i18n/messages/hi.json index 8339d175e..e1d288693 100644 --- a/ui/desktop/src/i18n/messages/hi.json +++ b/ui/desktop/src/i18n/messages/hi.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "सहेजा जा रहा है..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "कोई मिलान सत्र नहीं मिला" }, diff --git a/ui/desktop/src/i18n/messages/id.json b/ui/desktop/src/i18n/messages/id.json index 9b8a3094e..0eb1f3b80 100644 --- a/ui/desktop/src/i18n/messages/id.json +++ b/ui/desktop/src/i18n/messages/id.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Menyimpan..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Tidak ada sesi yang cocok ditemukan" }, diff --git a/ui/desktop/src/i18n/messages/it.json b/ui/desktop/src/i18n/messages/it.json index 2bb28af3e..39d62d619 100644 --- a/ui/desktop/src/i18n/messages/it.json +++ b/ui/desktop/src/i18n/messages/it.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Salvataggio in corso..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Nessuna sessione corrispondente trovata" }, diff --git a/ui/desktop/src/i18n/messages/ja.json b/ui/desktop/src/i18n/messages/ja.json index aaa2b1034..e987d1724 100644 --- a/ui/desktop/src/i18n/messages/ja.json +++ b/ui/desktop/src/i18n/messages/ja.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "保存中..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "一致するセッションが見つかりません" }, diff --git a/ui/desktop/src/i18n/messages/ko.json b/ui/desktop/src/i18n/messages/ko.json index ed2e74ca0..3bfc05c3e 100644 --- a/ui/desktop/src/i18n/messages/ko.json +++ b/ui/desktop/src/i18n/messages/ko.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "저장 중..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "일치하는 세션이 없습니다." }, diff --git a/ui/desktop/src/i18n/messages/ms.json b/ui/desktop/src/i18n/messages/ms.json index 42befa101..d3fdef03b 100644 --- a/ui/desktop/src/i18n/messages/ms.json +++ b/ui/desktop/src/i18n/messages/ms.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Menyimpan..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Tiada sesi sepadan ditemui" }, diff --git a/ui/desktop/src/i18n/messages/pt.json b/ui/desktop/src/i18n/messages/pt.json index 15ffa4d4c..b0fabdc30 100644 --- a/ui/desktop/src/i18n/messages/pt.json +++ b/ui/desktop/src/i18n/messages/pt.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "A guardar..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Nenhuma sessão correspondente encontrada" }, diff --git a/ui/desktop/src/i18n/messages/ru.json b/ui/desktop/src/i18n/messages/ru.json index 56ab1dfaf..4ab148e1f 100644 --- a/ui/desktop/src/i18n/messages/ru.json +++ b/ui/desktop/src/i18n/messages/ru.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Сохранение..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Подходящие сеансы не найдены" }, diff --git a/ui/desktop/src/i18n/messages/tr.json b/ui/desktop/src/i18n/messages/tr.json index 742807fce..3deee415b 100644 --- a/ui/desktop/src/i18n/messages/tr.json +++ b/ui/desktop/src/i18n/messages/tr.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Kaydediliyor..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Eşleşen oturum bulunamadı" }, diff --git a/ui/desktop/src/i18n/messages/vi.json b/ui/desktop/src/i18n/messages/vi.json index d58a569e3..008c68de7 100644 --- a/ui/desktop/src/i18n/messages/vi.json +++ b/ui/desktop/src/i18n/messages/vi.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "Đang lưu..." }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "Không tìm thấy phiên phù hợp" }, diff --git a/ui/desktop/src/i18n/messages/zh-CN.json b/ui/desktop/src/i18n/messages/zh-CN.json index c1f0ab54e..9494c2f62 100644 --- a/ui/desktop/src/i18n/messages/zh-CN.json +++ b/ui/desktop/src/i18n/messages/zh-CN.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "保存中…" }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "未找到匹配的会话" }, diff --git a/ui/desktop/src/i18n/messages/zh-TW.json b/ui/desktop/src/i18n/messages/zh-TW.json index e93b9622a..7249b31d3 100644 --- a/ui/desktop/src/i18n/messages/zh-TW.json +++ b/ui/desktop/src/i18n/messages/zh-TW.json @@ -3839,6 +3839,12 @@ "sessions.saving": { "defaultMessage": "正在儲存…" }, + "sessions.scheduledJobs": { + "defaultMessage": "Scheduled Jobs" + }, + "sessions.scheduledJobsCount": { + "defaultMessage": "{count,plural,one{{count} job} other{{count} jobs}}" + }, "sessions.search.noResults": { "defaultMessage": "找不到符合的工作階段" },