Bläddra i källkod

添加placeholder管理

kagg886 1 månad sedan
förälder
incheckning
de0ae32cdc
3 ändrade filer med 96 tillägg och 38 borttagningar
  1. 1 0
      src/api/assist/type.ts
  2. 74 36
      src/views/assistant/index.vue
  3. 21 2
      src/views/assistant/prompt.vue

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

@@ -188,6 +188,7 @@ export type Prompt = {
 	id: number
 	title: string
 	prompt: string
+	placeholder?:string
 	createdAt?: string // 创建时间
 	updatedAt?: string // 更新时间
 	createdBy?: number // 创建者ID

+ 74 - 36
src/views/assistant/index.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, nextTick, onMounted, computed, onUnmounted, reactive, isReactive } from 'vue'
+import { ref, nextTick, onMounted, computed, onUnmounted, reactive, isReactive, watch } from 'vue'
 import { Local } from '/@/utils/storage'
 import {
 	User,
@@ -15,7 +15,6 @@ import {
 	Download,
 	MoreFilled,
 	Setting,
-	Document,
 	Loading,
 	Promotion,
 	VideoPause,
@@ -27,7 +26,7 @@ import ToolsLoadingPlugin from '/@/components/markdown/plugins/tools-loading'
 import TablePlugin from '/@/components/markdown/plugins/table'
 import Markdown from '/@/components/markdown/Markdown.vue'
 import assist from '/@/api/assist'
-import { ChatResponse, LmConfigInfo, LmSession, Message, Prompt, PromptListParams } from '/@/api/assist/type'
+import { ChatResponse, LmConfigInfo, LmSession, Message, Prompt } from '/@/api/assist/type'
 import { useLoading } from '/@/utils/loading-util'
 import { Setting as EleSetting } from '@element-plus/icons-vue'
 import { useRouter } from 'vue-router'
@@ -159,15 +158,31 @@ const { loading: loadingEmbeddingModels, doLoading: loadEmbeddingModel } = useLo
 
 onMounted(loadEmbeddingModel)
 
-
 // 提示词列表相关状态
+const customPrompt = ref('')
 const promptList = ref<Prompt[]>([])
-const selectPromptId = ref<number|undefined>(undefined)
+
+const displayPromptList = computed(() => {
+	//有提示词展示自定义提示词
+	let r = [...promptList.value]
+	if (customPrompt.value.trim() !== '') {
+		r.splice(1, 0, {
+			id: -2,
+			title: `自定义提示词 (${customPrompt.value.length}) 字`,
+			prompt: customPrompt.value,
+		})
+	}
+	return r
+})
+const selectPromptId = ref<number | undefined>(undefined)
+watch(selectPromptId,(newVal)=>{
+	inputMessage.value = displayPromptList.value.find(i=>i.id === newVal)?.placeholder ?? ''
+})
 const promptLabel = computed(() => {
 	if (!loadingPromptList.value && selectPromptId.value === undefined) {
 		return '未配置提示词'
 	}
-	const select = promptList.value.filter((i) => i.id === selectPromptId.value)
+	const select = displayPromptList.value.filter((i) => i.id === selectPromptId.value)
 
 	if (select.length === 0) {
 		return '加载中'
@@ -176,7 +191,7 @@ const promptLabel = computed(() => {
 	return select[0].title
 })
 const { loading: loadingPromptList, doLoading: loadPromptList } = useLoading(async () => {
-	const data: { list: Prompt[]; total: number } = await assist.chat.prompt.list({pageSize:10,pageNum:1,keyWord:''}).catch(() => {
+	const data: { list: Prompt[]; total: number } = await assist.chat.prompt.list({ pageSize: 10, pageNum: 1, keyWord: '' }).catch(() => {
 		return {
 			list: [],
 			total: 0,
@@ -198,6 +213,9 @@ onMounted(loadPromptList)
 
 const selectedModel = ref<number | undefined>(undefined)
 
+// 提示词管理对话框可见性
+const promptDialogVisible = ref(false)
+
 const chatInstance = ref<(() => void) | undefined>(undefined)
 onUnmounted(() => chatInstance.value?.())
 // 是否正在对话
@@ -324,8 +342,8 @@ 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 ?? ''
+	if (selectPromptId.value != undefined && selectPromptId.value != -1) {
+		const prompt = displayPromptList.value.find((i) => i.id === selectPromptId.value)?.prompt ?? ''
 		//insert element on top
 		r.unshift({
 			id: messages.value.length,
@@ -743,30 +761,26 @@ const pageSizeOptions = [3, 10, 20, 30, 50, 100]
 const maxPages = 100
 
 const exportToMarkDown = (message: Message) => {
-	try {
-		// 创建CSV内容
-		const csvContent = message.render_content
+	// 创建CSV内容
+	const csvContent = message.render_content
 
-		// 创建Blob对象
-		const blob = new Blob([csvContent], { type: 'text/markdown;charset=utf-8;' })
+	// 创建Blob对象
+	const blob = new Blob([csvContent], { type: 'text/markdown;charset=utf-8;' })
 
-		// 创建下载链接
-		const link = document.createElement('a')
-		const url = URL.createObjectURL(blob)
-		link.setAttribute('href', url)
-		link.setAttribute('download', `export_${new Date().getTime()}.md`)
-		link.style.visibility = 'hidden'
+	// 创建下载链接
+	const link = document.createElement('a')
+	const url = URL.createObjectURL(blob)
+	link.setAttribute('href', url)
+	link.setAttribute('download', `export_${new Date().getTime()}.md`)
+	link.style.visibility = 'hidden'
 
-		// 触发下载
-		document.body.appendChild(link)
-		link.click()
-		document.body.removeChild(link)
+	// 触发下载
+	document.body.appendChild(link)
+	link.click()
+	document.body.removeChild(link)
 
-		// 清理URL对象
-		URL.revokeObjectURL(url)
-	} catch (error) {
-		console.error('导出CSV失败:', error)
-	}
+	// 清理URL对象
+	URL.revokeObjectURL(url)
 }
 
 // 导出对话历史
@@ -837,6 +851,9 @@ const isBlank = (str: string) => {
 							<el-dropdown-item @click="redirectToModelManager">
 								<span class="settings-label">模型管理</span>
 							</el-dropdown-item>
+							<el-dropdown-item @click="promptDialogVisible = true">
+								<span class="settings-label">提示词管理</span>
+							</el-dropdown-item>
 							<el-dropdown-item @click="multiDeleteConversationModel.visible = true">
 								<span class="settings-label">对话管理</span>
 							</el-dropdown-item>
@@ -1119,8 +1136,8 @@ const isBlank = (str: string) => {
 							<el-dropdown trigger="click" placement="top-start" :disabled="loadingModels || modelLabel == '未配置模型'">
 								<button class="control-btn model-btn">
 									<el-icon>
-										<Setting v-if="!loadingModels"/>
-										<Loading v-else class="spin"/>
+										<Setting v-if="!loadingModels" />
+										<Loading v-else class="spin" />
 									</el-icon>
 									<span>{{ modelLabel }}</span>
 								</button>
@@ -1142,8 +1159,8 @@ const isBlank = (str: string) => {
 							<el-dropdown trigger="click" placement="top-start" :disabled="loadingModels || embeddingModelLabel == '未配置词嵌入'">
 								<button class="control-btn embedding-model-btn">
 									<el-icon>
-										<CopyDocument v-if="!loadingEmbeddingModels"/>
-										<Loading v-else class="spin"/>
+										<CopyDocument v-if="!loadingEmbeddingModels" />
+										<Loading v-else class="spin" />
 									</el-icon>
 									<span>{{ embeddingModelLabel }}</span>
 								</button>
@@ -1165,15 +1182,15 @@ const isBlank = (str: string) => {
 							<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"/>
+										<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"
+											v-for="item in displayPromptList"
 											:key="item.id"
 											@click="selectPromptId = item.id"
 											:class="{ 'is-selected': selectPromptId === item.id }"
@@ -1241,6 +1258,27 @@ const isBlank = (str: string) => {
 				</div>
 			</div>
 		</el-main>
+
+		<!-- 提示词管理对话框 -->
+		<el-dialog v-model="promptDialogVisible" title="提示词管理" width="60%" append-to-body>
+			<div class="prompt-dialog-content">
+				<div class="prompt-input-section">
+					<h4>自定义提示词</h4>
+					<el-input
+						v-model="customPrompt"
+						type="textarea"
+						:autosize="{ minRows: 12, maxRows: 24 }"
+						placeholder="在此编写你的提示词...(将用于本次会话的系统提示)"
+					/>
+				</div>
+			</div>
+			<template #footer>
+				<div class="dialog-footer">
+					<el-button @click="promptDialogVisible = false">取 消</el-button>
+					<el-button type="primary" @click="promptDialogVisible = false">确 认</el-button>
+				</div>
+			</template>
+		</el-dialog>
 	</el-container>
 </template>
 

+ 21 - 2
src/views/assistant/prompt.vue

@@ -107,6 +107,7 @@ const formData = reactive<Prompt>({
 	id: 0,
 	title: '',
 	prompt: '',
+	placeholder: '',
 })
 
 // 表单验证规则
@@ -234,9 +235,16 @@ onMounted(() => {
 			<el-table-column label="ID" prop="id" width="80" align="center" />
 			<el-table-column label="标题" prop="title" align="center" show-overflow-tooltip />
 			<el-table-column label="提示词内容" prop="prompt" align="center" show-overflow-tooltip>
-				<template #default="scope">
+				<template #default="{row}:{row:Prompt}">
 					<div class="prompt-content">
-						{{ scope.row.prompt.length > 100 ? scope.row.prompt.substring(0, 100) + '...' : scope.row.prompt }}
+						{{ row.prompt.length > 100 ? row.prompt.substring(0, 100) + '...' : row.prompt }}
+					</div>
+				</template>
+			</el-table-column>
+			<el-table-column label="占位符" prop="placeholder" align="center" show-overflow-tooltip>
+				<template #default="{row}:{row:Prompt}">
+					<div class="prompt-placeholder">
+						{{ (row.placeholder?.length ?? 0) > 100 ? row.placeholder!.substring(0, 100) + '...' : row.placeholder}}
 					</div>
 				</template>
 			</el-table-column>
@@ -281,6 +289,17 @@ onMounted(() => {
 						maxlength="2000"
 					/>
 				</el-form-item>
+				<el-form-item label="占位符" prop="placeholder">
+					<el-input
+						v-model="formData.placeholder"
+						type="textarea"
+						:rows="3"
+						placeholder="请输入占位符"
+						clearable
+						show-word-limit
+						maxlength="2000"
+					/>
+				</el-form-item>
 			</el-form>
 
 			<template #footer>