864 lines
30 KiB
Plaintext
864 lines
30 KiB
Plaintext
算了,撤销之前所有的修改,这是你之前的修改记录
|
|
Edited static/src/App.vue (+14 -0)
|
|
170 </template>
|
|
171 + <div v-else class="conversation-loading">
|
|
172 + <div class="loading-card">
|
|
173 + <div class="loader">
|
|
174 + <p>正在加载</p>
|
|
175 + <div class="words" aria-hidden="true">
|
|
176 + <span class="word">对话</span>
|
|
177 + <span class="word">工具</span>
|
|
178 + <span class="word">思考</span>
|
|
179 + <span class="word">文件</span>
|
|
180 + <span class="word">对话</span>
|
|
181 + </div>
|
|
182 + </div>
|
|
183 + </div>
|
|
184 + </div>
|
|
185 </AppShell>
|
|
|
|
• Edited static/src/styles/layout/_app-shell.scss (+98 -2)
|
|
99 opacity: 0.7;
|
|
100 - }
|
|
100 +}
|
|
101
|
|
102 - .boxes {
|
|
102 +.boxes {
|
|
103 --size: 34px;
|
|
⋮
|
|
122
|
|
123 +.conversation-loading {
|
|
124 + min-height: var(--app-viewport, 100vh);
|
|
125 + display: flex;
|
|
126 + align-items: center;
|
|
127 + justify-content: center;
|
|
128 + background: var(--claude-bg);
|
|
129 + padding: 24px;
|
|
130 +}
|
|
131 +
|
|
132 +.loading-card {
|
|
133 + background: rgba(0, 0, 0, 0.65);
|
|
134 + border-radius: 20px;
|
|
135 + padding: 18px 28px;
|
|
136 + color: #fff;
|
|
137 + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18);
|
|
138 +}
|
|
139 +
|
|
140 +.loader {
|
|
141 + font-family: 'Iowan Old Style', ui-serif, Georgia, serif;
|
|
142 + font-size: 22px;
|
|
143 + font-weight: 500;
|
|
144 + display: flex;
|
|
145 + align-items: center;
|
|
146 + gap: 8px;
|
|
147 + letter-spacing: 0.04em;
|
|
148 + color: rgba(255, 255, 255, 0.9);
|
|
149 +}
|
|
150 +
|
|
151 +.loader p {
|
|
152 + margin: 0;
|
|
153 +}
|
|
154 +
|
|
155 +.words {
|
|
156 + overflow: hidden;
|
|
157 + position: relative;
|
|
158 + height: 28px;
|
|
159 + line-height: 28px;
|
|
160 +}
|
|
161 +
|
|
162 +.words::after {
|
|
163 + content: '';
|
|
164 + position: absolute;
|
|
165 + inset: 0;
|
|
166 + background: linear-gradient(
|
|
167 + rgba(0, 0, 0, 0.65) 10%,
|
|
168 + transparent 30%,
|
|
169 + transparent 70%,
|
|
170 + rgba(0, 0, 0, 0.65) 90%
|
|
171 + );
|
|
172 + z-index: 1;
|
|
173 +}
|
|
174 +
|
|
175 +.word {
|
|
176 + display: block;
|
|
177 + height: 28px;
|
|
178 + color: var(--claude-accent);
|
|
179 + animation: conversation-spin 4.5s linear infinite;
|
|
180 +}
|
|
181 +
|
|
182 +.word:nth-child(2) {
|
|
183 + animation-delay: -0.9s;
|
|
184 +}
|
|
185 +
|
|
186 +.word:nth-child(3) {
|
|
187 + animation-delay: -1.8s;
|
|
188 +}
|
|
189 +
|
|
190 +.word:nth-child(4) {
|
|
191 + animation-delay: -2.7s;
|
|
192 +}
|
|
193 +
|
|
194 +.word:nth-child(5) {
|
|
195 + animation-delay: -3.6s;
|
|
196 +}
|
|
197 +
|
|
198 +@keyframes conversation-spin {
|
|
199 + 0% {
|
|
200 + transform: translateY(0%);
|
|
201 + }
|
|
202 + 20% {
|
|
203 + transform: translateY(-100%);
|
|
204 + }
|
|
205 + 40% {
|
|
206 + transform: translateY(-200%);
|
|
207 + }
|
|
208 + 60% {
|
|
209 + transform: translateY(-300%);
|
|
210 + }
|
|
211 + 80% {
|
|
212 + transform: translateY(-400%);
|
|
213 + }
|
|
214 + 100% {
|
|
215 + transform: translateY(-400%);
|
|
216 + }
|
|
217 +}
|
|
218 +
|
|
219 .boxes .box:nth-child(1) {
|
|
Edited static/src/App.vue (+2 -15)
|
|
36 <!-- Main UI (只在连接后显示) -->
|
|
37 - <template v-else-if="!conversationsLoading">
|
|
37 + <template v-else>
|
|
38 <div class="main-container">
|
|
⋮
|
|
115 :format-search-time="formatSearchTime"
|
|
116 + :conversation-loading="conversationsLoading"
|
|
117 @toggle-scroll-lock="toggleScrollLock"
|
|
⋮
|
|
171 </template>
|
|
171 - <div v-else class="conversation-loading">
|
|
172 - <div class="loading-card">
|
|
173 - <div class="loader">
|
|
174 - <p>正在加载</p>
|
|
175 - <div class="words" aria-hidden="true">
|
|
176 - <span class="word">对话</span>
|
|
177 - <span class="word">工具</span>
|
|
178 - <span class="word">思考</span>
|
|
179 - <span class="word">文件</span>
|
|
180 - <span class="word">对话</span>
|
|
181 - </div>
|
|
182 - </div>
|
|
183 - </div>
|
|
184 - </div>
|
|
172 </AppShell>
|
|
|
|
• Edited static/src/components/chat/ChatArea.vue (+16 -0)
|
|
2 <div class="messages-area" ref="rootEl">
|
|
3 + <div v-if="conversationLoading" class="conversation-loader">
|
|
4 + <div class="loader">
|
|
5 + <p>正在加载</p>
|
|
6 + <div class="words" aria-hidden="true">
|
|
7 + <span class="word">对话</span>
|
|
8 + <span class="word">工具</span>
|
|
9 + <span class="word">思考</span>
|
|
10 + <span class="word">文件</span>
|
|
11 + <span class="word">对话</span>
|
|
12 + </div>
|
|
13 + </div>
|
|
14 + </div>
|
|
15 + <template v-else>
|
|
16 <div v-for="(msg, index) in messages" :key="index" class="message-block">
|
|
⋮
|
|
138 </div>
|
|
139 + </template>
|
|
140 </div>
|
|
⋮
|
|
167 formatSearchTime
|
|
168 + conversationLoading
|
|
169 } = defineProps<{
|
|
⋮
|
|
176 formatSearchTime: (filters: Record<string, any>) => string;
|
|
177 + conversationLoading: boolean;
|
|
178 }>();
|
|
|
|
• Edited static/src/styles/layout/_app-shell.scss (+0 -95)
|
|
122
|
|
123 -.conversation-loading {
|
|
124 - min-height: var(--app-viewport, 100vh);
|
|
125 - display: flex;
|
|
126 - align-items: center;
|
|
127 - justify-content: center;
|
|
128 - background: var(--claude-bg);
|
|
129 - padding: 24px;
|
|
130 -}
|
|
131 -
|
|
132 -.loading-card {
|
|
133 - background: rgba(0, 0, 0, 0.65);
|
|
134 - border-radius: 20px;
|
|
135 - padding: 18px 28px;
|
|
136 - color: #fff;
|
|
137 - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18);
|
|
138 -}
|
|
139 -
|
|
140 -.loader {
|
|
141 - font-family: 'Iowan Old Style', ui-serif, Georgia, serif;
|
|
142 - font-size: 22px;
|
|
143 - font-weight: 500;
|
|
144 - display: flex;
|
|
145 - align-items: center;
|
|
146 - gap: 8px;
|
|
147 - letter-spacing: 0.04em;
|
|
148 - color: rgba(255, 255, 255, 0.9);
|
|
149 -}
|
|
150 -
|
|
151 -.loader p {
|
|
152 - margin: 0;
|
|
153 -}
|
|
154 -
|
|
155 -.words {
|
|
156 - overflow: hidden;
|
|
157 - position: relative;
|
|
158 - height: 28px;
|
|
159 - line-height: 28px;
|
|
160 -}
|
|
161 -
|
|
162 -.words::after {
|
|
163 - content: '';
|
|
164 - position: absolute;
|
|
165 - inset: 0;
|
|
166 - background: linear-gradient(
|
|
167 - rgba(0, 0, 0, 0.65) 10%,
|
|
168 - transparent 30%,
|
|
169 - transparent 70%,
|
|
170 - rgba(0, 0, 0, 0.65) 90%
|
|
171 - );
|
|
172 - z-index: 1;
|
|
173 -}
|
|
174 -
|
|
175 -.word {
|
|
176 - display: block;
|
|
177 - height: 28px;
|
|
178 - color: var(--claude-accent);
|
|
179 - animation: conversation-spin 4.5s linear infinite;
|
|
180 -}
|
|
181 -
|
|
182 -.word:nth-child(2) {
|
|
183 - animation-delay: -0.9s;
|
|
184 -}
|
|
185 -
|
|
186 -.word:nth-child(3) {
|
|
187 - animation-delay: -1.8s;
|
|
188 -}
|
|
189 -
|
|
190 -.word:nth-child(4) {
|
|
191 - animation-delay: -2.7s;
|
|
192 -}
|
|
193 -
|
|
194 -.word:nth-child(5) {
|
|
195 - animation-delay: -3.6s;
|
|
196 -}
|
|
197 -
|
|
198 -@keyframes conversation-spin {
|
|
199 - 0% {
|
|
200 - transform: translateY(0%);
|
|
201 - }
|
|
202 - 20% {
|
|
203 - transform: translateY(-100%);
|
|
204 - }
|
|
205 - 40% {
|
|
206 - transform: translateY(-200%);
|
|
207 - }
|
|
208 - 60% {
|
|
209 - transform: translateY(-300%);
|
|
210 - }
|
|
211 - 80% {
|
|
212 - transform: translateY(-400%);
|
|
213 - }
|
|
214 - 100% {
|
|
215 - transform: translateY(-400%);
|
|
216 - }
|
|
217 -}
|
|
123
|
|
|
|
• Explored
|
|
└ Read _chat-area.scss
|
|
|
|
• Edited static/src/styles/components/chat/_chat-area.scss (+66 -0)
|
|
32
|
|
33 +.conversation-loader {
|
|
34 + min-height: calc(var(--app-viewport, 100vh) - 200px);
|
|
35 + display: flex;
|
|
36 + align-items: center;
|
|
37 + justify-content: center;
|
|
38 + text-align: center;
|
|
39 + color: var(--claude-text);
|
|
40 + font-family: 'Iowan Old Style', ui-serif, Georgia, serif;
|
|
41 + letter-spacing: 0.03em;
|
|
42 +}
|
|
43 +
|
|
44 +.conversation-loader .loader {
|
|
45 + display: flex;
|
|
46 + align-items: center;
|
|
47 + gap: 10px;
|
|
48 + font-size: 20px;
|
|
49 +}
|
|
50 +
|
|
51 +.conversation-loader .loader p {
|
|
52 + margin: 0;
|
|
53 + color: var(--claude-text);
|
|
54 +}
|
|
55 +
|
|
56 +.conversation-loader .words {
|
|
57 + overflow: hidden;
|
|
58 + position: relative;
|
|
59 + height: 28px;
|
|
60 + line-height: 28px;
|
|
61 +}
|
|
62 +
|
|
63 +.conversation-loader .words::after {
|
|
64 + content: '';
|
|
65 + position: absolute;
|
|
66 + inset: 0;
|
|
67 + background: linear-gradient(
|
|
68 + rgba(255, 255, 255, 0.9) 5%,
|
|
69 + transparent 30%,
|
|
70 + transparent 70%,
|
|
71 + rgba(255, 255, 255, 0.9) 95%
|
|
72 + );
|
|
73 + pointer-events: none;
|
|
74 +}
|
|
75 +
|
|
76 +.conversation-loader .word {
|
|
77 + display: block;
|
|
78 + height: 28px;
|
|
79 + color: var(--claude-accent);
|
|
80 + animation: conversation-word-spin 4s linear infinite;
|
|
81 +}
|
|
82 +
|
|
83 +.conversation-loader .word:nth-child(2) {
|
|
84 + animation-delay: -0.8s;
|
|
85 +}
|
|
86 +
|
|
87 +.conversation-loader .word:nth-child(3) {
|
|
88 + animation-delay: -1.6s;
|
|
89 +}
|
|
90 +
|
|
91 +.conversation-loader .word:nth-child(4) {
|
|
92 + animation-delay: -2.4s;
|
|
93 +}
|
|
94 +
|
|
95 +.conversation-loader .word:nth-child(5) {
|
|
96 + animation-delay: -3.2s;
|
|
97 +}
|
|
98 +
|
|
99 .scroll-lock-toggle {
|
|
|
|
• Edited static/src/styles/components/chat/_chat-area.scss (+22 -0)
|
|
511
|
|
512 +
|
|
513 +.conversation-loader .word:nth-child(5) {
|
|
514 + animation-delay: -3.2s;
|
|
515 +}
|
|
516 +
|
|
517 +@keyframes conversation-word-spin {
|
|
518 + 0% {
|
|
519 + transform: translateY(0%);
|
|
520 + }
|
|
521 + 25% {
|
|
522 + transform: translateY(-100%);
|
|
523 + }
|
|
524 + 50% {
|
|
525 + transform: translateY(-200%);
|
|
526 + }
|
|
527 + 75% {
|
|
528 + transform: translateY(-300%);
|
|
529 + }
|
|
530 + 100% {
|
|
531 + transform: translateY(-400%);
|
|
532 + }
|
|
533 +}
|
|
534 /* 代码块内容区 */
|
|
• Edited static/src/components/chat/ChatArea.vue (+1 -1)
|
|
166 formatSearchTopic,
|
|
167 - formatSearchTime
|
|
167 + formatSearchTime,
|
|
168 conversationLoading
|
|
|
|
Edited static/src/components/chat/ChatArea.vue (+1 -13)
|
|
2 <div class="messages-area" ref="rootEl">
|
|
3 - <div v-if="conversationLoading" class="conversation-loader">
|
|
4 - <div class="loader">
|
|
5 - <p>正在加载</p>
|
|
6 - <div class="words" aria-hidden="true">
|
|
7 - <span class="word">对话</span>
|
|
8 - <span class="word">工具</span>
|
|
9 - <span class="word">思考</span>
|
|
10 - <span class="word">文件</span>
|
|
11 - <span class="word">对话</span>
|
|
12 - </div>
|
|
13 - </div>
|
|
14 - </div>
|
|
15 - <template v-else>
|
|
3 + <template>
|
|
4 <div v-for="(msg, index) in messages" :key="index" class="message-block">
|
|
|
|
• Edited static/src/components/chat/ChatArea.vue (+0 -0)
|
|
|
|
• Edited static/src/components/chat/ChatArea.vue (+1 -3)
|
|
154 formatSearchTopic,
|
|
155 - formatSearchTime,
|
|
156 - conversationLoading
|
|
155 + formatSearchTime
|
|
156 } = defineProps<{
|
|
⋮
|
|
163 formatSearchTime: (filters: Record<string, any>) => string;
|
|
165 - conversationLoading: boolean;
|
|
164 }>();
|
|
|
|
• Edited static/src/components/chat/ChatArea.vue (+0 -0)
|
|
|
|
• Edited static/src/styles/components/chat/_chat-area.scss (+0 -65)
|
|
32
|
|
33 -.conversation-loader {
|
|
34 - min-height: calc(var(--app-viewport, 100vh) - 200px);
|
|
35 - display: flex;
|
|
36 - align-items: center;
|
|
37 - justify-content: center;
|
|
38 - text-align: center;
|
|
39 - color: var(--claude-text);
|
|
40 - font-family: 'Iowan Old Style', ui-serif, Georgia, serif;
|
|
41 - letter-spacing: 0.03em;
|
|
42 -}
|
|
43 -
|
|
44 -.conversation-loader .loader {
|
|
45 - display: flex;
|
|
46 - align-items: center;
|
|
47 - gap: 10px;
|
|
48 - font-size: 20px;
|
|
49 -}
|
|
50 -
|
|
51 -.conversation-loader .loader p {
|
|
52 - margin: 0;
|
|
53 - color: var(--claude-text);
|
|
54 -}
|
|
55 -
|
|
56 -.conversation-loader .words {
|
|
57 - overflow: hidden;
|
|
58 - position: relative;
|
|
59 - height: 28px;
|
|
60 - line-height: 28px;
|
|
61 -}
|
|
62 -
|
|
63 -.conversation-loader .words::after {
|
|
64 - content: '';
|
|
65 - position: absolute;
|
|
66 - inset: 0;
|
|
67 - background: linear-gradient(
|
|
68 - rgba(255, 255, 255, 0.9) 5%,
|
|
69 - transparent 30%,
|
|
70 - transparent 70%,
|
|
71 - rgba(255, 255, 255, 0.9) 95%
|
|
72 - );
|
|
73 - pointer-events: none;
|
|
74 -}
|
|
75 -
|
|
76 -.conversation-loader .word {
|
|
77 - display: block;
|
|
78 - height: 28px;
|
|
79 - color: var(--claude-accent);
|
|
80 - animation: conversation-word-spin 4s linear infinite;
|
|
81 -}
|
|
82 -
|
|
83 -.conversation-loader .word:nth-child(2) {
|
|
84 - animation-delay: -0.8s;
|
|
85 -}
|
|
86 -
|
|
87 -.conversation-loader .word:nth-child(3) {
|
|
88 - animation-delay: -1.6s;
|
|
89 -}
|
|
90 -
|
|
91 -.conversation-loader .word:nth-child(4) {
|
|
92 - animation-delay: -2.4s;
|
|
93 -}
|
|
94 -
|
|
95 -.conversation-loader .word:nth-child(5) {
|
|
96 - animation-delay: -3.2s;
|
|
97 -}
|
|
33
|
|
|
|
• Edited static/src/styles/components/chat/_chat-area.scss (+0 -23)
|
|
445 }
|
|
446 -
|
|
447 -
|
|
448 -.conversation-loader .word:nth-child(5) {
|
|
449 - animation-delay: -3.2s;
|
|
450 -}
|
|
451 -
|
|
452 -@keyframes conversation-word-spin {
|
|
453 - 0% {
|
|
454 - transform: translateY(0%);
|
|
455 - }
|
|
456 - 25% {
|
|
457 - transform: translateY(-100%);
|
|
458 - }
|
|
459 - 50% {
|
|
460 - transform: translateY(-200%);
|
|
461 - }
|
|
462 - 75% {
|
|
463 - transform: translateY(-300%);
|
|
464 - }
|
|
465 - 100% {
|
|
466 - transform: translateY(-400%);
|
|
467 - }
|
|
468 -}
|
|
446 /* 代码块内容区 */
|
|
Edited static/src/App.vue (+5 -2)
|
|
36 <!-- Main UI (只在连接后显示) -->
|
|
37 - <template v-else>
|
|
37 + <template v-else-if="!conversationsLoading">
|
|
38 <div class="main-container">
|
|
⋮
|
|
106
|
|
107 - <ChatArea
|
|
107 + <ChatArea
|
|
108 ref="messagesArea"
|
|
⋮
|
|
170 </template>
|
|
171 + <div v-else class="conversation-loading-simple">
|
|
172 + 正在加载对话…
|
|
173 + </div>
|
|
174 </AppShell>
|
|
|
|
• Edited static/src/styles/layout/_app-shell.scss (+11 -0)
|
|
122
|
|
123 +.conversation-loading-simple {
|
|
124 + min-height: var(--app-viewport, 100vh);
|
|
125 + display: flex;
|
|
126 + align-items: center;
|
|
127 + justify-content: center;
|
|
128 + font-size: 18px;
|
|
129 + color: var(--claude-text);
|
|
130 + letter-spacing: 0.05em;
|
|
131 + background: var(--claude-bg);
|
|
132 +}
|
|
133 +
|
|
134
|
|
|
|
|
|
Edited static/src/App.vue (+8 -0)
|
|
1 +<template>
|
|
2 <div id="app">
|
|
⋮
|
|
1085 </div>
|
|
1086 +</template>
|
|
1087 +
|
|
1088 +<script setup lang="ts">
|
|
1089 +import appOptions from './app';
|
|
1090 +
|
|
1091 +defineOptions(appOptions);
|
|
1092 +</script>
|
|
|
|
Edited static/src/app.ts (+0 -1)
|
|
12 import InputComposer from './components/input/InputComposer.vue';
|
|
13 -import ChatArea from './components/chat/ChatArea.vue';
|
|
13 import AppShell from './components/shell/AppShell.vue';
|
|
|
|
• Edited static/src/app.ts (+0 -1)
|
|
1930 InputComposer,
|
|
1931 - ChatArea,
|
|
1931 AppShell
|
|
|
|
• Edited static/src/app.ts (+5 -1)
|
|
196 'minPanelWidth',
|
|
197 - 'maxPanelWidth'
|
|
197 + 'maxPanelWidth',
|
|
198 + 'quotaToast',
|
|
199 + 'toastQueue',
|
|
200 + 'confirmDialog',
|
|
201 + 'easterEgg'
|
|
202 ]),
|
|
|
|
Edited static/src/app.ts (+56 -0)
|
|
458
|
|
459 + hasContainerStats() {
|
|
460 + return !!(
|
|
461 + this.containerStatus &&
|
|
462 + this.containerStatus.mode === 'docker' &&
|
|
463 + this.containerStatus.stats
|
|
464 + );
|
|
465 + },
|
|
466 +
|
|
467 + containerStatusClass() {
|
|
468 + if (!this.containerStatus) {
|
|
469 + return 'status-pill--host';
|
|
470 + }
|
|
471 + if (this.containerStatus.mode !== 'docker') {
|
|
472 + return 'status-pill--host';
|
|
473 + }
|
|
474 + const rawStatus = (
|
|
475 + this.containerStatus.state &&
|
|
476 + (this.containerStatus.state.status || this.containerStatus.state.Status)
|
|
477 + ) || '';
|
|
478 + const status = String(rawStatus).toLowerCase();
|
|
479 + if (status.includes('running')) {
|
|
480 + return 'status-pill--running';
|
|
481 + }
|
|
482 + if (status.includes('paused')) {
|
|
483 + return 'status-pill--stopped';
|
|
484 + }
|
|
485 + if (status.includes('exited') || status.includes('dead')) {
|
|
486 + return 'status-pill--stopped';
|
|
487 + }
|
|
488 + return 'status-pill--running';
|
|
489 + },
|
|
490 +
|
|
491 + containerStatusText() {
|
|
492 + if (!this.containerStatus) {
|
|
493 + return '未知';
|
|
494 + }
|
|
495 + if (this.containerStatus.mode !== 'docker') {
|
|
496 + return '宿主机模式';
|
|
497 + }
|
|
498 + const rawStatus = (
|
|
499 + this.containerStatus.state &&
|
|
500 + (this.containerStatus.state.status || this.containerStatus.state.Status)
|
|
501 + ) || '';
|
|
502 + const status = String(rawStatus).toLowerCase();
|
|
503 + if (status.includes('running')) {
|
|
504 + return '运行中';
|
|
505 + }
|
|
506 + if (status.includes('paused')) {
|
|
507 + return '已暂停';
|
|
508 + }
|
|
509 + if (status.includes('exited') || status.includes('dead')) {
|
|
510 + return '已停止';
|
|
511 + }
|
|
512 + return rawStatus || '容器模式';
|
|
513 + },
|
|
514 +
|
|
515 async bootstrapRoute() {
|
|
|
|
• Edited static/src/app.ts (+5 -0)
|
|
29 import { useEasterEgg } from './composables/useEasterEgg';
|
|
30 +import { renderMarkdown as renderMarkdownHelper } from './composables/useMarkdownRenderer';
|
|
31 import {
|
|
⋮
|
|
459
|
|
460 + renderMarkdown(content, isStreaming = false) {
|
|
461 + return renderMarkdownHelper(content, isStreaming);
|
|
462 + },
|
|
463 +
|
|
464 hasContainerStats() {
|
|
|
|
• Edited static/src/app.ts (+1 -0)
|
|
250 ]),
|
|
251 + ...mapWritableState(useFocusStore, ['focusedFiles']),
|
|
252 ...mapWritableState(useUploadStore, ['uploading'])
|
|
|
|
Edited static/src/app.ts (+45 -0)
|
|
520
|
|
521 + formatTime(value) {
|
|
522 + if (!value) {
|
|
523 + return '未知时间';
|
|
524 + }
|
|
525 + let date;
|
|
526 + if (typeof value === 'number') {
|
|
527 + date = new Date(value);
|
|
528 + } else if (typeof value === 'string') {
|
|
529 + const parsed = Date.parse(value);
|
|
530 + if (!Number.isNaN(parsed)) {
|
|
531 + date = new Date(parsed);
|
|
532 + } else {
|
|
533 + const numeric = Number(value);
|
|
534 + if (!Number.isNaN(numeric)) {
|
|
535 + date = new Date(numeric);
|
|
536 + }
|
|
537 + }
|
|
538 + } else if (value instanceof Date) {
|
|
539 + date = value;
|
|
540 + }
|
|
541 + if (!date || Number.isNaN(date.getTime())) {
|
|
542 + return String(value);
|
|
543 + }
|
|
544 + const now = Date.now();
|
|
545 + const diff = now - date.getTime();
|
|
546 + if (diff < 60000) {
|
|
547 + return '刚刚';
|
|
548 + }
|
|
549 + if (diff < 3600000) {
|
|
550 + const mins = Math.floor(diff / 60000);
|
|
551 + return `${mins} 分钟前`;
|
|
552 + }
|
|
553 + if (diff < 86400000) {
|
|
554 + const hours = Math.floor(diff / 3600000);
|
|
555 + return `${hours} 小时前`;
|
|
556 + }
|
|
557 + const formatter = new Intl.DateTimeFormat('zh-CN', {
|
|
558 + month: '2-digit',
|
|
559 + day: '2-digit',
|
|
560 + hour: '2-digit',
|
|
561 + minute: '2-digit'
|
|
562 + });
|
|
563 + return formatter.format(date);
|
|
564 + },
|
|
565 +
|
|
566 async bootstrapRoute() {
|
|
|
|
Edited static/src/app.ts (+19 -0)
|
|
122 skipConversationLoadedEvent: false,
|
|
123 + skipConversationHistoryReload: false,
|
|
124
|
|
⋮
|
|
272 this.autoResizeInput();
|
|
273 + },
|
|
274 + currentConversationId: {
|
|
275 + immediate: false,
|
|
276 + handler(newValue, oldValue) {
|
|
277 + if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
|
|
278 + return;
|
|
279 + }
|
|
280 + if (this.skipConversationHistoryReload) {
|
|
281 + this.skipConversationHistoryReload = false;
|
|
282 + return;
|
|
283 + }
|
|
284 + if (oldValue && newValue === oldValue) {
|
|
285 + return;
|
|
286 + }
|
|
287 + this.fetchAndDisplayHistory();
|
|
288 + this.fetchConversationTokenStatistics();
|
|
289 + this.updateCurrentContextTokens();
|
|
290 + }
|
|
291 }
|
|
|
|
• Explored
|
|
└ Search fetchAndDisplayHistory in app.ts
|
|
Read app.ts
|
|
|
|
• Edited static/src/app.ts (+2 -0)
|
|
834 if (!this.currentConversationId) {
|
|
835 + this.skipConversationHistoryReload = true;
|
|
836 this.currentConversationId = statusConversationId;
|
|
⋮
|
|
987 // 2. 更新当前对话信息
|
|
988 + this.skipConversationHistoryReload = true;
|
|
989 this.currentConversationId = conversationId;
|
|
|
|
Edited static/src/composables/useLegacySocket.ts (+24 -1)
|
|
22
|
|
23 + const scheduleHistoryReload = (delay = 0) => {
|
|
24 + if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') {
|
|
25 + return;
|
|
26 + }
|
|
27 + if (!ctx.currentConversationId || ctx.currentConversationId.startsWith('temp_')) {
|
|
28 + return;
|
|
29 + }
|
|
30 + setTimeout(() => {
|
|
31 + try {
|
|
32 + ctx.fetchAndDisplayHistory();
|
|
33 + if (typeof ctx.fetchConversationTokenStatistics === 'function') {
|
|
34 + ctx.fetchConversationTokenStatistics();
|
|
35 + }
|
|
36 + if (typeof ctx.updateCurrentContextTokens === 'function') {
|
|
37 + ctx.updateCurrentContextTokens();
|
|
38 + }
|
|
39 + } catch (error) {
|
|
40 + console.warn('重新加载对话历史失败:', error);
|
|
41 + }
|
|
42 + }, delay);
|
|
43 + };
|
|
44 +
|
|
45 const assignSocketToken = async () => {
|
|
⋮
|
|
69 console.log('WebSocket已连接');
|
|
48 - // 连接时重置所有状态
|
|
70 + // 连接时重置所有状态并刷新当前对话
|
|
71 ctx.resetAllStates();
|
|
72 + scheduleHistoryReload(200);
|
|
73 });
|
|
|
|
Edited static/src/app.ts (+10 -4)
|
|
156 await this.initSocket();
|
|
157 - this.initScrollListener();
|
|
157 + this.$nextTick(() => {
|
|
158 + this.initScrollListener();
|
|
159 + });
|
|
160
|
|
⋮
|
|
175 });
|
|
174 - this.resourceStartContainerStatsPolling();
|
|
175 - this.resourceStartProjectStoragePolling();
|
|
176 - this.resourceStartUsageQuotaPolling();
|
|
176 + this.resourceStartContainerStatsPolling();
|
|
177 + this.resourceStartProjectStoragePolling();
|
|
178 + this.resourceStartUsageQuotaPolling();
|
|
179 },
|
|
⋮
|
|
802 console.log('前端状态重置完成');
|
|
803 +
|
|
804 + this.$nextTick(() => {
|
|
805 + this.initScrollListener();
|
|
806 + });
|
|
807 },
|
|
|
|
Edited static/src/app.ts (+11 -0)
|
|
296 methods: {
|
|
297 + ensureScrollListener() {
|
|
298 + if (this._scrollListenerReady) {
|
|
299 + return;
|
|
300 + }
|
|
301 + const area = this.getMessagesAreaElement();
|
|
302 + if (!area) {
|
|
303 + return;
|
|
304 + }
|
|
305 + this.initScrollListener();
|
|
306 + this._scrollListenerReady = true;
|
|
307 + },
|
|
308 ...mapActions(useUiStore, {
|
|
|
|
• Explored
|
|
└ Read app.ts
|
|
|
|
• Edited static/src/app.ts (+3 -2)
|
|
716 }
|
|
717 + this._scrollListenerReady = true;
|
|
718
|
|
⋮
|
|
814 console.log('前端状态重置完成');
|
|
814 -
|
|
815 + this._scrollListenerReady = false;
|
|
816 this.$nextTick(() => {
|
|
816 - this.initScrollListener();
|
|
817 + this.ensureScrollListener();
|
|
818 });
|
|
|
|
• Edited static/src/app.ts (+1 -1)
|
|
157 this.$nextTick(() => {
|
|
158 - this.initScrollListener();
|
|
158 + this.ensureScrollListener();
|
|
159 });
|
|
|
|
• Explored
|
|
└ Search _scrollListenerReady in app.ts
|
|
Search _scrollListenerReady
|
|
|
|
• Edited static/src/app.ts (+1 -0)
|
|
123 skipConversationHistoryReload: false,
|
|
124 + _scrollListenerReady: false,
|
|
125
|
|
|
|
|
|
• Edited static/src/app.ts (+11 -0)
|
|
819 });
|
|
820 +
|
|
821 + const activeConversationId = this.currentConversationId;
|
|
822 + if (activeConversationId && !activeConversationId.startsWith('temp_')) {
|
|
823 + setTimeout(() => {
|
|
824 + if (this.currentConversationId === activeConversationId) {
|
|
825 + this.fetchAndDisplayHistory();
|
|
826 + this.fetchConversationTokenStatistics();
|
|
827 + this.updateCurrentContextTokens();
|
|
828 + }
|
|
829 + }, 250);
|
|
830 + }
|
|
831 },
|
|
|