Skip to content

feat: add clone persona functionality#6429

Merged
LIghtJUNction merged 1 commit intoAstrBotDevs:devfrom
xkeyC:feat/persona_clone
Mar 16, 2026
Merged

feat: add clone persona functionality#6429
LIghtJUNction merged 1 commit intoAstrBotDevs:devfrom
xkeyC:feat/persona_clone

Conversation

@xkeyC
Copy link

@xkeyC xkeyC commented Mar 16, 2026

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 / 后端:

  • Added clone_persona() method to PersonaManager class (astrbot/core/persona_mgr.py)
  • Added /api/persona/clone API 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 / 运行截图或测试结果

image image image

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.txtpyproject.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.txt and pyproject.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:

  • Introduce backend support to clone an existing persona into a new one with a different ID.
  • Expose a /api/persona/clone endpoint for creating persona copies via the dashboard.
  • Add UI controls and dialog in the persona manager to trigger cloning from a persona card and specify a new ID.

Enhancements:

  • Refresh the current folder view after cloning so the new persona appears immediately.
  • Extend i18n strings to cover the new clone persona actions and dialog across supported languages.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 16, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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

  • Backend Functionality: Added a new clone_persona method to the PersonaManager class, enabling the duplication of existing personas with a new ID.
  • API Endpoint: Introduced a new POST API endpoint /api/persona/clone to handle persona cloning requests from the frontend.
  • Frontend Integration: Implemented a clonePersona action in the persona store, added a 'Clone' menu item to the persona card, and created a dedicated clone dialog in the persona manager view.
  • Internationalization: Updated localization files for English (en-US), Russian (ru-RU), and Simplified Chinese (zh-CN) to include new strings for the cloning feature.
Changelog
  • astrbot/core/persona_mgr.py
    • Added an asynchronous clone_persona method to duplicate a persona's data.
  • astrbot/dashboard/routes/persona.py
    • Registered a new POST route /persona/clone for the cloning functionality.
    • Implemented the clone_persona handler to process cloning requests and return the new persona data.
  • dashboard/src/i18n/locales/en-US/features/persona.json
    • Added 'clone' button text.
    • Included new translation keys for the persona cloning dialog, including title, description, new persona ID input, hints, success/error messages, and validation rules.
  • dashboard/src/i18n/locales/ru-RU/features/persona.json
    • Added 'Клонировать' button text.
    • Included new translation keys for the persona cloning dialog in Russian.
  • dashboard/src/i18n/locales/zh-CN/features/persona.json
    • Added '克隆' button text.
    • Included new translation keys for the persona cloning dialog in Simplified Chinese.
  • dashboard/src/stores/personaStore.ts
    • Implemented the clonePersona action to send cloning requests to the backend and refresh the persona list.
  • dashboard/src/views/persona/PersonaCard.vue
    • Added a 'Clone' menu item with an associated icon to the persona card's action menu.
    • Updated the component's emitted events to include 'clone'.
  • dashboard/src/views/persona/PersonaManager.vue
    • Integrated a new cloning dialog component.
    • Added state variables (showCloneDialog, cloningPersona, cloneNewPersonaId, cloneLoading) to manage the cloning process.
    • Implemented openClonePersonaDialog to prepare the dialog with the source persona's data.
    • Implemented submitClonePersona to handle the submission of the cloning form, call the store action, and manage UI feedback.
    • Mapped the clonePersona action from the persona store.
Activity
  • No specific activity (comments, reviews, progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the feature:persona The bug / feature is about astrbot AI persona system (system prompt) label Mar 16, 2026
@xkeyC xkeyC changed the title Title: feat: add clone persona functionality feat: add clone persona functionality Mar 16, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +284 to +308
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__
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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')]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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:

  1. Add a data property, e.g., allPersonaIds: [].
  2. Create a method to fetch all persona IDs (e.g., GET /api/persona/list) and populate allPersonaIds.
  3. Call this method in openClonePersonaDialog.
  4. Update the :rules prop as suggested below.
                        :rules="[v => !!v || tm('cloneDialog.validation.required'), v => !allPersonaIds.includes(v) || tm('cloneDialog.validation.exists')]"

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +515 to +524
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'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@LIghtJUNction
Copy link
Member

@xkeyC 很好的PR,合并后在这里跟踪进度:#6325

@LIghtJUNction LIghtJUNction merged commit 7733ccc into AstrBotDevs:dev Mar 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature:persona The bug / feature is about astrbot AI persona system (system prompt) size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants