feat: add clone persona functionality#6429
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a highly requested feature that allows users to clone existing personas. This enhancement significantly improves user experience by providing a quick and efficient way to create variations of personas, reducing the effort required for setup and configuration. The changes span both the backend API and the frontend user interface, ensuring a seamless and localized experience. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request adds a useful feature for cloning personas. The backend implementation in Python and the frontend implementation in Vue/TypeScript are well-structured. I've identified a couple of areas for improvement. In the backend, there's an opportunity to refactor the persona serialization logic to make it more maintainable and less repetitive. On the frontend, adding client-side validation to check for duplicate persona IDs in the clone dialog would improve the user experience by providing instant feedback. Overall, great work on implementing this feature.
| return ( | ||
| Response() | ||
| .ok( | ||
| { | ||
| "message": "人格克隆成功", | ||
| "persona": { | ||
| "persona_id": persona.persona_id, | ||
| "system_prompt": persona.system_prompt, | ||
| "begin_dialogs": persona.begin_dialogs or [], | ||
| "tools": persona.tools or [], | ||
| "skills": persona.skills or [], | ||
| "custom_error_message": persona.custom_error_message, | ||
| "folder_id": persona.folder_id, | ||
| "sort_order": persona.sort_order, | ||
| "created_at": persona.created_at.isoformat() | ||
| if persona.created_at | ||
| else None, | ||
| "updated_at": persona.updated_at.isoformat() | ||
| if persona.updated_at | ||
| else None, | ||
| }, | ||
| }, | ||
| ) | ||
| .__dict__ | ||
| ) |
There was a problem hiding this comment.
The manual serialization of the Persona object is verbose and increases maintenance effort, as any changes to the Persona model require updates in multiple places. You can make this more concise and maintainable by using model_dump() from Pydantic/SQLModel, which is already a dependency. This will automatically handle most fields, including datetime to string conversion.
persona_data = persona.model_dump(mode='json', exclude={'id'})
persona_data.update({
"begin_dialogs": persona.begin_dialogs or [],
"tools": persona.tools or [],
"skills": persona.skills or [],
})
return (
Response()
.ok(
{
"message": "人格克隆成功",
"persona": persona_data,
},
)
.__dict__
)| <v-text-field v-model="cloneNewPersonaId" :label="tm('cloneDialog.newPersonaId')" | ||
| :hint="tm('cloneDialog.newPersonaIdHint')" persistent-hint variant="outlined" | ||
| density="comfortable" autofocus | ||
| :rules="[v => !!v || tm('cloneDialog.validation.required')]" |
There was a problem hiding this comment.
The validation for the new persona ID only checks if it's required. For a better user experience, you should also validate if the ID already exists on the client-side to provide immediate feedback and avoid an unnecessary server request. You can achieve this by fetching all existing persona IDs when the dialog opens and adding a validation rule, similar to the implementation in PersonaForm.vue.
To implement this, you would need to:
- Add a data property, e.g.,
allPersonaIds: []. - Create a method to fetch all persona IDs (e.g.,
GET /api/persona/list) and populateallPersonaIds. - Call this method in
openClonePersonaDialog. - Update the
:rulesprop as suggested below.
:rules="[v => !!v || tm('cloneDialog.validation.required'), v => !allPersonaIds.includes(v) || tm('cloneDialog.validation.exists')]"
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
PersonaManager.clone_persona, reusing the source persona’ssort_ordermay result in multiple personas sharing the same sort order inside a folder; consider defaulting to a new sort order (e.g., placing clones at the end) or delegating sort assignment to the DB helper for consistency with creation logic. - The clone dialog currently interpolates
persona_idinto the description (cloneDialog.description); if personas also have a human-readable name/title field, using that instead would make the UI clearer and less technical for end users.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `PersonaManager.clone_persona`, reusing the source persona’s `sort_order` may result in multiple personas sharing the same sort order inside a folder; consider defaulting to a new sort order (e.g., placing clones at the end) or delegating sort assignment to the DB helper for consistency with creation logic.
- The clone dialog currently interpolates `persona_id` into the description (`cloneDialog.description`); if personas also have a human-readable name/title field, using that instead would make the UI clearer and less technical for end users.
## Individual Comments
### Comment 1
<location path="dashboard/src/views/persona/PersonaManager.vue" line_range="515-524" />
<code_context>
+ this.showCloneDialog = true;
+ },
+
+ async submitClonePersona() {
+ if (!this.cloneNewPersonaId || !this.cloningPersona) return;
+
+ this.cloneLoading = true;
+ try {
+ await this.clonePersona(this.cloningPersona.persona_id, this.cloneNewPersonaId);
+ this.showSuccess(this.tm('cloneDialog.success'));
+ this.showCloneDialog = false;
+ } catch (error: any) {
+ this.showError(error.message || this.tm('cloneDialog.error'));
+ } finally {
+ this.cloneLoading = false;
+ }
+ },
</code_context>
<issue_to_address>
**suggestion:** Trim the new persona ID on the client to match backend behavior and avoid surprise whitespace issues.
The backend strips `new_persona_id`, but the UI uses `this.cloneNewPersonaId` as‑is for validation and enabling the button. With trailing spaces, the field appears valid while the stored ID is different. Trim on the client (e.g. `const trimmed = this.cloneNewPersonaId.trim(); if (!trimmed) return;` and use `trimmed` for validation and the request) so the UI state matches the persisted value.
Suggested implementation:
```
async submitClonePersona() {
const trimmedPersonaId = this.cloneNewPersonaId ? this.cloneNewPersonaId.trim() : '';
if (!trimmedPersonaId || !this.cloningPersona) return;
this.cloneLoading = true;
try {
await this.clonePersona(this.cloningPersona.persona_id, trimmedPersonaId);
this.showSuccess(this.tm('cloneDialog.success'));
this.showCloneDialog = false;
} catch (error: any) {
this.showError(error.message || this.tm('cloneDialog.error'));
} finally {
this.cloneLoading = false;
}
},
```
To fully align the UI behavior with the trimmed value, you should also:
1. Update any validation logic or computed properties that enable/disable the "Clone" button to use a trimmed version of `cloneNewPersonaId` (e.g. `const trimmed = this.cloneNewPersonaId?.trim(); return !!trimmed && ...`).
2. Ensure any client-side checks for uniqueness or format of the new persona ID are performed against the trimmed value, not the raw `cloneNewPersonaId`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| async submitClonePersona() { | ||
| if (!this.cloneNewPersonaId || !this.cloningPersona) return; | ||
|
|
||
| this.cloneLoading = true; | ||
| try { | ||
| await this.clonePersona(this.cloningPersona.persona_id, this.cloneNewPersonaId); | ||
| this.showSuccess(this.tm('cloneDialog.success')); | ||
| this.showCloneDialog = false; | ||
| } catch (error: any) { | ||
| this.showError(error.message || this.tm('cloneDialog.error')); |
There was a problem hiding this comment.
suggestion: Trim the new persona ID on the client to match backend behavior and avoid surprise whitespace issues.
The backend strips new_persona_id, but the UI uses this.cloneNewPersonaId as‑is for validation and enabling the button. With trailing spaces, the field appears valid while the stored ID is different. Trim on the client (e.g. const trimmed = this.cloneNewPersonaId.trim(); if (!trimmed) return; and use trimmed for validation and the request) so the UI state matches the persisted value.
Suggested implementation:
async submitClonePersona() {
const trimmedPersonaId = this.cloneNewPersonaId ? this.cloneNewPersonaId.trim() : '';
if (!trimmedPersonaId || !this.cloningPersona) return;
this.cloneLoading = true;
try {
await this.clonePersona(this.cloningPersona.persona_id, trimmedPersonaId);
this.showSuccess(this.tm('cloneDialog.success'));
this.showCloneDialog = false;
} catch (error: any) {
this.showError(error.message || this.tm('cloneDialog.error'));
} finally {
this.cloneLoading = false;
}
},
To fully align the UI behavior with the trimmed value, you should also:
- Update any validation logic or computed properties that enable/disable the "Clone" button to use a trimmed version of
cloneNewPersonaId(e.g.const trimmed = this.cloneNewPersonaId?.trim(); return !!trimmed && ...). - Ensure any client-side checks for uniqueness or format of the new persona ID are performed against the trimmed value, not the raw
cloneNewPersonaId.
Motivation / 动机
Adds the ability to clone an existing persona, allowing users to quickly create a copy based on an existing persona and make minor adjustments. This is useful for users who want to create multiple persona variations.
添加克隆现有人格的功能,允许用户基于现有人格快速创建副本,并进行小幅度调整,这对于想创建多个变体人格的人来说非常有用。
This feature was raised as an issue in 2025 but received no response. I am creating a PR here because I have this need.
该功能在 25 年有人提出 issue ,但未得到回复,我因为有这个需求,在此创建 PR 。
Close:#2883
Modifications / 改动点
Backend / 后端:
clone_persona()method toPersonaManagerclass (astrbot/core/persona_mgr.py)/api/persona/cloneAPI endpoint (astrbot/dashboard/routes/persona.py)Frontend / 前端:
Added
clonePersona()action (dashboard/src/stores/personaStore.ts)Added clone menu item to persona card (
dashboard/src/views/persona/PersonaCard.vue)Added clone dialog (
dashboard/src/views/persona/PersonaManager.vue)Updated i18n for all 3 languages (en-US, zh-CN, ru-RU)
This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
👀 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。
/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml.😮 我的更改没有引入恶意代码。
/ My changes do not introduce malicious code.
/ I have read and understood all the above and confirm this PR follows the rules.
🚀 我确保本次开发基于 dev 分支,并将代码合并至开发分支(除非极其紧急,才允许合并到主分支)。
/ I confirm that this development is based on the dev branch and will be merged into the development branch, unless it is extremely urgent to merge into the main branch.
/ I did not read the above carefully before submitting.
Summary by Sourcery
Add support for cloning existing personas from both the backend API and the dashboard UI.
New Features:
Enhancements: