Files
sionrui/frontend/app/web-gold/src/components/agents/MyFavoritesModal.vue
sion123 09a567a542
Some checks failed
Build and Deploy / deploy (push) Has been cancelled
feat(agent): 支持自定义系统提示词进行对话
- 前端 API 新增 customSystemPrompt 参数,agentId 变为可选
- 聊天抽屉支持自定义提示词时传递参数并调整宽度
- 我的收藏模态框返回提示词内容供聊天使用
- 后端 Dify 服务优先使用自定义提示词,支持无 agentId 的对话
- Dify 请求 VO 中 agentId 改为非必填,新增 customSystemPrompt 字段
2026-04-11 16:22:11 +08:00

281 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, computed, watch } from 'vue'
import { toast } from 'vue-sonner'
import { Icon } from '@iconify/vue'
import { UserPromptApi } from '@/api/userPrompt'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from '@/components/ui/alert-dialog'
// Props
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
// Emits
const emit = defineEmits(['update:visible', 'refresh', 'chat'])
// State
const loading = ref(false)
const promptList = ref([])
const searchKeyword = ref('')
// Computed - 只搜索名称
const filteredList = computed(() => {
if (!searchKeyword.value.trim()) return promptList.value
const keyword = searchKeyword.value.trim().toLowerCase()
return promptList.value.filter(item =>
item.name.toLowerCase().includes(keyword)
)
})
// Watch
watch(() => props.visible, (newVal) => {
if (newVal) {
loadList()
}
})
// Methods
async function loadList() {
loading.value = true
try {
const res = await UserPromptApi.getUserPromptList()
if (res.code === 0 && res.data) {
promptList.value = res.data.map(item => ({
id: item.id,
name: item.name,
content: item.content,
category: item.category,
useCount: item.useCount || 0
}))
}
} catch (error) {
console.error('加载风格列表失败:', error)
toast.error('加载风格列表失败')
} finally {
loading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
async function handleDelete(id) {
try {
await UserPromptApi.deleteUserPrompt(id)
const index = promptList.value.findIndex(p => p.id === id)
if (index !== -1) {
promptList.value.splice(index, 1)
}
toast.success('删除成功')
emit('refresh')
} catch (error) {
console.error('删除失败:', error)
toast.error('删除失败')
}
}
function handleUse(item) {
emit('chat', {
id: item.id,
name: item.name,
categoryName: item.category || '我的风格',
customSystemPrompt: item.content
})
handleClose()
}
</script>
<template>
<Dialog :open="visible" @update:open="$emit('update:visible', $event)">
<DialogContent class="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>我创建的风格</DialogTitle>
</DialogHeader>
<!-- 操作栏 -->
<div class="flex gap-3 mb-4">
<Button variant="outline" @click="loadList" :disabled="loading">
<Icon v-if="loading" icon="lucide:loader-2" class="size-4 animate-spin" />
<Icon v-else icon="lucide:refresh-cw" class="size-4" />
刷新
</Button>
</div>
<!-- 搜索框 -->
<div class="mb-4">
<Input
v-model="searchKeyword"
placeholder="搜索风格名称..."
/>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-12">
<Icon icon="lucide:loader-2" class="size-8 animate-spin text-primary" />
</div>
<!-- 列表 -->
<div class="prompt-list" v-else-if="filteredList.length > 0">
<div
v-for="item in filteredList"
:key="item.id"
class="prompt-card"
>
<div class="card-header">
<div class="card-info">
<span class="card-name">{{ item.name }}</span>
<span class="card-category" v-if="item.category">{{ item.category }}</span>
</div>
<div class="flex gap-2">
<Button variant="default" size="sm" @click="handleUse(item)">
<Icon icon="lucide:message-circle" class="size-4" />
使用
</Button>
<AlertDialog>
<AlertDialogTrigger as-child>
<Button variant="ghost" size="sm" class="text-muted-foreground hover:text-destructive">
<Icon icon="lucide:trash-2" class="size-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认删除</AlertDialogTitle>
<AlertDialogDescription>
确定要删除"{{ item.name }}"此操作无法撤销
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction @click="handleDelete(item.id)">确认删除</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
<div class="card-footer">
<span class="text-xs text-muted-foreground">使用 {{ item.useCount || 0 }} </span>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-else>
<Icon icon="lucide:sparkles" class="empty-icon" />
<p class="empty-title">暂无创建的风格</p>
<p class="empty-desc">在对话中可保存生成的内容为风格</p>
</div>
</DialogContent>
</Dialog>
</template>
<style scoped lang="less">
.prompt-list {
display: flex;
flex-direction: column;
gap: 10px;
max-height: 400px;
overflow-y: auto;
}
.prompt-card {
padding: 14px 16px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--card);
transition: all 0.2s ease;
&:hover {
border-color: var(--primary);
box-shadow: 0 2px 8px oklch(0.55 0.14 270 / 0.1);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-info {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.card-name {
font-weight: 600;
font-size: 14px;
color: var(--foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-category {
font-size: 11px;
padding: 2px 8px;
background: var(--muted);
color: var(--muted-foreground);
border-radius: 6px;
flex-shrink: 0;
}
.card-footer {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--border);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
text-align: center;
}
.empty-icon {
font-size: 40px;
color: var(--primary);
margin-bottom: 16px;
opacity: 0.5;
}
.empty-title {
font-size: 15px;
font-weight: 500;
color: var(--foreground);
margin: 0 0 8px 0;
}
.empty-desc {
font-size: 13px;
color: var(--muted-foreground);
margin: 0;
}
</style>