1<script lang="ts">
2 import { untrack } from 'svelte';
3 import { PROCESSING_INFO_TIMEOUT } from '$lib/constants/processing-info';
4 import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
5 import { chatStore, isLoading, isChatStreaming } from '$lib/stores/chat.svelte';
6 import { activeMessages, activeConversation } from '$lib/stores/conversations.svelte';
7 import { config } from '$lib/stores/settings.svelte';
8
9 const processingState = useProcessingState();
10
11 let isCurrentConversationLoading = $derived(isLoading());
12 let isStreaming = $derived(isChatStreaming());
13 let hasProcessingData = $derived(processingState.processingState !== null);
14 let processingDetails = $derived(processingState.getProcessingDetails());
15
16 let showProcessingInfo = $derived(
17 isCurrentConversationLoading || isStreaming || config().keepStatsVisible || hasProcessingData
18 );
19
20 $effect(() => {
21 const conversation = activeConversation();
22
23 untrack(() => chatStore.setActiveProcessingConversation(conversation?.id ?? null));
24 });
25
26 $effect(() => {
27 const keepStatsVisible = config().keepStatsVisible;
28 const shouldMonitor = keepStatsVisible || isCurrentConversationLoading || isStreaming;
29
30 if (shouldMonitor) {
31 processingState.startMonitoring();
32 }
33
34 if (!isCurrentConversationLoading && !isStreaming && !keepStatsVisible) {
35 const timeout = setTimeout(() => {
36 if (!config().keepStatsVisible && !isChatStreaming()) {
37 processingState.stopMonitoring();
38 }
39 }, PROCESSING_INFO_TIMEOUT);
40
41 return () => clearTimeout(timeout);
42 }
43 });
44
45 $effect(() => {
46 const conversation = activeConversation();
47 const messages = activeMessages() as DatabaseMessage[];
48 const keepStatsVisible = config().keepStatsVisible;
49
50 if (keepStatsVisible && conversation) {
51 if (messages.length === 0) {
52 untrack(() => chatStore.clearProcessingState(conversation.id));
53 return;
54 }
55
56 if (!isCurrentConversationLoading && !isStreaming) {
57 untrack(() => chatStore.restoreProcessingStateFromMessages(messages, conversation.id));
58 }
59 }
60 });
61</script>
62
63<div class="chat-processing-info-container pointer-events-none" class:visible={showProcessingInfo}>
64 <div class="chat-processing-info-content">
65 {#each processingDetails as detail (detail)}
66 <span class="chat-processing-info-detail pointer-events-auto">{detail}</span>
67 {/each}
68 </div>
69</div>
70
71<style>
72 .chat-processing-info-container {
73 position: sticky;
74 top: 0;
75 z-index: 10;
76 padding: 1.5rem 1rem;
77 opacity: 0;
78 transform: translateY(50%);
79 transition:
80 opacity 300ms ease-out,
81 transform 300ms ease-out;
82 }
83
84 .chat-processing-info-container.visible {
85 opacity: 1;
86 transform: translateY(0);
87 }
88
89 .chat-processing-info-content {
90 display: flex;
91 flex-wrap: wrap;
92 align-items: center;
93 gap: 1rem;
94 justify-content: center;
95 max-width: 48rem;
96 margin: 0 auto;
97 }
98
99 .chat-processing-info-detail {
100 color: var(--muted-foreground);
101 font-size: 0.75rem;
102 padding: 0.25rem 0.75rem;
103 background: var(--muted);
104 border-radius: 0.375rem;
105 font-family:
106 ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
107 white-space: nowrap;
108 }
109
110 @media (max-width: 768px) {
111 .chat-processing-info-content {
112 gap: 0.5rem;
113 }
114
115 .chat-processing-info-detail {
116 font-size: 0.7rem;
117 padding: 0.2rem 0.5rem;
118 }
119 }
120</style>