소스 검색

Add initial cookie and localStorage state for baijiahao.baidu.com

ethanfly 1 주 전
부모
커밋
ebc0e046a4
100개의 변경된 파일1435개의 추가작업 그리고 308개의 파일을 삭제
  1. 1 0
      .claude/worktrees/agent-a0ea95e3a0aaf90db
  2. 413 0
      .omc/project-memory.json
  3. 1 0
      .omc/state/agent-replay-4a7355c7-277a-4ce0-9706-6694df755de5.jsonl
  4. 4 0
      .omc/state/agent-replay-e43a383e-3552-4383-a827-3315dc2143c4.jsonl
  5. 15 0
      .omc/state/agent-replay-eaf783b1-7e09-4d54-b09d-57d798ddc8df.jsonl
  6. 6 0
      .omc/state/hud-state.json
  7. 1 0
      .omc/state/hud-stdin-cache.json
  8. 3 0
      .omc/state/idle-notif-cooldown.json
  9. 7 0
      .omc/state/last-tool-error.json
  10. 207 0
      .omc/state/mission-state.json
  11. 35 0
      .omc/state/subagent-tracking.json
  12. 2 0
      client/.omc/state/agent-replay-4a7355c7-277a-4ce0-9706-6694df755de5.jsonl
  13. 3 0
      client/.omc/state/idle-notif-cooldown.json
  14. 7 0
      client/.omc/state/last-tool-error.json
  15. 7 0
      client/.omc/state/subagent-tracking.json
  16. 2 2
      client/index.html
  17. 48 35
      client/public/favicon.svg
  18. BIN
      client/public/icons/icon-128.png
  19. BIN
      client/public/icons/icon-16.png
  20. BIN
      client/public/icons/icon-24.png
  21. BIN
      client/public/icons/icon-256.png
  22. BIN
      client/public/icons/icon-32.png
  23. BIN
      client/public/icons/icon-48.png
  24. BIN
      client/public/icons/icon-512.png
  25. BIN
      client/public/icons/icon-64.png
  26. BIN
      client/public/icons/logo.png
  27. BIN
      client/public/icons/tray-icon.png
  28. BIN
      client/public/icons/tray-icon@2x.png
  29. 16 0
      client/public/tray-icon.svg
  30. 54 78
      client/scripts/generate-icons.js
  31. 1 0
      client/src/components.d.ts
  32. 36 0
      client/src/components/AppIconMark.vue
  33. 4 31
      client/src/components/AppLogo.vue
  34. 2 3
      client/src/components/BrowserTab.vue
  35. 31 7
      client/src/components/CaptchaDialog.vue
  36. 23 39
      client/src/layouts/MainLayout.vue
  37. 12 3
      client/src/stores/taskQueue.ts
  38. 38 0
      client/src/utils/captchaImage.ts
  39. 19 1
      client/src/utils/format.ts
  40. 11 1
      client/src/views/Accounts/index.vue
  41. 15 8
      client/src/views/Publish/index.vue
  42. 24 1
      client/src/views/Works/index.vue
  43. 4 0
      client/vite.config.ts
  44. BIN
      docs/logo.png
  45. 0 99
      draw_icon.py
  46. BIN
      minimax-output/accounts-layout-fix.png
  47. BIN
      minimax-output/accounts-layout-hover-fix.png
  48. BIN
      minimax-output/accounts-title-fix.png
  49. BIN
      minimax-output/after-auth/login.png
  50. BIN
      minimax-output/after-auth/register.png
  51. BIN
      minimax-output/after/accounts.png
  52. BIN
      minimax-output/after/analytics-account.png
  53. BIN
      minimax-output/after/analytics-overview.png
  54. BIN
      minimax-output/after/analytics-platform.png
  55. BIN
      minimax-output/after/analytics-work.png
  56. BIN
      minimax-output/after/comments.png
  57. BIN
      minimax-output/after/dashboard.png
  58. BIN
      minimax-output/after/login.png
  59. BIN
      minimax-output/after/profile.png
  60. BIN
      minimax-output/after/publish.png
  61. BIN
      minimax-output/after/register.png
  62. BIN
      minimax-output/after/schedule.png
  63. BIN
      minimax-output/after/server-config.png
  64. BIN
      minimax-output/after/settings.png
  65. BIN
      minimax-output/after/works.png
  66. BIN
      minimax-output/analytics-overview-title-fix.png
  67. 83 0
      minimax-output/auth-layout-module-5174.js
  68. BIN
      minimax-output/baseline/accounts.png
  69. BIN
      minimax-output/baseline/analytics-account.png
  70. BIN
      minimax-output/baseline/analytics-overview.png
  71. BIN
      minimax-output/baseline/analytics-platform.png
  72. BIN
      minimax-output/baseline/analytics-work.png
  73. BIN
      minimax-output/baseline/comments.png
  74. BIN
      minimax-output/baseline/dashboard.png
  75. BIN
      minimax-output/baseline/profile.png
  76. BIN
      minimax-output/baseline/publish.png
  77. BIN
      minimax-output/baseline/schedule.png
  78. BIN
      minimax-output/baseline/server-config.png
  79. BIN
      minimax-output/baseline/settings.png
  80. BIN
      minimax-output/baseline/works.png
  81. BIN
      minimax-output/dashboard-auth.png
  82. BIN
      minimax-output/dashboard-fix-check.png
  83. BIN
      minimax-output/dashboard-title-fix.png
  84. BIN
      minimax-output/debug-body.png
  85. BIN
      minimax-output/electron-initial.png
  86. BIN
      minimax-output/login-check.png
  87. 266 0
      minimax-output/login-module-5174.js
  88. BIN
      minimax-output/publish-title-fix.png
  89. BIN
      minimax-output/regression-current-login.png
  90. BIN
      minimax-output/regression-probe-after-login.png
  91. BIN
      minimax-output/regression-probe-dashboard.png
  92. BIN
      minimax-output/regression-probe-login.png
  93. BIN
      minimax-output/regression/latest-dev/accounts.png
  94. BIN
      minimax-output/regression/latest-dev/analytics-account.png
  95. BIN
      minimax-output/regression/latest-dev/analytics-overview.png
  96. BIN
      minimax-output/regression/latest-dev/analytics-platform.png
  97. BIN
      minimax-output/regression/latest-dev/analytics-work.png
  98. BIN
      minimax-output/regression/latest-dev/dashboard.png
  99. 34 0
      minimax-output/regression/latest-dev/manifest.json
  100. BIN
      minimax-output/regression/latest-dev/publish.png

+ 1 - 0
.claude/worktrees/agent-a0ea95e3a0aaf90db

@@ -0,0 +1 @@
+Subproject commit 091c051a6d2220f1036c8f795b25676e316af5d2

+ 413 - 0
.omc/project-memory.json

@@ -0,0 +1,413 @@
+{
+  "version": "1.0.0",
+  "lastScanned": 1777429590245,
+  "projectRoot": "C:\\workspace\\multi-platform-media-manage",
+  "techStack": {
+    "languages": [
+      {
+        "name": "JavaScript/TypeScript",
+        "version": ">=20.0.0",
+        "confidence": "high",
+        "markers": [
+          "package.json"
+        ]
+      }
+    ],
+    "frameworks": [],
+    "packageManager": "pnpm",
+    "runtime": "Node.js 20.0.0"
+  },
+  "build": {
+    "buildCommand": "pnpm build",
+    "testCommand": null,
+    "lintCommand": "pnpm lint",
+    "devCommand": "pnpm dev",
+    "scripts": {
+      "dev": "concurrently \"pnpm --filter server dev\" \"pnpm --filter client dev\"",
+      "dev:server": "pnpm --filter server dev",
+      "dev:client": "pnpm --filter client dev",
+      "build": "pnpm --filter client build",
+      "build:server": "pnpm --filter server build",
+      "build:client": "pnpm --filter client build",
+      "check:publish-flow": "node scripts/check-publish-flow.mjs",
+      "ui:screenshots": "node scripts/capture-ui-screenshots.mjs",
+      "lint": "pnpm -r lint",
+      "clean": "pnpm -r clean"
+    }
+  },
+  "conventions": {
+    "namingStyle": null,
+    "importStyle": null,
+    "testPattern": null,
+    "fileOrganization": null
+  },
+  "structure": {
+    "isMonorepo": true,
+    "workspaces": [],
+    "mainDirectories": [
+      "docs",
+      "scripts"
+    ],
+    "gitBranches": null
+  },
+  "customNotes": [],
+  "directoryMap": {
+    "client": {
+      "path": "client",
+      "purpose": null,
+      "fileCount": 5,
+      "lastAccessed": 1777429590213,
+      "keyFiles": [
+        "electron-builder.json",
+        "index.html",
+        "package.json",
+        "tsconfig.json",
+        "vite.config.ts"
+      ]
+    },
+    "database": {
+      "path": "database",
+      "purpose": null,
+      "fileCount": 1,
+      "lastAccessed": 1777429590214,
+      "keyFiles": [
+        "schema.sql"
+      ]
+    },
+    "docs": {
+      "path": "docs",
+      "purpose": "Documentation",
+      "fileCount": 18,
+      "lastAccessed": 1777429590221,
+      "keyFiles": [
+        "baijiahao-api-complete.md",
+        "baijiahao-api-debug.md",
+        "baijiahao-backend-access-fix.md",
+        "baijiahao-cookie-validation.md",
+        "baijiahao-python-api-implementation.md"
+      ]
+    },
+    "minimax-output": {
+      "path": "minimax-output",
+      "purpose": null,
+      "fileCount": 27,
+      "lastAccessed": 1777429590222,
+      "keyFiles": [
+        "accounts-layout-fix.png",
+        "accounts-layout-hover-fix.png",
+        "accounts-title-fix.png",
+        "analytics-overview-title-fix.png",
+        "auth-layout-module-5174.js"
+      ]
+    },
+    "scripts": {
+      "path": "scripts",
+      "purpose": "Build/utility scripts",
+      "fileCount": 7,
+      "lastAccessed": 1777429590222,
+      "keyFiles": [
+        "capture-ui-screenshots.mjs",
+        "check-captcha-image-src.mjs",
+        "check-publish-flow.mjs",
+        "check-work-cover-cache.ts",
+        "check-work-cover-extraction.mjs"
+      ]
+    },
+    "server": {
+      "path": "server",
+      "purpose": null,
+      "fileCount": 11,
+      "lastAccessed": 1777429590222,
+      "keyFiles": [
+        "docker-compose.yml",
+        "Dockerfile",
+        "env.example",
+        "get_account.cjs",
+        "get_shenlong_config.cjs"
+      ]
+    },
+    "shared": {
+      "path": "shared",
+      "purpose": null,
+      "fileCount": 2,
+      "lastAccessed": 1777429590223,
+      "keyFiles": [
+        "package.json",
+        "tsconfig.json"
+      ]
+    },
+    "uploads": {
+      "path": "uploads",
+      "purpose": null,
+      "fileCount": 0,
+      "lastAccessed": 1777429590223,
+      "keyFiles": []
+    },
+    "client\\build": {
+      "path": "client\\build",
+      "purpose": "Build output",
+      "fileCount": 1,
+      "lastAccessed": 1777429590224,
+      "keyFiles": [
+        "icon.png"
+      ]
+    },
+    "client\\dist": {
+      "path": "client\\dist",
+      "purpose": "Distribution/build output",
+      "fileCount": 3,
+      "lastAccessed": 1777429590224,
+      "keyFiles": [
+        "favicon.svg",
+        "index.html",
+        "tray-icon.svg"
+      ]
+    },
+    "client\\node_modules": {
+      "path": "client\\node_modules",
+      "purpose": "Dependencies",
+      "fileCount": 0,
+      "lastAccessed": 1777429590225,
+      "keyFiles": []
+    },
+    "client\\public": {
+      "path": "client\\public",
+      "purpose": "Public files",
+      "fileCount": 2,
+      "lastAccessed": 1777429590225,
+      "keyFiles": [
+        "favicon.svg",
+        "tray-icon.svg"
+      ]
+    },
+    "database\\migrations": {
+      "path": "database\\migrations",
+      "purpose": "Database migrations",
+      "fileCount": 20,
+      "lastAccessed": 1777429590225,
+      "keyFiles": [
+        "add_exposure_count_to_user_day_statistics.sql",
+        "add_fields_to_user_day_statistics.sql",
+        "add_fields_to_work_day_statistics.sql"
+      ]
+    },
+    "server\\dist": {
+      "path": "server\\dist",
+      "purpose": "Distribution/build output",
+      "fileCount": 4,
+      "lastAccessed": 1777429590227,
+      "keyFiles": [
+        "app.d.ts",
+        "app.d.ts.map",
+        "app.js"
+      ]
+    },
+    "server\\node_modules": {
+      "path": "server\\node_modules",
+      "purpose": "Dependencies",
+      "fileCount": 0,
+      "lastAccessed": 1777429590227,
+      "keyFiles": []
+    },
+    "server\\src": {
+      "path": "server\\src",
+      "purpose": "Source code",
+      "fileCount": 1,
+      "lastAccessed": 1777429590227,
+      "keyFiles": [
+        "app.ts"
+      ]
+    },
+    "shared\\dist": {
+      "path": "shared\\dist",
+      "purpose": "Distribution/build output",
+      "fileCount": 4,
+      "lastAccessed": 1777429590228,
+      "keyFiles": [
+        "index.d.ts",
+        "index.d.ts.map",
+        "index.js"
+      ]
+    },
+    "shared\\src": {
+      "path": "shared\\src",
+      "purpose": "Source code",
+      "fileCount": 1,
+      "lastAccessed": 1777429590228,
+      "keyFiles": [
+        "index.ts"
+      ]
+    }
+  },
+  "hotPaths": [
+    {
+      "path": "server\\src\\services\\HeadlessBrowserService.ts",
+      "accessCount": 56,
+      "lastAccessed": 1777431958453,
+      "type": "file"
+    },
+    {
+      "path": "server\\src",
+      "accessCount": 9,
+      "lastAccessed": 1777431605918,
+      "type": "directory"
+    },
+    {
+      "path": "server\\src\\services\\login\\BaseLoginService.ts",
+      "accessCount": 6,
+      "lastAccessed": 1777430456122,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\automation\\browser.ts",
+      "accessCount": 4,
+      "lastAccessed": 1777430493051,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\app.ts",
+      "accessCount": 4,
+      "lastAccessed": 1777431578166,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\utils\\logger.ts",
+      "accessCount": 3,
+      "lastAccessed": 1777431717984,
+      "type": "file"
+    },
+    {
+      "path": "",
+      "accessCount": 2,
+      "lastAccessed": 1777429637491,
+      "type": "directory"
+    },
+    {
+      "path": "server\\src\\websocket\\index.ts",
+      "accessCount": 2,
+      "lastAccessed": 1777429869955,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\services\\TaskQueueService.ts",
+      "accessCount": 2,
+      "lastAccessed": 1777429869968,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\scheduler\\index.ts",
+      "accessCount": 2,
+      "lastAccessed": 1777429881860,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\services\\login\\LoginServiceManager.ts",
+      "accessCount": 2,
+      "lastAccessed": 1777429939199,
+      "type": "file"
+    },
+    {
+      "path": "package.json",
+      "accessCount": 1,
+      "lastAccessed": 1777429621794,
+      "type": "file"
+    },
+    {
+      "path": "server\\package.json",
+      "accessCount": 1,
+      "lastAccessed": 1777429621848,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\services\\taskExecutors.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429650571,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\config\\redis.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429661380,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\automation\\platforms\\index.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429661434,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\routes\\index.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429661472,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\models\\index.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429661478,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\automation\\cookie.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429661559,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\ai\\index.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429661730,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\config\\index.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429684839,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\utils\\workCoverCache.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429684857,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\services\\login\\index.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429684903,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\middleware\\error.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429684916,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\middleware\\auth.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429684977,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\utils\\platformWorkCover.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429695591,
+      "type": "file"
+    },
+    {
+      "path": "server\\src\\services",
+      "accessCount": 1,
+      "lastAccessed": 1777429695668,
+      "type": "directory"
+    },
+    {
+      "path": "server\\src\\services\\BrowserLoginService.ts",
+      "accessCount": 1,
+      "lastAccessed": 1777429939210,
+      "type": "file"
+    }
+  ],
+  "userDirectives": []
+}

+ 1 - 0
.omc/state/agent-replay-4a7355c7-277a-4ce0-9706-6694df755de5.jsonl

@@ -0,0 +1 @@
+{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:systematic-debugging"}

+ 4 - 0
.omc/state/agent-replay-e43a383e-3552-4383-a827-3315dc2143c4.jsonl

@@ -0,0 +1,4 @@
+{"t":0,"agent":"a8908b0","agent_type":"Explore","event":"agent_start","parent_mode":"none"}
+{"t":0,"agent":"a8908b0","agent_type":"Explore","event":"agent_stop","success":true,"duration_ms":245247}
+{"t":0,"agent":"ab92ba7","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"abb12fd","agent_type":"unknown","event":"agent_stop","success":true}

+ 15 - 0
.omc/state/agent-replay-eaf783b1-7e09-4d54-b09d-57d798ddc8df.jsonl

@@ -0,0 +1,15 @@
+{"t":0,"agent":"a03e9f6","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a7f5d3a","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a4546a9","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a3a8981","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"}
+{"t":0,"agent":"a21f30a","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"}
+{"t":0,"agent":"a0072e5","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a3a8981","agent_type":"general-purpose","event":"agent_stop","success":true,"duration_ms":414161}
+{"t":0,"agent":"ae7bd94","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a21f30a","agent_type":"general-purpose","event":"agent_stop","success":true,"duration_ms":1209189}
+{"t":0,"agent":"ae9297a","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a8438bb","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a952060","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"ac92d48","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a17f61b","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a9e2477","agent_type":"unknown","event":"agent_stop","success":true}

+ 6 - 0
.omc/state/hud-state.json

@@ -0,0 +1,6 @@
+{
+  "timestamp": "2026-04-29T02:26:51.313Z",
+  "backgroundTasks": [],
+  "sessionStartTimestamp": "2026-04-29T02:26:30.180Z",
+  "sessionId": "e43a383e-3552-4383-a827-3315dc2143c4"
+}

+ 1 - 0
.omc/state/hud-stdin-cache.json

@@ -0,0 +1 @@
+{"session_id":"e43a383e-3552-4383-a827-3315dc2143c4","transcript_path":"C:\\Users\\Ethan\\.claude\\projects\\C--workspace-multi-platform-media-manage\\e43a383e-3552-4383-a827-3315dc2143c4.jsonl","cwd":"C:\\workspace\\multi-platform-media-manage","model":{"id":"glm-5.1","display_name":"glm-5.1"},"workspace":{"current_dir":"C:\\workspace\\multi-platform-media-manage","project_dir":"C:\\workspace\\multi-platform-media-manage","added_dirs":[]},"version":"2.1.119","output_style":{"name":"default"},"cost":{"total_cost_usd":6.792232000000002,"total_duration_ms":2761277,"total_api_duration_ms":1683183,"total_lines_added":55,"total_lines_removed":23},"context_window":{"total_input_tokens":387153,"total_output_tokens":28843,"context_window_size":200000,"current_usage":{"input_tokens":9466,"output_tokens":87,"cache_creation_input_tokens":0,"cache_read_input_tokens":87744},"used_percentage":49,"remaining_percentage":51},"exceeds_200k_tokens":false,"fast_mode":false,"effort":{"level":"high"},"thinking":{"enabled":true}}

+ 3 - 0
.omc/state/idle-notif-cooldown.json

@@ -0,0 +1,3 @@
+{
+  "lastSentAt": "2026-04-29T03:12:30.523Z"
+}

+ 7 - 0
.omc/state/last-tool-error.json

@@ -0,0 +1,7 @@
+{
+  "tool_name": "Read",
+  "tool_input_preview": "{\"file_path\":\"C:\\\\workspace\\\\multi-platform-media-manage\\\\server\\\\src\\\\services\\\\HeadlessBrowserService.ts\"}",
+  "error": "File content (48138 tokens) exceeds maximum allowed tokens (25000). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.",
+  "timestamp": "2026-04-29T02:28:05.721Z",
+  "retry_count": 2
+}

+ 207 - 0
.omc/state/mission-state.json

@@ -0,0 +1,207 @@
+{
+  "updatedAt": "2026-04-29T03:15:44.485Z",
+  "missions": [
+    {
+      "id": "session:eaf783b1-7e09-4d54-b09d-57d798ddc8df:none",
+      "source": "session",
+      "name": "none",
+      "objective": "Session mission",
+      "createdAt": "2026-03-30T02:34:25.722Z",
+      "updatedAt": "2026-03-30T03:17:23.484Z",
+      "status": "done",
+      "workerCount": 2,
+      "taskCounts": {
+        "total": 2,
+        "pending": 0,
+        "blocked": 0,
+        "inProgress": 0,
+        "completed": 2,
+        "failed": 0
+      },
+      "agents": [
+        {
+          "name": "general-purpose:a3a8981",
+          "role": "general-purpose",
+          "ownership": "a3a8981b0affb63b9",
+          "status": "done",
+          "currentStep": null,
+          "latestUpdate": "completed",
+          "completedSummary": null,
+          "updatedAt": "2026-03-30T03:17:23.484Z"
+        },
+        {
+          "name": "general-purpose:a21f30a",
+          "role": "general-purpose",
+          "ownership": "a21f30a4c1773148b",
+          "status": "done",
+          "currentStep": null,
+          "latestUpdate": "completed",
+          "completedSummary": null,
+          "updatedAt": "2026-03-30T02:54:34.972Z"
+        }
+      ],
+      "timeline": [
+        {
+          "id": "session-start:a3a8981b0affb63b9:2026-03-30T02:34:25.722Z",
+          "at": "2026-03-30T02:34:25.722Z",
+          "kind": "update",
+          "agent": "general-purpose:a3a8981",
+          "detail": "started general-purpose:a3a8981",
+          "sourceKey": "session-start:a3a8981b0affb63b9"
+        },
+        {
+          "id": "session-start:a21f30a4c1773148b:2026-03-30T02:34:25.783Z",
+          "at": "2026-03-30T02:34:25.783Z",
+          "kind": "update",
+          "agent": "general-purpose:a21f30a",
+          "detail": "started general-purpose:a21f30a",
+          "sourceKey": "session-start:a21f30a4c1773148b"
+        },
+        {
+          "id": "session-stop:a0072e5b40aed2637:2026-03-30T02:34:40.887Z",
+          "at": "2026-03-30T02:34:40.887Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:a0072e5b40aed2637"
+        },
+        {
+          "id": "session-stop:a3a8981b0affb63b9:2026-03-30T02:41:19.883Z",
+          "at": "2026-03-30T02:41:19.883Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:a3a8981b0affb63b9"
+        },
+        {
+          "id": "session-stop:ae7bd94cf693af524:2026-03-30T02:41:31.731Z",
+          "at": "2026-03-30T02:41:31.731Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:ae7bd94cf693af524"
+        },
+        {
+          "id": "session-stop:a21f30a4c1773148b:2026-03-30T02:54:34.972Z",
+          "at": "2026-03-30T02:54:34.972Z",
+          "kind": "completion",
+          "agent": "general-purpose:a21f30a",
+          "detail": "completed",
+          "sourceKey": "session-stop:a21f30a4c1773148b"
+        },
+        {
+          "id": "session-stop:ae9297a684eb95554:2026-03-30T02:55:53.389Z",
+          "at": "2026-03-30T02:55:53.389Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:ae9297a684eb95554"
+        },
+        {
+          "id": "session-stop:a8438bb33d7be261c:2026-03-30T03:08:53.982Z",
+          "at": "2026-03-30T03:08:53.982Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:a8438bb33d7be261c"
+        },
+        {
+          "id": "session-stop:a952060e6eedb6099:2026-03-30T03:11:02.403Z",
+          "at": "2026-03-30T03:11:02.403Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:a952060e6eedb6099"
+        },
+        {
+          "id": "session-stop:ac92d485b5d9e7f37:2026-03-30T03:14:01.282Z",
+          "at": "2026-03-30T03:14:01.282Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:ac92d485b5d9e7f37"
+        },
+        {
+          "id": "session-stop:a17f61beed89fb45f:2026-03-30T03:16:48.738Z",
+          "at": "2026-03-30T03:16:48.738Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:a17f61beed89fb45f"
+        },
+        {
+          "id": "session-stop:a9e24770bd8f88014:2026-03-30T03:17:23.484Z",
+          "at": "2026-03-30T03:17:23.484Z",
+          "kind": "completion",
+          "agent": "general-purpose:a3a8981",
+          "detail": "completed",
+          "sourceKey": "session-stop:a9e24770bd8f88014"
+        }
+      ]
+    },
+    {
+      "id": "session:e43a383e-3552-4383-a827-3315dc2143c4:none",
+      "source": "session",
+      "name": "none",
+      "objective": "Session mission",
+      "createdAt": "2026-04-29T02:26:54.843Z",
+      "updatedAt": "2026-04-29T03:15:44.485Z",
+      "status": "done",
+      "workerCount": 1,
+      "taskCounts": {
+        "total": 1,
+        "pending": 0,
+        "blocked": 0,
+        "inProgress": 0,
+        "completed": 1,
+        "failed": 0
+      },
+      "agents": [
+        {
+          "name": "Explore:a8908b0",
+          "role": "Explore",
+          "ownership": "a8908b0462688d715",
+          "status": "done",
+          "currentStep": null,
+          "latestUpdate": "completed",
+          "completedSummary": null,
+          "updatedAt": "2026-04-29T03:15:44.485Z"
+        }
+      ],
+      "timeline": [
+        {
+          "id": "session-start:a8908b0462688d715:2026-04-29T02:26:54.843Z",
+          "at": "2026-04-29T02:26:54.843Z",
+          "kind": "update",
+          "agent": "Explore:a8908b0",
+          "detail": "started Explore:a8908b0",
+          "sourceKey": "session-start:a8908b0462688d715"
+        },
+        {
+          "id": "session-stop:a8908b0462688d715:2026-04-29T02:31:00.090Z",
+          "at": "2026-04-29T02:31:00.090Z",
+          "kind": "completion",
+          "agent": "Explore:a8908b0",
+          "detail": "completed",
+          "sourceKey": "session-stop:a8908b0462688d715"
+        },
+        {
+          "id": "session-stop:ab92ba70314846fa2:2026-04-29T02:38:18.135Z",
+          "at": "2026-04-29T02:38:18.135Z",
+          "kind": "completion",
+          "agent": "Explore:a8908b0",
+          "detail": "completed",
+          "sourceKey": "session-stop:ab92ba70314846fa2"
+        },
+        {
+          "id": "session-stop:abb12fd3ef4bbdeec:2026-04-29T03:15:44.485Z",
+          "at": "2026-04-29T03:15:44.485Z",
+          "kind": "completion",
+          "agent": "Explore:a8908b0",
+          "detail": "completed",
+          "sourceKey": "session-stop:abb12fd3ef4bbdeec"
+        }
+      ]
+    }
+  ]
+}

+ 35 - 0
.omc/state/subagent-tracking.json

@@ -0,0 +1,35 @@
+{
+  "agents": [
+    {
+      "agent_id": "a3a8981b0affb63b9",
+      "agent_type": "general-purpose",
+      "started_at": "2026-03-30T02:34:25.722Z",
+      "parent_mode": "none",
+      "status": "completed",
+      "completed_at": "2026-03-30T02:41:19.883Z",
+      "duration_ms": 414161
+    },
+    {
+      "agent_id": "a21f30a4c1773148b",
+      "agent_type": "general-purpose",
+      "started_at": "2026-03-30T02:34:25.783Z",
+      "parent_mode": "none",
+      "status": "completed",
+      "completed_at": "2026-03-30T02:54:34.972Z",
+      "duration_ms": 1209189
+    },
+    {
+      "agent_id": "a8908b0462688d715",
+      "agent_type": "Explore",
+      "started_at": "2026-04-29T02:26:54.843Z",
+      "parent_mode": "none",
+      "status": "completed",
+      "completed_at": "2026-04-29T02:31:00.090Z",
+      "duration_ms": 245247
+    }
+  ],
+  "total_spawned": 2,
+  "total_completed": 3,
+  "total_failed": 0,
+  "last_updated": "2026-04-29T03:15:44.595Z"
+}

+ 2 - 0
client/.omc/state/agent-replay-4a7355c7-277a-4ce0-9706-6694df755de5.jsonl

@@ -0,0 +1,2 @@
+{"t":0,"agent":"a3decce","agent_type":"unknown","event":"agent_stop","success":true}
+{"t":0,"agent":"a8e80d9","agent_type":"unknown","event":"agent_stop","success":true}

+ 3 - 0
client/.omc/state/idle-notif-cooldown.json

@@ -0,0 +1,3 @@
+{
+  "lastSentAt": "2026-03-30T04:15:07.226Z"
+}

+ 7 - 0
client/.omc/state/last-tool-error.json

@@ -0,0 +1,7 @@
+{
+  "tool_name": "Bash",
+  "tool_input_preview": "{\"command\":\"timeout 20 npx electron dist-electron/main.js 2>&1 | tee /tmp/electron-final.log | tail -5\",\"timeout\":30000,\"description\":\"最终验证 Electron 启动\"}",
+  "error": "Exit code 143\nTerminated",
+  "timestamp": "2026-03-30T03:59:53.869Z",
+  "retry_count": 1
+}

+ 7 - 0
client/.omc/state/subagent-tracking.json

@@ -0,0 +1,7 @@
+{
+  "agents": [],
+  "total_spawned": 0,
+  "total_completed": 0,
+  "total_failed": 0,
+  "last_updated": "2026-03-30T04:15:11.666Z"
+}

+ 2 - 2
client/index.html

@@ -5,7 +5,7 @@
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <meta http-equiv="Content-Security-Policy"
-    content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' http://localhost:* http://127.0.0.1:* http: https: ws://localhost:* ws://127.0.0.1:* ws: wss:" />
+    content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https: http://localhost:* http://127.0.0.1:*; connect-src 'self' http://localhost:* http://127.0.0.1:* http: https: ws://localhost:* ws://127.0.0.1:* ws: wss:" />
   <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
   <title>智媒通</title>
 </head>
@@ -15,4 +15,4 @@
   <script type="module" src="/src/main.ts"></script>
 </body>
 
-</html>
+</html>

+ 48 - 35
client/public/favicon.svg

@@ -1,43 +1,56 @@
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
   <defs>
-    <linearGradient id="bg-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
-      <stop offset="0%" style="stop-color:#4f8cff;stop-opacity:1" />
-      <stop offset="100%" style="stop-color:#6366f1;stop-opacity:1" />
+    <linearGradient id="app-bg" x1="88" y1="64" x2="424" y2="456" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#232833"/>
+      <stop offset="1" stop-color="#11151C"/>
     </linearGradient>
-    <linearGradient id="play-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
-      <stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
-      <stop offset="100%" style="stop-color:#e0e7ff;stop-opacity:1" />
+    <linearGradient id="app-accent" x1="154" y1="118" x2="358" y2="380" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#FF9852"/>
+      <stop offset="0.58" stop-color="#FF6B1A"/>
+      <stop offset="1" stop-color="#F04E00"/>
     </linearGradient>
+    <linearGradient id="app-cyan" x1="104" y1="104" x2="408" y2="408" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#2FE7C4"/>
+      <stop offset="1" stop-color="#18A6FF"/>
+    </linearGradient>
+    <filter id="app-shadow" x="-20%" y="-20%" width="140%" height="150%" color-interpolation-filters="sRGB">
+      <feDropShadow dx="0" dy="18" stdDeviation="20" flood-color="#05070A" flood-opacity="0.34"/>
+    </filter>
   </defs>
-  
-  <!-- 背景圆角矩形 -->
-  <rect x="32" y="32" width="448" height="448" rx="96" ry="96" fill="url(#bg-gradient)"/>
-  
-  <!-- 多平台网格图案 -->
-  <g opacity="0.15">
-    <rect x="80" y="80" width="160" height="160" rx="24" fill="#fff"/>
-    <rect x="272" y="80" width="160" height="160" rx="24" fill="#fff"/>
-    <rect x="80" y="272" width="160" height="160" rx="24" fill="#fff"/>
-    <rect x="272" y="272" width="160" height="160" rx="24" fill="#fff"/>
+
+  <rect x="32" y="32" width="448" height="448" rx="108" fill="url(#app-bg)"/>
+  <path d="M76 380C142 422 230 444 320 428C382 417 430 386 480 342V372C480 431.6 431.6 480 372 480H140C80.4 480 32 431.6 32 372V330C46 348 60 365 76 380Z" fill="#070A0F" opacity="0.28"/>
+
+  <g fill="#262C37" opacity="0.95">
+    <rect x="72" y="108" width="92" height="92" rx="28"/>
+    <rect x="348" y="108" width="92" height="92" rx="28"/>
+    <rect x="72" y="312" width="92" height="92" rx="28"/>
+    <rect x="348" y="312" width="92" height="92" rx="28"/>
+  </g>
+
+  <g fill="none" stroke-linecap="round" stroke-width="18">
+    <path d="M152 162L210 210" stroke="#FF7A2A" opacity="0.78"/>
+    <path d="M360 162L302 210" stroke="#FF7A2A" opacity="0.78"/>
+    <path d="M152 350L210 302" stroke="#FF7A2A" opacity="0.78"/>
+    <path d="M360 350L302 302" stroke="url(#app-cyan)" opacity="0.72"/>
+  </g>
+
+  <g filter="url(#app-shadow)">
+    <rect x="148" y="132" width="216" height="248" rx="58" fill="url(#app-accent)"/>
+    <rect x="180" y="166" width="116" height="20" rx="10" fill="#FFFFFF" opacity="0.28"/>
+    <rect x="180" y="326" width="152" height="20" rx="10" fill="#FFFFFF" opacity="0.55"/>
+    <rect x="180" y="356" width="90" height="14" rx="7" fill="#FFFFFF" opacity="0.28"/>
+    <path d="M225 211V301L307 256L225 211Z" fill="#FFFFFF"/>
   </g>
-  
-  <!-- 中心播放按钮 -->
-  <circle cx="256" cy="256" r="120" fill="url(#play-gradient)" opacity="0.95"/>
-  
-  <!-- 播放三角形 -->
-  <path d="M 220 180 L 220 332 L 340 256 Z" fill="url(#bg-gradient)"/>
-  
-  <!-- 装饰性连接线 -->
-  <g stroke="#fff" stroke-width="4" stroke-linecap="round" opacity="0.6">
-    <line x1="136" y1="160" x2="180" y2="200"/>
-    <line x1="376" y1="160" x2="332" y2="200"/>
-    <line x1="136" y1="352" x2="180" y2="312"/>
-    <line x1="376" y1="352" x2="332" y2="312"/>
+
+  <g>
+    <circle cx="118" cy="154" r="24" fill="#FF7A2A"/>
+    <circle cx="394" cy="154" r="24" fill="#FFD166"/>
+    <circle cx="118" cy="358" r="24" fill="#FFFFFF" opacity="0.92"/>
+    <circle cx="394" cy="358" r="24" fill="url(#app-cyan)"/>
+    <circle cx="118" cy="154" r="9" fill="#FFFFFF" opacity="0.84"/>
+    <circle cx="394" cy="154" r="9" fill="#171A21" opacity="0.72"/>
+    <circle cx="118" cy="358" r="9" fill="#FF5C00" opacity="0.9"/>
+    <circle cx="394" cy="358" r="9" fill="#FFFFFF" opacity="0.88"/>
   </g>
-  
-  <!-- 角落小圆点 -->
-  <circle cx="136" cy="160" r="12" fill="#fff" opacity="0.8"/>
-  <circle cx="376" cy="160" r="12" fill="#fff" opacity="0.8"/>
-  <circle cx="136" cy="352" r="12" fill="#fff" opacity="0.8"/>
-  <circle cx="376" cy="352" r="12" fill="#fff" opacity="0.8"/>
 </svg>

BIN
client/public/icons/icon-128.png


BIN
client/public/icons/icon-16.png


BIN
client/public/icons/icon-24.png


BIN
client/public/icons/icon-256.png


BIN
client/public/icons/icon-32.png


BIN
client/public/icons/icon-48.png


BIN
client/public/icons/icon-512.png


BIN
client/public/icons/icon-64.png


BIN
client/public/icons/logo.png


BIN
client/public/icons/tray-icon.png


BIN
client/public/icons/tray-icon@2x.png


+ 16 - 0
client/public/tray-icon.svg

@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+  <defs>
+    <linearGradient id="tray-bg" x1="82" y1="72" x2="430" y2="440" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#FF9852"/>
+      <stop offset="0.62" stop-color="#FF6B1A"/>
+      <stop offset="1" stop-color="#F04E00"/>
+    </linearGradient>
+  </defs>
+  <rect x="32" y="32" width="448" height="448" rx="112" fill="url(#tray-bg)"/>
+  <circle cx="148" cy="154" r="30" fill="#151A22" opacity="0.92"/>
+  <circle cx="148" cy="358" r="30" fill="#151A22" opacity="0.92"/>
+  <path d="M174 172L234 212" stroke="#151A22" stroke-width="24" stroke-linecap="round" opacity="0.88"/>
+  <path d="M174 340L234 300" stroke="#151A22" stroke-width="24" stroke-linecap="round" opacity="0.88"/>
+  <circle cx="256" cy="256" r="118" fill="#151A22"/>
+  <path d="M230 184V328L344 256L230 184Z" fill="#FFFFFF"/>
+</svg>

+ 54 - 78
client/scripts/generate-icons.js

@@ -1,101 +1,77 @@
-/**
- * 图标生成脚本
- * 从 SVG 生成各种尺寸的 PNG 图标
- * 
- * 使用方法:
- *   cd client
- *   npm install sharp --save-dev
- *   node scripts/generate-icons.js
- */
-
 const fs = require('fs');
 const path = require('path');
 
-// 检查是否安装了 sharp
 let sharp;
 try {
   sharp = require('sharp');
-} catch (e) {
-  console.error('请先安装 sharp: npm install sharp --save-dev');
+} catch (error) {
+  console.error('Missing dependency: sharp. Run pnpm install first.');
   process.exit(1);
 }
 
-const SVG_PATH = path.join(__dirname, '../public/favicon.svg');
-const OUTPUT_DIR = path.join(__dirname, '../public/icons');
-const BUILD_DIR = path.join(__dirname, '../build');
+const appSvgPath = path.join(__dirname, '../public/favicon.svg');
+const traySvgPath = path.join(__dirname, '../public/tray-icon.svg');
+const iconsDir = path.join(__dirname, '../public/icons');
+const buildDir = path.join(__dirname, '../build');
+const docsDir = path.join(__dirname, '../../docs');
+
+const iconSizes = [16, 24, 32, 48, 64, 128, 256, 512];
+const trayIcons = [
+  { name: 'tray-icon.png', size: 32 },
+  { name: 'tray-icon@2x.png', size: 64 },
+];
 
-// 需要生成的图标尺寸
-const ICON_SIZES = {
-  // 应用图标
-  'icon-16.png': 16,
-  'icon-24.png': 24,
-  'icon-32.png': 32,
-  'icon-48.png': 48,
-  'icon-64.png': 64,
-  'icon-128.png': 128,
-  'icon-256.png': 256,
-  'icon-512.png': 512,
-  
-  // 托盘图标 (Windows 需要 16x16 或 32x32)
-  'tray-icon.png': 32,
-  'tray-icon@2x.png': 64,
-  
-  // README logo
-  'logo.png': 128,
-};
+async function renderPng(sourceBuffer, targetPath, size) {
+  await sharp(sourceBuffer)
+    .resize(size, size, {
+      fit: 'contain',
+      kernel: sharp.kernel.lanczos3,
+    })
+    .png({
+      compressionLevel: 9,
+      adaptiveFiltering: true,
+      palette: size <= 64,
+    })
+    .toFile(targetPath);
+}
 
 async function generateIcons() {
-  // 确保输出目录存在
-  if (!fs.existsSync(OUTPUT_DIR)) {
-    fs.mkdirSync(OUTPUT_DIR, { recursive: true });
+  if (!fs.existsSync(appSvgPath)) {
+    throw new Error(`App icon SVG not found: ${appSvgPath}`);
   }
-  if (!fs.existsSync(BUILD_DIR)) {
-    fs.mkdirSync(BUILD_DIR, { recursive: true });
+  if (!fs.existsSync(traySvgPath)) {
+    throw new Error(`Tray icon SVG not found: ${traySvgPath}`);
   }
 
-  // 读取 SVG 文件
-  const svgBuffer = fs.readFileSync(SVG_PATH);
+  fs.mkdirSync(iconsDir, { recursive: true });
+  fs.mkdirSync(buildDir, { recursive: true });
+  fs.mkdirSync(docsDir, { recursive: true });
 
-  console.log('开始生成图标...\n');
+  const appSvg = fs.readFileSync(appSvgPath);
+  const traySvg = fs.readFileSync(traySvgPath);
 
-  // 生成各尺寸 PNG
-  for (const [filename, size] of Object.entries(ICON_SIZES)) {
-    const outputPath = path.join(OUTPUT_DIR, filename);
-    
-    await sharp(svgBuffer)
-      .resize(size, size)
-      .png()
-      .toFile(outputPath);
-    
-    console.log(`✓ ${filename} (${size}x${size})`);
+  for (const size of iconSizes) {
+    const target = path.join(iconsDir, `icon-${size}.png`);
+    await renderPng(appSvg, target, size);
+    console.log(`generated icon-${size}.png`);
   }
 
-  // 复制主图标到 build 目录
-  const icon256Path = path.join(OUTPUT_DIR, 'icon-256.png');
-  const buildIconPath = path.join(BUILD_DIR, 'icon.png');
-  fs.copyFileSync(icon256Path, buildIconPath);
-  console.log(`\n✓ 复制 icon.png 到 build 目录`);
-
-  // 复制 logo 到根目录用于 README
-  const logoPath = path.join(OUTPUT_DIR, 'logo.png');
-  const readmeLogoPath = path.join(__dirname, '../../docs/logo.png');
-  const docsDir = path.join(__dirname, '../../docs');
-  if (!fs.existsSync(docsDir)) {
-    fs.mkdirSync(docsDir, { recursive: true });
+  for (const tray of trayIcons) {
+    const target = path.join(iconsDir, tray.name);
+    await renderPng(traySvg, target, tray.size);
+    console.log(`generated ${tray.name}`);
   }
-  fs.copyFileSync(logoPath, readmeLogoPath);
-  console.log(`✓ 复制 logo.png 到 docs 目录`);
 
-  console.log('\n图标生成完成!');
-  console.log('\n生成的文件:');
-  console.log(`  - client/public/icons/ (应用图标)`);
-  console.log(`  - client/build/icon.png (打包用图标)`);
-  console.log(`  - docs/logo.png (README logo)`);
-  
-  console.log('\n下一步:');
-  console.log('  1. 使用在线工具将 icon-256.png 转换为 icon.ico (Windows)');
-  console.log('     推荐: https://convertio.co/png-ico/');
-  console.log('  2. 将生成的 icon.ico 放到 client/build/ 目录');
+  const logoPath = path.join(iconsDir, 'logo.png');
+  await renderPng(appSvg, logoPath, 512);
+
+  fs.copyFileSync(path.join(iconsDir, 'icon-256.png'), path.join(buildDir, 'icon.png'));
+  fs.copyFileSync(logoPath, path.join(docsDir, 'logo.png'));
+
+  console.log('icon generation complete');
 }
 
-generateIcons().catch(console.error);
+generateIcons().catch((error) => {
+  console.error(error);
+  process.exit(1);
+});

+ 1 - 0
client/src/components.d.ts

@@ -7,6 +7,7 @@ export {}
 
 declare module 'vue' {
   export interface GlobalComponents {
+    AppIconMark: typeof import('./components/AppIconMark.vue')['default']
     AppLogo: typeof import('./components/AppLogo.vue')['default']
     AuthPageLayout: typeof import('./components/AuthPageLayout.vue')['default']
     BrowserTab: typeof import('./components/BrowserTab.vue')['default']

+ 36 - 0
client/src/components/AppIconMark.vue

@@ -0,0 +1,36 @@
+<template>
+  <svg class="app-icon-mark" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
+    <rect x="32" y="32" width="448" height="448" rx="108" fill="#171A21" />
+    <path
+      d="M76 380C142 422 230 444 320 428C382 417 430 386 480 342V372C480 431.6 431.6 480 372 480H140C80.4 480 32 431.6 32 372V330C46 348 60 365 76 380Z"
+      fill="#070A0F"
+      opacity="0.28"
+    />
+    <g fill="#262C37" opacity="0.96">
+      <rect x="72" y="108" width="92" height="92" rx="28" />
+      <rect x="348" y="108" width="92" height="92" rx="28" />
+      <rect x="72" y="312" width="92" height="92" rx="28" />
+      <rect x="348" y="312" width="92" height="92" rx="28" />
+    </g>
+    <g fill="none" stroke-linecap="round" stroke-width="18">
+      <path d="M152 162L210 210" stroke="#FF7A2A" opacity="0.78" />
+      <path d="M360 162L302 210" stroke="#FF7A2A" opacity="0.78" />
+      <path d="M152 350L210 302" stroke="#FF7A2A" opacity="0.78" />
+      <path d="M360 350L302 302" stroke="#22C7D5" opacity="0.72" />
+    </g>
+    <rect x="148" y="132" width="216" height="248" rx="58" fill="#FF5C00" />
+    <path d="M148 190C190 154 270 130 364 148V190H148Z" fill="#FF8A3D" opacity="0.85" />
+    <rect x="180" y="166" width="116" height="20" rx="10" fill="#FFFFFF" opacity="0.28" />
+    <rect x="180" y="326" width="152" height="20" rx="10" fill="#FFFFFF" opacity="0.55" />
+    <rect x="180" y="356" width="90" height="14" rx="7" fill="#FFFFFF" opacity="0.28" />
+    <path d="M225 211V301L307 256L225 211Z" fill="#FFFFFF" />
+    <circle cx="118" cy="154" r="24" fill="#FF7A2A" />
+    <circle cx="394" cy="154" r="24" fill="#FFD166" />
+    <circle cx="118" cy="358" r="24" fill="#FFFFFF" opacity="0.92" />
+    <circle cx="394" cy="358" r="24" fill="#22C7D5" />
+    <circle cx="118" cy="154" r="9" fill="#FFFFFF" opacity="0.84" />
+    <circle cx="394" cy="154" r="9" fill="#171A21" opacity="0.72" />
+    <circle cx="118" cy="358" r="9" fill="#FF5C00" opacity="0.9" />
+    <circle cx="394" cy="358" r="9" fill="#FFFFFF" opacity="0.88" />
+  </svg>
+</template>

+ 4 - 31
client/src/components/AppLogo.vue

@@ -1,41 +1,13 @@
 <template>
   <div class="app-logo" :class="size">
-    <svg class="logo-icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
-      <defs>
-        <linearGradient id="logo-gradient-unique" x1="0%" y1="0%" x2="100%" y2="100%">
-          <stop offset="0%" stop-color="#4f8cff"/>
-          <stop offset="100%" stop-color="#6366f1"/>
-        </linearGradient>
-        <linearGradient id="logo-play-unique" x1="0%" y1="0%" x2="100%" y2="100%">
-          <stop offset="0%" stop-color="#ffffff"/>
-          <stop offset="100%" stop-color="#e8ecff"/>
-        </linearGradient>
-      </defs>
-      <rect x="24" y="24" width="464" height="464" rx="104" fill="url(#logo-gradient-unique)"/>
-      <g opacity="0.12">
-        <rect x="72" y="72" width="150" height="150" rx="22" fill="#fff"/>
-        <rect x="290" y="72" width="150" height="150" rx="22" fill="#fff"/>
-        <rect x="72" y="290" width="150" height="150" rx="22" fill="#fff"/>
-        <rect x="290" y="290" width="150" height="150" rx="22" fill="#fff"/>
-      </g>
-      <circle cx="256" cy="256" r="110" fill="url(#logo-play-unique)" opacity="0.95"/>
-      <path d="M 224 190 L 224 322 L 332 256 Z" fill="url(#logo-gradient-unique)"/>
-      <g stroke="#fff" stroke-width="3" stroke-linecap="round" opacity="0.5">
-        <line x1="140" y1="152" x2="190" y2="202"/>
-        <line x1="372" y1="152" x2="322" y2="202"/>
-        <line x1="140" y1="360" x2="190" y2="310"/>
-        <line x1="372" y1="360" x2="322" y2="310"/>
-      </g>
-      <circle cx="140" cy="152" r="10" fill="#fff" opacity="0.75"/>
-      <circle cx="372" cy="152" r="10" fill="#fff" opacity="0.75"/>
-      <circle cx="140" cy="360" r="10" fill="#fff" opacity="0.75"/>
-      <circle cx="372" cy="360" r="10" fill="#fff" opacity="0.75"/>
-    </svg>
+    <AppIconMark class="logo-icon" />
     <span v-if="showText" class="logo-text">智媒通</span>
   </div>
 </template>
 
 <script setup lang="ts">
+import AppIconMark from './AppIconMark.vue';
+
 withDefaults(defineProps<{
   size?: 'sm' | 'md' | 'lg' | 'xl';
   showText?: boolean;
@@ -53,6 +25,7 @@ withDefaults(defineProps<{
 
   .logo-icon {
     flex-shrink: 0;
+    border-radius: 24%;
   }
 
   .logo-text {

+ 2 - 3
client/src/components/BrowserTab.vue

@@ -4383,7 +4383,6 @@ defineExpose({
   flex-direction: column;
   height: 100%;
   background: #fff;
-  margin: -20px;
   border-radius: $radius-lg;
   overflow: hidden;
 }
@@ -4392,10 +4391,10 @@ defineExpose({
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 10px 16px;
+  padding: 12px 18px;
   background: linear-gradient(180deg, #fafbfc 0%, #f3f4f6 100%);
   border-bottom: 1px solid $border-light;
-  gap: 16px;
+  gap: 18px;
   
   .toolbar-left {
     display: flex;

+ 31 - 7
client/src/components/CaptchaDialog.vue

@@ -9,7 +9,7 @@
   >
     <div class="captcha-content">
       <!-- 短信验证码 -->
-      <template v-if="type === 'sms'">
+      <template v-if="captchaType === 'sms'">
         <el-icon class="captcha-icon"><Message /></el-icon>
         <p class="captcha-desc">
           为确保是本人操作抖音账号,请输入手机号 
@@ -20,13 +20,18 @@
       </template>
       
       <!-- 图形验证码 -->
-      <template v-else-if="type === 'image'">
+      <template v-else-if="captchaType === 'image'">
         <el-icon class="captcha-icon"><Picture /></el-icon>
         <p class="captcha-desc">
           为保护帐号安全,请根据图片输入验证码
         </p>
-        <div class="captcha-image-wrapper" v-if="imageBase64">
-          <img :src="imageBase64" alt="验证码" class="captcha-image" />
+        <div class="captcha-image-wrapper" v-if="hasCaptchaImage">
+          <img
+            :src="captchaImageSrc"
+            alt="验证码"
+            class="captcha-image"
+            @error="handleImageLoadError"
+          />
           <el-button 
             text 
             type="primary" 
@@ -47,11 +52,11 @@
       </template>
       
       <!-- 只有在有图片或者是短信验证码时才显示输入框 -->
-      <template v-if="type === 'sms' || imageBase64">
+      <template v-if="captchaType === 'sms' || hasCaptchaImage">
         <el-input
           v-model="captchaCode"
           :placeholder="inputPlaceholder"
-          :maxlength="type === 'sms' ? 6 : 10"
+          :maxlength="captchaType === 'sms' ? 6 : 10"
           class="captcha-input"
           @keyup.enter="handleSubmit"
         >
@@ -64,7 +69,7 @@
     
     <template #footer>
       <!-- 浏览器窗口模式只显示关闭按钮 -->
-      <template v-if="type === 'image' && !imageBase64">
+      <template v-if="captchaType === 'image' && !hasCaptchaImage">
         <el-button @click="handleCancel">关闭提示</el-button>
       </template>
       <template v-else>
@@ -85,6 +90,7 @@
 import { ref, watch, computed } from 'vue';
 import { Message, Lock, Picture, Refresh, Monitor } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
+import { normalizeCaptchaImageSrc } from '@/utils/captchaImage';
 
 const props = defineProps<{
   modelValue: boolean;
@@ -104,9 +110,17 @@ const emit = defineEmits<{
 
 const visible = ref(false);
 const captchaCode = ref('');
+const imageLoadError = ref(false);
 
 const captchaType = computed(() => props.type || 'sms');
 
+const captchaImageSrc = computed(() => {
+  if (imageLoadError.value) return '';
+  return normalizeCaptchaImageSrc(props.imageBase64);
+});
+
+const hasCaptchaImage = computed(() => captchaImageSrc.value.length > 0);
+
 const dialogTitle = computed(() => {
   return captchaType.value === 'sms' ? '短信验证码' : '图形验证码';
 });
@@ -123,9 +137,14 @@ watch(() => props.modelValue, (val) => {
   visible.value = val;
   if (val) {
     captchaCode.value = '';
+    imageLoadError.value = false;
   }
 });
 
+watch(() => props.imageBase64, () => {
+  imageLoadError.value = false;
+});
+
 watch(visible, (val) => {
   emit('update:modelValue', val);
 });
@@ -144,6 +163,11 @@ function handleCancel() {
   emit('cancel');
   visible.value = false;
 }
+
+function handleImageLoadError() {
+  imageLoadError.value = true;
+  console.warn('[CaptchaDialog] Captcha image failed to load, falling back to browser verification notice');
+}
 </script>
 
 <style lang="scss" scoped>

+ 23 - 39
client/src/layouts/MainLayout.vue

@@ -3,21 +3,7 @@
     <div class="title-bar">
       <div class="title-bar-drag">
         <div class="title-bar-logo">
-          <svg class="logo-icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
-            <defs>
-              <linearGradient id="titlebar-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
-                <stop offset="0%" style="stop-color:#ff8533" />
-                <stop offset="100%" style="stop-color:#ff5c00" />
-              </linearGradient>
-            </defs>
-            <rect x="32" y="32" width="448" height="448" rx="96" fill="url(#titlebar-gradient)" />
-            <circle cx="256" cy="256" r="100" fill="#fff" opacity="0.95" />
-            <path d="M 228 190 L 228 322 L 330 256 Z" fill="url(#titlebar-gradient)" />
-            <circle cx="136" cy="160" r="10" fill="#fff" opacity="0.7" />
-            <circle cx="376" cy="160" r="10" fill="#fff" opacity="0.7" />
-            <circle cx="136" cy="352" r="10" fill="#fff" opacity="0.7" />
-            <circle cx="376" cy="352" r="10" fill="#fff" opacity="0.7" />
-          </svg>
+          <AppIconMark class="logo-icon" />
           <span class="logo-text">智媒通</span>
         </div>
       </div>
@@ -47,21 +33,7 @@
     <el-container class="shell">
       <el-aside :width="isCollapsed ? '64px' : '240px'" class="sidebar" :class="{ collapsed: isCollapsed }">
         <div class="sidebar-header">
-          <svg class="app-logo" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
-            <defs>
-              <linearGradient id="sidebar-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
-                <stop offset="0%" style="stop-color:#ff8533" />
-                <stop offset="100%" style="stop-color:#ff5c00" />
-              </linearGradient>
-            </defs>
-            <rect x="32" y="32" width="448" height="448" rx="96" fill="url(#sidebar-gradient)" />
-            <circle cx="256" cy="256" r="100" fill="#fff" opacity="0.95" />
-            <path d="M 228 190 L 228 322 L 330 256 Z" fill="url(#sidebar-gradient)" />
-            <circle cx="136" cy="160" r="10" fill="#fff" opacity="0.7" />
-            <circle cx="376" cy="160" r="10" fill="#fff" opacity="0.7" />
-            <circle cx="136" cy="352" r="10" fill="#fff" opacity="0.7" />
-            <circle cx="376" cy="352" r="10" fill="#fff" opacity="0.7" />
-          </svg>
+          <AppIconMark class="app-logo" />
           <div v-if="!isCollapsed" class="sidebar-brand">
             <span class="sidebar-title">智媒通</span>
             <span class="sidebar-subtitle">智能媒体运营系统</span>
@@ -262,6 +234,7 @@ import { useTaskQueueStore } from '@/stores/taskQueue';
 import TaskProgressDialog from '@/components/TaskProgressDialog.vue';
 import CaptchaDialog from '@/components/CaptchaDialog.vue';
 import BrowserTab from '@/components/BrowserTab.vue';
+import AppIconMark from '@/components/AppIconMark.vue';
 
 const PAGE_META: Record<string, { title: string; kicker: string }> = {
   '/': { title: '数据看板', kicker: 'Dashboard' },
@@ -795,8 +768,8 @@ onUnmounted(() => {
 
 .browser-panel {
   position: relative;
-  width: min(980px, calc(100vw - 120px));
-  margin: 24px;
+  width: min(1120px, calc(100vw - 64px));
+  margin: 32px;
   border-radius: 28px;
   background: #fff;
   box-shadow: $shadow-lg;
@@ -810,8 +783,8 @@ onUnmounted(() => {
   display: flex;
   align-items: flex-start;
   justify-content: space-between;
-  gap: 16px;
-  padding: 18px 20px;
+  gap: 24px;
+  padding: 22px 28px 20px;
   border-bottom: 1px solid rgba(26, 26, 26, 0.06);
   background: linear-gradient(180deg, rgba(255, 92, 0, 0.06), rgba(255, 255, 255, 0.92));
 }
@@ -839,14 +812,14 @@ onUnmounted(() => {
   display: flex;
   align-items: center;
   justify-content: flex-end;
-  gap: 12px;
+  gap: 18px;
   min-width: 0;
 }
 
 .browser-tab-list {
   display: flex;
   align-items: center;
-  gap: 8px;
+  gap: 10px;
   flex-wrap: wrap;
   justify-content: flex-end;
 }
@@ -855,8 +828,8 @@ onUnmounted(() => {
   display: inline-flex;
   align-items: center;
   gap: 8px;
-  padding: 0 8px 0 12px;
-  height: 38px;
+  padding: 0 10px 0 14px;
+  height: 40px;
   border: 1px solid rgba(26, 26, 26, 0.08);
   border-radius: 999px;
   background: #fff;
@@ -898,11 +871,14 @@ onUnmounted(() => {
 .browser-panel-body {
   min-height: 0;
   flex: 1;
-  background: #fff;
+  padding: 14px 20px 20px;
+  background: #f5f6f8;
+  box-sizing: border-box;
 
   :deep(.browser-tab-container) {
     height: 100%;
     min-height: 620px;
+    box-shadow: 0 0 0 1px rgba(26, 26, 26, 0.06);
   }
 }
 
@@ -960,6 +936,14 @@ onUnmounted(() => {
     width: calc(100vw - 32px);
     margin: 16px;
   }
+
+  .browser-panel-header {
+    padding: 18px 20px;
+  }
+
+  .browser-panel-body {
+    padding: 12px 14px 14px;
+  }
 }
 
 @media (max-width: 780px) {

+ 12 - 3
client/src/stores/taskQueue.ts

@@ -31,6 +31,15 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
   const captchaImageBase64 = ref('');
   const captchaMessage = ref('');
 
+  function summarizeCaptchaPayload(payload: Record<string, unknown> | undefined) {
+    if (!payload) return payload;
+    const imageBase64 = typeof payload.imageBase64 === 'string' ? payload.imageBase64 : '';
+    return {
+      ...payload,
+      imageBase64: imageBase64 ? `[image:${imageBase64.length} chars]` : '',
+    };
+  }
+
   // 计算属性
   const activeTasks = computed(() => 
     tasks.value.filter(t => t.status === 'pending' || t.status === 'running')
@@ -304,7 +313,7 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
     const payload = data.payload as Record<string, unknown> | undefined;
     if (data.type === 'captcha:required' || 
         (payload && 'captchaTaskId' in payload && payload.captchaTaskId)) {
-      console.log('[TaskQueue] Captcha event detected, type:', data.type, 'payload:', payload);
+      console.log('[TaskQueue] Captcha event detected, type:', data.type, 'payload:', summarizeCaptchaPayload(payload));
       handleCaptchaRequired(payload as { 
         captchaTaskId: string; 
         type?: 'sms' | 'image';
@@ -322,7 +331,7 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
     imageBase64?: string;
     message?: string;
   }) {
-    console.log('[TaskQueue] Captcha required:', payload);
+    console.log('[TaskQueue] Captcha required:', summarizeCaptchaPayload(payload as Record<string, unknown>));
     captchaTaskId.value = payload.captchaTaskId || '';
     captchaType.value = payload.type || 'sms';
     captchaPhone.value = payload.phone || '';
@@ -341,7 +350,7 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
           code,
         },
       }));
-      console.log('[TaskQueue] Captcha submitted:', taskId, code);
+      console.log('[TaskQueue] Captcha submitted:', taskId);
     }
     showCaptchaDialog.value = false;
   }

+ 38 - 0
client/src/utils/captchaImage.ts

@@ -0,0 +1,38 @@
+const DATA_IMAGE_SRC_RE = /^data:image\/(?:png|jpe?g|gif|webp|bmp|svg\+xml);base64,/i;
+const HTTP_IMAGE_SRC_RE = /^https?:\/\//i;
+const BLOB_IMAGE_SRC_RE = /^blob:/i;
+const BASE64_RE = /^[A-Za-z0-9+/]+={0,2}$/;
+
+function inferImageMimeFromBase64(value: string): string {
+  if (value.startsWith('/9j/')) return 'image/jpeg';
+  if (value.startsWith('iVBORw0KGgo')) return 'image/png';
+  if (value.startsWith('R0lGOD')) return 'image/gif';
+  if (value.startsWith('UklGR')) return 'image/webp';
+  if (value.startsWith('Qk')) return 'image/bmp';
+  return '';
+}
+
+export function normalizeCaptchaImageSrc(value?: string | null): string {
+  const trimmed = (value || '').trim();
+  if (!trimmed) return '';
+
+  if (
+    DATA_IMAGE_SRC_RE.test(trimmed) ||
+    HTTP_IMAGE_SRC_RE.test(trimmed) ||
+    BLOB_IMAGE_SRC_RE.test(trimmed)
+  ) {
+    return trimmed;
+  }
+
+  const compactBase64 = trimmed.replace(/\s+/g, '');
+  if (!BASE64_RE.test(compactBase64)) return '';
+
+  const mimeType = inferImageMimeFromBase64(compactBase64);
+  if (!mimeType) return '';
+
+  return `data:${mimeType};base64,${compactBase64}`;
+}
+
+export function isDisplayableCaptchaImageSrc(value?: string | null): boolean {
+  return normalizeCaptchaImageSrc(value).length > 0;
+}

+ 19 - 1
client/src/utils/format.ts

@@ -140,11 +140,29 @@ export const COVER_PLACEHOLDER = 'data:image/svg+xml,<svg xmlns="http://www.w3.o
 /**
  * Get cover image URL for a work, falling back to placeholder
  */
-export function getWorkCoverSrc(work: { coverUrl?: string; cover_url?: string }): string {
+export function getWorkCoverSrc(work: { coverUrl?: string; cover_url?: string }, assetBaseUrl = ''): string {
   const url = work.coverUrl ?? (work as any).cover_url ?? '';
   if (!url || typeof url !== 'string' || !url.trim()) return COVER_PLACEHOLDER;
+  if (url.startsWith('/uploads/') && import.meta.env.DEV) {
+    return url;
+  }
+  if (url.startsWith('/uploads/') && assetBaseUrl) {
+    return `${normalizeAssetBaseUrl(assetBaseUrl)}${url}`;
+  }
   if (url.startsWith('http://')) {
     return url.replace('http://', 'https://');
   }
   return url;
 }
+
+function normalizeAssetBaseUrl(baseUrl: string): string {
+  try {
+    const parsed = new URL(baseUrl);
+    if (parsed.hostname === '0.0.0.0' || parsed.hostname === '::' || parsed.hostname === 'localhost') {
+      parsed.hostname = '127.0.0.1';
+    }
+    return parsed.origin.replace(/\/$/, '');
+  } catch {
+    return baseUrl.replace(/\/$/, '');
+  }
+}

+ 11 - 1
client/src/views/Accounts/index.vue

@@ -46,14 +46,19 @@
         </el-select>
         <el-input
           v-model="filter.keyword"
+          class="filter-keyword"
           placeholder="搜索账号名称或 ID"
           clearable
-          style="width: 220px"
+          @keyup.enter="loadAccounts"
         >
           <template #prefix>
             <el-icon><Search /></el-icon>
           </template>
         </el-input>
+        <el-button type="primary" @click="loadAccounts">
+          <el-icon><Search /></el-icon>
+          搜索
+        </el-button>
         <div class="filter-actions">
           <el-button @click="loadAccounts">
             <el-icon><Refresh /></el-icon>
@@ -699,6 +704,11 @@ onMounted(async () => {
   margin-left: auto;
 }
 
+.filter-keyword {
+  width: 220px;
+  flex: 0 0 220px;
+}
+
 .account-cell {
   display: flex;
   align-items: center;

+ 15 - 8
client/src/views/Publish/index.vue

@@ -9,9 +9,10 @@
     </div>
 
     <div class="filter-bar">
-      <el-select placeholder="全部平台" style="width: 120px" disabled />
+      <el-select placeholder="全部平台" style="width: 140px" disabled />
       <el-input
         v-model="searchKeyword"
+        class="filter-keyword"
         placeholder="搜索标题名称"
         clearable
         @clear="loadTasks"
@@ -21,6 +22,10 @@
           <el-icon><Search /></el-icon>
         </template>
       </el-input>
+      <el-button type="primary" @click="loadTasks">
+        <el-icon><Search /></el-icon>
+        搜索
+      </el-button>
     </div>
 
     <div class="page-card table-card">
@@ -1217,16 +1222,18 @@ watch(showCreateDialog, (visible) => {
 .filter-bar {
   display: flex;
   align-items: center;
+  flex-wrap: wrap;
   gap: 12px;
   width: 100%;
-  height: 36px;
-  padding: 0 12px;
-  border-radius: 8px;
-  background: $surface-primary;
+  min-height: 36px;
+  margin-bottom: 0;
+  padding: 0;
+  background: transparent;
+}
 
-  :deep(.el-input) {
-    flex: 1;
-  }
+.filter-keyword {
+  width: 220px;
+  flex: 0 0 220px;
 }
 
 .table-card {

+ 24 - 1
client/src/views/Works/index.vue

@@ -36,9 +36,9 @@
       </el-select>
       <el-input 
         v-model="filter.keyword" 
+        class="filter-keyword"
         placeholder="搜索作品标题" 
         clearable 
-        style="width: 180px"
         @keyup.enter="loadWorks"
       />
       <el-button type="primary" @click="loadWorks">
@@ -534,6 +534,12 @@ function getWorkCoverSrc(work: Work): string {
 // 将 HTTP 图片 URL 转换为 HTTPS(小红书等平台的图片 URL 可能是 HTTP)
 function getSecureCoverUrl(url: string): string {
   if (!url) return '';
+  if (url.startsWith('/uploads/')) {
+    if (import.meta.env.DEV) return url;
+    const backendUrl = serverStore.currentServer?.url || '';
+    if (!backendUrl) return url;
+    return `${normalizeLocalAssetBaseUrl(backendUrl)}${url}`;
+  }
   // 将 http:// 转换为 https://
   if (url.startsWith('http://')) {
     return url.replace('http://', 'https://');
@@ -541,6 +547,18 @@ function getSecureCoverUrl(url: string): string {
   return url;
 }
 
+function normalizeLocalAssetBaseUrl(baseUrl: string): string {
+  try {
+    const parsed = new URL(baseUrl);
+    if (parsed.hostname === '0.0.0.0' || parsed.hostname === '::' || parsed.hostname === 'localhost') {
+      parsed.hostname = '127.0.0.1';
+    }
+    return parsed.origin.replace(/\/$/, '');
+  } catch {
+    return baseUrl.replace(/\/$/, '');
+  }
+}
+
 function handleImageError(e: Event) {
   const img = e.target as HTMLImageElement;
   img.src = COVER_PLACEHOLDER;
@@ -982,6 +1000,11 @@ onUnmounted(() => {
   padding: 0;
 }
 
+.filter-keyword {
+  width: 220px;
+  flex: 0 0 220px;
+}
+
 .table-card {
   padding: 0;
   overflow: hidden;

+ 4 - 0
client/vite.config.ts

@@ -152,6 +152,10 @@ export default defineConfig(({ command }) => {
           target: devApiTarget.replace(/^http/, 'ws'),
           ws: true,
         },
+        '/uploads': {
+          target: devApiTarget,
+          changeOrigin: true,
+        },
       },
     },
     build: {

BIN
docs/logo.png


+ 0 - 99
draw_icon.py

@@ -1,99 +0,0 @@
-from PIL import Image
-import os
-
-def draw_icon(size):
-    img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
-    BG    = (255, 107, 53)
-    CIRC  = (30, 58, 138)
-    WHITE = (255, 255, 255)
-    PAD   = int(round(24 * size / 512.0))
-    RX    = int(round(104 * size / 512.0))
-    ccx   = size // 2
-    ccy   = size // 2
-    ccr   = int(round(112 * size / 512.0))
-
-    # 1. Orange rounded square
-    x1, y1 = PAD, PAD
-    x2, y2 = size - PAD, size - PAD
-    for y in range(y1, y2 + 1):
-        for x in range(x1, x2 + 1):
-            if _in_rrect(x, y, x1, y1, x2, y2, RX):
-                img.putpixel((x, y), BG + (255,))
-
-    # 2. Deep blue circle
-    for y in range(ccy - ccr, ccy + ccr + 1):
-        for x in range(ccx - ccr, ccx + ccr + 1):
-            if (x - ccx) ** 2 + (y - ccy) ** 2 <= ccr ** 2:
-                img.putpixel((x, y), CIRC + (255,))
-
-    # 3. White play triangle — right-pointing isosceles, apex at circle's right edge
-    # Width-to-height ratio ~2:1 for a balanced play button look
-    # bh=ccr*0.35 ensures the left vertex stays inside the circle
-    # At y=ccy: triangle base spans ~67% of circle diameter (visually balanced)
-    bh    = int(round(ccr * 0.35))
-    w_tri = bh * 2       # width from left vertex to right vertex = 0.7*ccr
-    rx2   = ccx + ccr    # right vertex at circle's right edge
-    lx    = rx2 - w_tri  # left vertex
-    ty_   = ccy - bh
-    by_   = ccy + bh
-
-    for y in range(ty_, by_ + 1):
-        frac = float(y - ty_) / float(by_ - ty_) if by_ > ty_ else 0.0
-        xl = lx
-        xr = lx + int(round(w_tri * 2 * frac))
-        xl, xr = min(xl, xr), max(xl, xr)
-        for x in range(xl, xr + 1):
-            if 0 <= x < size and 0 <= y < size:
-                img.putpixel((x, y), WHITE + (255,))
-
-    return img
-
-
-def _in_rrect(px, py, x1, y1, x2, y2, r):
-    if px < x1 or px > x2 or py < y1 or py > y2:
-        return False
-    if px < x1 + r and py < y1 + r:
-        return (px - x1 - r) ** 2 + (py - y1 - r) ** 2 <= r ** 2
-    if px > x2 - r and py < y1 + r:
-        return (px - x2 + r) ** 2 + (py - y1 - r) ** 2 <= r ** 2
-    if px < x1 + r and py > y2 - r:
-        return (px - x1 - r) ** 2 + (py - y2 + r) ** 2 <= r ** 2
-    if px > x2 - r and py > y2 - r:
-        return (px - x2 + r) ** 2 + (py - y2 + r) ** 2 <= r ** 2
-    return True
-
-
-src_size = 1024
-img = draw_icon(src_size)
-vp = img.load()
-ccr = int(round(112 * src_size / 512.0))
-bh_draw = int(round(ccr * 0.35))
-w_tri_draw = bh_draw * 2
-rx2_draw = src_size // 2 + ccr
-lx_draw = rx2_draw - w_tri_draw
-ty__draw = src_size // 2 - bh_draw
-by__draw = src_size // 2 + bh_draw
-print("ccr=%d, w_tri=%d, bh=%d, lx=%d, rx2=%d, ty_=%d, by_=%d" % (ccr, w_tri_draw, bh_draw, lx_draw, rx2_draw, ty__draw, by__draw))
-print("Midpoint at y=ccy: %d [expect %d]" % ((lx_draw+rx2_draw)//2, src_size//2))
-print("Center(512,512): %s" % str(vp[512, 512]))
-print("Corner(5,5): %s" % str(vp[5, 5]))
-# Verify triangle left vertex is inside circle
-# Left vertex distance from center must be < ccr
-left_dist = abs(lx_draw - src_size//2)
-# Also check top-left corner (most likely to be outside)
-top_lx = lx_draw
-top_ly = ty__draw
-tl_dist_sq = (top_lx - src_size//2)**2 + (top_ly - src_size//2)**2
-print("Left vertex dist from center: %.1f < ccr=%d: %s" % (left_dist, ccr, left_dist < ccr))
-print("Top-left corner dist: %.1f < ccr=%d: %s" % (tl_dist_sq**0.5, ccr, tl_dist_sq**0.5 < ccr))
-
-out_dir   = "minimax-output/vector"
-icons_dir = "client/public/icons"
-os.makedirs(out_dir, exist_ok=True)
-img.save(os.path.join(out_dir, "icon-orange-1024.png"), "PNG")
-for sz in [16, 24, 32, 48, 64, 128, 256, 512]:
-    img.resize((sz, sz), Image.NEAREST).save(os.path.join(icons_dir, "icon-%d.png" % sz), "PNG")
-img.resize((512, 512), Image.NEAREST).save(os.path.join(icons_dir, "logo.png"), "PNG")
-img.resize((16, 16), Image.NEAREST).save(os.path.join(icons_dir, "tray-icon.png"), "PNG")
-img.resize((32, 32), Image.NEAREST).save(os.path.join(icons_dir, "tray-icon@2x.png"), "PNG")
-print("Done!")

BIN
minimax-output/accounts-layout-fix.png


BIN
minimax-output/accounts-layout-hover-fix.png


BIN
minimax-output/accounts-title-fix.png


BIN
minimax-output/after-auth/login.png


BIN
minimax-output/after-auth/register.png


BIN
minimax-output/after/accounts.png


BIN
minimax-output/after/analytics-account.png


BIN
minimax-output/after/analytics-overview.png


BIN
minimax-output/after/analytics-platform.png


BIN
minimax-output/after/analytics-work.png


BIN
minimax-output/after/comments.png


BIN
minimax-output/after/dashboard.png


BIN
minimax-output/after/login.png


BIN
minimax-output/after/profile.png


BIN
minimax-output/after/publish.png


BIN
minimax-output/after/register.png


BIN
minimax-output/after/schedule.png


BIN
minimax-output/after/server-config.png


BIN
minimax-output/after/settings.png


BIN
minimax-output/after/works.png


BIN
minimax-output/analytics-overview-title-fix.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 83 - 0
minimax-output/auth-layout-module-5174.js


BIN
minimax-output/baseline/accounts.png


BIN
minimax-output/baseline/analytics-account.png


BIN
minimax-output/baseline/analytics-overview.png


BIN
minimax-output/baseline/analytics-platform.png


BIN
minimax-output/baseline/analytics-work.png


BIN
minimax-output/baseline/comments.png


BIN
minimax-output/baseline/dashboard.png


BIN
minimax-output/baseline/profile.png


BIN
minimax-output/baseline/publish.png


BIN
minimax-output/baseline/schedule.png


BIN
minimax-output/baseline/server-config.png


BIN
minimax-output/baseline/settings.png


BIN
minimax-output/baseline/works.png


BIN
minimax-output/dashboard-auth.png


BIN
minimax-output/dashboard-fix-check.png


BIN
minimax-output/dashboard-title-fix.png


BIN
minimax-output/debug-body.png


BIN
minimax-output/electron-initial.png


BIN
minimax-output/login-check.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 266 - 0
minimax-output/login-module-5174.js


BIN
minimax-output/publish-title-fix.png


BIN
minimax-output/regression-current-login.png


BIN
minimax-output/regression-probe-after-login.png


BIN
minimax-output/regression-probe-dashboard.png


BIN
minimax-output/regression-probe-login.png


BIN
minimax-output/regression/latest-dev/accounts.png


BIN
minimax-output/regression/latest-dev/analytics-account.png


BIN
minimax-output/regression/latest-dev/analytics-overview.png


BIN
minimax-output/regression/latest-dev/analytics-platform.png


BIN
minimax-output/regression/latest-dev/analytics-work.png


BIN
minimax-output/regression/latest-dev/dashboard.png


+ 34 - 0
minimax-output/regression/latest-dev/manifest.json

@@ -0,0 +1,34 @@
+[
+  {
+    "page": "dashboard",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\dashboard.png"
+  },
+  {
+    "page": "accounts",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\accounts.png"
+  },
+  {
+    "page": "works",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\works.png"
+  },
+  {
+    "page": "publish",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\publish.png"
+  },
+  {
+    "page": "analytics-overview",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\analytics-overview.png"
+  },
+  {
+    "page": "analytics-platform",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\analytics-platform.png"
+  },
+  {
+    "page": "analytics-account",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\analytics-account.png"
+  },
+  {
+    "page": "analytics-work",
+    "path": "C:\\workspace\\multi-platform-media-manage\\minimax-output\\regression\\latest-dev\\analytics-work.png"
+  }
+]

BIN
minimax-output/regression/latest-dev/publish.png


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.