diff --git a/dashboard/src/i18n/locales/en-US/features/subagent.json b/dashboard/src/i18n/locales/en-US/features/subagent.json
index 7bfb0b08da..5f047af349 100644
--- a/dashboard/src/i18n/locales/en-US/features/subagent.json
+++ b/dashboard/src/i18n/locales/en-US/features/subagent.json
@@ -7,6 +7,8 @@
"actions": {
"refresh": "Refresh",
"save": "Save",
+ "import": "Import JSON",
+ "export": "Export JSON",
"add": "Add SubAgent",
"delete": "Delete",
"close": "Close"
@@ -48,6 +50,11 @@
"messages": {
"loadConfigFailed": "Failed to load config",
"loadPersonaFailed": "Failed to load persona list",
+ "importSuccess": "Configuration imported successfully",
+ "importFailed": "Failed to import configuration",
+ "importInvalidJson": "The imported file must be a valid JSON object",
+ "exportSuccess": "Configuration exported successfully",
+ "exportFailed": "Failed to export configuration",
"nameMissing": "A SubAgent is missing a name",
"nameInvalid": "Invalid SubAgent name: only lowercase letters/numbers/underscores, starting with a letter",
"nameDuplicate": "Duplicate SubAgent name: {name}",
diff --git a/dashboard/src/i18n/locales/ru-RU/features/subagent.json b/dashboard/src/i18n/locales/ru-RU/features/subagent.json
index 368bd20467..b6b1791237 100644
--- a/dashboard/src/i18n/locales/ru-RU/features/subagent.json
+++ b/dashboard/src/i18n/locales/ru-RU/features/subagent.json
@@ -7,6 +7,8 @@
"actions": {
"refresh": "Обновить",
"save": "Сохранить",
+ "import": "Импорт JSON",
+ "export": "Экспорт JSON",
"add": "Добавить SubAgent",
"delete": "Удалить",
"close": "Закрыть"
@@ -48,6 +50,11 @@
"messages": {
"loadConfigFailed": "Не удалось загрузить конфигурацию",
"loadPersonaFailed": "Не удалось загрузить список персонажей",
+ "importSuccess": "Конфигурация успешно импортирована",
+ "importFailed": "Не удалось импортировать конфигурацию",
+ "importInvalidJson": "Импортируемый файл должен быть валидным JSON-объектом",
+ "exportSuccess": "Конфигурация успешно экспортирована",
+ "exportFailed": "Не удалось экспортировать конфигурацию",
"nameMissing": "У SubAgent отсутствует имя",
"nameInvalid": "Недопустимое имя SubAgent: только строчные латинские буквы/цифры/подчеркивания, должно начинаться с буквы",
"nameDuplicate": "Дублирующееся имя SubAgent: {name}",
@@ -62,4 +69,4 @@
"subtitle": "Добавьте первого под-агента, чтобы начать",
"action": "Создать первого агента"
}
-}
\ No newline at end of file
+}
diff --git a/dashboard/src/i18n/locales/zh-CN/features/subagent.json b/dashboard/src/i18n/locales/zh-CN/features/subagent.json
index 9c7a43d7f2..7de92d9bad 100644
--- a/dashboard/src/i18n/locales/zh-CN/features/subagent.json
+++ b/dashboard/src/i18n/locales/zh-CN/features/subagent.json
@@ -7,6 +7,8 @@
"actions": {
"refresh": "刷新",
"save": "保存",
+ "import": "导入 JSON",
+ "export": "导出 JSON",
"add": "新增 SubAgent",
"delete": "删除",
"close": "关闭"
@@ -49,6 +51,11 @@
"messages": {
"loadConfigFailed": "获取配置失败",
"loadPersonaFailed": "获取 Persona 列表失败",
+ "importSuccess": "配置导入成功",
+ "importFailed": "导入配置失败",
+ "importInvalidJson": "导入文件不是有效的 JSON 对象",
+ "exportSuccess": "配置导出成功",
+ "exportFailed": "导出配置失败",
"nameMissing": "存在未填写名称的 SubAgent",
"nameInvalid": "SubAgent 名称不合法:仅允许英文小写字母/数字/下划线,且需以字母开头",
"nameDuplicate": "SubAgent 名称重复:{name}",
diff --git a/dashboard/src/views/SubAgentPage.vue b/dashboard/src/views/SubAgentPage.vue
index 029cc5a82c..f124a80f6e 100644
--- a/dashboard/src/views/SubAgentPage.vue
+++ b/dashboard/src/views/SubAgentPage.vue
@@ -14,6 +14,22 @@
+
+ {{ tm('actions.export') }}
+
+
+ {{ tm('actions.import') }}
+
{{ tm('actions.close') }}
+
+
@@ -275,6 +299,7 @@ const { tm } = useModuleI18n('features/subagent')
const loading = ref(false)
const saving = ref(false)
+const importFileInputRef = ref(null)
const snackbar = ref({
show: false,
@@ -297,7 +322,7 @@ const mainStateDescription = computed(() =>
)
function normalizeConfig(raw: any): SubAgentConfig {
- const main_enable = !!raw?.main_enable
+ const main_enable = raw?.main_enable !== undefined ? !!raw.main_enable : !!raw?.enable
const remove_main_duplicate_tools = !!raw?.remove_main_duplicate_tools
const agentsRaw = Array.isArray(raw?.agents) ? raw.agents : []
@@ -352,6 +377,82 @@ function removeAgent(idx: number) {
cfg.value.agents.splice(idx, 1)
}
+function toPersistedConfig(source: SubAgentConfig) {
+ return {
+ main_enable: !!source.main_enable,
+ remove_main_duplicate_tools: !!source.remove_main_duplicate_tools,
+ agents: source.agents.map((a) => ({
+ name: (a.name || '').trim(),
+ persona_id: (a.persona_id || '').trim(),
+ public_description: a.public_description || '',
+ enabled: a.enabled,
+ provider_id: a.provider_id
+ }))
+ }
+}
+
+function exportConfig() {
+ let url: string | null = null
+ let link: HTMLAnchorElement | null = null
+
+ try {
+ const payload = toPersistedConfig(cfg.value)
+ const json = JSON.stringify(payload, null, 2)
+ const blob = new Blob([json], { type: 'application/json;charset=utf-8' })
+ url = URL.createObjectURL(blob)
+ link = document.createElement('a')
+ const date = new Date().toISOString().slice(0, 10)
+ link.href = url
+ link.download = `subagent-config-${date}.json`
+ document.body.appendChild(link)
+ link.click()
+ toast(tm('messages.exportSuccess'), 'success')
+ } catch (e: unknown) {
+ toast(tm('messages.exportFailed'), 'error')
+ } finally {
+ if (link?.parentNode) {
+ link.parentNode.removeChild(link)
+ }
+ if (url) {
+ URL.revokeObjectURL(url)
+ }
+ }
+}
+
+function openImportDialog() {
+ importFileInputRef.value?.click()
+}
+
+async function handleImportFile(event: Event) {
+ const target = event.target as HTMLInputElement | null
+ const file = target?.files?.[0]
+ if (!file) return
+
+ try {
+ const text = await file.text()
+ const parsed = JSON.parse(text) as unknown
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+ toast(tm('messages.importInvalidJson'), 'error')
+ return
+ }
+
+ const obj = parsed as Record
+ const hasExpectedTopLevelKey =
+ 'agents' in obj || 'main_enable' in obj || 'enable' in obj
+ if (!hasExpectedTopLevelKey) {
+ toast(tm('messages.importInvalidJson'), 'error')
+ return
+ }
+
+ cfg.value = normalizeConfig(parsed)
+ toast(tm('messages.importSuccess'), 'success')
+ } catch (e: unknown) {
+ toast(tm('messages.importFailed'), 'error')
+ } finally {
+ if (target) target.value = ''
+ }
+}
+
function validateBeforeSave(): boolean {
const nameRe = /^[a-z][a-z0-9_]{0,63}$/
const seen = new Set()
@@ -382,17 +483,7 @@ async function save() {
if (!validateBeforeSave()) return
saving.value = true
try {
- const payload = {
- main_enable: cfg.value.main_enable,
- remove_main_duplicate_tools: cfg.value.remove_main_duplicate_tools,
- agents: cfg.value.agents.map((a) => ({
- name: a.name,
- persona_id: a.persona_id,
- public_description: a.public_description,
- enabled: a.enabled,
- provider_id: a.provider_id
- }))
- }
+ const payload = toPersistedConfig(cfg.value)
const res = await axios.post('/api/subagent/config', payload)
if (res.data.status === 'ok') {