feat(monitoring): decouple AI platforms from media_platforms and expand catalog to 6
Add ai_platforms table + shared catalog (yuanbao/kimi/wenxin/deepseek/doubao/qwen), drop FKs from platform_accounts and desktop_tasks to media_platforms, and wire target_account_id + platform into DesktopTaskEvent so the desktop runtime no longer has to infer them from local state. Desktop client gains generic AI page detection for binding, drops stale-business-date monitor tasks at the edge, and refactors AccountsView/AiPlatformsView around the shared catalog. Admin tracking view surfaces per-platform sampling status cards. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,7 +145,9 @@ func (s *DesktopAccountService) Upsert(ctx context.Context, client *repository.D
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, response.ErrConflict(40982, "desktop_account_sync_conflict", "desktop account sync version conflict")
|
||||
}
|
||||
return nil, response.ErrInternal(50083, "desktop_account_upsert_failed", "failed to upsert desktop account")
|
||||
appErr := response.ErrInternal(50083, "desktop_account_upsert_failed", "failed to upsert desktop account")
|
||||
appErr.Cause = err
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
view := s.buildDesktopAccountView(account, nil, nil)
|
||||
|
||||
@@ -9,14 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type DesktopTaskEvent struct {
|
||||
Type string `json:"type"`
|
||||
TaskID string `json:"task_id"`
|
||||
JobID string `json:"job_id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
TargetClientID string `json:"target_client_id"`
|
||||
Status string `json:"status"`
|
||||
Kind string `json:"kind"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Type string `json:"type"`
|
||||
TaskID string `json:"task_id"`
|
||||
JobID string `json:"job_id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
TargetAccountID string `json:"target_account_id"`
|
||||
TargetClientID string `json:"target_client_id"`
|
||||
Platform string `json:"platform"`
|
||||
Status string `json:"status"`
|
||||
Kind string `json:"kind"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func publishDesktopTaskEvent(ctx context.Context, client *rabbitmq.Client, event DesktopTaskEvent) error {
|
||||
|
||||
@@ -726,14 +726,16 @@ func (s *DesktopTaskService) publishTaskEvent(ctx context.Context, task *reposit
|
||||
}
|
||||
|
||||
err := publishDesktopTaskEvent(ctx, s.messaging, DesktopTaskEvent{
|
||||
Type: eventType,
|
||||
TaskID: task.DesktopID.String(),
|
||||
JobID: task.JobID.String(),
|
||||
WorkspaceID: task.WorkspaceID,
|
||||
TargetClientID: task.TargetClientID.String(),
|
||||
Status: task.Status,
|
||||
Kind: task.Kind,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
Type: eventType,
|
||||
TaskID: task.DesktopID.String(),
|
||||
JobID: task.JobID.String(),
|
||||
WorkspaceID: task.WorkspaceID,
|
||||
TargetAccountID: task.TargetAccountID.String(),
|
||||
TargetClientID: task.TargetClientID.String(),
|
||||
Platform: task.Platform,
|
||||
Status: task.Status,
|
||||
Kind: task.Kind,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
s.logWarn("desktop task event publish failed", err, zap.String("task_id", task.DesktopID.String()), zap.String("event_type", eventType))
|
||||
|
||||
@@ -134,7 +134,7 @@ func (s *MonitoringCallbackService) rebuildBrandDailyProjection(ctx context.Cont
|
||||
}
|
||||
|
||||
func loadMonitoringProjectionPlatforms(ctx context.Context, businessPool *pgxpool.Pool, tenantID int64) ([]monitoringPlatformMetadata, error) {
|
||||
enabled := []string{"deepseek", "qwen", "doubao"}
|
||||
enabled := []string{"yuanbao", "kimi", "wenxin", "deepseek", "doubao", "qwen"}
|
||||
|
||||
var enabledJSON []byte
|
||||
if err := businessPool.QueryRow(ctx, `
|
||||
|
||||
@@ -242,17 +242,21 @@ type monitoringConfiguredQuestion struct {
|
||||
}
|
||||
|
||||
var defaultMonitoringPlatforms = []monitoringPlatformMetadata{
|
||||
{ID: "yuanbao", Name: "混元 / 元宝"},
|
||||
{ID: "kimi", Name: "Kimi"},
|
||||
{ID: "wenxin", Name: "文心一言"},
|
||||
{ID: "deepseek", Name: "DeepSeek"},
|
||||
{ID: "qwen", Name: "通义千问"},
|
||||
{ID: "doubao", Name: "豆包"},
|
||||
{ID: "qwen", Name: "通义千问"},
|
||||
}
|
||||
|
||||
var monitoringPlatformNames = map[string]string{
|
||||
"deepseek": "DeepSeek",
|
||||
"qwen": "通义千问",
|
||||
"doubao": "豆包",
|
||||
"kimi": "Kimi",
|
||||
"yuanbao": "腾讯元宝",
|
||||
"qwen": "通义千问",
|
||||
"wenxin": "文心一言",
|
||||
"yuanbao": "混元 / 元宝",
|
||||
}
|
||||
|
||||
func (s *MonitoringService) DashboardComposite(
|
||||
@@ -891,7 +895,7 @@ func (s *MonitoringService) ensureCollectNowTasks(
|
||||
func (s *MonitoringService) loadQuota(ctx context.Context, tenantID int64) (monitoringQuotaConfig, error) {
|
||||
cfg := monitoringQuotaConfig{
|
||||
CollectionMode: monitoringCollectorType,
|
||||
EnabledPlatforms: []string{"deepseek", "qwen", "doubao"},
|
||||
EnabledPlatforms: []string{"yuanbao", "kimi", "wenxin", "deepseek", "doubao", "qwen"},
|
||||
}
|
||||
|
||||
var enabledJSON []byte
|
||||
|
||||
@@ -214,14 +214,16 @@ func (s *PublishJobService) Create(ctx context.Context, actor auth.Actor, req Cr
|
||||
|
||||
for _, task := range createdTasks {
|
||||
if publishErr := publishDesktopTaskEvent(context.Background(), s.messaging, DesktopTaskEvent{
|
||||
Type: "task_available",
|
||||
TaskID: task.DesktopID.String(),
|
||||
JobID: task.JobID.String(),
|
||||
WorkspaceID: task.WorkspaceID,
|
||||
TargetClientID: task.TargetClientID.String(),
|
||||
Status: task.Status,
|
||||
Kind: task.Kind,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
Type: "task_available",
|
||||
TaskID: task.DesktopID.String(),
|
||||
JobID: task.JobID.String(),
|
||||
WorkspaceID: task.WorkspaceID,
|
||||
TargetAccountID: task.TargetAccountID.String(),
|
||||
TargetClientID: task.TargetClientID.String(),
|
||||
Platform: task.Platform,
|
||||
Status: task.Status,
|
||||
Kind: task.Kind,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
}); publishErr != nil {
|
||||
s.logWarn("desktop task available event publish failed", publishErr, zap.String("task_id", task.DesktopID.String()))
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
DELETE FROM desktop_tasks
|
||||
WHERE platform_id IN ('yuanbao', 'kimi', 'wenxin', 'deepseek', 'doubao', 'qwen');
|
||||
|
||||
DELETE FROM platform_accounts
|
||||
WHERE platform_id IN ('yuanbao', 'kimi', 'wenxin', 'deepseek', 'doubao', 'qwen');
|
||||
|
||||
ALTER TABLE platform_accounts
|
||||
ADD CONSTRAINT platform_accounts_platform_id_fkey
|
||||
FOREIGN KEY (platform_id) REFERENCES media_platforms(platform_id);
|
||||
|
||||
ALTER TABLE desktop_tasks
|
||||
ADD CONSTRAINT desktop_tasks_platform_id_fkey
|
||||
FOREIGN KEY (platform_id) REFERENCES media_platforms(platform_id);
|
||||
|
||||
DROP INDEX IF EXISTS idx_ai_platforms_status_sort;
|
||||
DROP INDEX IF EXISTS uk_ai_platforms_platform_id;
|
||||
DROP TABLE IF EXISTS ai_platforms;
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
CREATE TABLE IF NOT EXISTS ai_platforms (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ai_platform_id VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
short_name VARCHAR(16) NOT NULL,
|
||||
accent_color VARCHAR(16) NOT NULL DEFAULT '#1677ff',
|
||||
login_url TEXT,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_ai_platforms_platform_id
|
||||
ON ai_platforms(ai_platform_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_platforms_status_sort
|
||||
ON ai_platforms(status, sort_order, id);
|
||||
|
||||
INSERT INTO ai_platforms (
|
||||
ai_platform_id,
|
||||
name,
|
||||
short_name,
|
||||
accent_color,
|
||||
login_url,
|
||||
status,
|
||||
sort_order
|
||||
)
|
||||
VALUES
|
||||
('yuanbao', '混元 / 元宝', '元', '#19bf6b', 'https://yuanbao.tencent.com/', 'active', 10),
|
||||
('kimi', 'Kimi', 'K', '#111827', 'https://kimi.moonshot.cn/', 'active', 20),
|
||||
('wenxin', '文心一言', '文', '#20c6d7', 'https://yiyan.baidu.com/', 'active', 30),
|
||||
('deepseek', 'DeepSeek', 'D', '#4468ff', 'https://chat.deepseek.com/', 'active', 40),
|
||||
('doubao', '豆包', '豆', '#ff9348', 'https://www.doubao.com/', 'active', 50),
|
||||
('qwen', '通义千问', '通', '#276ef1', 'https://www.qianwen.com/', 'active', 60)
|
||||
ON CONFLICT (ai_platform_id)
|
||||
DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
short_name = EXCLUDED.short_name,
|
||||
accent_color = EXCLUDED.accent_color,
|
||||
login_url = EXCLUDED.login_url,
|
||||
status = EXCLUDED.status,
|
||||
sort_order = EXCLUDED.sort_order,
|
||||
updated_at = NOW();
|
||||
|
||||
ALTER TABLE platform_accounts
|
||||
DROP CONSTRAINT IF EXISTS platform_accounts_platform_id_fkey;
|
||||
|
||||
ALTER TABLE desktop_tasks
|
||||
DROP CONSTRAINT IF EXISTS desktop_tasks_platform_id_fkey;
|
||||
Reference in New Issue
Block a user