feat: add streaming support with toggle in chat interface and adjust layout for mobile
This commit is contained in:
@@ -429,6 +429,10 @@ class LLMRequestSubStage(Stage):
|
||||
logger.error(f"选择的提供商类型无效({type(provider)}),跳过 LLM 请求处理。")
|
||||
return
|
||||
|
||||
streaming_response = self.streaming_response
|
||||
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
|
||||
streaming_response = bool(enable_streaming)
|
||||
|
||||
if event.get_extra("provider_request"):
|
||||
req = event.get_extra("provider_request")
|
||||
assert isinstance(req, ProviderRequest), (
|
||||
@@ -548,7 +552,7 @@ class LLMRequestSubStage(Stage):
|
||||
provider=provider,
|
||||
first_provider_request=req,
|
||||
curr_provider_request=req,
|
||||
streaming=self.streaming_response,
|
||||
streaming=streaming_response,
|
||||
event=event,
|
||||
)
|
||||
await agent_runner.reset(
|
||||
@@ -560,10 +564,10 @@ class LLMRequestSubStage(Stage):
|
||||
),
|
||||
tool_executor=FunctionToolExecutor(),
|
||||
agent_hooks=MAIN_AGENT_HOOKS,
|
||||
streaming=self.streaming_response,
|
||||
streaming=streaming_response,
|
||||
)
|
||||
|
||||
if self.streaming_response:
|
||||
if streaming_response:
|
||||
# 流式响应
|
||||
event.set_result(
|
||||
MessageEventResult()
|
||||
|
||||
@@ -163,6 +163,9 @@ class WebChatAdapter(Platform):
|
||||
_, _, payload = message.raw_message # type: ignore
|
||||
message_event.set_extra("selected_provider", payload.get("selected_provider"))
|
||||
message_event.set_extra("selected_model", payload.get("selected_model"))
|
||||
message_event.set_extra(
|
||||
"enable_streaming", payload.get("enable_streaming", True)
|
||||
)
|
||||
|
||||
self.commit_event(message_event)
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ class ChatRoute(Route):
|
||||
audio_url = post_data.get("audio_url")
|
||||
selected_provider = post_data.get("selected_provider")
|
||||
selected_model = post_data.get("selected_model")
|
||||
enable_streaming = post_data.get("enable_streaming", True) # 默认为 True
|
||||
|
||||
if not message and not image_url and not audio_url:
|
||||
return (
|
||||
Response()
|
||||
@@ -224,6 +226,7 @@ class ChatRoute(Route):
|
||||
"audio_url": audio_url,
|
||||
"selected_provider": selected_provider,
|
||||
"selected_model": selected_model,
|
||||
"enable_streaming": enable_streaming,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,55 +1,70 @@
|
||||
<template>
|
||||
<v-card class="chat-page-card">
|
||||
<v-card class="chat-page-card" elevation="0" rounded="0">
|
||||
<v-card-text class="chat-page-container">
|
||||
<!-- 遮罩层 (手机端) -->
|
||||
<div class="mobile-overlay" v-if="isMobile && mobileMenuOpen" @click="closeMobileSidebar"></div>
|
||||
|
||||
<div class="chat-layout">
|
||||
<div class="sidebar-panel" :class="{ 'sidebar-collapsed': sidebarCollapsed }"
|
||||
:style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f5f5f5' }"
|
||||
<div class="sidebar-panel"
|
||||
:class="{
|
||||
'sidebar-collapsed': sidebarCollapsed && !isMobile,
|
||||
'mobile-sidebar-open': isMobile && mobileMenuOpen,
|
||||
'mobile-sidebar': isMobile
|
||||
}"
|
||||
:style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f1f4f9' }"
|
||||
@mouseenter="handleSidebarMouseEnter" @mouseleave="handleSidebarMouseLeave">
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: center; padding: 16px; padding-bottom: 0px;"
|
||||
v-if="chatboxMode">
|
||||
<img width="50" src="@/assets/images/astrbot_logo_mini.webp" alt="AstrBot Logo">
|
||||
<img width="50" src="@/assets/images/icon-no-shadow.svg" alt="AstrBot Logo">
|
||||
<span v-if="!sidebarCollapsed"
|
||||
style="font-weight: 1000; font-size: 26px; margin-left: 8px;">AstrBot</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="sidebar-collapse-btn-container">
|
||||
<div class="sidebar-collapse-btn-container" v-if="!isMobile">
|
||||
<v-btn icon class="sidebar-collapse-btn" @click="toggleSidebar" variant="text"
|
||||
color="deep-purple">
|
||||
<v-icon>{{ (sidebarCollapsed || (!sidebarCollapsed && sidebarHoverExpanded)) ?
|
||||
'mdi-chevron-right' : 'mdi-chevron-left' }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<!-- 手机端关闭按钮 -->
|
||||
<div class="sidebar-collapse-btn-container" v-if="isMobile">
|
||||
<v-btn icon class="sidebar-collapse-btn" @click="closeMobileSidebar" variant="text"
|
||||
color="deep-purple">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; padding-top: 8px;">
|
||||
<v-btn block variant="text" class="new-chat-btn" @click="newC" :disabled="!currCid"
|
||||
v-if="!sidebarCollapsed" prepend-icon="mdi-plus"
|
||||
v-if="!sidebarCollapsed || isMobile" prepend-icon="mdi-plus"
|
||||
style="background-color: transparent !important; border-radius: 4px;">{{
|
||||
tm('actions.newChat') }}</v-btn>
|
||||
<v-btn icon="mdi-plus" rounded="lg" @click="newC" :disabled="!currCid" v-if="sidebarCollapsed"
|
||||
<v-btn icon="mdi-plus" rounded="lg" @click="newC" :disabled="!currCid" v-if="sidebarCollapsed && !isMobile"
|
||||
elevation="0"></v-btn>
|
||||
</div>
|
||||
<div v-if="!sidebarCollapsed">
|
||||
<div v-if="!sidebarCollapsed || isMobile">
|
||||
<v-divider class="mx-4"></v-divider>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="overflow-y: auto; flex-grow: 1;" :class="{ 'fade-in': sidebarHoverExpanded }"
|
||||
v-if="!sidebarCollapsed">
|
||||
v-if="!sidebarCollapsed || isMobile">
|
||||
<v-card v-if="conversations.length > 0" flat style="background-color: transparent;">
|
||||
<v-list density="compact" nav class="conversation-list"
|
||||
style="background-color: transparent;" v-model:selected="selectedConversations"
|
||||
@update:selected="getConversationMessages">
|
||||
<v-list-item v-for="(item, i) in conversations" :key="item.cid" :value="item.cid"
|
||||
rounded="lg" class="conversation-item" active-color="secondary">
|
||||
<v-list-item-title v-if="!sidebarCollapsed" class="conversation-title">{{ item.title
|
||||
<v-list-item-title v-if="!sidebarCollapsed || isMobile" class="conversation-title">{{ item.title
|
||||
|| tm('conversation.newConversation') }}</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="!sidebarCollapsed" class="timestamp">{{
|
||||
<v-list-item-subtitle v-if="!sidebarCollapsed || isMobile" class="timestamp">{{
|
||||
formatDate(item.updated_at)
|
||||
}}</v-list-item-subtitle>
|
||||
}}</v-list-item-subtitle>
|
||||
|
||||
<template v-if="!sidebarCollapsed" v-slot:append>
|
||||
<template v-if="!sidebarCollapsed || isMobile" v-slot:append>
|
||||
<div class="conversation-actions">
|
||||
<v-btn icon="mdi-pencil" size="x-small" variant="text"
|
||||
class="edit-title-btn"
|
||||
@@ -66,7 +81,7 @@
|
||||
<v-fade-transition>
|
||||
<div class="no-conversations" v-if="conversations.length === 0">
|
||||
<v-icon icon="mdi-message-text-outline" size="large" color="grey-lighten-1"></v-icon>
|
||||
<div class="no-conversations-text" v-if="!sidebarCollapsed || sidebarHoverExpanded">
|
||||
<div class="no-conversations-text" v-if="!sidebarCollapsed || sidebarHoverExpanded || isMobile">
|
||||
{{ tm('conversation.noHistory') }}</div>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
@@ -78,12 +93,17 @@
|
||||
<div class="chat-content-panel">
|
||||
|
||||
<div class="conversation-header fade-in">
|
||||
<div v-if="currCid && getCurrentConversation">
|
||||
<!-- 手机端菜单按钮 -->
|
||||
<v-btn icon class="mobile-menu-btn" @click="toggleMobileSidebar" v-if="isMobile" variant="text">
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- <div v-if="currCid && getCurrentConversation">
|
||||
<h3
|
||||
style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
{{ getCurrentConversation.title || tm('conversation.newConversation') }}</h3>
|
||||
<span style="font-size: 12px;">{{ formatDate(getCurrentConversation.updated_at) }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="conversation-header-actions">
|
||||
<!-- router 推送到 /chatbox -->
|
||||
<v-tooltip :text="tm('actions.fullscreen')" v-if="!chatboxMode">
|
||||
@@ -117,7 +137,6 @@
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<v-divider v-if="currCid && getCurrentConversation" class="conversation-divider"></v-divider>
|
||||
|
||||
<MessageList v-if="messages && messages.length > 0" :messages="messages" :isDark="isDark"
|
||||
:isStreaming="isStreaming || isConvRunning" @openImagePreview="openImagePreview"
|
||||
@@ -146,17 +165,30 @@
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="input-area fade-in">
|
||||
<div
|
||||
<div class="input-container"
|
||||
style="width: 85%; max-width: 900px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 24px;">
|
||||
<textarea id="input-field" v-model="prompt" @keydown="handleInputKeyDown"
|
||||
:disabled="isStreaming" @click:clear="clearMessage"
|
||||
placeholder="Ask AstrBot..."
|
||||
:disabled="isStreaming" @click:clear="clearMessage" placeholder="Ask AstrBot..."
|
||||
style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 8px 16px; min-height: 40px; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea>
|
||||
<div
|
||||
style="display: flex; justify-content: space-between; align-items: center; padding: 0px 8px;">
|
||||
<div style="display: flex; justify-content: flex-start; margin-top: 4px;">
|
||||
style="display: flex; justify-content: space-between; align-items: center; padding: 0px 12px;">
|
||||
<div
|
||||
style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;">
|
||||
<!-- 选择提供商和模型 -->
|
||||
<ProviderModelSelector ref="providerModelSelector" />
|
||||
<!-- 流式响应开关 -->
|
||||
<v-tooltip
|
||||
:text="enableStreaming ? tm('streaming.enabled') : tm('streaming.disabled')"
|
||||
location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-chip v-bind="props" @click="toggleStreaming" size="x-small"
|
||||
class="streaming-toggle-chip">
|
||||
<v-icon start :icon="enableStreaming ? 'mdi-flash' : 'mdi-flash-off'"
|
||||
size="small"></v-icon>
|
||||
{{ enableStreaming ? tm('streaming.on') : tm('streaming.off') }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center;">
|
||||
@@ -175,7 +207,6 @@
|
||||
class="send-btn" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 附件预览区 -->
|
||||
@@ -290,7 +321,7 @@ export default {
|
||||
// Ctrl键长按相关变量
|
||||
ctrlKeyDown: false,
|
||||
ctrlKeyTimer: null,
|
||||
ctrlKeyLongPressThreshold: 300, // 长按阈值,单位毫秒
|
||||
ctrlKeyLongPressThreshold: 300, // 长按阈值,单位毫秒
|
||||
|
||||
mediaCache: {}, // Add a cache to store media blobs
|
||||
|
||||
@@ -316,6 +347,13 @@ export default {
|
||||
|
||||
isToastedRunningInfo: false, // To avoid multiple toasts
|
||||
activeSSECount: 0, // Track number of active SSE connections
|
||||
|
||||
// 流式响应开关
|
||||
enableStreaming: true, // 默认开启流式响应
|
||||
|
||||
// 手机端相关变量
|
||||
isMobile: false,
|
||||
mobileMenuOpen: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -394,6 +432,18 @@ export default {
|
||||
this.sidebarCollapsed = true; // 默认折叠状态
|
||||
}
|
||||
|
||||
// 从 localStorage 读取流式响应开关状态,默认为 true(开启)
|
||||
const savedStreamingState = localStorage.getItem('enableStreaming');
|
||||
if (savedStreamingState !== null) {
|
||||
this.enableStreaming = JSON.parse(savedStreamingState);
|
||||
} else {
|
||||
this.enableStreaming = true; // 默认开启
|
||||
}
|
||||
|
||||
// 检测是否为手机端
|
||||
this.checkMobile();
|
||||
window.addEventListener('resize', this.checkMobile);
|
||||
|
||||
// 设置输入框标签
|
||||
this.inputFieldLabel = this.tm('input.chatPrompt');
|
||||
this.getConversations();
|
||||
@@ -416,6 +466,9 @@ export default {
|
||||
beforeUnmount() {
|
||||
// 移除keyup事件监听
|
||||
document.removeEventListener('keyup', this.handleInputKeyUp);
|
||||
|
||||
// 移除resize事件监听
|
||||
window.removeEventListener('resize', this.checkMobile);
|
||||
|
||||
// 清除悬停定时器
|
||||
if (this.sidebarHoverTimer) {
|
||||
@@ -432,6 +485,27 @@ export default {
|
||||
customizer.SET_UI_THEME(newTheme);
|
||||
this.theme.global.name.value = newTheme;
|
||||
},
|
||||
// 检测是否为手机端
|
||||
checkMobile() {
|
||||
this.isMobile = window.innerWidth <= 768;
|
||||
// 如果切换到桌面端,关闭手机菜单
|
||||
if (!this.isMobile) {
|
||||
this.mobileMenuOpen = false;
|
||||
}
|
||||
},
|
||||
// 切换手机端菜单
|
||||
toggleMobileSidebar() {
|
||||
this.mobileMenuOpen = !this.mobileMenuOpen;
|
||||
},
|
||||
// 关闭手机端菜单
|
||||
closeMobileSidebar() {
|
||||
this.mobileMenuOpen = false;
|
||||
},
|
||||
// 切换流式响应
|
||||
toggleStreaming() {
|
||||
this.enableStreaming = !this.enableStreaming;
|
||||
localStorage.setItem('enableStreaming', JSON.stringify(this.enableStreaming));
|
||||
},
|
||||
// 切换侧边栏折叠状态
|
||||
toggleSidebar() {
|
||||
if (this.sidebarHoverExpanded) {
|
||||
@@ -445,7 +519,7 @@ export default {
|
||||
|
||||
// 侧边栏鼠标悬停处理
|
||||
handleSidebarMouseEnter() {
|
||||
if (!this.sidebarCollapsed) return;
|
||||
if (!this.sidebarCollapsed || this.isMobile) return;
|
||||
|
||||
this.sidebarHovered = true;
|
||||
|
||||
@@ -672,6 +746,11 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 手机端关闭侧边栏
|
||||
if (this.isMobile) {
|
||||
this.closeMobileSidebar();
|
||||
}
|
||||
|
||||
axios.get('/api/chat/get_conversation?conversation_id=' + cid[0]).then(async response => {
|
||||
this.currCid = cid[0];
|
||||
// Update the selected conversation in the sidebar
|
||||
@@ -752,6 +831,10 @@ export default {
|
||||
this.currCid = '';
|
||||
this.selectedConversations = []; // 清除选中状态
|
||||
this.messages = [];
|
||||
// 手机端关闭侧边栏
|
||||
if (this.isMobile) {
|
||||
this.closeMobileSidebar();
|
||||
}
|
||||
if (this.$route.path.startsWith('/chatbox')) {
|
||||
this.$router.push('/chatbox');
|
||||
} else {
|
||||
@@ -871,7 +954,8 @@ export default {
|
||||
image_url: imageNamesToSend,
|
||||
audio_url: audioNameToSend ? [audioNameToSend] : [],
|
||||
selected_provider: selectedProviderId,
|
||||
selected_model: selectedModelName
|
||||
selected_model: selectedModelName,
|
||||
enable_streaming: this.enableStreaming
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1105,6 +1189,17 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 流式响应开关芯片样式 */
|
||||
.streaming-toggle-chip {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.streaming-toggle-chip:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 28px;
|
||||
margin-bottom: 16px;
|
||||
@@ -1145,7 +1240,6 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1170,7 +1264,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.04);
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
@@ -1192,6 +1286,77 @@ export default {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 手机端菜单按钮 */
|
||||
.mobile-menu-btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 手机端遮罩层 */
|
||||
.mobile-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
/* 手机端侧边栏 */
|
||||
.mobile-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
max-width: 280px !important;
|
||||
min-width: 280px !important;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mobile-sidebar-open {
|
||||
transform: translateX(0) !important;
|
||||
}
|
||||
|
||||
/* 手机端样式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar-panel:not(.mobile-sidebar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-content-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 手机端去掉容器padding */
|
||||
.chat-page-container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 手机端输入区域样式 */
|
||||
.input-area {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
#input-field {
|
||||
border-radius: 0 !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 侧边栏折叠按钮 */
|
||||
.sidebar-collapse-btn-container {
|
||||
margin: 16px;
|
||||
@@ -1271,25 +1436,12 @@ export default {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-chips {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.status-chips .v-chip {
|
||||
.v-chip {
|
||||
flex: 1 1 0;
|
||||
justify-content: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.status-chip {
|
||||
font-size: 12px;
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
.no-conversations {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<v-avatar class="bot-avatar" size="36">
|
||||
<v-progress-circular :index="index" v-if="isStreaming && index === messages.length - 1" indeterminate size="28"
|
||||
width="2"></v-progress-circular>
|
||||
<span v-else-if="messages[index - 1]?.content.type !== 'bot'" class="text-h2">✨</span>
|
||||
<v-icon v-else-if="messages[index - 1]?.content.type !== 'bot'" size="64" color="#8fb6d2">mdi-star-four-points-small</v-icon>
|
||||
</v-avatar>
|
||||
<div class="bot-message-content">
|
||||
<div class="message-bubble bot-bubble">
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 选择提供商和模型按钮 -->
|
||||
<v-btn class="text-none" variant="tonal" rounded="xl" size="small"
|
||||
<v-chip class="text-none" variant="tonal" size="x-small"
|
||||
v-if="selectedProviderId && selectedModelName" @click="openDialog">
|
||||
{{ selectedProviderId }} / {{ selectedModelName }}
|
||||
</v-btn>
|
||||
<v-btn variant="tonal" rounded="xl" size="small" v-else @click="openDialog">
|
||||
选择模型
|
||||
</v-btn>
|
||||
</v-chip>
|
||||
|
||||
<!-- 选择提供商和模型对话框 -->
|
||||
<v-dialog v-model="showDialog" max-width="800" persistent>
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
"voiceRecord": "Record Voice",
|
||||
"pasteImage": "Paste Image"
|
||||
},
|
||||
"streaming": {
|
||||
"enabled": "Streaming enabled",
|
||||
"disabled": "Streaming disabled",
|
||||
"on": "Stream",
|
||||
"off": "Normal"
|
||||
},
|
||||
"connection": {
|
||||
"title": "Connection Status Notice",
|
||||
"message": "The system detected that the chat connection needs to be re-established.",
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
"voiceRecord": "录制语音",
|
||||
"pasteImage": "粘贴图片"
|
||||
},
|
||||
"streaming": {
|
||||
"enabled": "流式响应已开启",
|
||||
"disabled": "流式响应已关闭",
|
||||
"on": "流式",
|
||||
"off": "普通"
|
||||
},
|
||||
"connection": {
|
||||
"title": "连接状态提醒",
|
||||
"message": "系统检测到聊天连接需要重新建立。",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { RouterView, useRoute } from 'vue-router';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import axios from 'axios';
|
||||
import VerticalSidebarVue from './vertical-sidebar/VerticalSidebar.vue';
|
||||
import VerticalHeaderVue from './vertical-header/VerticalHeader.vue';
|
||||
@@ -8,6 +8,12 @@ import MigrationDialog from '@/components/shared/MigrationDialog.vue';
|
||||
import { useCustomizerStore } from '@/stores/customizer';
|
||||
|
||||
const customizer = useCustomizerStore();
|
||||
const route = useRoute();
|
||||
|
||||
// 计算是否在聊天页面(非全屏模式)
|
||||
const isChatPage = computed(() => {
|
||||
return route.path.startsWith('/chat');
|
||||
});
|
||||
const migrationDialog = ref<InstanceType<typeof MigrationDialog> | null>(null);
|
||||
|
||||
// 检查是否需要迁移
|
||||
@@ -45,7 +51,10 @@ onMounted(() => {
|
||||
<VerticalHeaderVue />
|
||||
<VerticalSidebarVue />
|
||||
<v-main>
|
||||
<v-container fluid class="page-wrapper" style="height: calc(100% - 8px)">
|
||||
<v-container fluid class="page-wrapper" :style="{
|
||||
height: 'calc(100% - 8px)',
|
||||
padding: isChatPage ? '0' : undefined
|
||||
}">
|
||||
<div style="height: 100%;">
|
||||
<RouterView />
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ html {
|
||||
|
||||
.page-wrapper {
|
||||
min-height: calc(100vh - 100px);
|
||||
padding: 15px;
|
||||
padding: 8px;
|
||||
border-radius: $border-radius-root;
|
||||
background: rgb(var(--v-theme-containerBg));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ import Chat from '@/components/chat/Chat.vue'
|
||||
|
||||
<style scoped>
|
||||
.chat-container {
|
||||
height: calc(100vh - 88px)
|
||||
height: calc(100vh - 60px)
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user