kagg886 1 месяц назад
Родитель
Сommit
0087a36ffa
1 измененных файлов с 133 добавлено и 188 удалено
  1. 133 188
      src/views/assistant/index.vue

+ 133 - 188
src/views/assistant/index.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, nextTick, onMounted, computed, onUnmounted, reactive, watch, isReactive } from 'vue'
+import { ref, nextTick, onMounted, computed, onUnmounted, reactive, isReactive } from 'vue'
 import { Local } from '/@/utils/storage'
 import {
 	User,
@@ -40,10 +40,6 @@ const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin(), ToolsLoadingPlugin
 // 消息列表
 const messages = ref<Message[]>([])
 
-watch(messages, (newVal) => {
-	console.log(newVal.map((it) => it.render_content))
-})
-
 // 输入框内容
 const inputMessage = ref('')
 const messagesContainer = ref<HTMLElement>()
@@ -100,25 +96,16 @@ const messagesContainer = ref<HTMLElement>()
 // 	},
 // ])
 
-const prompt = ref<string>('')
-const openPromptDialog = ref(false)
-
-// 提示词列表相关状态
-const promptList = ref<Prompt[]>([])
-const promptListParams = ref<PromptListParams>({
-	pageNum: 1,
-	pageSize: 10,
-	keyWord: '',
-})
-const promptListTotal = ref(0)
-
 // 模型选择
 const modelOptions = ref<LmConfigInfo[]>([])
 const modelLabel = computed(() => {
+	if (!loadingModels.value && selectedModel.value === undefined) {
+		return '未配置模型'
+	}
 	const select = modelOptions.value.filter((i) => i.id === selectedModel.value)
 
 	if (select.length === 0) {
-		return '选择模型'
+		return '加载中'
 	}
 
 	return select[0].modelName
@@ -131,22 +118,26 @@ const { loading: loadingModels, doLoading: loadModel } = useLoading(async () =>
 		}
 	})
 
-	modelOptions.value = data.list
-	selectedModel.value = data.list[0]?.id ?? undefined
+	modelOptions.value = data.list ?? []
+	selectedModel.value = modelOptions.value[0]?.id ?? undefined
 })
 
 // 词嵌入模型选择
 const embeddingModelOptions = ref<LmConfigInfo[]>([])
 const selectedEmbeddingModel = ref<number | undefined>(undefined)
 const embeddingModelLabel = computed(() => {
+	if (!loadingEmbeddingModels.value && selectedEmbeddingModel.value === undefined) {
+		return '未配置词嵌入'
+	}
 	const select = embeddingModelOptions.value.filter((i) => i.id === selectedEmbeddingModel.value)
 
 	if (select.length === 0) {
-		return '选择词嵌入模型'
+		return '加载中'
 	}
 
 	return select[0].modelName
 })
+onMounted(loadModel)
 
 const { loading: loadingEmbeddingModels, doLoading: loadEmbeddingModel } = useLoading(async () => {
 	const data: { list: LmConfigInfo[]; total: number } = await assist.model.getList({ modelType: 'embedding' }).catch(() => {
@@ -155,44 +146,56 @@ const { loading: loadingEmbeddingModels, doLoading: loadEmbeddingModel } = useLo
 		}
 	})
 
-	data.list = [
-		...(data.list ?? []),
-		{
+	embeddingModelOptions.value = data.list ?? []
+	if (embeddingModelOptions.value.length !== 0) {
+		embeddingModelOptions.value.unshift({
 			id: -1,
 			modelName: '不启用词嵌入',
 			status: true,
-		},
-	]
-
-	embeddingModelOptions.value = data.list
-	selectedEmbeddingModel.value = data.list[0]?.id ?? undefined
+		})
+	}
+	selectedEmbeddingModel.value = embeddingModelOptions.value[0]?.id ?? undefined
 })
 
-onMounted(() => {
-	loadModel()
-	loadEmbeddingModel()
-})
+onMounted(loadEmbeddingModel)
+
+
+// 提示词列表相关状态
+const promptList = ref<Prompt[]>([])
+const selectPromptId = ref<number|undefined>(undefined)
+const promptLabel = computed(() => {
+	if (!loadingPromptList.value && selectPromptId.value === undefined) {
+		return '未配置提示词'
+	}
+	const select = promptList.value.filter((i) => i.id === selectPromptId.value)
+
+	if (select.length === 0) {
+		return '加载中'
+	}
 
-// 加载提示词列表
+	return select[0].title
+})
 const { loading: loadingPromptList, doLoading: loadPromptList } = useLoading(async () => {
-	const data: { list: Prompt[]; total: number } = await assist.chat.prompt.list(promptListParams.value).catch(() => {
+	const data: { list: Prompt[]; total: number } = await assist.chat.prompt.list({pageSize:10,pageNum:1,keyWord:''}).catch(() => {
 		return {
 			list: [],
 			total: 0,
 		}
 	})
 
-	promptList.value = data.list
-	promptListTotal.value = data.total
-})
-
-// 监听对话框打开状态,打开时加载提示词列表
-watch(openPromptDialog, (newVal) => {
-	if (newVal) {
-		loadPromptList()
+	promptList.value = data.list ?? []
+	if (promptList.value.length !== 0) {
+		promptList.value.unshift({
+			id: -1,
+			title: '不启用提示词',
+			prompt: '',
+		})
 	}
+	selectPromptId.value = promptList.value[0]?.id ?? undefined
 })
 
+onMounted(loadPromptList)
+
 const selectedModel = ref<number | undefined>(undefined)
 
 const chatInstance = ref<(() => void) | undefined>(undefined)
@@ -320,21 +323,22 @@ const replaceMessage = async (index: number) => {
 }
 
 const chatInternal = (rtn: Message, context: Message[] = messages.value) => {
+	let r = [...context]
+	if (selectPromptId.value != undefined) {
+		const prompt = promptList.value.find((i) => i.id === selectPromptId.value)?.prompt ?? ''
+		//insert element on top
+		r.unshift({
+			id: messages.value.length,
+			role: 'system',
+			render_content: prompt,
+			content: prompt,
+			timestamp: Date.now(),
+		})
+	}
 	chatInstance.value = assist.chat.sse({
 		chatRequest: {
 			session_id: activeConversationId.value!,
-			message: prompt.value
-				? [
-						{
-							id: messages.value.length,
-							role: 'system',
-							render_content: prompt.value,
-							content: prompt.value,
-							timestamp: Date.now(),
-						},
-						...context,
-				  ]
-				: context,
+			message: r,
 			modelClassId: selectedModel.value,
 			modelEmbeddingId: (selectedEmbeddingModel.value ?? -1) > 0 ? selectedEmbeddingModel.value : undefined,
 		},
@@ -604,20 +608,14 @@ const startMultidelete = async () => {
 
 // 创建新对话
 const { loading: creatingConversation, doLoading: createConversationAndSetItActive } = useLoading(async () => {
-	try {
-		// 调用API创建新对话,默认标题为"新对话"
-		const { id } = await assist.session.add('新对话')
-		// 刷新对话列表
-		await doLoadConversations()
-
-		await nextTick()
-		activeConversationId.value = id
-		messages.value = []
-	} catch (error) {
-		console.error('创建对话失败:', error)
-		// 可以在这里添加错误提示
-		throw error
-	}
+	// 调用API创建新对话,默认标题为"新对话"
+	const { id } = await assist.session.add('新对话')
+	// 刷新对话列表
+	await doLoadConversations()
+
+	await nextTick()
+	activeConversationId.value = id
+	messages.value = []
 })
 
 // 编辑会话状态管理
@@ -814,38 +812,6 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
 const isBlank = (str: string) => {
 	return str == null || str.trim().length === 0
 }
-
-// 提示词列表相关函数
-// 选择提示词
-const selectPrompt = (selectedPrompt: Prompt) => {
-	prompt.value = selectedPrompt.prompt
-}
-
-// 提示词分页变化
-const handlePromptPageChange = async (page: number) => {
-	promptListParams.value.pageNum = page
-	await loadPromptList()
-}
-
-// 提示词每页数目变化
-const handlePromptPageSizeChange = async (pageSize: number) => {
-	promptListParams.value.pageSize = pageSize
-	promptListParams.value.pageNum = 1 // 重置到第一页
-	await loadPromptList()
-}
-
-// 提示词搜索
-const handlePromptSearch = async () => {
-	promptListParams.value.pageNum = 1
-	await loadPromptList()
-}
-
-// 重置提示词搜索
-const handlePromptReset = async () => {
-	promptListParams.value.keyWord = ''
-	promptListParams.value.pageNum = 1
-	await loadPromptList()
-}
 </script>
 
 <template>
@@ -1150,9 +1116,12 @@ const handlePromptReset = async () => {
 						<!-- 左下角按钮组 -->
 						<div class="left-controls">
 							<!-- 模型选择按钮 -->
-							<el-dropdown trigger="click" placement="top-start">
+							<el-dropdown trigger="click" placement="top-start" :disabled="loadingModels || modelLabel == '未配置模型'">
 								<button class="control-btn model-btn">
-									<el-icon><Setting /></el-icon>
+									<el-icon>
+										<Setting v-if="!loadingModels"/>
+										<Loading v-else class="spin"/>
+									</el-icon>
 									<span>{{ modelLabel }}</span>
 								</button>
 								<template #dropdown>
@@ -1170,9 +1139,12 @@ const handlePromptReset = async () => {
 							</el-dropdown>
 
 							<!-- 词嵌入模型选择按钮 -->
-							<el-dropdown trigger="click" placement="top-start">
+							<el-dropdown trigger="click" placement="top-start" :disabled="loadingModels || embeddingModelLabel == '未配置词嵌入'">
 								<button class="control-btn embedding-model-btn">
-									<el-icon><CopyDocument /></el-icon>
+									<el-icon>
+										<CopyDocument v-if="!loadingEmbeddingModels"/>
+										<Loading v-else class="spin"/>
+									</el-icon>
 									<span>{{ embeddingModelLabel }}</span>
 								</button>
 								<template #dropdown>
@@ -1189,11 +1161,28 @@ const handlePromptReset = async () => {
 								</template>
 							</el-dropdown>
 
-							<!-- 提示词选择按钮 -->
-							<button class="control-btn prompt-btn" @click="openPromptDialog = true">
-								<el-icon><Document /></el-icon>
-								<span v-if="prompt">提示词:{{ prompt.length }}字</span>
-							</button>
+							<!-- 词嵌入模型选择按钮 -->
+							<el-dropdown trigger="click" placement="top-start" :disabled="loadingModels || promptLabel == '未配置提示词'">
+								<button class="control-btn embedding-model-btn">
+									<el-icon>
+										<CopyDocument v-if="!loadingPromptList"/>
+										<Loading v-else class="spin"/>
+									</el-icon>
+									<span>{{ promptLabel }}</span>
+								</button>
+								<template #dropdown>
+									<el-dropdown-menu>
+										<el-dropdown-item
+											v-for="item in promptList"
+											:key="item.id"
+											@click="selectPromptId = item.id"
+											:class="{ 'is-selected': selectPromptId === item.id }"
+										>
+											{{ item.title }}
+										</el-dropdown-item>
+									</el-dropdown-menu>
+								</template>
+							</el-dropdown>
 						</div>
 
 						<!-- 右下角按钮组 -->
@@ -1252,84 +1241,6 @@ const handlePromptReset = async () => {
 				</div>
 			</div>
 		</el-main>
-
-		<!-- 提示词设置对话框 -->
-		<el-dialog
-			v-model="openPromptDialog"
-			title="设置提示词"
-			width="800px"
-			:before-close="
-				() => {
-					openPromptDialog = false
-				}
-			"
-		>
-			<div class="prompt-dialog-content">
-				<!-- 提示词输入框 -->
-				<div class="prompt-input-section">
-					<h4>提示词内容</h4>
-					<el-input v-model="prompt" type="textarea" placeholder="请输入提示词..." :rows="8" resize="none" />
-				</div>
-
-				<!-- 提示词列表 -->
-				<div class="prompt-list-section">
-					<div class="prompt-list-header">
-						<h4>选择提示词模板</h4>
-						<div class="prompt-search">
-							<el-input
-								v-model="promptListParams.keyWord"
-								placeholder="搜索提示词..."
-								:prefix-icon="Search"
-								clearable
-								@keydown.enter="handlePromptSearch"
-								@clear="handlePromptReset"
-								style="width: 200px"
-							/>
-							<el-button type="primary" :icon="Search" @click="handlePromptSearch" :loading="loadingPromptList" size="small"> 搜索 </el-button>
-						</div>
-					</div>
-
-					<!-- 提示词列表 -->
-					<div class="prompt-list-container" v-loading="loadingPromptList">
-						<div v-if="promptList.length === 0" class="prompt-list-empty">
-							<el-empty description="暂无提示词模板" />
-						</div>
-						<div v-else class="prompt-list">
-							<div v-for="item in promptList" :key="item.id" class="prompt-item" @click="selectPrompt(item)">
-								<div class="prompt-item-header">
-									<span class="prompt-title">{{ item.title }}</span>
-								</div>
-								<div class="prompt-preview">
-									{{ item.prompt.length > 60 ? item.prompt.substring(0, 60) + '...' : item.prompt }}
-								</div>
-							</div>
-						</div>
-					</div>
-
-					<!-- 分页 -->
-					<div class="prompt-pagination" v-if="promptListTotal > 0">
-						<el-pagination
-							v-model:current-page="promptListParams.pageNum"
-							v-model:page-size="promptListParams.pageSize"
-							:total="promptListTotal"
-							:page-sizes="[5, 10, 20, 30]"
-							layout="total, sizes, prev, pager, next"
-							@current-change="handlePromptPageChange"
-							@size-change="handlePromptPageSizeChange"
-							:disabled="loadingPromptList"
-							small
-						/>
-					</div>
-				</div>
-			</div>
-
-			<template #footer>
-				<div class="dialog-footer">
-					<el-button @click="openPromptDialog = false">取消</el-button>
-					<el-button type="primary" @click="openPromptDialog = false">确定</el-button>
-				</div>
-			</template>
-		</el-dialog>
 	</el-container>
 </template>
 
@@ -2311,4 +2222,38 @@ const handlePromptReset = async () => {
 		}
 	}
 }
+
+// 无限旋转动画
+@keyframes spin {
+	to {
+		transform: rotate(360deg);
+	}
+}
+
+// 基础旋转类
+.spin {
+	display: inline-block;
+	animation: spin 1s linear infinite;
+	transform-origin: center center;
+
+	// 变体:慢速
+	&-slow {
+		animation-duration: 3s;
+	}
+
+	// 变体:快速
+	&-fast {
+		animation-duration: 0.6s;
+	}
+
+	// 变体:反向
+	&-rev {
+		animation-direction: reverse;
+	}
+
+	// 变体:悬停暂停
+	&-pause:hover {
+		animation-play-state: paused;
+	}
+}
 </style>