Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions dashboard/src/components/shared/PersonaForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -378,16 +378,26 @@ export default {
}

this.saving = true;
try {
const url = this.editingPersona ? '/api/persona/update' : '/api/persona/create';
const response = await axios.post(url, this.personaForm);

if (response.data.status === 'ok') {
this.$emit('saved', response.data.message || this.tm('messages.saveSuccess'));
this.closeDialog();
} else {
this.$emit('error', response.data.message || this.tm('messages.saveError'));
try {
const url = this.editingPersona ? '/api/persona/update' : '/api/persona/create';

// 白名单过滤字段
const allowedFields = ['persona_id', 'system_prompt', 'begin_dialogs', 'tools'];
const filteredData = {};
allowedFields.forEach(field => {
if (this.personaForm.hasOwnProperty(field)) {
filteredData[field] = this.personaForm[field];
}
});

const response = await axios.post(url, filteredData);

if (response.data.status === 'ok') {
this.$emit('saved', response.data.message || this.tm('messages.saveSuccess'));
this.closeDialog();
} else {
this.$emit('error', response.data.message || this.tm('messages.saveError'));
}
} catch (error) {
this.$emit('error', error.response?.data?.message || this.tm('messages.saveError'));
}
Expand Down Expand Up @@ -533,4 +543,4 @@ export default {
.v-virtual-scroll {
padding-bottom: 16px;
}
</style>
</style>
2 changes: 2 additions & 0 deletions dashboard/src/i18n/locales/en-US/features/persona.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"createFirst": "Create First Persona",
"edit": "Edit",
"delete": "Delete",
"export": "Export JSON",
"import": "Import",
"cancel": "Cancel",
"save": "Save",
"addDialogPair": "Add Dialog Pair"
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/i18n/locales/zh-CN/features/persona.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"createFirst": "创建第一个人格",
"edit": "编辑",
"delete": "删除",
"export": "导出 JSON",
"import": "导入",
"cancel": "取消",
"save": "保存",
"addDialogPair": "添加对话对"
Expand Down
112 changes: 111 additions & 1 deletion dashboard/src/views/PersonaPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
{{ tm('page.description') }}
</p>
</div>
<div>
<div class="d-flex ga-2">
<v-btn color="secondary" variant="tonal" prepend-icon="mdi-import" @click="triggerImport"
rounded="xl" size="x-large">
{{ tm('buttons.import') || '导入' }}
</v-btn>
<v-btn color="primary" variant="tonal" prepend-icon="mdi-plus" @click="openCreateDialog"
rounded="xl" size="x-large">
{{ tm('buttons.create') }}
</v-btn>
<input type="file" ref="importInput" style="display: none" accept=".json" @change="handleImport">
</div>
</v-row>

Expand All @@ -40,6 +45,12 @@
{{ tm('buttons.edit') }}
</v-list-item-title>
</v-list-item>
<v-list-item @click.stop="downloadPersonaJson(persona)">
<v-list-item-title>
<v-icon class="mr-2" size="small">mdi-content-copy</v-icon>
{{ tm('buttons.export') || '导出 JSON' }}
</v-list-item-title>
</v-list-item>
<v-list-item @click="deletePersona(persona)" class="text-error">
<v-list-item-title>
<v-icon class="mr-2" size="small">mdi-delete</v-icon>
Expand Down Expand Up @@ -270,6 +281,105 @@ export default {
this.message = message;
this.messageType = 'error';
this.showMessage = true;
},

async downloadPersonaJson(persona) {
try {
// 创建清洁副本,排除系统字段
const cleanPersona = {
persona_id: persona.persona_id,
system_prompt: persona.system_prompt,
begin_dialogs: persona.begin_dialogs,
tools: persona.tools
};

// 格式化 JSON
const jsonString = JSON.stringify(cleanPersona, null, 4);

// 创建 Blob 对象
const blob = new Blob([jsonString], { type: 'application/json' });

// 创建下载链接
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${persona.persona_id}.json`;

// 触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

// 清理 URL 对象
URL.revokeObjectURL(url);

// 显示成功消息
this.showSuccess(this.tm('messages.downloadSuccess') || 'JSON 文件已下载');
} catch (error) {
// 显示错误消息
this.showError(error.message || this.tm('messages.downloadError') || '下载 JSON 文件失败');
}
},

triggerImport() {
this.$refs.importInput.click();
},

async handleImport(event) {
const file = event.target.files[0];
if (!file) return;

try {
const text = await file.text();
const parsedData = JSON.parse(text);

console.log("Parsed Data:", parsedData);

// 验证必需字段
if (!parsedData.persona_id || !parsedData.system_prompt) {
this.showError('人格 JSON 缺少必需字段喵!');
event.target.value = '';
return;
}

// 检查重复 ID
const id = parsedData.persona_id;
const exists = this.personas.some(persona => persona.persona_id === id);
if (exists) {
this.showError('人格 ID [' + id + '] 已存在喵!');
event.target.value = '';
return;
}

// 白名单过滤字段
const allowedFields = ['persona_id', 'system_prompt', 'begin_dialogs', 'tools'];
const filteredData = {};
allowedFields.forEach(field => {
if (parsedData.hasOwnProperty(field)) {
filteredData[field] = parsedData[field];
}
});

// 调用 API 保存(使用正确的端点)
const response = await axios.post('/api/persona/create', filteredData);

if (response.data.status === 'ok') {
this.showSuccess(response.data.message || '导入成功喵!');
await this.loadPersonas();
} else {
this.showError(response.data.message || '导入失败喵!');
}
} catch (error) {
console.error("Import Error:", error);
if (error instanceof SyntaxError) {
this.showError('JSON 格式错误喵!' + error.message);
} else {
this.showError('导入失败喵!' + (error.response?.data?.message || error.message));
}
}

// 清理文件输入
event.target.value = '';
}
}
}
Expand Down