feat: add blank chat hero and limit quick menu actions
This commit is contained in:
parent
1efd6aa0d3
commit
099de1e922
@ -127,6 +127,11 @@
|
||||
/>
|
||||
<VirtualMonitorSurface v-show="chatDisplayMode === 'monitor'" />
|
||||
|
||||
<div v-if="blankHeroActive" class="blank-hero-overlay">
|
||||
<span class="icon icon-lg" :style="iconStyle('bot')" aria-hidden="true"></span>
|
||||
<p class="blank-hero-text">{{ blankWelcomeText }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="chatDisplayMode === 'chat'"
|
||||
class="scroll-lock-toggle"
|
||||
@ -150,46 +155,48 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<InputComposer
|
||||
ref="inputComposer"
|
||||
:input-message="inputMessage"
|
||||
:input-is-multiline="inputIsMultiline"
|
||||
:input-is-focused="inputIsFocused"
|
||||
:is-connected="isConnected"
|
||||
:streaming-message="composerBusy"
|
||||
:input-locked="displayLockEngaged"
|
||||
:uploading="uploading"
|
||||
:thinking-mode="thinkingMode"
|
||||
:run-mode="resolvedRunMode"
|
||||
:quick-menu-open="quickMenuOpen"
|
||||
:tool-menu-open="toolMenuOpen"
|
||||
:mode-menu-open="modeMenuOpen"
|
||||
:tool-settings="toolSettings"
|
||||
:tool-settings-loading="toolSettingsLoading"
|
||||
:settings-open="settingsOpen"
|
||||
:compressing="compressing"
|
||||
:current-conversation-id="currentConversationId"
|
||||
:icon-style="iconStyle"
|
||||
:tool-category-icon="toolCategoryIcon"
|
||||
@update:input-message="inputSetMessage"
|
||||
@input-change="handleInputChange"
|
||||
@input-focus="handleInputFocus"
|
||||
@input-blur="handleInputBlur"
|
||||
@toggle-quick-menu="toggleQuickMenu"
|
||||
@send-message="sendMessage"
|
||||
@send-or-stop="handleSendOrStop"
|
||||
@quick-upload="handleQuickUpload"
|
||||
@toggle-tool-menu="toggleToolMenu"
|
||||
@toggle-mode-menu="toggleModeMenu"
|
||||
@select-run-mode="handleModeSelect"
|
||||
@toggle-settings="toggleSettings"
|
||||
@update-tool-category="updateToolCategory"
|
||||
@realtime-terminal="handleRealtimeTerminalClick"
|
||||
@toggle-focus-panel="handleFocusPanelToggleClick"
|
||||
@toggle-token-panel="handleTokenPanelToggleClick"
|
||||
@compress-conversation="handleCompressConversationClick"
|
||||
@file-selected="handleFileSelected"
|
||||
/>
|
||||
<div class="composer-container" :class="{ 'blank-hero-mode': composerHeroActive }">
|
||||
<InputComposer
|
||||
ref="inputComposer"
|
||||
:input-message="inputMessage"
|
||||
:input-is-multiline="inputIsMultiline"
|
||||
:input-is-focused="inputIsFocused"
|
||||
:is-connected="isConnected"
|
||||
:streaming-message="composerBusy"
|
||||
:input-locked="displayLockEngaged"
|
||||
:uploading="uploading"
|
||||
:thinking-mode="thinkingMode"
|
||||
:run-mode="resolvedRunMode"
|
||||
:quick-menu-open="quickMenuOpen"
|
||||
:tool-menu-open="toolMenuOpen"
|
||||
:mode-menu-open="modeMenuOpen"
|
||||
:tool-settings="toolSettings"
|
||||
:tool-settings-loading="toolSettingsLoading"
|
||||
:settings-open="settingsOpen"
|
||||
:compressing="compressing"
|
||||
:current-conversation-id="currentConversationId"
|
||||
:icon-style="iconStyle"
|
||||
:tool-category-icon="toolCategoryIcon"
|
||||
@update:input-message="inputSetMessage"
|
||||
@input-change="handleInputChange"
|
||||
@input-focus="handleInputFocus"
|
||||
@input-blur="handleInputBlur"
|
||||
@toggle-quick-menu="toggleQuickMenu"
|
||||
@send-message="sendMessage"
|
||||
@send-or-stop="handleSendOrStop"
|
||||
@quick-upload="handleQuickUpload"
|
||||
@toggle-tool-menu="toggleToolMenu"
|
||||
@toggle-mode-menu="toggleModeMenu"
|
||||
@select-run-mode="handleModeSelect"
|
||||
@toggle-settings="toggleSettings"
|
||||
@update-tool-category="updateToolCategory"
|
||||
@realtime-terminal="handleRealtimeTerminalClick"
|
||||
@toggle-focus-panel="handleFocusPanelToggleClick"
|
||||
@toggle-token-panel="handleTokenPanelToggleClick"
|
||||
@compress-conversation="handleCompressConversationClick"
|
||||
@file-selected="handleFileSelected"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div v-if="!isMobileViewport" class="resize-handle" @mousedown="startResize('right', $event)"></div>
|
||||
|
||||
@ -148,6 +148,19 @@ const appOptions = {
|
||||
historyLoading: false,
|
||||
historyLoadingFor: null,
|
||||
historyLoadSeq: 0,
|
||||
blankHeroActive: false,
|
||||
blankHeroExiting: false,
|
||||
blankWelcomeText: '',
|
||||
blankWelcomePool: [
|
||||
'有什么可以帮忙的?',
|
||||
'想了解些热点吗?',
|
||||
'要我帮你完成作业吗?',
|
||||
'整点代码?',
|
||||
'随便聊点什么?',
|
||||
'想让我帮你整理一下思路吗?',
|
||||
'要不要我帮你写个小工具?',
|
||||
'发我一句话,我来接着做。'
|
||||
],
|
||||
mobileViewportQuery: null,
|
||||
modeMenuOpen: false,
|
||||
conversationListRequestSeq: 0,
|
||||
@ -318,6 +331,9 @@ const appOptions = {
|
||||
composerBusy() {
|
||||
const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
|
||||
return this.streamingUi || this.taskInProgress || monitorLock || this.stopRequested;
|
||||
},
|
||||
composerHeroActive() {
|
||||
return this.blankHeroActive || this.blankHeroExiting;
|
||||
}
|
||||
},
|
||||
|
||||
@ -342,6 +358,12 @@ const appOptions = {
|
||||
inputMessage() {
|
||||
this.autoResizeInput();
|
||||
},
|
||||
messages: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.refreshBlankHeroState();
|
||||
}
|
||||
},
|
||||
currentConversationId: {
|
||||
immediate: false,
|
||||
handler(newValue, oldValue) {
|
||||
@ -354,6 +376,7 @@ const appOptions = {
|
||||
historyLoadingFor: this.historyLoadingFor,
|
||||
historyLoadSeq: this.historyLoadSeq
|
||||
});
|
||||
this.refreshBlankHeroState();
|
||||
this.logMessageState('watch:currentConversationId', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
||||
if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
|
||||
return;
|
||||
@ -1397,6 +1420,7 @@ const appOptions = {
|
||||
const targetConversationId = this.currentConversationId;
|
||||
if (!targetConversationId || targetConversationId.startsWith('temp_')) {
|
||||
debugLog('没有当前对话ID,跳过历史加载');
|
||||
this.refreshBlankHeroState();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1481,16 +1505,17 @@ const appOptions = {
|
||||
debugLog('尝试不显示错误弹窗,仅在控制台记录');
|
||||
// 不显示alert,避免打断用户体验
|
||||
this.logMessageState('fetchAndDisplayHistory:error-clear', { error: error?.message || String(error) });
|
||||
this.messages = [];
|
||||
this.logMessageState('fetchAndDisplayHistory:error-cleared');
|
||||
this.messages = [];
|
||||
this.logMessageState('fetchAndDisplayHistory:error-cleared');
|
||||
}
|
||||
} finally {
|
||||
// 仅在本次加载仍是最新请求时清除 loading 状态
|
||||
if (loadSeq === this.historyLoadSeq) {
|
||||
this.historyLoading = false;
|
||||
this.historyLoadingFor = null;
|
||||
}
|
||||
this.refreshBlankHeroState();
|
||||
}
|
||||
} finally {
|
||||
// 仅在本次加载仍是最新请求时清除 loading 状态
|
||||
if (loadSeq === this.historyLoadSeq) {
|
||||
this.historyLoading = false;
|
||||
this.historyLoadingFor = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
@ -2020,6 +2045,16 @@ const appOptions = {
|
||||
return;
|
||||
}
|
||||
|
||||
const wasBlank = this.isConversationBlank();
|
||||
if (wasBlank) {
|
||||
this.blankHeroExiting = true;
|
||||
this.blankHeroActive = true;
|
||||
setTimeout(() => {
|
||||
this.blankHeroExiting = false;
|
||||
this.blankHeroActive = false;
|
||||
}, 320);
|
||||
}
|
||||
|
||||
// 标记任务进行中,直到任务完成或用户手动停止
|
||||
this.taskInProgress = true;
|
||||
this.chatAddUserMessage(message);
|
||||
@ -2373,6 +2408,36 @@ const appOptions = {
|
||||
this.uiSetPanelMenuOpen(false);
|
||||
},
|
||||
|
||||
isConversationBlank() {
|
||||
if (!Array.isArray(this.messages) || !this.messages.length) return true;
|
||||
return !this.messages.some(
|
||||
(msg) => msg && (msg.role === 'user' || msg.role === 'assistant')
|
||||
);
|
||||
},
|
||||
|
||||
pickWelcomeText() {
|
||||
const pool = this.blankWelcomePool;
|
||||
if (!Array.isArray(pool) || !pool.length) {
|
||||
this.blankWelcomeText = '有什么可以帮忙的?';
|
||||
return;
|
||||
}
|
||||
const idx = Math.floor(Math.random() * pool.length);
|
||||
this.blankWelcomeText = pool[idx];
|
||||
},
|
||||
|
||||
refreshBlankHeroState() {
|
||||
const isBlank = this.isConversationBlank();
|
||||
if (isBlank) {
|
||||
if (!this.blankHeroExiting) {
|
||||
this.pickWelcomeText();
|
||||
}
|
||||
this.blankHeroActive = true;
|
||||
} else {
|
||||
this.blankHeroActive = false;
|
||||
this.blankHeroExiting = false;
|
||||
}
|
||||
},
|
||||
|
||||
applyToolSettingsSnapshot(categories) {
|
||||
if (!Array.isArray(categories)) {
|
||||
console.warn('[ToolSettings] Snapshot skipped: categories not array', categories);
|
||||
|
||||
@ -260,6 +260,61 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* Blank conversation hero */
|
||||
.chat-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blank-hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
gap: 10px;
|
||||
padding-bottom: 140px;
|
||||
}
|
||||
|
||||
.blank-hero-text {
|
||||
font-size: 32px;
|
||||
color: #2f3a4a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.blank-hero-overlay .icon-lg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.composer-container {
|
||||
position: relative;
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.composer-container.blank-hero-mode {
|
||||
transform: translateY(-38vh);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.blank-hero-text {
|
||||
font-size: 28px;
|
||||
}
|
||||
.blank-hero-overlay .icon-lg {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.composer-container.blank-hero-mode {
|
||||
transform: translateY(-32vh);
|
||||
}
|
||||
.blank-hero-overlay {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-submenu.tool-submenu {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user