feat: Optimize agent style and prompt information (#11813)

This commit is contained in:
ssongliu
2026-02-06 07:46:56 +00:00
committed by GitHub
parent 77ef3b7096
commit c0404a4ec5
27 changed files with 360 additions and 126 deletions
+19 -19
View File
@@ -43,20 +43,20 @@ func NewIAgentService() IAgentService {
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
provider := strings.ToLower(strings.TrimSpace(req.Provider))
if !isSupportedAgentProvider(provider) {
return nil, buserr.WithDetail("ErrInvalidParams", "provider", nil)
return nil, buserr.New("ErrAgentProviderNotSupported")
}
if req.AccountID == 0 {
return nil, buserr.WithDetail("ErrInvalidParams", "accountId", nil)
return nil, buserr.New("ErrAgentAccountRequired")
}
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
if err != nil {
return nil, err
}
if !account.Verified {
return nil, buserr.WithDetail("ErrInvalidParams", "account", nil)
return nil, buserr.New("ErrAgentAccountNotVerified")
}
if account.Provider != "" && provider != "" && account.Provider != provider {
return nil, buserr.WithDetail("ErrInvalidParams", "provider", nil)
return nil, buserr.New("ErrAgentProviderMismatch")
}
provider = strings.ToLower(strings.TrimSpace(account.Provider))
baseURL := strings.TrimSpace(account.BaseURL)
@@ -66,10 +66,10 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
}
}
if provider == "ollama" && baseURL == "" {
return nil, buserr.WithDetail("ErrInvalidParams", "baseURL", nil)
return nil, buserr.New("ErrAgentBaseURLRequired")
}
if provider != "ollama" && strings.TrimSpace(account.APIKey) == "" {
return nil, buserr.WithDetail("ErrInvalidParams", "apiKey", nil)
return nil, buserr.New("ErrAgentApiKeyRequired")
}
if err := checkPortExist(req.WebUIPort); err != nil {
return nil, err
@@ -110,7 +110,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
}
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
return nil, buserr.WithDetail("ErrInvalidParams", "dockerCompose", nil)
return nil, buserr.New("ErrAgentComposeRequired")
}
installReq := request.AppInstallCreate{
AppDetailId: detail.ID,
@@ -178,7 +178,7 @@ func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, erro
func (a AgentService) Delete(req dto.AgentDeleteReq) error {
if req.ID == 0 {
return buserr.WithDetail("ErrInvalidParams", "id", nil)
return buserr.New("ErrAgentIDRequired")
}
agent, err := agentRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
@@ -216,11 +216,11 @@ func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
provider := strings.ToLower(strings.TrimSpace(req.Provider))
if !isSupportedAgentProvider(provider) {
return buserr.WithDetail("ErrInvalidParams", "provider", nil)
return buserr.New("ErrAgentProviderNotSupported")
}
apiKey := strings.TrimSpace(req.APIKey)
if apiKey == "" {
return buserr.WithDetail("ErrInvalidParams", "apiKey", nil)
return buserr.New("ErrAgentApiKeyRequired")
}
baseURL := strings.TrimSpace(req.BaseURL)
if provider != "ollama" {
@@ -229,7 +229,7 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
}
}
if provider == "ollama" && baseURL == "" {
return buserr.WithDetail("ErrInvalidParams", "baseURL", nil)
return buserr.New("ErrAgentBaseURLRequired")
}
if exist, _ := agentAccountRepo.GetFirst(repo.WithByProvider(provider), repo.WithByName(req.Name)); exist != nil && exist.ID > 0 {
return buserr.New("ErrRecordExist")
@@ -261,7 +261,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
}
}
if provider == "ollama" && baseURL == "" {
return buserr.WithDetail("ErrInvalidParams", "baseURL", nil)
return buserr.New("ErrAgentBaseURLRequired")
}
if err := a.VerifyAccount(dto.AgentAccountVerifyReq{Provider: provider, BaseURL: baseURL, APIKey: req.APIKey}); err != nil {
return err
@@ -313,11 +313,11 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
provider := strings.ToLower(strings.TrimSpace(req.Provider))
if !isSupportedAgentProvider(provider) {
return buserr.WithDetail("ErrInvalidParams", "provider", nil)
return buserr.New("ErrAgentProviderNotSupported")
}
apiKey := strings.TrimSpace(req.APIKey)
if apiKey == "" {
return buserr.WithDetail("ErrInvalidParams", "apiKey", nil)
return buserr.New("ErrAgentApiKeyRequired")
}
baseURL := strings.TrimSpace(req.BaseURL)
if baseURL == "" {
@@ -326,7 +326,7 @@ func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
}
}
if provider == "ollama" && baseURL == "" {
return buserr.WithDetail("ErrInvalidParams", "baseURL", nil)
return buserr.New("ErrAgentBaseURLRequired")
}
if provider == "ollama" {
return nil
@@ -336,10 +336,10 @@ func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
if req.ID == 0 {
return buserr.WithDetail("ErrInvalidParams", "id", nil)
return buserr.New("ErrAgentAccountIDRequired")
}
if exists, _ := agentRepo.GetFirst(repo.WithByAccountID(req.ID)); exists != nil && exists.ID > 0 {
return buserr.New("ErrRecordExist")
return buserr.New("ErrAgentAccountBound")
}
return agentAccountRepo.DeleteByID(req.ID)
}
@@ -391,11 +391,11 @@ func verifyProvider(provider, baseURL, apiKey string) error {
}
resp, err := client.Do(request)
if err != nil {
return err
return buserr.WithErr("ErrAgentAccountUnavailable", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return buserr.WithDetail("ErrInvalidParams", fmt.Sprintf("verify failed: %d", resp.StatusCode), nil)
return buserr.WithErr("ErrAgentAccountUnavailable", fmt.Errorf("verify failed: %s", resp.Status))
}
return nil
}
+14 -1
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: 'Default group, cannot be deleted'
ErrGroupIsInWebsiteUse: 'The group is being used by another website and cannot be deleted.'
Decrypt: "Decrypt"
#agent
ErrAgentAccountBound: 'This account is bound to an agent and cannot be deleted. Please check and try again!'
ErrAgentAccountUnavailable: 'The account connection information is unavailable. Error: {{ .err }}. Please check and try again!'
ErrAgentProviderNotSupported: 'This agent provider is not supported. Please check and try again!'
ErrAgentAccountRequired: 'Please select an agent account and try again.'
ErrAgentAccountNotVerified: 'The account has not been verified. Please check and try again!'
ErrAgentProviderMismatch: 'The account provider does not match. Please check and try again!'
ErrAgentBaseURLRequired: 'Base URL cannot be empty. Please check and try again!'
ErrAgentApiKeyRequired: 'API Key cannot be empty. Please check and try again!'
ErrAgentComposeRequired: 'Custom compose content cannot be empty. Please check and try again!'
ErrAgentIDRequired: 'Agent ID cannot be empty. Please check and try again!'
ErrAgentAccountIDRequired: 'Account ID cannot be empty. Please check and try again!'
#backup
Localhost: 'Local Machine'
ErrBackupInUsed: 'The backup account has been used in the scheduled task and cannot be deleted.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "Failed to partition, {{ .err }}"
FormatDiskErr: "Failed to format disk, {{ .err }}"
MountDiskErr: "Failed to mount disk, {{ .err }}"
UnMountDiskErr: "Failed to unmount disk, {{ .err }}"
XfsNotFound: "xfs filesystem not detected, please install xfsprogs first"
XfsNotFound: "xfs filesystem not detected, please install xfsprogs first"
+13
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: 'Grupo predeterminado, no se puede eliminar'
ErrGroupIsInWebsiteUse: 'El grupo está siendo usado por otro sitio web y no se puede eliminar.'
Decrypt: "Descifrar"
#agent
ErrAgentAccountBound: 'Esta cuenta está vinculada a un agente y no se puede eliminar. Verifique e inténtelo de nuevo!'
ErrAgentAccountUnavailable: 'La información de conexión de la cuenta no está disponible. Error: {{ .err }}. Verifique e inténtelo de nuevo!'
ErrAgentProviderNotSupported: 'Este proveedor de agente no es compatible. Verifique e inténtelo de nuevo!'
ErrAgentAccountRequired: 'Seleccione una cuenta de agente y vuelva a intentarlo.'
ErrAgentAccountNotVerified: 'La cuenta no está verificada. Verifique e inténtelo de nuevo!'
ErrAgentProviderMismatch: 'El proveedor de la cuenta no coincide. Verifique e inténtelo de nuevo!'
ErrAgentBaseURLRequired: 'Base URL no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentApiKeyRequired: 'API Key no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentComposeRequired: 'El contenido personalizado de compose no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentIDRequired: 'El ID del agente no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentAccountIDRequired: 'El ID de la cuenta no puede estar vacío. Verifique e inténtelo de nuevo!'
#backup
ErrBackupInUsed: 'La cuenta de respaldo está siendo utilizada en una tarea programada y no se puede eliminar.'
ErrBackupCheck: 'Error al probar la conexión de la cuenta de respaldo {{ .err }}'
+14 -1
View File
@@ -37,6 +37,19 @@ ErrGroupIsDefault: 'デフォルト グループ、削除できません'
ErrGroupIsInWebsiteUse: 'グループは別の Web サイトで使用されているため、削除できません。'
Decrypt: "復号化"
#agent
ErrAgentAccountBound: 'このアカウントはエージェントに紐付いているため削除できません。確認して再試行してください!'
ErrAgentAccountUnavailable: 'アカウント接続情報を利用できません。エラー: {{ .err }}。確認して再試行してください!'
ErrAgentProviderNotSupported: 'このエージェント提供元はサポートされていません。確認して再試行してください!'
ErrAgentAccountRequired: 'エージェントアカウントを選択して再試行してください。'
ErrAgentAccountNotVerified: 'アカウントの検証が完了していません。確認して再試行してください!'
ErrAgentProviderMismatch: 'アカウントの提供元が一致しません。確認して再試行してください!'
ErrAgentBaseURLRequired: 'Base URL を空にできません。確認して再試行してください!'
ErrAgentApiKeyRequired: 'API Key を空にできません。確認して再試行してください!'
ErrAgentComposeRequired: 'カスタム compose 内容を空にできません。確認して再試行してください!'
ErrAgentIDRequired: 'エージェント ID を空にできません。確認して再試行してください!'
ErrAgentAccountIDRequired: 'アカウント ID を空にできません。確認して再試行してください!'
#backup
Localhost: 'ローカルマシン'
ErrBackupInUsed: 'バックアップ アカウントはスケジュールされたタスクで使用されているため、削除できません。'
@@ -526,4 +539,4 @@ PartitionDiskErr: "パーティションに失敗しました、{{ .err }}"
FormatDiskErr: "ディスクのフォーマットに失敗しました、{{ .err }}"
MountDiskErr: "ディスクのマウントに失敗しました、{{ .err }}"
UnMountDiskErr: "ディスクのアンマウントに失敗しました、{{ .err }}"
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
+14 -1
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: '기본 그룹, 삭제할 수 없습니다'
ErrGroupIsInWebsiteUse: '그룹이 다른 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
Decrypt: "복호화"
#agent
ErrAgentAccountBound: '이 계정은 에이전트에 바인딩되어 있어 삭제할 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentAccountUnavailable: '계정 연결 정보를 사용할 수 없습니다. 오류: {{ .err }}. 확인 후 다시 시도하세요!'
ErrAgentProviderNotSupported: '이 에이전트 제공자는 지원되지 않습니다. 확인 후 다시 시도하세요!'
ErrAgentAccountRequired: '에이전트 계정을 선택한 후 다시 시도하세요.'
ErrAgentAccountNotVerified: '계정이 검증되지 않았습니다. 확인 후 다시 시도하세요!'
ErrAgentProviderMismatch: '계정 제공자가 일치하지 않습니다. 확인 후 다시 시도하세요!'
ErrAgentBaseURLRequired: 'Base URL은 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentApiKeyRequired: 'API Key는 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentComposeRequired: '사용자 정의 compose 내용은 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentIDRequired: '에이전트 ID는 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentAccountIDRequired: '계정 ID는 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
#지원
Localhost: '로컬 머신'
ErrBackupInUsed: '백업 계정이 예약된 작업에 사용되었으므로 삭제할 수 없습니다.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "파티션 분할에 실패했습니다, {{ .err }}"
FormatDiskErr: "디스크 포맷에 실패했습니다, {{ .err }}"
MountDiskErr: "디스크 마운트에 실패했습니다, {{ .err }}"
UnMountDiskErr: "디스크 마운트 해제에 실패했습니다, {{ .err }}"
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
+14 -1
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Kumpulan lalai, tidak boleh dipadamkan'
ErrGroupIsInWebsiteUse: 'Kumpulan sedang digunakan oleh tapak web lain dan tidak boleh dipadamkan.'
Decrypt: "Dekripsi"
#agent
ErrAgentAccountBound: 'Akaun ini telah dipautkan kepada ejen dan tidak boleh dipadam. Sila semak dan cuba lagi!'
ErrAgentAccountUnavailable: 'Maklumat sambungan akaun tidak tersedia. Ralat: {{ .err }}. Sila semak dan cuba lagi!'
ErrAgentProviderNotSupported: 'Penyedia ejen ini tidak disokong. Sila semak dan cuba lagi!'
ErrAgentAccountRequired: 'Sila pilih akaun ejen dan cuba lagi.'
ErrAgentAccountNotVerified: 'Akaun belum disahkan. Sila semak dan cuba lagi!'
ErrAgentProviderMismatch: 'Penyedia akaun tidak sepadan. Sila semak dan cuba lagi!'
ErrAgentBaseURLRequired: 'Base URL tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentApiKeyRequired: 'API Key tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentComposeRequired: 'Kandungan compose tersuai tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentIDRequired: 'ID ejen tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentAccountIDRequired: 'ID akaun tidak boleh kosong. Sila semak dan cuba lagi!'
#sandaran
Localhost: 'Mesin Tempatan'
ErrBackupInUsed: 'Akaun sandaran telah digunakan dalam tugas yang dijadualkan dan tidak boleh dipadamkan.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "Gagal membahagikan, {{ .err }}"
FormatDiskErr: "Gagal memformat cakera, {{ .err }}"
MountDiskErr: "Gagal mengkaitkan cakera, {{ .err }}"
UnMountDiskErr: "Gagal nyahkaitkan cakera, {{ .err }}"
XfsNotFound: "Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu"
XfsNotFound: "Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu"
+14 -1
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Grupo padrão, não pode ser excluído'
ErrGroupIsInWebsiteUse: 'O grupo está sendo usado por outro site e não pode ser excluído.'
Decrypt: "Descriptografar"
#agent
ErrAgentAccountBound: 'Esta conta está vinculada a um agente e não pode ser excluída. Verifique e tente novamente!'
ErrAgentAccountUnavailable: 'As informações de conexão da conta não estão disponíveis. Erro: {{ .err }}. Verifique e tente novamente!'
ErrAgentProviderNotSupported: 'Este provedor de agente não é suportado. Verifique e tente novamente!'
ErrAgentAccountRequired: 'Selecione uma conta de agente e tente novamente.'
ErrAgentAccountNotVerified: 'A conta não foi verificada. Verifique e tente novamente!'
ErrAgentProviderMismatch: 'O provedor da conta não corresponde. Verifique e tente novamente!'
ErrAgentBaseURLRequired: 'Base URL não pode estar vazio. Verifique e tente novamente!'
ErrAgentApiKeyRequired: 'API Key não pode estar vazio. Verifique e tente novamente!'
ErrAgentComposeRequired: 'O conteúdo personalizado do compose não pode estar vazio. Verifique e tente novamente!'
ErrAgentIDRequired: 'ID do agente não pode estar vazio. Verifique e tente novamente!'
ErrAgentAccountIDRequired: 'ID da conta não pode estar vazio. Verifique e tente novamente!'
#backup
Localhost: 'Máquina Local'
ErrBackupInUsed: 'A conta de backup foi usada na tarefa agendada e não pode ser excluída.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "Falha ao particionar, {{ .err }}"
FormatDiskErr: "Falha ao formatar disco, {{ .err }}"
MountDiskErr: "Falha ao montar disco, {{ .err }}"
UnMountDiskErr: "Falha ao desmontar disco, {{ .err }}"
XfsNotFound: "Sistema de arquivos xfs não detectado, por favor instale xfsprogs primeiro"
XfsNotFound: "Sistema de arquivos xfs não detectado, por favor instale xfsprogs primeiro"
+14 -1
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Группа по умолчанию, не может быт
ErrGroupIsInWebsiteUse: 'Группа используется другим веб-сайтом и не может быть удалена.'
Decrypt: "Расшифровать"
#agent
ErrAgentAccountBound: 'Эта учетная запись привязана к агенту и не может быть удалена. Проверьте и повторите попытку!'
ErrAgentAccountUnavailable: 'Информация о подключении учетной записи недоступна. Ошибка: {{ .err }}. Проверьте и повторите попытку!'
ErrAgentProviderNotSupported: 'Этот провайдер агента не поддерживается. Проверьте и повторите попытку!'
ErrAgentAccountRequired: 'Выберите учетную запись агента и повторите попытку.'
ErrAgentAccountNotVerified: 'Учетная запись не прошла проверку. Проверьте и повторите попытку!'
ErrAgentProviderMismatch: 'Провайдер учетной записи не совпадает. Проверьте и повторите попытку!'
ErrAgentBaseURLRequired: 'Base URL не может быть пустым. Проверьте и повторите попытку!'
ErrAgentApiKeyRequired: 'API Key не может быть пустым. Проверьте и повторите попытку!'
ErrAgentComposeRequired: 'Пользовательское содержимое compose не может быть пустым. Проверьте и повторите попытку!'
ErrAgentIDRequired: 'ID агента не может быть пустым. Проверьте и повторите попытку!'
ErrAgentAccountIDRequired: 'ID учетной записи не может быть пустым. Проверьте и повторите попытку!'
#резервное копирование
Localhost: 'Локальная машина'
ErrBackupInUsed: 'Учетная запись резервного копирования использовалась в запланированной задаче и не может быть удалена.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "Не удалось разделить, {{ .err }}"
FormatDiskErr: "Не удалось отформатировать диск, {{ .err }}"
MountDiskErr: "Не удалось подключить диск, {{ .err }}"
UnMountDiskErr: "Не удалось отключить диск, {{ .err }}"
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
+13
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Varsayılan grup, silinemez'
ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.'
Decrypt: "Şifre Çöz"
#agent
ErrAgentAccountBound: 'Bu hesap bir ajana bağlı ve silinemez. Kontrol edip tekrar deneyin!'
ErrAgentAccountUnavailable: 'Hesap bağlantı bilgileri kullanılamıyor. Hata: {{ .err }}. Kontrol edip tekrar deneyin!'
ErrAgentProviderNotSupported: 'Bu ajan sağlayıcısı desteklenmiyor. Kontrol edip tekrar deneyin!'
ErrAgentAccountRequired: 'Lütfen bir ajan hesabı seçip tekrar deneyin.'
ErrAgentAccountNotVerified: 'Hesap doğrulanmadı. Kontrol edip tekrar deneyin!'
ErrAgentProviderMismatch: 'Hesap sağlayıcısı eşleşmiyor. Kontrol edip tekrar deneyin!'
ErrAgentBaseURLRequired: 'Base URL boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentApiKeyRequired: 'API Key boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentComposeRequired: 'Özel compose içeriği boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentIDRequired: 'Ajan ID boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentAccountIDRequired: 'Hesap ID boş olamaz. Kontrol edip tekrar deneyin!'
#backup
Localhost: 'Yerel Makine'
ErrBackupInUsed: 'Yedekleme hesabı zamanlanmış görevde kullanıldı ve silinemez.'
+19 -6
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: '預設分組,無法刪除'
ErrGroupIsInWebsiteUse: '分組正在被其他網站使用,無法刪除'
Decrypt: "解密"
#agent
ErrAgentAccountBound: '該帳號已綁定到智能體,無法刪除,請檢查後重試!!'
ErrAgentAccountUnavailable: '帳號連線資訊不可用,錯誤:{{ .err }},請檢查後重試!'
ErrAgentProviderNotSupported: '暫不支援該智能體提供商,請檢查後重試!'
ErrAgentAccountRequired: '請選擇智能體帳號後重試'
ErrAgentAccountNotVerified: '帳號未驗證通過,請檢查後重試!'
ErrAgentProviderMismatch: '帳號提供商不匹配,請檢查後重試!'
ErrAgentBaseURLRequired: 'Base URL 不能為空,請檢查後重試!'
ErrAgentApiKeyRequired: 'API Key 不能為空,請檢查後重試!'
ErrAgentComposeRequired: '自訂編排內容不能為空,請檢查後重試!'
ErrAgentIDRequired: '智能體 ID 不能為空,請檢查後重試!'
ErrAgentAccountIDRequired: '帳號 ID 不能為空,請檢查後重試!'
#backup
Localhost: '本機'
ErrBackupInUsed: '此備份帳號已在排程任務中使用,無法刪除'
@@ -124,7 +137,7 @@ ErrLogFileToLarge: "日誌檔案超過 500M,無法打開"
FileDropFailed: "清理文件 {{ .name }} 失敗: {{ .err }}"
FileDropSuccess: "清理文件 {{ .name }} 成功,清理 {{ .count }} 個文件,釋放 {{ .size }} 磁碟空間"
FileDropSum: "文件清理完成,共清理 {{ .count }} 個文件,共釋放 {{ .size }} 磁碟空間"
ErrBadDecrypt: "壓縮檔解壓密碼錯誤,解壓失敗,請檢查後重試!"
ErrBadDecrypt: "壓縮檔解壓密碼錯誤,解壓失敗,請檢查後重試!"
#website
ErrAliasIsExist: '代號已存在'
@@ -492,11 +505,11 @@ AppBackupFileIncomplete: '備份檔案不完整缺少 app.json 或 app.tar.gz
AppAttributesNotMatch: '應用程式類型或名稱不一致'
#alert
ErrAlert: '警告訊息格式錯誤,請檢查後重試!'
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!'
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!'
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!'
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
ErrAlert: '警告訊息格式錯誤,請檢查後重試!'
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!'
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!'
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!'
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
#task - runtime
ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
+21 -8
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: "默认分组,无法删除"
ErrGroupIsInWebsiteUse: "分组正在被其他网站使用,无法删除"
Decrypt: "解密"
#agent
ErrAgentAccountBound: "该账号已经绑定到智能体,无法删除,请检查后重试!!"
ErrAgentAccountUnavailable: "账号连接信息不可用,错误:{{ .err }},请检查后重试!"
ErrAgentProviderNotSupported: "暂不支持该智能体提供商,请检查后重试!"
ErrAgentAccountRequired: "请选择智能体账号后重试"
ErrAgentAccountNotVerified: "账号未验证通过,请检查后重试!"
ErrAgentProviderMismatch: "账号提供商不匹配,请检查后重试!"
ErrAgentBaseURLRequired: "Base URL 不能为空,请检查后重试!"
ErrAgentApiKeyRequired: "API Key 不能为空,请检查后重试!"
ErrAgentComposeRequired: "自定义编排内容不能为空,请检查后重试!"
ErrAgentIDRequired: "智能体 ID 不能为空,请检查后重试!"
ErrAgentAccountIDRequired: "账号 ID 不能为空,请检查后重试!"
#backup
Localhost: '本机'
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
@@ -125,7 +138,7 @@ ErrLogFileToLarge: "日志文件超过 500M,无法打开"
FileDropFailed: "清理文件 {{ .name }} 失败 {{ .err }}"
FileDropSuccess: "清理文件 {{ .name }} 成功,清理文件 {{ .count }},释放磁盘空间 {{ .size }}"
FileDropSum: "文件清理成功,清理文件共计 {{ .count }},释放磁盘空间共计 {{ .size }}"
ErrBadDecrypt: "压缩包解压密码错误,解压失败,请检查后重试!"
ErrBadDecrypt: "压缩包解压密码错误,解压失败,请检查后重试!"
#website
ErrAliasIsExist: "代号已存在"
@@ -263,7 +276,7 @@ WafIpGroupNotFound: "WAF IP组未找到,请先到【高级功能-WAF-黑白名
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
ErrClamdscanNotFound: "未检测到 clamdscan 命令,请参考文档安装!"
TaskSwapSet: "设置 Swap"
SetSwap: "设置 Swap {{ .path }} - {{ .size }}"
@@ -494,11 +507,11 @@ AppBackupFileIncomplete: "备份文件不完整 缺少 app.json 或者 app.tar.
AppAttributesNotMatch: "应用类型或者名称不一致"
#alert
ErrAlert: "告警信息格式错误,请检查后重试!"
ErrAlertPush: "告警信息推送错误,请检查后重试!"
ErrAlertSave: "告警信息保存错误,请检查后重试!"
ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
ErrAlert: "告警信息格式错误,请检查后重试!"
ErrAlertPush: "告警信息推送错误,请检查后重试!"
ErrAlertSave: "告警信息保存错误,请检查后重试!"
ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
#task - runtime
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
@@ -528,4 +541,4 @@ PartitionDiskErr: "分区失败,{{ .err }}"
FormatDiskErr: "格式化磁盘失败,{{ .err }}"
MountDiskErr: "挂载磁盘失败,{{ .err }}"
UnMountDiskErr: "卸载磁盘失败,{{ .err }}"
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
+2
View File
@@ -64,6 +64,8 @@ var WebUrlMap = map[string]struct{}{
"/ai/mcp": {},
"/ai/model/tensorrt": {},
"/ai/model/ollama": {},
"/ai/agents/agent": {},
"/ai/agents/model": {},
"/containers": {},
"/containers/container/operate": {},
+2
View File
@@ -687,6 +687,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: 'Choose an existing account or add a new one.',
accountCount: '{0} accounts',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -683,6 +683,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: 'Selecciona una cuenta existente o agrega una nueva.',
accountCount: '{0} cuentas',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -672,6 +672,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: '既存のアカウントを選択するか新規に追加してください',
accountCount: 'アカウント {0} ',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -669,6 +669,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: '기존 계정을 선택하거나 새로 추가하세요.',
accountCount: '계정 {0}개',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -684,6 +684,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: 'Pilih akaun sedia ada atau tambah yang baharu.',
accountCount: 'Akaun {0}',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -681,6 +681,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: 'Selecione uma conta existente ou adicione uma nova.',
accountCount: '{0} contas',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -677,6 +677,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: 'Выберите существующий аккаунт или добавьте новый.',
accountCount: '{0} аккаунтов',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -691,6 +691,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: 'Mevcut bir hesabı seçin veya yeni bir tane ekleyin.',
accountCount: '{0} hesap',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2
View File
@@ -659,6 +659,8 @@ const message = {
accountName: 'Account Name',
accountList: 'Account List',
createAccount: 'Create Account',
noAccountHint: '選擇已有帳號或直接新增',
accountCount: '帳號 {0} ',
syncAgents: 'Sync related agents',
syncAgentsHelper: 'Update openclaw.json for agents using this account',
appVersion: 'App Version',
+2 -1
View File
@@ -660,7 +660,8 @@ const message = {
account: '账号',
accountName: '账号名称',
accountList: '账号列表',
createAccount: '创建账号',
noAccountHint: '选择已有账号或者直接添加',
accountCount: '账号 {0} ',
syncAgents: '同步关联智能体',
syncAgentsHelper: '更新使用该账号的智能体 openclaw.json',
appVersion: '应用版本',
+127 -60
View File
@@ -1,63 +1,81 @@
<template>
<DrawerPro v-model="open" :header="$t('aiTools.agents.createAgent')" size="large" @close="handleClose">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.appVersion')" prop="appVersion">
<el-select v-model="form.appVersion" filterable>
<el-option v-for="item in versions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item :label="$t('aiTools.agents.webuiPort')" prop="webUIPort">
<el-input-number v-model="form.webUIPort" :min="1" :max="65535" />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.bridgePort')" prop="bridgePort">
<el-input-number v-model="form.bridgePort" :min="1" :max="65535" />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.provider')" prop="provider">
<el-select v-model="form.provider" @change="handleProviderChange">
<el-option
v-for="item in providerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="accountOptions.length > 0" :label="$t('aiTools.agents.account')" prop="accountId">
<el-select v-model="form.accountId" @change="handleAccountChange">
<el-option v-for="item in accountOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item v-else :label="$t('aiTools.agents.account')">
<el-button type="text" @click="openAccountCreate">
{{ $t('aiTools.agents.createAccount') }}
</el-button>
</el-form-item>
<el-form-item>
<el-checkbox v-model="manualModel">{{ $t('aiTools.agents.manualModel') }}</el-checkbox>
</el-form-item>
<el-form-item :label="$t('aiTools.model.model')" prop="model">
<el-input v-if="manualModel" v-model="form.model" />
<el-select v-else v-model="form.model" filterable @change="handleModelChange">
<el-option v-for="item in filteredModels" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('aiTools.agents.apiKey')" prop="apiKey">
<el-input v-model="form.apiKey" type="password" show-password readonly />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.baseUrl')" prop="baseURL">
<el-input v-model="form.baseURL" readonly />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.token')">
<el-input v-model="form.token" readonly>
<template #append>
<CopyButton :content="form.token" />
</template>
</el-input>
</el-form-item>
<AdvancedSetting :form="form" />
<el-card class="form-card">
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.appVersion')" prop="appVersion">
<el-select v-model="form.appVersion" filterable>
<el-option v-for="item in versions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item :label="$t('aiTools.agents.webuiPort')" prop="webUIPort">
<el-input-number v-model="form.webUIPort" :min="1" :max="65535" />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.bridgePort')" prop="bridgePort">
<el-input-number v-model="form.bridgePort" :min="1" :max="65535" />
</el-form-item>
</el-card>
<el-card class="form-card">
<el-form-item :label="$t('aiTools.agents.provider')" prop="provider">
<el-select v-model="form.provider" @change="handleProviderChange">
<el-option
v-for="item in providerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<div class="option-row">
<span class="option-label">{{ item.label }}</span>
<el-tag
size="small"
:type="providerAccountCount[item.value] > 0 ? 'success' : 'info'"
class="option-tag"
>
{{ $t('aiTools.agents.accountCount', [providerAccountCount[item.value] || 0]) }}
</el-tag>
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-checkbox v-model="manualModel">{{ $t('aiTools.agents.manualModel') }}</el-checkbox>
</el-form-item>
<el-form-item :label="$t('aiTools.model.model')" prop="model">
<el-input v-if="manualModel" v-model="form.model" />
<el-select v-else v-model="form.model" filterable @change="handleModelChange">
<el-option v-for="item in filteredModels" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('aiTools.agents.account')" prop="accountId">
<el-select v-model="form.accountId" @change="handleAccountChange">
<el-option v-for="item in accountOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<span v-if="accountOptions.length === 0" class="input-help">
{{ $t('aiTools.agents.noAccountHint') }}
<el-button type="primary" link class="inline-link" @click="openAccountCreate">
{{ $t('commons.button.add') }}
</el-button>
</span>
</el-form-item>
<el-form-item :label="$t('aiTools.agents.apiKey')" prop="apiKey">
<el-input v-model="form.apiKey" type="password" show-password readonly />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.baseUrl')" prop="baseURL">
<el-input v-model="form.baseURL" readonly />
</el-form-item>
<el-form-item :label="$t('aiTools.agents.token')">
<el-input v-model="form.token" readonly>
<template #append>
<CopyButton :content="form.token" />
</template>
</el-input>
</el-form-item>
</el-card>
<el-card class="form-card">
<AdvancedSetting :form="form" />
</el-card>
</el-form>
<template #footer>
<span class="dialog-footer">
@@ -89,6 +107,7 @@ const versions = ref<string[]>([]);
const accountOptions = ref<AI.AgentAccountItem[]>([]);
const providerOptions = ref<Array<{ label: string; value: string }>>([]);
const providerModels = ref<Record<string, AI.ProviderModelInfo[]>>({});
const providerAccountCount = ref<Record<string, number>>({});
const manualModel = ref(false);
const appInfo = ref<App.AppDTO>();
const accountAddRef = ref();
@@ -99,7 +118,7 @@ const form = reactive({
webUIPort: 18789,
bridgePort: 18790,
provider: 'deepseek',
accountId: 0,
accountId: undefined as unknown as number,
model: '',
apiKey: '',
baseURL: '',
@@ -179,9 +198,24 @@ const loadProviders = async () => {
if (!providerOptions.value.find((item) => item.value === form.provider) && providerOptions.value.length > 0) {
form.provider = providerOptions.value[0].value;
}
await loadProviderAccountCounts(providerOptions.value.map((item) => item.value));
setDefaultModel();
};
const loadProviderAccountCounts = async (providers: string[]) => {
const tasks = providers.map(async (provider) => {
const req: AI.AgentAccountSearch = {
page: 1,
pageSize: 1,
provider: provider,
name: '',
};
const res = await pageAgentAccounts(req);
providerAccountCount.value[provider] = res.data.total || 0;
});
await Promise.all(tasks);
};
const loadAccounts = async () => {
if (!form.provider) {
accountOptions.value = [];
@@ -195,11 +229,12 @@ const loadAccounts = async () => {
};
const res = await pageAgentAccounts(req);
accountOptions.value = res.data.items || [];
providerAccountCount.value[form.provider] = res.data.total || accountOptions.value.length;
if (accountOptions.value.length > 0) {
form.accountId = accountOptions.value[0].id;
handleAccountChange();
} else {
form.accountId = 0;
form.accountId = undefined as unknown as number;
form.apiKey = '';
form.baseURL = '';
}
@@ -209,7 +244,7 @@ const handleProviderChange = () => {
form.model = '';
form.apiKey = '';
form.baseURL = '';
form.accountId = 0;
form.accountId = undefined as unknown as number;
loadAccounts();
setDefaultModel();
};
@@ -328,3 +363,35 @@ defineExpose({
open: openDrawer,
});
</script>
<style scoped>
.form-card {
margin-bottom: 16px;
}
.inline-link {
padding: 0;
margin-top: -1px;
margin-left: 5px;
height: auto;
line-height: inherit;
font-size: inherit;
}
.option-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.option-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.option-tag {
flex-shrink: 0;
}
</style>
+38 -22
View File
@@ -17,32 +17,48 @@
</template>
<template #main>
<ComplexTable :data="items" :pagination-config="paginationConfig" @search="search">
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="160" />
<el-table-column :label="$t('aiTools.agents.appVersion')" prop="appVersion" width="120" />
<el-table-column :label="$t('aiTools.agents.webuiPort')" prop="webUIPort" width="140">
<template #default="{ row }">
<el-button icon="Position" plain size="small" @click="jumpWebUI(row)">
{{ row.webUIPort }}
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.agents.bridgePort')" prop="bridgePort" width="120" />
<el-table-column :label="$t('aiTools.agents.provider')" prop="provider" width="120">
<template #default="{ row }">
{{ getProviderLabel(row.provider) }}
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.model.model')" prop="model" min-width="180" />
<el-table-column :label="$t('aiTools.agents.token')" width="120">
<template #default="{ row }">
<CopyButton :content="row.token" />
</template>
</el-table-column>
<el-table-column
:label="$t('commons.table.name')"
show-overflow-tooltip
prop="name"
min-width="120"
/>
<el-table-column :label="$t('commons.table.status')" prop="status" width="120">
<template #default="{ row }">
<Status :status="row.status" />
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.agents.appVersion')" prop="appVersion" min-width="100" />
<el-table-column
:label="$t('aiTools.model.model')"
show-overflow-tooltip
prop="provider"
min-width="120"
>
<template #default="{ row }">
{{ getProviderLabel(row.provider) }}
<div>
<span>{{ row.model }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.port')" prop="webUIPort" min-width="150">
<template #default="{ row }">
<el-button icon="Position" plain size="small" @click="jumpWebUI(row)">
{{ $t('aiTools.agents.webuiPort') }}: {{ row.webUIPort }}
</el-button>
<div class="mt-0.5">
<el-button plain size="small">
{{ $t('aiTools.agents.bridgePort') }} {{ row.bridgePort }}
</el-button>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.agents.token')" min-width="80">
<template #default="{ row }">
<CopyButton :content="row.token" />
</template>
</el-table-column>
<el-table-column
:label="$t('commons.table.date')"
prop="createdAt"
@@ -52,7 +68,7 @@
/>
<fu-table-operations
:buttons="buttons"
min-width="200"
min-width="220"
:label="$t('commons.table.operate')"
fixed="right"
:ellipsis="2"
@@ -78,7 +78,7 @@ const form = reactive({
});
const headerTitle = computed(() =>
form.id ? i18n.global.t('commons.button.edit') : i18n.global.t('aiTools.agents.createAccount'),
form.id ? i18n.global.t('commons.button.edit') : i18n.global.t('commons.button.add'),
);
const rules = reactive({
+2 -2
View File
@@ -3,7 +3,7 @@
<RouterMenu />
<LayoutContent :title="$t('aiTools.agents.accountList')">
<template #leftToolBar>
<el-button type="primary" @click="openCreate">{{ $t('aiTools.agents.createAccount') }}</el-button>
<el-button type="primary" @click="openCreate">{{ $t('commons.button.add') }}</el-button>
</template>
<template #rightToolBar>
<TableSearch v-model:searchName="searchName" @search="search" />
@@ -131,7 +131,7 @@ const maskKey = (value: string) => {
const onDelete = async (row: AI.AgentAccountItem) => {
await ElMessageBox.confirm(
i18n.global.t('commons.msg.confirmDelete', [row.name]),
i18n.global.t('commons.msg.delete', [row.name]),
i18n.global.t('commons.button.delete'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
@@ -353,7 +353,7 @@
/>
<fu-table-operations
fix
width="200px"
width="220px"
:ellipsis="2"
:buttons="buttons"
:label="$t('commons.table.operate')"