Browse Source

添加清空按钮

kagg886 3 months ago
parent
commit
cb45769bbf
3 changed files with 153 additions and 59 deletions
  1. 9 1
      src/api/assist/index.ts
  2. 5 1
      src/api/assist/type.ts
  3. 139 57
      src/views/assistant/index.vue

+ 9 - 1
src/api/assist/index.ts

@@ -49,7 +49,11 @@ export default {
 					headers,
 					body: JSON.stringify(chatRequest),
 					signal: controller.signal,
-				});
+				}).catch((e:Error)=> e);
+
+				if (response instanceof Error) {
+					throw new Error('无法连接到服务器');
+				}
 
 				if (!response.ok) {
 					throw new Error(`HTTP error! status: ${response.status}`);
@@ -157,6 +161,10 @@ export default {
 									break
 								}
 
+								case 'meta': {
+									break
+								}
+
 								default: {
 									// 如果没有明确的事件类型,默认作为消息处理
 									const defaultResponse: ChatResponse = {

+ 5 - 1
src/api/assist/type.ts

@@ -17,7 +17,7 @@
 // 消息类型定义
 export type Message = {
 	id: number
-	role: 'user' | 'assistant'
+	role: 'user' | 'assistant' | 'system' | 'meta'
 	content: string
 	timestamp: number
 }
@@ -56,4 +56,8 @@ export type ErrorResponse = ChatResponseBase<'error'> & {
 	error: string
 }
 
+export type MetaResponse = ChatResponseBase<'meta'> & {
+	meta: string
+}
+
 export type ChatResponse = Text | ToolCallRequest | ToolCallResponse | ErrorResponse

+ 139 - 57
src/views/assistant/index.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, nextTick, onMounted, computed, onUnmounted, reactive, watch } from 'vue'
+import { ref, nextTick, onMounted, computed, onUnmounted, reactive } from 'vue'
 import { Local } from '/@/utils/storage'
 import { User, ChatDotRound, Delete, Edit, Check, Close } from '@element-plus/icons-vue'
 import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
@@ -69,6 +69,9 @@ const toolOptions = ref([
 	},
 ])
 
+const prompt = ref<string>('')
+const openPromptDialog = ref(false)
+
 // 模型选择
 const modelOptions = ref([
 	{ label: 'Claude Sonnet 4', value: 'claude-sonnet-4' },
@@ -86,6 +89,11 @@ onUnmounted(() => chatInstance.value?.())
 // 是否正在对话
 const isConversationActive = computed(() => chatInstance.value !== undefined)
 
+const clearMessage = () => {
+	stopConversation()
+	messages.value = []
+}
+
 // 发送消息
 const sendMessage = () => {
 	if (!inputMessage.value.trim()) return
@@ -111,7 +119,15 @@ const sendMessage = () => {
 	// let toolcall: { name: string; param?: string; value?: string } | undefined = undefined
 	chatInstance.value = assist.chat({
 		chatRequest: {
-			message: messages.value,
+			message: prompt.value ? [
+				{
+					id: 0,
+					role: 'system',
+					content: prompt.value,
+					timestamp: Date.now(),
+				},
+				...messages.value,
+			]: messages.value
 		},
 		onReceive: (resp: ChatResponse) => {
 			switch (resp.type) {
@@ -154,60 +170,11 @@ ${resp.response.data.replace('\n', '')}
 
 				`
 			}
+			rtn.content += '\n'
 			chatInstance.value = undefined
 		},
 	})
 	messages.value.push(rtn)
-
-	//
-	// 	// 添加用户消息
-	// 	messages.value.push({
-	// 		id: Date.now(),
-	// 		type: 'user',
-	// 		content: inputMessage.value,
-	// 		timestamp: new Date(),
-	// 	})
-	//
-	// 	//清空用户聊天框
-	// 	const userMessage = inputMessage.value
-	// 	inputMessage.value = ''
-	//
-	// 	// 滚动到底部
-	// 	scrollToBottom()
-	//
-	// 	const ai = reactive<Message>({
-	// 		id: Date.now()+1,
-	// 		type: 'ai',
-	// 		content: '',
-	// 		timestamp: new Date()
-	// 	})
-	//
-	// 	// 添加一个回复
-	// 	messages.value.push(ai)
-	//
-	// 	const delay = async (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
-	//
-	// 	const action = async () => {
-	// 		const message = `我收到了您的消息:"${userMessage}"。这是一个示例回复,包含markdown格式的内容。
-	//
-	// ## 回复内容
-	//
-	// - 列表项 1
-	// - 列表项 2
-	// - 列表项 3
-	//
-	// \`\`\`javascript
-	// console.log('这是代码示例');
-	// \`\`\``
-	// 		for (let i = 0; i < message.length; i++) {
-	// 			ai.content += message[i]
-	// 			await delay(50)
-	// 		}
-	// 	}
-	// 	action().finally(()=> {
-	// 		isConversationActive.value = false
-	// 		scrollToBottom()
-	// 	})
 }
 
 // 终止对话
@@ -369,12 +336,20 @@ const getUserInfos = ref<{
 						<div v-if="message.role === 'assistant'" class="ai-message-container">
 							<el-avatar class="message-avatar" :icon="ChatDotRound" />
 							<div class="message-bubble ai-bubble">
-								<Markdown :content="message.content" :plugins="plugins" class="markdown-content" />
+								<Markdown v-if="message.content !== ''" :content="message.content" :plugins="plugins" class="markdown-content" />
+								<div v-else class="loading-container">
+									<div class="loading-dots">
+										<span class="dot"></span>
+										<span class="dot"></span>
+										<span class="dot"></span>
+									</div>
+									<span class="loading-text">AI正在思考中...</span>
+								</div>
 							</div>
 						</div>
 
 						<!-- 用户消息 -->
-						<div v-else class="user-message-container">
+						<div v-if="message.role === 'user'" class="user-message-container">
 							<div class="message-bubble user-bubble">
 								{{ message.content }}
 							</div>
@@ -457,6 +432,18 @@ const getUserInfos = ref<{
 							<el-option v-for="item in modelOptions" :key="item.value" :label="item.label" :value="item.value" />
 						</el-select>
 					</div>
+
+					<!-- 提示词输入框 -->
+					<div class="prompt-selector">
+						<el-input
+							v-model="prompt"
+							placeholder="点击设置提示词..."
+							size="small"
+							style="width: 200px"
+							readonly
+							@click="openPromptDialog = true"
+						/>
+					</div>
 				</div>
 
 				<!-- 输入框和按钮行 -->
@@ -478,6 +465,14 @@ const getUserInfos = ref<{
 					<!-- 按钮组 -->
 					<div class="button-group">
 						<el-button
+							type="warning"
+							size="small"
+							@click="clearMessage"
+							style="margin-left: 12px"
+						>
+							清空
+						</el-button>
+						<el-button
 							v-if="!isConversationActive"
 							type="primary"
 							size="small"
@@ -493,6 +488,28 @@ const getUserInfos = ref<{
 			</div>
 		</el-main>
 	</el-container>
+
+	<!-- 提示词设置对话框 -->
+	<el-dialog
+		v-model="openPromptDialog"
+		title="设置提示词"
+		width="600px"
+		:before-close="() => { openPromptDialog = false }"
+	>
+		<el-input
+			v-model="prompt"
+			type="textarea"
+			placeholder="请输入提示词..."
+			:rows="8"
+			resize="none"
+		/>
+		<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>
 </template>
 
 <style scoped lang="scss">
@@ -660,8 +677,8 @@ const getUserInfos = ref<{
 	background: var(--el-fill-color-light);
 	color: var(--el-text-color-primary);
 	position: relative;
-	min-width: 50%;
-	max-width: 70%;
+	min-width: 70%;
+	max-width: 90%;
 	border: 1px solid var(--el-border-color-lighter);
 
 	&::before {
@@ -718,6 +735,56 @@ const getUserInfos = ref<{
 	margin-top: 2px;
 }
 
+/* 加载动画样式 */
+.loading-container {
+	display: flex;
+	align-items: center;
+	gap: 12px;
+	padding: 8px 0;
+	color: var(--el-text-color-secondary);
+}
+
+.loading-dots {
+	display: flex;
+	gap: 4px;
+}
+
+.dot {
+	width: 6px;
+	height: 6px;
+	border-radius: 50%;
+	background-color: var(--el-color-primary);
+	animation: loading-bounce 1.4s ease-in-out infinite both;
+}
+
+.dot:nth-child(1) {
+	animation-delay: -0.32s;
+}
+
+.dot:nth-child(2) {
+	animation-delay: -0.16s;
+}
+
+.dot:nth-child(3) {
+	animation-delay: 0s;
+}
+
+@keyframes loading-bounce {
+	0%, 80%, 100% {
+		transform: scale(0.8);
+		opacity: 0.5;
+	}
+	40% {
+		transform: scale(1);
+		opacity: 1;
+	}
+}
+
+.loading-text {
+	font-size: 14px;
+	font-weight: 500;
+}
+
 .tools-button {
 	margin-bottom: 10px;
 	color: var(--el-text-color-regular);
@@ -901,7 +968,8 @@ const getUserInfos = ref<{
 }
 
 .tool-selector,
-.model-selector {
+.model-selector,
+.prompt-selector {
 	flex-shrink: 0;
 }
 
@@ -934,6 +1002,20 @@ const getUserInfos = ref<{
 	min-width: 60px;
 }
 
+/* 提示词输入框样式 */
+.prompt-selector .el-input {
+	cursor: pointer;
+}
+
+.prompt-selector .el-input__inner {
+	cursor: pointer;
+}
+
+/* 对话框样式 */
+.dialog-footer {
+	text-align: right;
+}
+
 /* 响应式调整 */
 /* 空状态页面样式 */
 .empty-content {