mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
Merge remote-tracking branch 'origin/master' into dev
# Conflicts: # dashboard/src/assets/mdi-subset/materialdesignicons-subset.css # dashboard/src/i18n/locales/ru-RU/features/cron.json # dashboard/src/i18n/locales/ru-RU/features/subagent.json # dashboard/src/views/ConversationPage.vue # dashboard/src/views/CronJobPage.vue # dashboard/src/views/SubAgentPage.vue # pyproject.toml
This commit is contained in:
2
.github/workflows/coverage_test.yml
vendored
2
.github/workflows/coverage_test.yml
vendored
@@ -41,6 +41,6 @@ jobs:
|
||||
|
||||
- name: Upload results to Codecov
|
||||
if: github.repository == 'AstrBotDevs/AstrBot'
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Auto-generated MDI subset – 250 icons */
|
||||
/* Auto-generated MDI subset – 256 icons */
|
||||
/* Do not edit manually. Run: pnpm run subset-icons */
|
||||
|
||||
@font-face {
|
||||
@@ -128,16 +128,24 @@
|
||||
content: "\F00ED";
|
||||
}
|
||||
|
||||
.mdi-calendar-blank-outline::before {
|
||||
content: "\F0B66";
|
||||
}
|
||||
|
||||
.mdi-calendar-clock-outline::before {
|
||||
content: "\F16E1";
|
||||
}
|
||||
|
||||
.mdi-calendar-edit::before {
|
||||
content: "\F08A7";
|
||||
}
|
||||
|
||||
.mdi-calendar-plus::before {
|
||||
content: "\F00F3";
|
||||
.mdi-calendar-multiple::before {
|
||||
content: "\F00F1";
|
||||
}
|
||||
|
||||
.mdi-calendar-range::before {
|
||||
content: "\F0679";
|
||||
.mdi-calendar-plus::before {
|
||||
content: "\F00F3";
|
||||
}
|
||||
|
||||
.mdi-chat::before {
|
||||
@@ -204,6 +212,10 @@
|
||||
content: "\F0143";
|
||||
}
|
||||
|
||||
.mdi-chip::before {
|
||||
content: "\F061A";
|
||||
}
|
||||
|
||||
.mdi-circle::before {
|
||||
content: "\F0765";
|
||||
}
|
||||
@@ -288,6 +300,10 @@
|
||||
content: "\F0674";
|
||||
}
|
||||
|
||||
.mdi-creation-outline::before {
|
||||
content: "\F1C2B";
|
||||
}
|
||||
|
||||
.mdi-cursor-default-click::before {
|
||||
content: "\F0CFD";
|
||||
}
|
||||
@@ -640,6 +656,10 @@
|
||||
content: "\F164E";
|
||||
}
|
||||
|
||||
.mdi-message-outline::before {
|
||||
content: "\F0365";
|
||||
}
|
||||
|
||||
.mdi-message-text::before {
|
||||
content: "\F0369";
|
||||
}
|
||||
@@ -664,10 +684,6 @@
|
||||
content: "\F0375";
|
||||
}
|
||||
|
||||
.mdi-moon-waning-crescent::before {
|
||||
content: "\F0F65";
|
||||
}
|
||||
|
||||
.mdi-note-text-outline::before {
|
||||
content: "\F11D7";
|
||||
}
|
||||
@@ -820,10 +836,18 @@
|
||||
content: "\F16A7";
|
||||
}
|
||||
|
||||
.mdi-robot-outline::before {
|
||||
content: "\F167A";
|
||||
}
|
||||
|
||||
.mdi-send::before {
|
||||
content: "\F048A";
|
||||
}
|
||||
|
||||
.mdi-send-clock-outline::before {
|
||||
content: "\F1164";
|
||||
}
|
||||
|
||||
.mdi-server::before {
|
||||
content: "\F048B";
|
||||
}
|
||||
@@ -840,10 +864,6 @@
|
||||
content: "\F0832";
|
||||
}
|
||||
|
||||
.mdi-share-variant::before {
|
||||
content: "\F0497";
|
||||
}
|
||||
|
||||
.mdi-shield-check::before {
|
||||
content: "\F0565";
|
||||
}
|
||||
@@ -940,6 +960,10 @@
|
||||
content: "\F0BD4";
|
||||
}
|
||||
|
||||
.mdi-timer-outline::before {
|
||||
content: "\F051B";
|
||||
}
|
||||
|
||||
.mdi-tools::before {
|
||||
content: "\F1064";
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -31,6 +31,10 @@
|
||||
"createdAt": "Created At",
|
||||
"updatedAt": "Updated At",
|
||||
"actions": "Actions"
|
||||
},
|
||||
"umoDisplay": {
|
||||
"parsed": "Parsed",
|
||||
"raw": "UMO"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
@@ -97,6 +101,8 @@
|
||||
"batchDeletePartial": "Delete completed: {deleted} successful, {failed} failed",
|
||||
"exportSuccess": "Export successful",
|
||||
"exportError": "Export failed",
|
||||
"noItemSelectedForExport": "Please select conversations to export first"
|
||||
"noItemSelectedForExport": "Please select conversations to export first",
|
||||
"copySuccess": "Copied",
|
||||
"copyError": "Copy failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"header": {
|
||||
"eyebrow": "Automation",
|
||||
"live": "Live Sync"
|
||||
},
|
||||
"page": {
|
||||
"title": "Future Task Management",
|
||||
"beta": "Experimental",
|
||||
"subtitle": "See scheduled tasks for AstrBot. AstrBot will wake up, run them, and deliver the results.",
|
||||
"proactive": {
|
||||
"supported": "Proactive delivery is available on: {platforms}",
|
||||
"supported": "Proactive delivery is available only on the configured platforms below",
|
||||
"unsupported": "No proactive messaging platforms enabled. Turn them on in Platform settings."
|
||||
}
|
||||
},
|
||||
@@ -15,8 +19,41 @@
|
||||
"cancel": "Cancel",
|
||||
"submit": "Create"
|
||||
},
|
||||
"overview": {
|
||||
"totalTasks": "Total Tasks",
|
||||
"totalTasksNote": "Registered future tasks",
|
||||
"enabledTasks": "Enabled Tasks",
|
||||
"enabledTasksNote": "Tasks ready to wake and run",
|
||||
"oneOffTasks": "One-off Tasks",
|
||||
"recurringTasksNote": "{count} recurring tasks",
|
||||
"proactivePlatforms": "Proactive Platforms",
|
||||
"proactivePlatformsNote": "Platforms able to deliver results proactively"
|
||||
},
|
||||
"section": {
|
||||
"registered": {
|
||||
"title": "Task List",
|
||||
"subtitle": "Review the registered tasks, execution timing, and current state"
|
||||
},
|
||||
"delivery": {
|
||||
"title": "Delivery Status",
|
||||
"subtitle": "After a task runs, results are proactively sent back through supported platforms",
|
||||
"support": "Proactive delivery",
|
||||
"available": "Available",
|
||||
"unavailable": "Unavailable",
|
||||
"enabledPlatforms": "Enabled platforms"
|
||||
},
|
||||
"quickCreate": {
|
||||
"title": "Quick Create",
|
||||
"runMode": "Task mode",
|
||||
"target": "Targeting"
|
||||
},
|
||||
"platforms": {
|
||||
"title": "Supported Platforms"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"title": "Registered Tasks",
|
||||
"subtitle": "Track cron setup, session target, last run history, and enabled state",
|
||||
"empty": "No tasks yet.",
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"header": {
|
||||
"eyebrow": "Orchestration"
|
||||
},
|
||||
"page": {
|
||||
"title": "SubAgent Orchestration",
|
||||
"beta": "Experimental",
|
||||
@@ -8,9 +11,20 @@
|
||||
"refresh": "Refresh",
|
||||
"save": "Save",
|
||||
"add": "Add SubAgent",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"delete": "Delete",
|
||||
"close": "Close"
|
||||
},
|
||||
"overview": {
|
||||
"totalAgents": "Total SubAgents",
|
||||
"totalAgentsNote": "Configured delegate agents",
|
||||
"enabledAgents": "Enabled Agents",
|
||||
"enabledAgentsNote": "Agents available for handoff",
|
||||
"mainOrchestration": "Main Orchestration",
|
||||
"boundPersonas": "Bound Personas",
|
||||
"boundPersonasNote": "Agents with an attached persona"
|
||||
},
|
||||
"switches": {
|
||||
"enable": "Enable SubAgent orchestration",
|
||||
"enableHint": "Enable sub-agent functionality",
|
||||
@@ -23,7 +37,9 @@
|
||||
},
|
||||
"section": {
|
||||
"title": "SubAgents",
|
||||
"globalSettings": "Global Settings"
|
||||
"subtitle": "Configure delegate agents, personas, and descriptions for the main LLM",
|
||||
"globalSettings": "Global Settings",
|
||||
"agentSetup": "Agent Setup"
|
||||
},
|
||||
"cards": {
|
||||
"statusEnabled": "Enabled",
|
||||
@@ -33,7 +49,9 @@
|
||||
"switchLabel": "Enable",
|
||||
"previewTitle": "Preview: handoff tool shown to the main LLM",
|
||||
"personaChip": "Persona: {id}",
|
||||
"personaPreview": "PERSONA PREVIEW"
|
||||
"personaPreview": "Persona Preview",
|
||||
"noDescription": "No description yet",
|
||||
"previewHint": "Review the currently selected persona to verify the handoff target."
|
||||
},
|
||||
"form": {
|
||||
"nameLabel": "Agent name (used for transfer_to_{name})",
|
||||
@@ -48,6 +66,9 @@
|
||||
"messages": {
|
||||
"loadConfigFailed": "Failed to load config",
|
||||
"loadPersonaFailed": "Failed to load persona list",
|
||||
"unsavedChangesNotice": "You have unsaved changes on this page. Save before leaving.",
|
||||
"unsavedChangesLeaveConfirm": "You have unsaved changes. Leaving will discard them. Continue?",
|
||||
"unsavedChangesReloadConfirm": "You have unsaved changes. Reloading will discard them. Continue?",
|
||||
"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}",
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
"createdAt": "Создан",
|
||||
"updatedAt": "Обновлен",
|
||||
"actions": "Действия"
|
||||
},
|
||||
"umoDisplay": {
|
||||
"parsed": "Разобрано",
|
||||
"raw": "UMO"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
@@ -97,6 +101,8 @@
|
||||
"batchDeletePartial": "Удаление завершено: успешно {deleted}, ошибок {failed}",
|
||||
"exportSuccess": "Экспорт завершен",
|
||||
"exportError": "Ошибка экспорта",
|
||||
"noItemSelectedForExport": "Сначала выберите диалоги для экспорта"
|
||||
"noItemSelectedForExport": "Сначала выберите диалоги для экспорта",
|
||||
"copySuccess": "Скопировано",
|
||||
"copyError": "Не удалось скопировать"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"header": {
|
||||
"eyebrow": "Automation",
|
||||
"live": "Живая синхронизация"
|
||||
},
|
||||
"page": {
|
||||
"title": "Запланированные задачи",
|
||||
"beta": "Экспериментальные функции",
|
||||
"subtitle": "Управление будущими задачами AstrBot. Бот автоматически проснется, выполнит задачу и отправит результат. Требуется включить «Проактивные способности» в конфигурации.",
|
||||
"proactive": {
|
||||
"supported": "Отправка результатов поддерживается на платформах: {platforms}",
|
||||
"supported": "Отправка результатов поддерживается только на указанных ниже настроенных платформах",
|
||||
"unsupported": "Нет платформ, поддерживающих проактивные сообщения. Включите их в настройках платформ."
|
||||
}
|
||||
},
|
||||
@@ -15,8 +19,41 @@
|
||||
"cancel": "Отмена",
|
||||
"submit": "Создать"
|
||||
},
|
||||
"overview": {
|
||||
"totalTasks": "Всего задач",
|
||||
"totalTasksNote": "Все зарегистрированные будущие задачи",
|
||||
"enabledTasks": "Активные задачи",
|
||||
"enabledTasksNote": "Задачи, которые будут автоматически выполнены",
|
||||
"oneOffTasks": "Разовые задачи",
|
||||
"recurringTasksNote": "Повторяющихся задач: {count}",
|
||||
"proactivePlatforms": "Проактивные платформы",
|
||||
"proactivePlatformsNote": "Платформы, способные отправлять результат сами"
|
||||
},
|
||||
"section": {
|
||||
"registered": {
|
||||
"title": "Список задач",
|
||||
"subtitle": "Просматривайте зарегистрированные задачи, время запуска и состояние"
|
||||
},
|
||||
"delivery": {
|
||||
"title": "Статус доставки",
|
||||
"subtitle": "После выполнения задачи результат будет отправлен обратно через поддерживаемые платформы",
|
||||
"support": "Проактивная доставка",
|
||||
"available": "Доступна",
|
||||
"unavailable": "Недоступна",
|
||||
"enabledPlatforms": "Включенные платформы"
|
||||
},
|
||||
"quickCreate": {
|
||||
"title": "Быстрое создание",
|
||||
"runMode": "Режим задачи",
|
||||
"target": "Целевой контекст"
|
||||
},
|
||||
"platforms": {
|
||||
"title": "Поддерживаемые платформы"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"title": "Список задач",
|
||||
"subtitle": "Отслеживайте cron, целевую сессию, историю запусков и состояние",
|
||||
"empty": "Задач пока нет.",
|
||||
"headers": {
|
||||
"name": "Имя",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"header": {
|
||||
"eyebrow": "Orchestration"
|
||||
},
|
||||
"page": {
|
||||
"title": "Оркестрация SubAgent",
|
||||
"beta": "Экспериментально",
|
||||
@@ -8,9 +11,20 @@
|
||||
"refresh": "Обновить",
|
||||
"save": "Сохранить",
|
||||
"add": "Добавить SubAgent",
|
||||
"expand": "Развернуть",
|
||||
"collapse": "Свернуть",
|
||||
"delete": "Удалить",
|
||||
"close": "Закрыть"
|
||||
},
|
||||
"overview": {
|
||||
"totalAgents": "Всего SubAgent",
|
||||
"totalAgentsNote": "Количество настроенных под-агентов",
|
||||
"enabledAgents": "Активные агенты",
|
||||
"enabledAgentsNote": "Под-агенты, доступные для handoff",
|
||||
"mainOrchestration": "Основная оркестрация",
|
||||
"boundPersonas": "Привязанные персонажи",
|
||||
"boundPersonasNote": "Под-агенты с выбранной персоной"
|
||||
},
|
||||
"switches": {
|
||||
"enable": "Включить оркестрацию SubAgent",
|
||||
"enableHint": "Включить функциональность под-агентов",
|
||||
@@ -23,7 +37,9 @@
|
||||
},
|
||||
"section": {
|
||||
"title": "Субагенты",
|
||||
"globalSettings": "Глобальные настройки"
|
||||
"subtitle": "Настройте делегируемых агентов, персонажей и описания для основного LLM",
|
||||
"globalSettings": "Глобальные настройки",
|
||||
"agentSetup": "Настройка агента"
|
||||
},
|
||||
"cards": {
|
||||
"statusEnabled": "Включено",
|
||||
@@ -33,7 +49,9 @@
|
||||
"switchLabel": "Включить",
|
||||
"previewTitle": "Предпросмотр: инструмент handoff, видимый основному LLM",
|
||||
"personaChip": "Персонаж: {id}",
|
||||
"personaPreview": "ПРЕДПРОСМОТР ПЕРСОНАЖА"
|
||||
"personaPreview": "Предпросмотр персонажа",
|
||||
"noDescription": "Описание пока не задано",
|
||||
"previewHint": "Просмотрите выбранную персону, чтобы проверить цель handoff."
|
||||
},
|
||||
"form": {
|
||||
"nameLabel": "Имя агента (используется для transfer_to_{name})",
|
||||
@@ -48,6 +66,9 @@
|
||||
"messages": {
|
||||
"loadConfigFailed": "Не удалось загрузить конфигурацию",
|
||||
"loadPersonaFailed": "Не удалось загрузить список персонажей",
|
||||
"unsavedChangesNotice": "На странице есть несохраненные изменения. Сохраните их перед уходом.",
|
||||
"unsavedChangesLeaveConfirm": "Есть несохраненные изменения. При уходе со страницы они будут потеряны. Продолжить?",
|
||||
"unsavedChangesReloadConfirm": "Есть несохраненные изменения. Обновление конфигурации их сбросит. Продолжить?",
|
||||
"nameMissing": "У SubAgent отсутствует имя",
|
||||
"nameInvalid": "Недопустимое имя SubAgent: только строчные латинские буквы/цифры/подчеркивания, должно начинаться с буквы",
|
||||
"nameDuplicate": "Дублирующееся имя SubAgent: {name}",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"providers": "模型提供商",
|
||||
"commands": "指令管理",
|
||||
"persona": "人格设定",
|
||||
"subagent": "SubAgent 编排",
|
||||
"subagent": "子代理编排",
|
||||
"toolUse": "MCP",
|
||||
"extension": "插件",
|
||||
"extensionTabs": {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"table": {
|
||||
"headers": {
|
||||
"title": "对话标题",
|
||||
"title": "标题",
|
||||
"platform": "机器人 ID",
|
||||
"type": "消息类型",
|
||||
"cid": "对话 ID",
|
||||
@@ -31,6 +31,10 @@
|
||||
"createdAt": "创建时间",
|
||||
"updatedAt": "更新时间",
|
||||
"actions": "操作"
|
||||
},
|
||||
"umoDisplay": {
|
||||
"parsed": "解析后",
|
||||
"raw": "UMO"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
@@ -97,6 +101,8 @@
|
||||
"batchDeletePartial": "删除完成:成功 {deleted} 个,失败 {failed} 个",
|
||||
"exportSuccess": "导出成功",
|
||||
"exportError": "导出失败",
|
||||
"noItemSelectedForExport": "请先选择要导出的对话"
|
||||
"noItemSelectedForExport": "请先选择要导出的对话",
|
||||
"copySuccess": "复制成功",
|
||||
"copyError": "复制失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,59 @@
|
||||
{
|
||||
"header": {
|
||||
"eyebrow": "Automation",
|
||||
"live": "实时同步"
|
||||
},
|
||||
"page": {
|
||||
"title": "未来任务管理",
|
||||
"beta": "实验性",
|
||||
"subtitle": "查看给 AstrBot 布置的未来任务。AstrBot 将会被自动唤醒、执行任务,然后将结果告知任务布置方。需要先在配置文件中启用“主动型能力”。",
|
||||
"subtitle": "AstrBot 可以被自动唤醒然后执行任务,并将结果告知任务布置方。需要先在配置文件中启用“主动型能力”。",
|
||||
"proactive": {
|
||||
"supported": "主动发送结果仅支持以下您已配置的平台:{platforms}",
|
||||
"supported": "主动发送结果仅支持以下您已配置的平台",
|
||||
"unsupported": "暂无支持主动消息的平台,请在平台设置中开启。"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"create": "新建任务",
|
||||
"create": "新建",
|
||||
"refresh": "刷新",
|
||||
"delete": "删除",
|
||||
"cancel": "取消",
|
||||
"submit": "创建"
|
||||
},
|
||||
"overview": {
|
||||
"totalTasks": "任务总数",
|
||||
"totalTasksNote": "当前已注册的未来任务",
|
||||
"enabledTasks": "启用任务",
|
||||
"enabledTasksNote": "执行器会按计划唤醒并运行",
|
||||
"oneOffTasks": "一次性任务",
|
||||
"recurringTasksNote": "循环任务 {count} 个",
|
||||
"proactivePlatforms": "主动平台",
|
||||
"proactivePlatformsNote": "已开启主动消息的平台数量"
|
||||
},
|
||||
"section": {
|
||||
"registered": {
|
||||
"title": "任务列表",
|
||||
"subtitle": "集中查看当前注册任务、执行时间与启用状态"
|
||||
},
|
||||
"delivery": {
|
||||
"title": "投递状态",
|
||||
"subtitle": "未来任务执行后的结果,会通过支持主动消息的平台回传",
|
||||
"support": "主动投递",
|
||||
"available": "可用",
|
||||
"unavailable": "不可用",
|
||||
"enabledPlatforms": "已启用平台"
|
||||
},
|
||||
"quickCreate": {
|
||||
"title": "快速创建",
|
||||
"runMode": "任务模式",
|
||||
"target": "目标定位"
|
||||
},
|
||||
"platforms": {
|
||||
"title": "适用平台"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"title": "已注册任务",
|
||||
"subtitle": "支持查看 cron、会话、最近执行记录与启用状态",
|
||||
"empty": "暂无任务。",
|
||||
"headers": {
|
||||
"name": "名称",
|
||||
|
||||
@@ -1,39 +1,57 @@
|
||||
{
|
||||
"header": {
|
||||
"eyebrow": "Subagent Orchestration"
|
||||
},
|
||||
"page": {
|
||||
"title": "SubAgent 编排",
|
||||
"title": "子代理编排",
|
||||
"beta": "实验性",
|
||||
"subtitle": "主 LLM 可直接使用自身工具,也可通过 handoff 分派给各个 SubAgent。"
|
||||
"subtitle": "主代理可直接使用自身工具,也可将部分任务委派给各个子代理,完成更复杂的任务,避免主代理上下文过长。"
|
||||
},
|
||||
"actions": {
|
||||
"refresh": "刷新",
|
||||
"save": "保存",
|
||||
"add": "新增 SubAgent",
|
||||
"add": "新增子代理",
|
||||
"expand": "展开设置",
|
||||
"collapse": "收起设置",
|
||||
"delete": "删除",
|
||||
"close": "关闭"
|
||||
},
|
||||
"overview": {
|
||||
"totalAgents": "子代理总数",
|
||||
"totalAgentsNote": "当前已配置的子代理数量",
|
||||
"enabledAgents": "启用中的 Agent",
|
||||
"enabledAgentsNote": "会参与 handoff 编排的子代理",
|
||||
"mainOrchestration": "主编排状态",
|
||||
"boundPersonas": "已绑定 Persona",
|
||||
"boundPersonasNote": "完成角色绑定的子代理数量"
|
||||
},
|
||||
"switches": {
|
||||
"enable": "启用 SubAgent 编排",
|
||||
"enable": "启用子代理编排",
|
||||
"enableHint": "启用子代理功能",
|
||||
"dedupe": "主 LLM 去重重复工具(与 SubAgent 重叠的工具将被隐藏)",
|
||||
"dedupeHint": "从主代理中移除重复工具"
|
||||
"dedupe": "主 LLM 去重重复工具(与子代理重叠的工具将被隐藏)",
|
||||
"dedupeHint": "从主代理中移除与子代理重复的工具"
|
||||
},
|
||||
"description": {
|
||||
"disabled": "不启动:SubAgent 关闭;主 LLM 按 persona 规则挂载工具(默认全部),并直接调用。",
|
||||
"enabled": "启动:主 LLM 会保留自身工具并挂载 transfer_to_* 委派工具。若开启“去重重复工具”,与 SubAgent 指定的工具重叠部分会从主 LLM 工具集中移除。"
|
||||
"disabled": "未启动子代理编排功能。",
|
||||
"enabled": "子代理将作为工具放在主代理的工具集中,主代理会在适当的时机调用子代理完成任务。"
|
||||
},
|
||||
"section": {
|
||||
"title": "SubAgents 配置",
|
||||
"globalSettings": "全局设置"
|
||||
"title": "子代理配置",
|
||||
"subtitle": "为主代理配置可委派的子代理、人格与描述信息",
|
||||
"globalSettings": "全局设置",
|
||||
"agentSetup": "Agent 设置"
|
||||
},
|
||||
"cards": {
|
||||
"statusEnabled": "启用",
|
||||
"statusDisabled": "停用",
|
||||
"unnamed": "未命名 SubAgent",
|
||||
"unnamed": "未命名子代理",
|
||||
"transferPrefix": "transfer_to_{name}",
|
||||
"switchLabel": "启用",
|
||||
"previewTitle": "预览:主 LLM 将看到的 handoff 工具",
|
||||
"personaChip": "Persona: {id}",
|
||||
"noDescription": "暂无描述"
|
||||
"noDescription": "暂无描述",
|
||||
"personaPreview": "人格预览",
|
||||
"previewHint": "这里展示当前所选 Persona 的预览,便于确认 handoff 目标。"
|
||||
},
|
||||
"form": {
|
||||
"nameLabel": "Agent 名称(用于 transfer_to_{name})",
|
||||
@@ -41,7 +59,7 @@
|
||||
"providerLabel": "Chat Provider(可选)",
|
||||
"providerHint": "留空表示跟随全局默认 provider。",
|
||||
"personaLabel": "选择人格设定",
|
||||
"personaHint": "SubAgent 将直接继承所选 Persona 的系统设定与工具。在人格设定页管理和新建人格。",
|
||||
"personaHint": "子代理 将直接继承所选 Persona 的系统设定与工具。在人格设定页管理和新建人格。",
|
||||
"personaPreview": "人格预览",
|
||||
"descriptionLabel": "对主 LLM 的描述(用于决定是否 handoff)",
|
||||
"descriptionHint": "这段会作为 transfer_to_* 工具的描述给主 LLM 看,建议简短明确。"
|
||||
@@ -49,17 +67,20 @@
|
||||
"messages": {
|
||||
"loadConfigFailed": "获取配置失败",
|
||||
"loadPersonaFailed": "获取 Persona 列表失败",
|
||||
"nameMissing": "存在未填写名称的 SubAgent",
|
||||
"nameInvalid": "SubAgent 名称不合法:仅允许英文小写字母/数字/下划线,且需以字母开头",
|
||||
"nameDuplicate": "SubAgent 名称重复:{name}",
|
||||
"personaMissing": "SubAgent {name} 未选择 Persona",
|
||||
"unsavedChangesNotice": "当前页面有未保存修改,请在离开前保存。",
|
||||
"unsavedChangesLeaveConfirm": "当前页面有未保存修改,离开后将丢失这些更改。是否继续离开?",
|
||||
"unsavedChangesReloadConfirm": "当前页面有未保存修改,刷新配置会丢失这些更改。是否继续刷新?",
|
||||
"nameMissing": "存在未填写名称的子代理",
|
||||
"nameInvalid": "子代理名称不合法:仅允许英文小写字母/数字/下划线,且需以字母开头",
|
||||
"nameDuplicate": "子代理名称重复:{name}",
|
||||
"personaMissing": "子代理 {name} 未选择 Persona",
|
||||
"saveSuccess": "保存成功",
|
||||
"saveFailed": "保存失败",
|
||||
"nameRequired": "名称必填",
|
||||
"namePattern": "仅支持小写字母、数字和下划线"
|
||||
},
|
||||
"empty": {
|
||||
"title": "未配置 SubAgent",
|
||||
"title": "未配置子代理",
|
||||
"subtitle": "添加一个新的子代理以开始",
|
||||
"action": "创建第一个 Agent"
|
||||
}
|
||||
|
||||
292
dashboard/src/styles/dashboard-shell.css
Normal file
292
dashboard/src/styles/dashboard-shell.css
Normal file
@@ -0,0 +1,292 @@
|
||||
.dashboard-page {
|
||||
--dashboard-bg: rgb(var(--v-theme-background));
|
||||
--dashboard-surface: rgb(var(--v-theme-surface));
|
||||
--dashboard-text: rgb(var(--v-theme-on-surface));
|
||||
--dashboard-muted: rgba(var(--v-theme-on-surface), 0.68);
|
||||
--dashboard-subtle: rgba(var(--v-theme-on-surface), 0.56);
|
||||
--dashboard-border: rgba(var(--v-theme-on-surface), 0.1);
|
||||
--dashboard-border-strong: rgba(var(--v-theme-on-surface), 0.14);
|
||||
--dashboard-soft: rgba(var(--v-theme-primary), 0.08);
|
||||
--dashboard-soft-strong: rgba(var(--v-theme-primary), 0.14);
|
||||
min-height: 100%;
|
||||
background: var(--dashboard-bg);
|
||||
}
|
||||
|
||||
.dashboard-page.is-dark {
|
||||
--dashboard-border: rgba(var(--v-theme-on-surface), 0.14);
|
||||
--dashboard-border-strong: rgba(var(--v-theme-on-surface), 0.18);
|
||||
--dashboard-soft: rgba(var(--v-theme-primary), 0.12);
|
||||
--dashboard-soft-strong: rgba(var(--v-theme-primary), 0.18);
|
||||
}
|
||||
|
||||
.dashboard-shell {
|
||||
max-width: 1560px;
|
||||
margin: 0 auto;
|
||||
color: var(--dashboard-text);
|
||||
font-family: "SF Pro Display", "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.dashboard-header-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-eyebrow {
|
||||
margin-bottom: 8px;
|
||||
color: var(--dashboard-subtle);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
margin: 0;
|
||||
font-size: clamp(32px, 4vw, 44px);
|
||||
line-height: 1.04;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
margin: 10px 0 0;
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
max-width: 860px;
|
||||
}
|
||||
|
||||
.dashboard-header-meta,
|
||||
.dashboard-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--dashboard-border);
|
||||
border-radius: 999px;
|
||||
background: var(--dashboard-surface);
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dashboard-overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
border: 1px solid var(--dashboard-border);
|
||||
border-radius: 16px;
|
||||
background: var(--dashboard-surface);
|
||||
}
|
||||
|
||||
.dashboard-card--padded {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.dashboard-overview-card {
|
||||
padding: 20px 20px 18px;
|
||||
}
|
||||
|
||||
.dashboard-card-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 12px;
|
||||
background: var(--dashboard-soft);
|
||||
color: rgb(var(--v-theme-primary));
|
||||
}
|
||||
|
||||
.dashboard-card-label {
|
||||
margin-top: 12px;
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dashboard-card-value {
|
||||
margin-top: 8px;
|
||||
font-size: clamp(24px, 2vw, 34px);
|
||||
line-height: 1.1;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.dashboard-card-note {
|
||||
margin-top: 8px;
|
||||
color: var(--dashboard-subtle);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.dashboard-section-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dashboard-section-title {
|
||||
font-size: 19px;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.dashboard-section-subtitle {
|
||||
margin-top: 6px;
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.dashboard-section-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard-split-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.6fr 0.9fr;
|
||||
gap: 20px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.dashboard-split-grid > * {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboard-side-column {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
align-content: start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-meta-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--dashboard-border);
|
||||
}
|
||||
|
||||
.dashboard-meta-row,
|
||||
.dashboard-list-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-meta-row {
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dashboard-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboard-list-row {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--dashboard-border);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dashboard-list-row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.dashboard-list-label {
|
||||
color: var(--dashboard-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dashboard-empty {
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dashboard-scrollable {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.dashboard-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dashboard-form-grid--single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dashboard-dialog-card {
|
||||
border: 1px solid var(--dashboard-border);
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.dashboard-overview-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.dashboard-split-grid,
|
||||
.dashboard-form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.dashboard-header,
|
||||
.dashboard-section-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.dashboard-overview-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dashboard-card--padded,
|
||||
.dashboard-overview-card {
|
||||
padding: 18px;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,431 +1,606 @@
|
||||
<template>
|
||||
<div class="cron-page">
|
||||
<div class="d-flex align-center justify-space-between mb-4">
|
||||
<div>
|
||||
<div class="d-flex align-center" style="gap: 8px">
|
||||
<h2 class="text-h5 font-weight-bold">
|
||||
{{ tm("page.title") }}
|
||||
</h2>
|
||||
<v-chip size="x-small" color="orange-darken-2" variant="tonal" label>
|
||||
{{ tm("page.beta") }}
|
||||
</v-chip>
|
||||
<div class="dashboard-page cron-page" :class="{ 'is-dark': isDark }">
|
||||
<v-container fluid class="dashboard-shell pa-4 pa-md-6">
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-header-main">
|
||||
<div class="dashboard-eyebrow">{{ tm('header.eyebrow') }}</div>
|
||||
<div class="d-flex align-center flex-wrap" style="gap: 8px;">
|
||||
<h1 class="dashboard-title">{{ tm('page.title') }}</h1>
|
||||
<v-chip size="x-small" color="orange-darken-2" variant="tonal" label>
|
||||
{{ tm('page.beta') }}
|
||||
</v-chip>
|
||||
</div>
|
||||
<p class="dashboard-subtitle">
|
||||
{{ tm('page.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
{{ tm("page.subtitle") }}
|
||||
<span v-if="proactivePlatforms.length">
|
||||
{{
|
||||
tm("page.proactive.supported", {
|
||||
platforms: proactivePlatformText,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else>{{ tm("page.proactive.unsupported") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-center" style="gap: 8px">
|
||||
<v-btn variant="tonal" color="primary" @click="openCreate">
|
||||
{{ tm("actions.create") }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
@click="loadJobs"
|
||||
>
|
||||
{{ tm("actions.refresh") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-card class="rounded-lg" variant="flat">
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center justify-space-between mb-3">
|
||||
<div class="text-subtitle-1 font-weight-bold">
|
||||
{{ tm("table.title") }}
|
||||
<div class="dashboard-header-actions">
|
||||
<v-btn variant="text" color="primary" :loading="loading" prepend-icon="mdi-refresh" @click="loadJobs">
|
||||
{{ tm('actions.refresh') }}
|
||||
</v-btn>
|
||||
<v-btn variant="tonal" color="primary" prepend-icon="mdi-plus" @click="openCreate">
|
||||
{{ tm('actions.create') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-overview-grid">
|
||||
<section
|
||||
v-for="card in overviewCards"
|
||||
:key="card.label"
|
||||
class="dashboard-card dashboard-overview-card"
|
||||
>
|
||||
<div class="dashboard-card-icon">
|
||||
<v-icon size="18">{{ card.icon }}</v-icon>
|
||||
</div>
|
||||
<div class="dashboard-card-label">{{ card.label }}</div>
|
||||
<div class="dashboard-card-value">{{ card.value }}</div>
|
||||
<div class="dashboard-card-note">{{ card.note }}</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-section-head">
|
||||
<div>
|
||||
<div class="dashboard-section-title">{{ tm('section.platforms.title') }}</div>
|
||||
<div class="dashboard-section-subtitle">
|
||||
{{
|
||||
proactivePlatforms.length
|
||||
? tm('page.proactive.supported', { platforms: proactivePlatformText })
|
||||
: tm('page.proactive.unsupported')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-alert v-if="!jobs.length && !loading" type="info" variant="tonal">
|
||||
{{ tm("table.empty") }}
|
||||
</v-alert>
|
||||
|
||||
<v-data-table
|
||||
:items="jobs"
|
||||
:headers="headers"
|
||||
:loading="loading"
|
||||
item-key="job_id"
|
||||
density="comfortable"
|
||||
class="elevation-0"
|
||||
>
|
||||
<template #item.name="{ item }">
|
||||
<div class="py-4">
|
||||
<div class="font-weight-medium">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
{{ item.description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #item.type="{ item }">
|
||||
<v-chip
|
||||
size="small"
|
||||
:color="item.run_once ? 'orange' : 'primary'"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ jobTypeLabel(item) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template #item.cron_expression="{ item }">
|
||||
<div v-if="item.run_once">
|
||||
{{ formatTime(item.run_at) }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>{{ item.cron_expression || tm("table.notAvailable") }}</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
{{ item.timezone || tm("table.timezoneLocal") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #item.session="{ item }">
|
||||
<div>{{ item.session || tm("table.notAvailable") }}</div>
|
||||
</template>
|
||||
<template #item.next_run_time="{ item }">
|
||||
{{ formatTime(item.next_run_time) }}
|
||||
</template>
|
||||
<template #item.last_run_at="{ item }">
|
||||
{{ formatTime(item.last_run_at) }}
|
||||
</template>
|
||||
<template #item.note="{ item }">
|
||||
{{ item.note || tm("table.notAvailable") }}
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<div
|
||||
class="d-flex align-center flex-nowrap"
|
||||
style="gap: 12px; min-width: 140px"
|
||||
>
|
||||
<v-switch
|
||||
v-model="item.enabled"
|
||||
inset
|
||||
density="compact"
|
||||
hide-details
|
||||
color="primary"
|
||||
class="mt-0"
|
||||
@change="toggleJob(item)"
|
||||
/>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="deleteJob(item)"
|
||||
>
|
||||
{{ tm("actions.delete") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" timeout="2600">
|
||||
{{ snackbar.message }}
|
||||
</v-snackbar>
|
||||
|
||||
<v-dialog v-model="createDialog" max-width="560">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6">
|
||||
{{ tm("form.title") }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle class="text-body-2 text-medium-emphasis">
|
||||
{{ tm("form.chatHint") }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-switch
|
||||
v-model="newJob.run_once"
|
||||
:label="tm('form.runOnce')"
|
||||
inset
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="newJob.name"
|
||||
:label="tm('form.name')"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="newJob.note"
|
||||
:label="tm('form.note')"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field
|
||||
v-if="!newJob.run_once"
|
||||
v-model="newJob.cron_expression"
|
||||
:label="tm('form.cron')"
|
||||
:placeholder="tm('form.cronPlaceholder')"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="newJob.run_at"
|
||||
:label="tm('form.runAt')"
|
||||
type="datetime-local"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="newJob.session"
|
||||
:label="tm('form.session')"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="newJob.timezone"
|
||||
:label="tm('form.timezone')"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="newJob.enabled"
|
||||
:label="tm('form.enabled')"
|
||||
inset
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn variant="text" @click="createDialog = false">
|
||||
{{ tm("actions.cancel") }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
<section v-if="proactivePlatforms.length" class="platform-section">
|
||||
<div class="platform-chip-wrap">
|
||||
<v-chip
|
||||
v-for="platform in proactivePlatforms"
|
||||
:key="platform.id"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
:loading="creating"
|
||||
@click="createJob"
|
||||
>
|
||||
{{ tm("actions.submit") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
{{ platform.display_name || platform.name }} · {{ platform.id }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</section>
|
||||
<div v-else class="dashboard-empty platform-empty">
|
||||
{{ tm('page.proactive.unsupported') }}
|
||||
</div>
|
||||
|
||||
<div class="dashboard-section-head">
|
||||
<div>
|
||||
<div class="dashboard-section-title">{{ tm('table.title') }}</div>
|
||||
<div class="dashboard-section-subtitle">{{ tm('table.subtitle') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="task-surface">
|
||||
<div v-if="loading && !jobs.length" class="state-panel">
|
||||
<v-progress-circular indeterminate size="22" width="2" color="primary" />
|
||||
<span>{{ tm('actions.refresh') }}...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!jobs.length" class="state-panel">
|
||||
<v-icon size="20" color="primary">mdi-calendar-blank-outline</v-icon>
|
||||
<span>{{ tm('table.empty') }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="task-table-wrap">
|
||||
<table class="task-table">
|
||||
<colgroup>
|
||||
<col class="col-name" />
|
||||
<col class="col-type" />
|
||||
<col class="col-cron" />
|
||||
<col class="col-session" />
|
||||
<col class="col-next-run" />
|
||||
<col class="col-last-run" />
|
||||
<col class="col-actions" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ tm('table.headers.name') }}</th>
|
||||
<th>{{ tm('table.headers.type') }}</th>
|
||||
<th>{{ tm('table.headers.cron') }}</th>
|
||||
<th>{{ tm('table.headers.session') }}</th>
|
||||
<th>{{ tm('table.headers.nextRun') }}</th>
|
||||
<th>{{ tm('table.headers.lastRun') }}</th>
|
||||
<th class="actions-col">{{ tm('table.headers.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in sortedJobs" :key="item.job_id">
|
||||
<td class="name-col">
|
||||
<div class="task-name">{{ item.name || tm('table.notAvailable') }}</div>
|
||||
<div class="task-subline">{{ item.description || item.job_id }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<v-chip size="small" :color="item.run_once ? 'orange' : 'primary'" variant="tonal">
|
||||
{{ jobTypeLabel(item) }}
|
||||
</v-chip>
|
||||
</td>
|
||||
<td>
|
||||
<div class="task-text">{{ scheduleLabel(item) }}</div>
|
||||
<div class="task-subline">{{ scheduleMeta(item) }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="task-session">{{ item.session || tm('table.notAvailable') }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="task-text">{{ formatTime(item.next_run_time) }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="task-text">{{ formatTime(item.last_run_at) }}</div>
|
||||
</td>
|
||||
<td class="actions-col">
|
||||
<div class="table-actions">
|
||||
<v-switch
|
||||
v-model="item.enabled"
|
||||
inset
|
||||
density="compact"
|
||||
hide-details
|
||||
color="primary"
|
||||
class="mt-0"
|
||||
@change="toggleJob(item)"
|
||||
/>
|
||||
<v-btn size="small" variant="text" color="error" @click="deleteJob(item)">
|
||||
{{ tm('actions.delete') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" timeout="2600">
|
||||
{{ snackbar.message }}
|
||||
</v-snackbar>
|
||||
|
||||
<v-dialog v-model="createDialog" max-width="640">
|
||||
<v-card class="dashboard-dialog-card">
|
||||
<v-card-title class="text-h6 pt-5 px-5">{{ tm('form.title') }}</v-card-title>
|
||||
<v-card-subtitle class="px-5 text-body-2 text-medium-emphasis">
|
||||
{{ tm('form.chatHint') }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text class="px-5 pb-2">
|
||||
<div class="dashboard-form-grid dashboard-form-grid--single">
|
||||
<v-switch
|
||||
v-model="newJob.run_once"
|
||||
:label="tm('form.runOnce')"
|
||||
inset
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field v-model="newJob.name" :label="tm('form.name')" variant="outlined" density="comfortable" />
|
||||
<v-text-field v-model="newJob.note" :label="tm('form.note')" variant="outlined" density="comfortable" />
|
||||
<v-text-field
|
||||
v-if="!newJob.run_once"
|
||||
v-model="newJob.cron_expression"
|
||||
:label="tm('form.cron')"
|
||||
:placeholder="tm('form.cronPlaceholder')"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="newJob.run_at"
|
||||
:label="tm('form.runAt')"
|
||||
type="datetime-local"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
/>
|
||||
<v-text-field v-model="newJob.session" :label="tm('form.session')" variant="outlined" density="comfortable" />
|
||||
<v-text-field v-model="newJob.timezone" :label="tm('form.timezone')" variant="outlined" density="comfortable" />
|
||||
<v-switch
|
||||
v-model="newJob.enabled"
|
||||
:label="tm('form.enabled')"
|
||||
inset
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-end px-5 pb-5">
|
||||
<v-btn variant="text" @click="createDialog = false">{{ tm('actions.cancel') }}</v-btn>
|
||||
<v-btn variant="tonal" color="primary" :loading="creating" @click="createJob">
|
||||
{{ tm('actions.submit') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import axios from "@/utils/request";
|
||||
import { useModuleI18n } from "@/i18n/composables";
|
||||
import axios from '@/utils/request'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { useModuleI18n } from '@/i18n/composables'
|
||||
|
||||
const { tm } = useModuleI18n("features/cron");
|
||||
const { tm } = useModuleI18n('features/cron')
|
||||
const theme = useTheme()
|
||||
|
||||
const loading = ref(false);
|
||||
const jobs = ref<any[]>([]);
|
||||
const proactivePlatforms = ref<
|
||||
{ id: string; name: string; display_name?: string }[]
|
||||
>([]);
|
||||
const createDialog = ref(false);
|
||||
const creating = ref(false);
|
||||
const isDark = computed(() => theme.global.current.value.dark)
|
||||
const loading = ref(false)
|
||||
const jobs = ref<any[]>([])
|
||||
const proactivePlatforms = ref<{ id: string; name: string; display_name?: string }[]>([])
|
||||
const createDialog = ref(false)
|
||||
const creating = ref(false)
|
||||
const newJob = ref({
|
||||
run_once: false,
|
||||
name: "",
|
||||
note: "",
|
||||
cron_expression: "",
|
||||
run_at: "",
|
||||
session: "",
|
||||
timezone: "",
|
||||
enabled: true,
|
||||
});
|
||||
name: '',
|
||||
note: '',
|
||||
cron_expression: '',
|
||||
run_at: '',
|
||||
session: '',
|
||||
timezone: '',
|
||||
enabled: true
|
||||
})
|
||||
|
||||
const snackbar = ref({ show: false, message: "", color: "success" });
|
||||
const snackbar = ref({ show: false, message: '', color: 'success' })
|
||||
|
||||
const proactivePlatformText = computed(() =>
|
||||
proactivePlatforms.value
|
||||
.map((p) => `${p.display_name || p.name}(${p.id})`)
|
||||
.join(" / "),
|
||||
);
|
||||
proactivePlatforms.value.map((p) => `${p.display_name || p.name}(${p.id})`).join(' / ')
|
||||
)
|
||||
|
||||
const headers = computed(() => [
|
||||
{ title: tm("table.headers.name"), key: "name", minWidth: "200px" },
|
||||
{ title: tm("table.headers.type"), key: "type", width: 110 },
|
||||
{
|
||||
title: tm("table.headers.cron"),
|
||||
key: "cron_expression",
|
||||
minWidth: "160px",
|
||||
},
|
||||
{ title: tm("table.headers.session"), key: "session", minWidth: "200px" },
|
||||
{
|
||||
title: tm("table.headers.nextRun"),
|
||||
key: "next_run_time",
|
||||
minWidth: "160px",
|
||||
},
|
||||
{ title: tm("table.headers.lastRun"), key: "last_run_at", minWidth: "160px" },
|
||||
{ title: tm("table.headers.note"), key: "note", minWidth: "220px" },
|
||||
{
|
||||
title: tm("table.headers.actions"),
|
||||
key: "actions",
|
||||
width: 160,
|
||||
sortable: false,
|
||||
},
|
||||
]);
|
||||
const enabledJobsCount = computed(() => jobs.value.filter((job) => job.enabled).length)
|
||||
const runOnceCount = computed(() => jobs.value.filter((job) => job.run_once).length)
|
||||
const recurringCount = computed(() => jobs.value.filter((job) => !job.run_once).length)
|
||||
|
||||
function toast(
|
||||
message: string,
|
||||
color: "success" | "error" | "warning" = "success",
|
||||
) {
|
||||
snackbar.value = { show: true, message, color };
|
||||
const sortedJobs = computed(() =>
|
||||
[...jobs.value].sort((a, b) => {
|
||||
if (a.enabled !== b.enabled) {
|
||||
return a.enabled ? -1 : 1
|
||||
}
|
||||
|
||||
const nextA = parseTimeValue(a.next_run_time ?? a.run_at)
|
||||
const nextB = parseTimeValue(b.next_run_time ?? b.run_at)
|
||||
|
||||
if (nextA !== nextB) {
|
||||
if (!nextA) return 1
|
||||
if (!nextB) return -1
|
||||
return nextA - nextB
|
||||
}
|
||||
|
||||
return String(a.name || '').localeCompare(String(b.name || ''))
|
||||
})
|
||||
)
|
||||
|
||||
const overviewCards = computed(() => [
|
||||
{
|
||||
label: tm('overview.totalTasks'),
|
||||
value: String(jobs.value.length),
|
||||
note: tm('overview.totalTasksNote'),
|
||||
icon: 'mdi-calendar-multiple'
|
||||
},
|
||||
{
|
||||
label: tm('overview.enabledTasks'),
|
||||
value: String(enabledJobsCount.value),
|
||||
note: tm('overview.enabledTasksNote'),
|
||||
icon: 'mdi-check-circle-outline'
|
||||
}
|
||||
])
|
||||
|
||||
function toast(message: string, color: 'success' | 'error' | 'warning' = 'success') {
|
||||
snackbar.value = { show: true, message, color }
|
||||
}
|
||||
|
||||
function parseTimeValue(value: any): number {
|
||||
if (!value) return 0
|
||||
const ts = new Date(value).getTime()
|
||||
return Number.isNaN(ts) ? 0 : ts
|
||||
}
|
||||
|
||||
function formatTime(val: any): string {
|
||||
if (!val) return tm("table.notAvailable");
|
||||
if (!val) return tm('table.notAvailable')
|
||||
try {
|
||||
return new Date(val).toLocaleString();
|
||||
} catch (e) {
|
||||
return String(val);
|
||||
return new Date(val).toLocaleString()
|
||||
} catch {
|
||||
return String(val)
|
||||
}
|
||||
}
|
||||
|
||||
function jobTypeLabel(item: any): string {
|
||||
if (item.run_once) return tm("table.type.once");
|
||||
const type = item.job_type || "active_agent";
|
||||
if (item.run_once) return tm('table.type.once')
|
||||
const type = item.job_type || 'active_agent'
|
||||
const map: Record<string, string> = {
|
||||
active_agent: tm("table.type.activeAgent"),
|
||||
workflow: tm("table.type.workflow"),
|
||||
};
|
||||
return map[type] || tm("table.type.unknown", { type });
|
||||
active_agent: tm('table.type.activeAgent'),
|
||||
workflow: tm('table.type.workflow')
|
||||
}
|
||||
return map[type] || tm('table.type.unknown', { type })
|
||||
}
|
||||
|
||||
function scheduleLabel(item: any): string {
|
||||
if (item.run_once) {
|
||||
return formatTime(item.run_at)
|
||||
}
|
||||
return item.cron_expression || tm('table.notAvailable')
|
||||
}
|
||||
|
||||
function scheduleMeta(item: any): string {
|
||||
if (item.run_once) {
|
||||
return tm('table.type.once')
|
||||
}
|
||||
return item.timezone || tm('table.timezoneLocal')
|
||||
}
|
||||
|
||||
async function loadJobs() {
|
||||
loading.value = true;
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await axios.get("/api/cron/jobs");
|
||||
if (res.data.status === "ok") {
|
||||
const data = Array.isArray(res.data.data) ? res.data.data : [];
|
||||
const res = await axios.get('/api/cron/jobs')
|
||||
if (res.data.status === 'ok') {
|
||||
const data = Array.isArray(res.data.data) ? res.data.data : []
|
||||
jobs.value = data.map((job: any) => ({
|
||||
...job,
|
||||
session: job?.payload?.session || job?.session || "",
|
||||
}));
|
||||
session: job?.payload?.session || job?.session || ''
|
||||
}))
|
||||
} else {
|
||||
toast(res.data.message || tm("messages.loadFailed"), "error");
|
||||
toast(res.data.message || tm('messages.loadFailed'), 'error')
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(e?.response?.data?.message || tm("messages.loadFailed"), "error");
|
||||
toast(e?.response?.data?.message || tm('messages.loadFailed'), 'error')
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlatforms() {
|
||||
try {
|
||||
const res = await axios.get("/api/platform/stats");
|
||||
if (res.data.status === "ok" && Array.isArray(res.data.data?.platforms)) {
|
||||
const res = await axios.get('/api/platform/stats')
|
||||
if (res.data.status === 'ok' && Array.isArray(res.data.data?.platforms)) {
|
||||
proactivePlatforms.value = res.data.data.platforms
|
||||
.filter((p: any) => p?.meta?.support_proactive_message)
|
||||
.map((p: any) => ({
|
||||
id: p?.id || p?.meta?.id || "unknown",
|
||||
name: p?.meta?.name || p?.type || "",
|
||||
display_name: p?.meta?.display_name || p?.display_name,
|
||||
}));
|
||||
id: p?.id || p?.meta?.id || 'unknown',
|
||||
name: p?.meta?.name || p?.type || '',
|
||||
display_name: p?.meta?.display_name || p?.display_name
|
||||
}))
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore platform fetch errors in UI; subtitle will show fallback
|
||||
} catch {
|
||||
// Ignore platform fetch failures and keep the fallback state.
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleJob(job: any) {
|
||||
try {
|
||||
const res = await axios.patch(`/api/cron/jobs/${job.job_id}`, {
|
||||
enabled: job.enabled,
|
||||
});
|
||||
if (res.data.status !== "ok") {
|
||||
toast(res.data.message || tm("messages.updateFailed"), "error");
|
||||
await loadJobs();
|
||||
const res = await axios.patch(`/api/cron/jobs/${job.job_id}`, { enabled: job.enabled })
|
||||
if (res.data.status !== 'ok') {
|
||||
toast(res.data.message || tm('messages.updateFailed'), 'error')
|
||||
await loadJobs()
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(e?.response?.data?.message || tm("messages.updateFailed"), "error");
|
||||
await loadJobs();
|
||||
toast(e?.response?.data?.message || tm('messages.updateFailed'), 'error')
|
||||
await loadJobs()
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteJob(job: any) {
|
||||
try {
|
||||
const res = await axios.delete(`/api/cron/jobs/${job.job_id}`);
|
||||
if (res.data.status === "ok") {
|
||||
toast(tm("messages.deleteSuccess"));
|
||||
jobs.value = jobs.value.filter((j) => j.job_id !== job.job_id);
|
||||
const res = await axios.delete(`/api/cron/jobs/${job.job_id}`)
|
||||
if (res.data.status === 'ok') {
|
||||
toast(tm('messages.deleteSuccess'))
|
||||
jobs.value = jobs.value.filter((item) => item.job_id !== job.job_id)
|
||||
} else {
|
||||
toast(res.data.message || tm("messages.deleteFailed"), "error");
|
||||
toast(res.data.message || tm('messages.deleteFailed'), 'error')
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(e?.response?.data?.message || tm("messages.deleteFailed"), "error");
|
||||
toast(e?.response?.data?.message || tm('messages.deleteFailed'), 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
resetNewJob();
|
||||
createDialog.value = true;
|
||||
resetNewJob()
|
||||
createDialog.value = true
|
||||
}
|
||||
|
||||
function resetNewJob() {
|
||||
newJob.value = {
|
||||
run_once: false,
|
||||
name: "",
|
||||
note: "",
|
||||
cron_expression: "",
|
||||
run_at: "",
|
||||
session: "",
|
||||
timezone: "",
|
||||
enabled: true,
|
||||
};
|
||||
name: '',
|
||||
note: '',
|
||||
cron_expression: '',
|
||||
run_at: '',
|
||||
session: '',
|
||||
timezone: '',
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
async function createJob() {
|
||||
if (!newJob.value.session) {
|
||||
toast(tm("messages.sessionRequired"), "warning");
|
||||
return;
|
||||
toast(tm('messages.sessionRequired'), 'warning')
|
||||
return
|
||||
}
|
||||
if (!newJob.value.note) {
|
||||
toast(tm("messages.noteRequired"), "warning");
|
||||
return;
|
||||
toast(tm('messages.noteRequired'), 'warning')
|
||||
return
|
||||
}
|
||||
if (!newJob.value.run_once && !newJob.value.cron_expression) {
|
||||
toast(tm("messages.cronRequired"), "warning");
|
||||
return;
|
||||
toast(tm('messages.cronRequired'), 'warning')
|
||||
return
|
||||
}
|
||||
if (newJob.value.run_once && !newJob.value.run_at) {
|
||||
toast(tm("messages.runAtRequired"), "warning");
|
||||
return;
|
||||
toast(tm('messages.runAtRequired'), 'warning')
|
||||
return
|
||||
}
|
||||
creating.value = true;
|
||||
|
||||
creating.value = true
|
||||
try {
|
||||
const payload: any = { ...newJob.value };
|
||||
const res = await axios.post("/api/cron/jobs", payload);
|
||||
if (res.data.status === "ok") {
|
||||
toast(tm("messages.createSuccess"));
|
||||
createDialog.value = false;
|
||||
resetNewJob();
|
||||
await loadJobs();
|
||||
const res = await axios.post('/api/cron/jobs', { ...newJob.value })
|
||||
if (res.data.status === 'ok') {
|
||||
toast(tm('messages.createSuccess'))
|
||||
createDialog.value = false
|
||||
resetNewJob()
|
||||
await loadJobs()
|
||||
} else {
|
||||
toast(res.data.message || tm("messages.createFailed"), "error");
|
||||
toast(res.data.message || tm('messages.createFailed'), 'error')
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(e?.response?.data?.message || tm("messages.createFailed"), "error");
|
||||
toast(e?.response?.data?.message || tm('messages.createFailed'), 'error')
|
||||
} finally {
|
||||
creating.value = false;
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadJobs();
|
||||
loadPlatforms();
|
||||
});
|
||||
loadJobs()
|
||||
loadPlatforms()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/styles/dashboard-shell.css';
|
||||
|
||||
.cron-page {
|
||||
padding: 20px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.task-surface {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.platform-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.platform-chip-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.platform-empty {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.task-table-wrap {
|
||||
border: 1px solid var(--dashboard-border);
|
||||
border-radius: 14px;
|
||||
overflow: auto;
|
||||
background: var(--dashboard-surface);
|
||||
}
|
||||
|
||||
.task-table {
|
||||
width: 100%;
|
||||
min-width: 1120px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.task-table .col-name {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.task-table .col-type {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.task-table .col-cron {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.task-table .col-session {
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
.task-table .col-next-run,
|
||||
.task-table .col-last-run {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.task-table .col-actions {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.task-table th {
|
||||
padding: 14px 16px;
|
||||
text-align: left;
|
||||
background: rgba(var(--v-theme-primary), 0.04);
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid var(--dashboard-border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-table td {
|
||||
padding: 16px;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px solid var(--dashboard-border);
|
||||
}
|
||||
|
||||
.task-table tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.name-col {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.task-name,
|
||||
.task-text {
|
||||
color: var(--dashboard-text);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.task-subline {
|
||||
margin-top: 6px;
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.task-session {
|
||||
max-width: 340px;
|
||||
color: var(--dashboard-text);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.actions-col {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.state-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
min-height: 220px;
|
||||
border: 1px dashed var(--dashboard-border-strong);
|
||||
border-radius: 14px;
|
||||
color: var(--dashboard-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cron-page :deep(.dashboard-overview-grid) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.table-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -814,6 +814,10 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.token-side-column > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: 1px solid var(--stats-border);
|
||||
border-radius: 16px;
|
||||
@@ -917,6 +921,8 @@ onBeforeUnmount(() => {
|
||||
font-size: 19px;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
@@ -1052,9 +1058,14 @@ onBeforeUnmount(() => {
|
||||
|
||||
.token-total-value {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-size: clamp(32px, 3vw, 44px);
|
||||
line-height: 1.02;
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.token-meta-list {
|
||||
@@ -1102,6 +1113,12 @@ onBeforeUnmount(() => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.token-total-card .card-label,
|
||||
.token-total-card .card-note,
|
||||
.token-side-column .section-subtitle {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: var(--stats-muted);
|
||||
font-size: 14px;
|
||||
|
||||
@@ -3,10 +3,10 @@ name = "AstrBot"
|
||||
version = "4.25.0"
|
||||
description = "Easy-to-use multi-platform LLM chatbot and development framework"
|
||||
readme = "README.md"
|
||||
license = { text = "AGPL-3.0-or-later" }
|
||||
requires-python = ">=3.12,<3.14"
|
||||
|
||||
|
||||
keywords = ["Astrbot", "Astrbot Module", "Astrbot Plugin"]
|
||||
keywords = ["AstrBot", "Agent", "Chatbot", "IM"]
|
||||
|
||||
dependencies = [
|
||||
"aiocqhttp>=1.4.4",
|
||||
@@ -28,6 +28,7 @@ dependencies = [
|
||||
"faiss-cpu>=1.12.0",
|
||||
"filelock>=3.18.0",
|
||||
"google-genai>=1.56.0",
|
||||
"httpx[socks]>=0.28.1",
|
||||
"lark-oapi>=1.4.15",
|
||||
"lxml-html-clean>=0.4.2",
|
||||
"mcp>=1.8.0",
|
||||
@@ -89,6 +90,9 @@ dev = [
|
||||
|
||||
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/AstrBotDevs/AstrBot"
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
"astrbot/core/utils/t2i/local_strategy.py",
|
||||
|
||||
@@ -186,6 +186,7 @@ httpx==0.28.1
|
||||
# via
|
||||
# aiocqhttp
|
||||
# anthropic
|
||||
# astrbot
|
||||
# google-genai
|
||||
# lark-oapi
|
||||
# mcp
|
||||
@@ -457,6 +458,8 @@ sniffio==1.3.1
|
||||
# anthropic
|
||||
# google-genai
|
||||
# openai
|
||||
socksio==1.0.0
|
||||
# via httpx
|
||||
soupsieve==2.8.3
|
||||
# via beautifulsoup4
|
||||
sqlalchemy==2.0.48
|
||||
|
||||
Reference in New Issue
Block a user