Files
AstrBot/dashboard/src/components/shared/PersonaSelector.vue
Copilot de1304dc6a feat: add edit button to persona selector dialog (#4826)
* Initial plan

* feat: add edit persona functionality in chatui selector dialog

Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>

* fix: address code review feedback - improve null checks and i18n consistency

Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
2026-02-03 10:32:20 +08:00

211 lines
5.8 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.
<template>
<BaseFolderItemSelector
:model-value="modelValue"
@update:model-value="handleUpdate"
:folder-tree="folderTree"
:items="currentPersonas as any"
:tree-loading="treeLoading"
:items-loading="itemsLoading"
:labels="labels"
:show-create-button="true"
:show-edit-button="true"
:default-item="defaultPersona"
item-id-field="persona_id"
item-name-field="persona_id"
item-description-field="system_prompt"
:display-value-formatter="formatDisplayValue"
@navigate="handleNavigate"
@create="openCreatePersona"
@edit="openEditPersona"
/>
<!-- 创建/编辑人格对话框 -->
<PersonaForm
v-model="showPersonaDialog"
:editing-persona="editingPersona ?? undefined"
:current-folder-id="currentFolderId ?? undefined"
:current-folder-name="currentFolderName ?? undefined"
@saved="handlePersonaSaved"
@error="handleError" />
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
import BaseFolderItemSelector from '@/components/folder/BaseFolderItemSelector.vue'
import PersonaForm from './PersonaForm.vue'
import { useI18n, useModuleI18n } from '@/i18n/composables'
import type { FolderTreeNode, SelectableItem } from '@/components/folder/types'
interface Persona {
persona_id: string
system_prompt: string
folder_id?: string | null
[key: string]: any
}
const props = defineProps({
modelValue: {
type: String,
default: ''
},
buttonText: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const { t } = useI18n()
const { tm } = useModuleI18n('core.shared')
// 状态
const folderTree = ref<FolderTreeNode[]>([])
const currentPersonas = ref<Persona[]>([])
const treeLoading = ref(false)
const itemsLoading = ref(false)
const showPersonaDialog = ref(false)
const editingPersona = ref<Persona | null>(null)
const currentFolderId = ref<string | null>(null)
// 默认人格
const defaultPersona: SelectableItem = {
id: 'default',
persona_id: 'default',
name: tm('personaSelector.defaultPersona'),
system_prompt: 'You are a helpful and friendly assistant.'
}
// 递归查找文件夹名称
function findFolderName(nodes: FolderTreeNode[], folderId: string): string | null {
for (const node of nodes) {
if (node.folder_id === folderId) {
return node.name
}
if (node.children && node.children.length > 0) {
const found = findFolderName(node.children, folderId)
if (found) return found
}
}
return null
}
// 当前文件夹名称
const currentFolderName = computed(() => {
if (!currentFolderId.value) {
return null // 根目录PersonaForm 会使用 tm('form.rootFolder')
}
return findFolderName(folderTree.value, currentFolderId.value)
})
// 标签配置
const labels = computed(() => ({
dialogTitle: tm('personaSelector.dialogTitle'),
notSelected: tm('personaSelector.notSelected'),
buttonText: props.buttonText || tm('personaSelector.buttonText'),
noItems: tm('personaSelector.noPersonas'),
defaultItem: tm('personaSelector.defaultPersona'),
noDescription: tm('personaSelector.noDescription'),
createButton: tm('personaSelector.createPersona'),
editButton: tm('personaSelector.editPersona') || 'Edit',
confirmButton: t('core.common.confirm'),
cancelButton: t('core.common.cancel'),
rootFolder: tm('personaSelector.rootFolder') || '全部人格',
emptyFolder: tm('personaSelector.emptyFolder') || '此文件夹为空'
}))
// 格式化显示值
function formatDisplayValue(value: string): string {
if (value === 'default') {
return tm('personaSelector.defaultPersona')
}
return value
}
// 处理值更新
function handleUpdate(value: string) {
emit('update:modelValue', value)
}
// 加载文件夹树
async function loadFolderTree() {
treeLoading.value = true
try {
const response = await axios.get('/api/persona/folder/tree')
if (response.data.status === 'ok') {
folderTree.value = response.data.data || []
}
} catch (error) {
console.error('加载文件夹树失败:', error)
folderTree.value = []
} finally {
treeLoading.value = false
}
}
// 加载指定文件夹的人格
async function loadPersonasInFolder(folderId: string | null) {
itemsLoading.value = true
try {
// 使用 /api/persona/list 端点,通过 folder_id 参数筛选
const params = new URLSearchParams()
if (folderId !== null) {
params.set('folder_id', folderId)
} else {
// 根目录folder_id 为空字符串表示获取根目录下的人格
params.set('folder_id', '')
}
const response = await axios.get(`/api/persona/list?${params.toString()}`)
if (response.data.status === 'ok') {
currentPersonas.value = response.data.data || []
}
} catch (error) {
console.error('加载人格列表失败:', error)
currentPersonas.value = []
} finally {
itemsLoading.value = false
}
}
// 处理文件夹导航
async function handleNavigate(folderId: string | null) {
currentFolderId.value = folderId
await loadPersonasInFolder(folderId)
}
// 打开创建人格对话框
function openCreatePersona() {
editingPersona.value = null
showPersonaDialog.value = true
}
// 打开编辑人格对话框
function openEditPersona(persona: Persona) {
editingPersona.value = persona
showPersonaDialog.value = true
}
// 人格保存成功(创建或编辑)
async function handlePersonaSaved(message: string) {
console.log('人格保存成功:', message)
showPersonaDialog.value = false
editingPersona.value = null
// 刷新当前文件夹的人格列表
await loadPersonasInFolder(currentFolderId.value)
}
// 错误处理
function handleError(error: string) {
console.error('创建人格失败:', error)
}
// 初始化加载文件夹树
onMounted(() => {
loadFolderTree()
})
</script>
<style scoped>
/* 样式继承自 BaseFolderItemSelector */
</style>