|
@@ -1,7 +1,25 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, nextTick, onMounted, computed, onUnmounted, reactive, watch, isReactive } from 'vue'
|
|
|
import { Local } from '/@/utils/storage'
|
|
|
-import { User, ChatDotRound, Delete, Edit, Check, Close, ArrowDown, Star, StarFilled, Search, Download, MoreFilled } from '@element-plus/icons-vue'
|
|
|
+import {
|
|
|
+ User,
|
|
|
+ ChatDotRound,
|
|
|
+ Delete,
|
|
|
+ Edit,
|
|
|
+ Check,
|
|
|
+ Close,
|
|
|
+ ArrowDown,
|
|
|
+ Star,
|
|
|
+ StarFilled,
|
|
|
+ Search,
|
|
|
+ Download,
|
|
|
+ MoreFilled,
|
|
|
+ Setting,
|
|
|
+ Document,
|
|
|
+ Loading,
|
|
|
+ Promotion,
|
|
|
+ VideoPause,
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
|
|
|
import EChartsPlugin from '/@/components/markdown/plugins/echarts'
|
|
|
import ToolsLoadingPlugin from '/@/components/markdown/plugins/tools-loading'
|
|
@@ -455,6 +473,31 @@ const { loading: loadingDeleteConversation, doLoading: deleteConversation } = us
|
|
|
await doLoadConversations()
|
|
|
})
|
|
|
|
|
|
+const {loading: loadingDeleteAllConversion,doLoading: deleteAllConversion } = useLoading(async () => {
|
|
|
+ const confirm = await ElMessageBox.confirm(`确定要删除所有对话吗?此操作不可恢复!`, "警告", {
|
|
|
+ confirmButtonText: "确定",
|
|
|
+ cancelButtonText: "取消",
|
|
|
+ type: "error",
|
|
|
+ })
|
|
|
+
|
|
|
+ if (confirm !== 'confirm') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await assist.session
|
|
|
+ .del(conversations.value.map((it)=>it.session_id))
|
|
|
+ .then(() => true)
|
|
|
+ .catch(() => false)
|
|
|
+ .finally(() => (currentDeletingConversation.value = -1))
|
|
|
+ if (!res) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ activeConversationId.value = undefined
|
|
|
+ messages.value = []
|
|
|
+ conversations.value = []
|
|
|
+})
|
|
|
+
|
|
|
// 创建新对话
|
|
|
const { loading: creatingConversation, doLoading: createConversationAndSetItActive } = useLoading(async () => {
|
|
|
try {
|
|
@@ -693,11 +736,14 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
|
|
|
<el-dropdown-item @click="redirectToModelManager">
|
|
|
<span class="settings-label">模型管理</span>
|
|
|
</el-dropdown-item>
|
|
|
+ <el-dropdown-item @click="deleteAllConversion">
|
|
|
+ <span class="settings-label">清空所有对话</span>
|
|
|
+ </el-dropdown-item>
|
|
|
</el-dropdown-menu>
|
|
|
</template>
|
|
|
</el-dropdown>
|
|
|
</div>
|
|
|
- <el-scrollbar class="conversation-list" v-loading="loadConversations">
|
|
|
+ <el-scrollbar class="conversation-list" v-loading="loadConversations || loadingDeleteAllConversion">
|
|
|
<div
|
|
|
v-for="conv in conversations"
|
|
|
:key="conv.session_id"
|
|
@@ -729,7 +775,7 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
|
|
|
<template #dropdown>
|
|
|
<el-dropdown-menu>
|
|
|
<el-dropdown-item @click="exportConversation(conv.session_id)">
|
|
|
- <el-button v-if="conv.session_id === exportId && exportConversationLoading" size="small" loading circle plain/>
|
|
|
+ <el-button v-if="conv.session_id === exportId && exportConversationLoading" size="small" loading circle plain />
|
|
|
<el-icon v-else>
|
|
|
<Download />
|
|
|
</el-icon>
|
|
@@ -930,75 +976,67 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
|
|
|
</div>
|
|
|
|
|
|
<div class="input-container" v-if="activeConversationId !== -1">
|
|
|
- <!-- 工具和模型选择行 -->
|
|
|
- <div class="selection-row">
|
|
|
-<!-- <!– 工具选择 –>-->
|
|
|
-<!-- <div class="tool-selector">-->
|
|
|
-<!-- <el-cascader-->
|
|
|
-<!-- v-model="selectedTool"-->
|
|
|
-<!-- :options="toolOptions"-->
|
|
|
-<!-- :show-all-levels="false"-->
|
|
|
-<!-- :props="{ multiple: true }"-->
|
|
|
-<!-- collapse-tags-->
|
|
|
-<!-- collapse-tags-tooltip-->
|
|
|
-<!-- clearable-->
|
|
|
-<!-- size="small"-->
|
|
|
-<!-- style="width: 200px"-->
|
|
|
-<!-- />-->
|
|
|
-<!-- </div>-->
|
|
|
-
|
|
|
- <!-- 模型选择 -->
|
|
|
- <div class="model-selector">
|
|
|
- <el-select :loading="loadingModels" v-model="selectedModel" placeholder="选择模型" size="small" style="width: 200px">
|
|
|
- <el-option v-for="item in modelOptions" :key="item.id" :value="item.id" :label="item.modelName" />
|
|
|
- </el-select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 提示词输入框 -->
|
|
|
- <div class="prompt-selector">
|
|
|
- <el-input v-model="prompt" placeholder="点击设置提示词..." size="small" style="width: 200px" readonly @click="openPromptDialog = true" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 输入框和按钮行 -->
|
|
|
- <div class="input-row">
|
|
|
+ <!-- 大输入框容器 -->
|
|
|
+ <div class="large-input-container">
|
|
|
<!-- 输入框 -->
|
|
|
- <div class="message-input-wrapper">
|
|
|
- <el-input
|
|
|
- v-model="inputMessage"
|
|
|
- type="textarea"
|
|
|
- placeholder="请输入您的问题..."
|
|
|
- :rows="2"
|
|
|
- resize="none"
|
|
|
- @keydown.enter.ctrl="sendMessage"
|
|
|
- @keydown.enter.meta="sendMessage"
|
|
|
- :disabled="isConversationActive"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <textarea
|
|
|
+ v-model="inputMessage"
|
|
|
+ class="large-textarea"
|
|
|
+ placeholder="请输入您的问题..."
|
|
|
+ @keydown.enter.ctrl="sendMessage"
|
|
|
+ @keydown.enter.meta="sendMessage"
|
|
|
+ :disabled="isConversationActive"
|
|
|
+ rows="3"
|
|
|
+ ></textarea>
|
|
|
+
|
|
|
+ <!-- 内嵌按钮区域 -->
|
|
|
+ <div class="embedded-controls">
|
|
|
+ <!-- 左下角按钮组 -->
|
|
|
+ <div class="left-controls">
|
|
|
+ <!-- 模型选择按钮 -->
|
|
|
+ <el-dropdown trigger="click" placement="top-start">
|
|
|
+ <button class="control-btn model-btn">
|
|
|
+ <el-icon><Setting /></el-icon>
|
|
|
+ <span>模型</span>
|
|
|
+ </button>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item
|
|
|
+ v-for="item in modelOptions"
|
|
|
+ :key="item.id"
|
|
|
+ @click="selectedModel = item.id"
|
|
|
+ :class="{ 'is-selected': selectedModel === item.id }"
|
|
|
+ >
|
|
|
+ {{ item.modelName }}
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
|
|
|
- <!-- 按钮组 -->
|
|
|
- <div class="button-group">
|
|
|
- <el-button
|
|
|
- v-show="messages.length !== 0"
|
|
|
- type="warning"
|
|
|
- size="small"
|
|
|
- @click="clearMessage"
|
|
|
- style="margin-left: 12px"
|
|
|
- :loading="loadingClearMessage"
|
|
|
- >
|
|
|
- 清空
|
|
|
- </el-button>
|
|
|
- <el-button
|
|
|
- v-if="!isConversationActive"
|
|
|
- type="primary"
|
|
|
- size="small"
|
|
|
- @click="sendMessage"
|
|
|
- @keyup.ctrl.enter="sendMessage"
|
|
|
- :disabled="canSendMessage"
|
|
|
- >
|
|
|
- 发送
|
|
|
- </el-button>
|
|
|
- <el-button v-else type="danger" size="small" @click="stopConversation"> 终止</el-button>
|
|
|
+ <!-- 提示词选择按钮 -->
|
|
|
+ <button class="control-btn prompt-btn" @click="openPromptDialog = true">
|
|
|
+ <el-icon><Document /></el-icon>
|
|
|
+ <span v-if="prompt">提示词:{{ prompt.length }}字</span>
|
|
|
+ <span v-else>提示词</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右下角按钮组 -->
|
|
|
+ <div class="right-controls">
|
|
|
+ <!-- 清空按钮 -->
|
|
|
+ <button v-show="messages.length !== 0" class="control-btn clear-btn" @click="clearMessage" :disabled="loadingClearMessage">
|
|
|
+ <el-icon v-if="loadingClearMessage"><Loading /></el-icon>
|
|
|
+ <el-icon v-else><Delete /></el-icon>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <!-- 发送按钮 -->
|
|
|
+ <button v-if="!isConversationActive" class="control-btn send-btn" @click="sendMessage" :disabled="canSendMessage">
|
|
|
+ <el-icon><Promotion /></el-icon>
|
|
|
+ </button>
|
|
|
+ <button v-else class="control-btn stop-btn" @click="stopConversation">
|
|
|
+ <el-icon><VideoPause /></el-icon>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -1633,6 +1671,7 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/* 新的输入容器样式 */
|
|
|
.input-container {
|
|
|
position: absolute;
|
|
|
width: 90%;
|
|
@@ -1641,63 +1680,126 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
|
|
|
transform: translate(-50%, 0);
|
|
|
background: var(--el-bg-color);
|
|
|
border: 1px solid var(--el-border-color-light);
|
|
|
- border-radius: 12px;
|
|
|
- padding: 16px;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 0;
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 12px;
|
|
|
}
|
|
|
|
|
|
-.selection-row {
|
|
|
+.large-input-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.large-textarea {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 120px;
|
|
|
+ padding: 16px 16px 60px 16px;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+ resize: none;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.5;
|
|
|
+ background: transparent;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ border-radius: 8px;
|
|
|
+ font-family: inherit;
|
|
|
+}
|
|
|
+
|
|
|
+.large-textarea::placeholder {
|
|
|
+ color: var(--el-text-color-placeholder);
|
|
|
+}
|
|
|
+
|
|
|
+.large-textarea:disabled {
|
|
|
+ color: var(--el-text-color-disabled);
|
|
|
+ cursor: not-allowed;
|
|
|
+}
|
|
|
+
|
|
|
+.embedded-controls {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
align-items: center;
|
|
|
- gap: 12px;
|
|
|
- flex-wrap: wrap;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
+ background: var(--el-fill-color-extra-light);
|
|
|
+ border-radius: 0 0 8px 8px;
|
|
|
}
|
|
|
|
|
|
-.tool-selector,
|
|
|
-.model-selector,
|
|
|
-.prompt-selector {
|
|
|
- flex-shrink: 0;
|
|
|
+.left-controls,
|
|
|
+.right-controls {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
|
|
|
-.input-row {
|
|
|
+.control-btn {
|
|
|
display: flex;
|
|
|
- align-items: flex-end;
|
|
|
- gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ border-radius: 6px;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ font-size: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
-.message-input-wrapper {
|
|
|
- flex: 1;
|
|
|
- min-width: 0;
|
|
|
+.control-btn:hover:not(:disabled) {
|
|
|
+ background: var(--el-fill-color-light);
|
|
|
+ border-color: var(--el-color-primary-light-7);
|
|
|
+ color: var(--el-color-primary);
|
|
|
}
|
|
|
|
|
|
-.message-input-wrapper :deep(.el-textarea__inner) {
|
|
|
- border-radius: 8px;
|
|
|
- resize: none;
|
|
|
+.control-btn:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+}
|
|
|
+
|
|
|
+.control-btn .el-icon {
|
|
|
font-size: 14px;
|
|
|
- line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
-.button-group {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 6px;
|
|
|
- flex-shrink: 0;
|
|
|
+/* 特定按钮样式 */
|
|
|
+.send-btn {
|
|
|
+ background: var(--el-color-primary);
|
|
|
+ color: white;
|
|
|
+ border-color: var(--el-color-primary);
|
|
|
}
|
|
|
|
|
|
-.button-group .el-button {
|
|
|
- min-width: 60px;
|
|
|
+.send-btn:hover:not(:disabled) {
|
|
|
+ background: var(--el-color-primary-light-3);
|
|
|
+ border-color: var(--el-color-primary-light-3);
|
|
|
}
|
|
|
|
|
|
-/* 提示词输入框样式 */
|
|
|
-.prompt-selector .el-input {
|
|
|
- cursor: pointer;
|
|
|
+.stop-btn {
|
|
|
+ background: var(--el-color-danger);
|
|
|
+ color: white;
|
|
|
+ border-color: var(--el-color-danger);
|
|
|
}
|
|
|
|
|
|
-.prompt-selector .el-input__inner {
|
|
|
- cursor: pointer;
|
|
|
+.stop-btn:hover:not(:disabled) {
|
|
|
+ background: var(--el-color-danger-light-3);
|
|
|
+ border-color: var(--el-color-danger-light-3);
|
|
|
+}
|
|
|
+
|
|
|
+.clear-btn:hover:not(:disabled) {
|
|
|
+ background: var(--el-color-warning-light-9);
|
|
|
+ border-color: var(--el-color-warning-light-7);
|
|
|
+ color: var(--el-color-warning);
|
|
|
+}
|
|
|
+
|
|
|
+/* 下拉菜单样式 */
|
|
|
+:deep(.el-dropdown-menu) {
|
|
|
+ .el-dropdown-menu__item.is-selected {
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* 对话框样式 */
|
|
@@ -1806,6 +1908,9 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
|
|
|
|
|
|
/* 收藏面板底部栏样式 */
|
|
|
.bookmark-footer {
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+
|
|
|
.bookmark-controls {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|