Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2e90c25
[Feature] 人格设定支持导出/导入。
Sjshi763 Jan 17, 2026
59e5a6f
导入(dev
Sjshi763 Jan 17, 2026
2285695
稍微进化
Sjshi763 Jan 17, 2026
d1c0122
改为直接导出文件
Sjshi763 Jan 17, 2026
d2ed543
删除未使用的i18n键值
Sjshi763 Jan 17, 2026
13461f9
fix:仅白名单字段可以作为文件名
Sjshi763 Jan 17, 2026
ec3d25c
PersonaForm.vue - savePersona 方法添加了白名单字段过滤
Sjshi763 Jan 17, 2026
98e7a9c
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Jan 18, 2026
125d10a
[Feature] 人格设定支持导出/导入。
Sjshi763 Jan 28, 2026
fd95665
update
Sjshi763 Jan 28, 2026
3ec909c
Merge pull request #1 from Sjshi763/fix-persona-conflict-v2
Sjshi763 Jan 28, 2026
1f75682
[Feature] 人格设定支持导出/导入。
Sjshi763 Jan 28, 2026
92cf0bc
再次完成
Sjshi763 Jan 28, 2026
60dfaf0
修复bug 405
Sjshi763 Jan 28, 2026
0e32d62
移除导入和导出按钮的本地化文本(上一次修改的
Sjshi763 Jan 28, 2026
03bdb51
移动到 persona 的 i18n JSON
Sjshi763 Jan 28, 2026
ebcc15b
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Jan 30, 2026
e79df4a
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Feb 2, 2026
d1e7105
[Feature] 人格设定支持导出/导入。
Sjshi763 Feb 2, 2026
fbe0d05
写错地方了,更正i18n
Sjshi763 Feb 2, 2026
3d3926b
保证相同的i18n
Sjshi763 Feb 2, 2026
986549b
在导出时说明不包含tools Skills
Sjshi763 Feb 2, 2026
d3e7104
修改导出格式
Sjshi763 Feb 2, 2026
351ce77
新格式导入
Sjshi763 Feb 2, 2026
c7eb1b8
重复检查
Sjshi763 Feb 2, 2026
edb0166
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Feb 3, 2026
75e2f99
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Feb 4, 2026
fbc7b2b
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Feb 5, 2026
ddddd8a
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Feb 7, 2026
4667729
Merge branch 'AstrBotDevs:master' into Sjshi763/issue4409
Sjshi763 Feb 10, 2026
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
28 changes: 19 additions & 9 deletions dashboard/src/components/shared/PersonaForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -578,16 +578,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
14 changes: 10 additions & 4 deletions dashboard/src/i18n/locales/en-US/features/persona.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"cancel": "Cancel",
"save": "Save",
"move": "Move",
"addDialogPair": "Add Dialog Pair"
"addDialogPair": "Add Dialog Pair",
"import": "Import"
},
"labels": {
"presetDialogs": "Preset Dialogs ({count} pairs)",
Expand Down Expand Up @@ -80,18 +81,23 @@
"saveError": "Save failed",
"deleteConfirm": "Are you sure you want to delete persona \"{id}\"? This action cannot be undone.",
"deleteSuccess": "Deleted successfully",
"deleteError": "Delete failed"
"deleteError": "Delete failed",
"importExists": "Persona with ID {id} already exists."
},
"persona": {
"personasTitle": "Personas",
"toolsCount": "tools",
"skillsCount": "skills",
"contextMenu": {
"moveTo": "Move to..."
"moveTo": "Move to...",
"export": "Export"
},
"messages": {
"moveSuccess": "Persona moved successfully",
"moveError": "Failed to move persona"
"moveError": "Failed to move persona",
"exportSuccess": "Exported successfully(excluding skills and tools)",
"importSuccess": "Imported successfully",
"importError": "Failed to import persona"
}
},
"folder": {
Expand Down
14 changes: 10 additions & 4 deletions dashboard/src/i18n/locales/zh-CN/features/persona.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"cancel": "取消",
"save": "保存",
"move": "移动",
"addDialogPair": "添加对话对"
"addDialogPair": "添加对话对",
"import": "导入"
},
"labels": {
"presetDialogs": "预设对话 ({count} 对)",
Expand Down Expand Up @@ -80,18 +81,23 @@
"saveError": "保存失败",
"deleteConfirm": "确定要删除人格 \"{id}\" 吗?此操作不可撤销。",
"deleteSuccess": "删除成功",
"deleteError": "删除失败"
"deleteError": "删除失败",
"importExists": "人格 ID {id} 已存在"
},
"persona": {
"personasTitle": "人格",
"toolsCount": "个工具",
"skillsCount": "个 Skills",
"contextMenu": {
"moveTo": "移动到..."
"moveTo": "移动到...",
"export": "导出"
},
"messages": {
"moveSuccess": "人格移动成功",
"moveError": "移动人格失败"
"moveError": "移动人格失败",
"exportSuccess": "导出成功(不包含 Skills 和工具)",
"importSuccess": "导入成功",
"importError": "导入失败"
}
},
"folder": {
Expand Down
22 changes: 22 additions & 0 deletions dashboard/src/stores/personaStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,5 +330,27 @@ export const usePersonaStore = defineStore({
};
return findNode(this.folderTree);
},

/**
* 导入人格数据
*/
async importPersona(data: Partial<Persona>): Promise<Persona> {
const response = await axios.post('/api/persona/create', {
persona_id: data.persona_id,
system_prompt: data.system_prompt,
begin_dialogs: data.begin_dialogs || [],
tools: data.tools,
skills: data.skills,
});

if (response.data.status !== 'ok') {
throw new Error(response.data.message || '导入人格失败');
}

// 刷新当前文件夹内容
await this.refreshCurrentFolder();

return response.data.data.persona;
},
}
});
10 changes: 8 additions & 2 deletions dashboard/src/views/persona/PersonaCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
</template>
<v-list-item-title>{{ tm('buttons.edit') }}</v-list-item-title>
</v-list-item>
<v-list-item @click.stop="$emit('move')">
<v-list-item @click.stop="$emit('move')">
<template v-slot:prepend>
<v-icon size="small">mdi-folder-move</v-icon>
</template>
<v-list-item-title>{{ tm('persona.contextMenu.moveTo') }}</v-list-item-title>
</v-list-item>
<v-list-item @click.stop="$emit('export')">
<template v-slot:prepend>
<v-icon size="small">mdi-download</v-icon>
</template>
<v-list-item-title>{{ tm('persona.contextMenu.export') }}</v-list-item-title>
</v-list-item>
<v-divider class="my-1" />
<v-list-item @click.stop="$emit('delete')" class="text-error">
<template v-slot:prepend>
Expand Down Expand Up @@ -96,7 +102,7 @@ export default defineComponent({
required: true
}
},
emits: ['view', 'edit', 'move', 'delete'],
emits: ['view', 'edit', 'move', 'export', 'delete'],
setup() {
const { tm } = useModuleI18n('features/persona');
return { tm };
Expand Down
161 changes: 159 additions & 2 deletions dashboard/src/views/persona/PersonaManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
rounded="lg">
{{ tm('folder.createButton') }}
</v-btn>
<v-btn variant="outlined" prepend-icon="mdi-upload" @click="handleImportPersona"
rounded="lg">
{{ tm('buttons.import') }}
</v-btn>
</div>
</div>

Expand Down Expand Up @@ -80,7 +84,7 @@
xl="3">
<PersonaCard :persona="persona" @view="viewPersona(persona)"
@edit="editPersona(persona)" @move="openMovePersonaDialog(persona)"
@delete="confirmDeletePersona(persona)" />
@export="handleExportPersona(persona)" @delete="confirmDeletePersona(persona)" />
</v-col>
</v-row>
</div>
Expand Down Expand Up @@ -384,7 +388,7 @@ export default defineComponent({
await this.initialize();
},
methods: {
...mapActions(usePersonaStore, ['loadFolderTree', 'navigateToFolder', 'updateFolder', 'deleteFolder', 'deletePersona', 'refreshCurrentFolder', 'movePersonaToFolder']),
...mapActions(usePersonaStore, ['loadFolderTree', 'navigateToFolder', 'updateFolder', 'deleteFolder', 'deletePersona', 'refreshCurrentFolder', 'movePersonaToFolder', 'importPersona']),

async initialize() {
await Promise.all([
Expand Down Expand Up @@ -449,6 +453,159 @@ export default defineComponent({
}
},

// 导出人格数据
async handleExportPersona(persona: Persona) {
try {
console.log(this.tm('persona.messages.exportStart'), persona.persona_id);

// 转换为新格式
const exportData = {
version: "1.0",
persona: [{
name: persona.persona_id,
prompt: persona.system_prompt,
begin_dialogs: [] as Array<{user: string, assistant: string}>
}]
};

// 处理对话对
if (persona.begin_dialogs && persona.begin_dialogs.length > 0) {
const dialogs = persona.begin_dialogs;
// 按顺序配对消息
for (let i = 0; i < dialogs.length; i += 2) {
// 获取用户消息
let userMessage = '';
if (i < dialogs.length) {
if (typeof dialogs[i] === 'string') {
userMessage = dialogs[i];
} else if (typeof dialogs[i] === 'object' && dialogs[i] !== null) {
userMessage = (dialogs[i] as any).user || '';
}
}

// 获取助手消息
let assistantMessage = '';
if (i + 1 < dialogs.length) {
if (typeof dialogs[i + 1] === 'string') {
assistantMessage = dialogs[i + 1];
} else if (typeof dialogs[i + 1] === 'object' && dialogs[i + 1] !== null) {
assistantMessage = (dialogs[i + 1] as any).assistant || '';
}
}
exportData.persona[0].begin_dialogs.push({
user: userMessage,
assistant: assistantMessage
});
}
}

// 清理文件名中的特殊字符
const safeFileName = persona.persona_id.replace(/[\/\\:*?"<>|]/g, '_');

// 创建 JSON 文件并下载
const jsonStr = JSON.stringify(exportData, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = url;
link.download = `${safeFileName}.json`;
document.body.appendChild(link);
link.click();

// 清理
document.body.removeChild(link);
URL.revokeObjectURL(url);

this.showSuccess(this.tm('persona.messages.exportSuccess'));
} catch (error: any) {
console.error(this.tm('persona.messages.exportFailed'), error);

// 构建详细的错误消息
let errorMessage = this.tm('persona.messages.exportError');
if (error.message) {
errorMessage += `: ${error.message}`;
}

this.showError(errorMessage);
}
},

// 导入人格数据
async handleImportPersona() {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json';

input.onchange = async (event: Event) => {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (!file) return;

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

let importData: any = {};

if (data.version && data.persona && Array.isArray(data.persona)) {
const firstPersona = data.persona[0];
importData = {
persona_id: firstPersona.name,
system_prompt: firstPersona.prompt,
begin_dialogs: []
};

// 转换对话对
if (firstPersona.begin_dialogs && Array.isArray(firstPersona.begin_dialogs)) {
for (const dialog of firstPersona.begin_dialogs) {
if (dialog.user) {
importData.begin_dialogs.push(dialog.user);
}
if (dialog.assistant) {
importData.begin_dialogs.push(dialog.assistant);
}
}
}
}

// 验证必需字段
if (!importData.persona_id || !importData.system_prompt) {
throw new Error(this.tm('persona.messages.importMissingFields'));
}

// 检查ID是否已存在
const existingPersonas = this.currentPersonas.map(p => p.persona_id);
if (existingPersonas.includes(importData.persona_id)) {
throw new Error(this.tm('messages.importExists', { id: importData.persona_id }));
}

// 执行导入
await this.importPersona(importData);
this.showSuccess(this.tm('persona.messages.importSuccess'));
} catch (error: any) {
console.error(this.tm('persona.messages.importFailed'), error);

// 构建详细的错误消息
let errorMessage = this.tm('persona.messages.importError');
if (error.response) {
// 后端返回的错误
errorMessage += `: ${error.response.data?.message || error.response.statusText}`;
} else if (error.request) {
// 请求发送但没有收到响应
errorMessage += `: ${this.tm('persona.messages.importNetworkError')}`;
} else if (error.message) {
// 其他错误
errorMessage += `: ${error.message}`;
}

this.showError(errorMessage);
}
};

input.click();
},

// 文件夹操作
openRenameFolderDialog(folder: Folder) {
this.renameFolderData = { folder, name: folder.name };
Expand Down