Parcourir la source

feat: AI模型管理增加默认模型功能,分析报告支持模型切换

kinglee il y a 2 jours
Parent
commit
af55a3fa2e

+ 8 - 4
.wolf/anatomy.md

@@ -1,7 +1,7 @@
 # anatomy.md
 
-> Auto-maintained by OpenWolf. Last scanned: 2026-05-25T03:57:19.497Z
-> Files: 548 tracked | Anatomy hits: 0 | Misses: 0
+> Auto-maintained by OpenWolf. Last scanned: 2026-05-25T07:23:35.945Z
+> Files: 552 tracked | Anatomy hits: 0 | Misses: 0
 
 ## ./
 
@@ -23,6 +23,7 @@
 - `fix_column.py` (~97 tok)
 - `fix_pg_sequences.py` — fix_sequences (~675 tok)
 - `init_db.py` — init_db (~386 tok)
+- `migrate_add_is_default.py` — Add is_default column to ai_model table if not exists. (~230 tok)
 - `migrate_db.py` — migrate (~485 tok)
 - `migrate_sqlite_to_pg.py` — get_sqlite_tables, get_sqlite_data, quote_identifier, migrate (~1738 tok)
 - `migrate.sql` — AI LiaoWang Web App 数据库迁移脚本 (~1180 tok)
@@ -781,6 +782,7 @@
 
 ## C:/Users/liwei/.claude/plans/
 
+- `mellow-frolicking-summit.md` — AI 模型默认设置 + 分析报告模型切换 (~297 tok)
 - `vivid-splashing-kay.md` — 深采管理页面添加"入库"按钮 (~657 tok)
 
 ## app/
@@ -788,12 +790,12 @@
 - `__init__.py` — Flask 应用工厂 (~500 tok)
 - `config.py` — 加载项目根目录下的 .env 文件 (~561 tok)
 - `knowledge_poller.py` — KnowledgePoller: start, stop (~1245 tok)
-- `models.py` — SQLAlchemy 数据模型(SpiderResult.link 已改为 Text) (~3000 tok)
+- `models.py` — User: set_password, check_password, load_user (~2230 tok)
 - `sample_center_client.py` — SampleCenterError: list_knowledge_bases, get_knowledge_base, batch_import, get_import_task (~1577 tok)
 
 ## app/routes/
 
-- `ai_routes.py` — AI 模型管理 & 对话 (~500 tok)
+- `ai_routes.py` — execute_sql, parse_tool_calls_from_content, process_chart_option (~9429 tok)
 - `collection_routes.py` — 采集任务管理 & save_selected 接口 (~270 tok)
 - `data_routes.py` — 数据查看/导出 (~300 tok)
 - `deep_routes.py` — dashboard, list_data, run_crawl, safe_commit (~3816 tok)
@@ -809,5 +811,7 @@
 
 ## app/templates/
 
+- `ai_analysis.html` — loadModelOptions: scrollToBottom (~4238 tok)
+- `ai_management.html` — Declares loadModels (~5523 tok)
 - `deep_management.html` — pageSize: searchData, loadData, renderTable (~5157 tok)
 - `knowledge_management.html` — loadKnowledgeList: renderKnowledgeList (~6217 tok)

+ 64 - 0
.wolf/buglog.json

@@ -130,6 +130,70 @@
       "related_bugs": [],
       "occurrences": 1,
       "last_seen": "2026-05-21T19:43:50.260Z"
+    },
+    {
+      "id": "bug-009",
+      "timestamp": "2026-05-25T06:59:07.097Z",
+      "error_message": "Incorrect value in code",
+      "file": "app/routes/ai_routes.py",
+      "root_cause": "Had \"Execute a SQL SELECT query to retrieve data. Avai",
+      "fix": "Changed to \"Execute a SQL SELECT query to retrieve data from ",
+      "tags": [
+        "auto-detected",
+        "wrong-value",
+        "py"
+      ],
+      "related_bugs": [],
+      "occurrences": 1,
+      "last_seen": "2026-05-25T06:59:07.097Z"
+    },
+    {
+      "id": "bug-010",
+      "timestamp": "2026-05-25T07:21:49.569Z",
+      "error_message": "Incorrect value in code",
+      "file": "app/routes/ai_routes.py",
+      "root_cause": "Had 'total_tokens'",
+      "fix": "Changed to 'is_default'",
+      "tags": [
+        "auto-detected",
+        "wrong-value",
+        "py"
+      ],
+      "related_bugs": [],
+      "occurrences": 1,
+      "last_seen": "2026-05-25T07:21:49.569Z"
+    },
+    {
+      "id": "bug-011",
+      "timestamp": "2026-05-25T07:22:30.858Z",
+      "error_message": "Null/undefined access in ",
+      "file": "app/templates/ai_management.html",
+      "root_cause": "Property access on potentially null/undefined value",
+      "fix": "Added null safety (optional chaining or null check)",
+      "tags": [
+        "auto-detected",
+        "null-safety",
+        "html"
+      ],
+      "related_bugs": [],
+      "occurrences": 1,
+      "last_seen": "2026-05-25T07:22:30.858Z"
+    },
+    {
+      "id": "bug-012",
+      "timestamp": "2026-05-25T07:23:19.952Z",
+      "error_message": "Missing guard clause",
+      "file": "app/templates/ai_analysis.html",
+      "root_cause": "No early return/throw for edge case: userInput",
+      "fix": "Added guard clause: if (userInput)",
+      "tags": [
+        "auto-detected",
+        "guard-clause",
+        "html"
+      ],
+      "related_bugs": [],
+      "occurrences": 1,
+      "last_seen": "2026-05-25T07:23:19.952Z"
     }
   ]
 }

+ 135 - 8
.wolf/hooks/_session.json

@@ -3,8 +3,8 @@
   "started": "2026-05-25T06:26:19.780Z",
   "files_read": {
     "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py": {
-      "count": 1,
-      "tokens": 500,
+      "count": 11,
+      "tokens": 9173,
       "first_read": "2026-05-25T06:31:27.249Z"
     },
     "D:/bigmodel/ai-LiaoWangweb-app/.env": {
@@ -16,13 +16,140 @@
       "count": 2,
       "tokens": 561,
       "first_read": "2026-05-25T06:37:41.375Z"
+    },
+    "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt": {
+      "count": 1,
+      "tokens": 0,
+      "first_read": "2026-05-25T06:55:40.468Z"
+    },
+    "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html": {
+      "count": 2,
+      "tokens": 0,
+      "first_read": "2026-05-25T07:17:00.836Z"
+    },
+    "/home/user/app/routes/ai_routes.py": {
+      "count": 1,
+      "tokens": 0,
+      "first_read": "2026-05-25T07:17:50.876Z"
+    },
+    "D:/bigmodel/ai-LiaoWangweb-app/migrations/alembic.ini": {
+      "count": 1,
+      "tokens": 0,
+      "first_read": "2026-05-25T07:18:00.556Z"
+    },
+    "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html": {
+      "count": 6,
+      "tokens": 0,
+      "first_read": "2026-05-25T07:18:10.757Z"
+    },
+    "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py": {
+      "count": 1,
+      "tokens": 500,
+      "first_read": "2026-05-25T07:18:10.833Z"
+    },
+    "D:/bigmodel/ai-LiaoWangweb-app/app/models.py": {
+      "count": 1,
+      "tokens": 3000,
+      "first_read": "2026-05-25T07:21:08.906Z"
+    }
+  },
+  "files_written": [
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 148,
+      "at": "2026-05-25T06:59:07.096Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 124,
+      "at": "2026-05-25T06:59:27.306Z"
+    },
+    {
+      "file": "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md",
+      "action": "create",
+      "tokens": 339,
+      "at": "2026-05-25T07:19:07.351Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+      "action": "edit",
+      "tokens": 46,
+      "at": "2026-05-25T07:21:12.811Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 182,
+      "at": "2026-05-25T07:21:43.880Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 38,
+      "at": "2026-05-25T07:21:49.567Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 212,
+      "at": "2026-05-25T07:21:59.754Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+      "action": "edit",
+      "tokens": 161,
+      "at": "2026-05-25T07:22:13.603Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+      "action": "edit",
+      "tokens": 245,
+      "at": "2026-05-25T07:22:24.574Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+      "action": "edit",
+      "tokens": 227,
+      "at": "2026-05-25T07:22:30.856Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+      "action": "edit",
+      "tokens": 193,
+      "at": "2026-05-25T07:23:07.322Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+      "action": "edit",
+      "tokens": 438,
+      "at": "2026-05-25T07:23:19.951Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+      "action": "edit",
+      "tokens": 60,
+      "at": "2026-05-25T07:23:24.350Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/migrate_add_is_default.py",
+      "action": "create",
+      "tokens": 230,
+      "at": "2026-05-25T07:23:35.954Z"
     }
+  ],
+  "edit_counts": {
+    "app/routes/ai_routes.py": 5,
+    "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md": 1,
+    "app/models.py": 1,
+    "app/templates/ai_management.html": 3,
+    "app/templates/ai_analysis.html": 3,
+    "migrate_add_is_default.py": 1
   },
-  "files_written": [],
-  "edit_counts": {},
-  "anatomy_hits": 2,
-  "anatomy_misses": 1,
-  "repeated_reads_warned": 1,
+  "anatomy_hits": 5,
+  "anatomy_misses": 5,
+  "repeated_reads_warned": 17,
   "cerebrum_warnings": 0,
-  "stop_count": 5
+  "stop_count": 11
 }

+ 18 - 0
.wolf/memory.md

@@ -95,3 +95,21 @@
 
 | Time | Action | File(s) | Outcome | ~Tokens |
 |------|--------|---------|---------|--------|
+| 14:59 | Edited app/routes/ai_routes.py | 2→2 lines | ~148 |
+| 14:59 | Edited app/routes/ai_routes.py | expanded (+6 lines) | ~124 |
+| 15:00 | Session end: 2 writes across 1 files (ai_routes.py) | 4 reads | ~10006 tok |
+| 15:01 | Session end: 2 writes across 1 files (ai_routes.py) | 4 reads | ~10006 tok |
+| 15:15 | Session end: 2 writes across 1 files (ai_routes.py) | 4 reads | ~10006 tok |
+| 15:19 | Created C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md | — | ~317 |
+| 15:21 | Edited app/models.py | 2→3 lines | ~46 |
+| 15:21 | Edited app/routes/ai_routes.py | expanded (+9 lines) | ~182 |
+| 15:21 | Edited app/routes/ai_routes.py | 2→3 lines | ~38 |
+| 15:21 | Edited app/routes/ai_routes.py | modified set_default_model() | ~212 |
+| 15:22 | Edited app/templates/ai_management.html | 5→5 lines | ~150 |
+| 15:22 | Edited app/templates/ai_management.html | 4→7 lines | ~229 |
+| 15:22 | Edited app/templates/ai_management.html | added optional chaining | ~212 |
+| 15:23 | Edited app/templates/ai_analysis.html | expanded (+7 lines) | ~180 |
+| 15:23 | Edited app/templates/ai_analysis.html | added 3 condition(s) | ~409 |
+| 15:23 | Edited app/templates/ai_analysis.html | 4→5 lines | ~56 |
+| 15:23 | Created migrate_add_is_default.py | — | ~230 |
+| 15:23 | Session end: 14 writes across 6 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 10 reads | ~15877 tok |

+ 381 - 7
.wolf/token-ledger.json

@@ -2,14 +2,14 @@
   "version": 1,
   "created_at": "2026-05-21T16:43:25.726Z",
   "lifetime": {
-    "total_tokens_estimated": 538353,
-    "total_reads": 500,
-    "total_writes": 519,
+    "total_tokens_estimated": 586370,
+    "total_reads": 529,
+    "total_writes": 539,
     "total_sessions": 4,
-    "anatomy_hits": 271,
-    "anatomy_misses": 229,
-    "repeated_reads_blocked": 173,
-    "estimated_savings_vs_bare_cli": 280885
+    "anatomy_hits": 286,
+    "anatomy_misses": 243,
+    "repeated_reads_blocked": 205,
+    "estimated_savings_vs_bare_cli": 462038
   },
   "sessions": [
     {
@@ -6212,6 +6212,380 @@
         "repeated_reads_blocked": 1,
         "anatomy_lookups": 2
       }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T06:52:54.795Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 500,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [],
+      "totals": {
+        "input_tokens_estimated": 1061,
+        "output_tokens_estimated": 0,
+        "reads_count": 3,
+        "writes_count": 0,
+        "repeated_reads_blocked": 1,
+        "anatomy_lookups": 2
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T06:58:09.452Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 500,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [],
+      "totals": {
+        "input_tokens_estimated": 1061,
+        "output_tokens_estimated": 0,
+        "reads_count": 4,
+        "writes_count": 0,
+        "repeated_reads_blocked": 2,
+        "anatomy_lookups": 2
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T07:00:20.042Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9173,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 9734,
+        "output_tokens_estimated": 272,
+        "reads_count": 4,
+        "writes_count": 2,
+        "repeated_reads_blocked": 4,
+        "anatomy_lookups": 2
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T07:01:00.114Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9173,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 9734,
+        "output_tokens_estimated": 272,
+        "reads_count": 4,
+        "writes_count": 2,
+        "repeated_reads_blocked": 4,
+        "anatomy_lookups": 2
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T07:15:23.842Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9173,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 9734,
+        "output_tokens_estimated": 272,
+        "reads_count": 4,
+        "writes_count": 2,
+        "repeated_reads_blocked": 4,
+        "anatomy_lookups": 2
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T07:23:56.012Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9173,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "/home/user/app/routes/ai_routes.py",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrations/alembic.ini",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py",
+          "tokens_estimated": 500,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 3000,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        },
+        {
+          "file": "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md",
+          "tokens_estimated": 339,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 46,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 182,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 38,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 212,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 161,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 245,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 227,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 193,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 438,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 60,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrate_add_is_default.py",
+          "tokens_estimated": 230,
+          "action": "create"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 13234,
+        "output_tokens_estimated": 2643,
+        "reads_count": 10,
+        "writes_count": 14,
+        "repeated_reads_blocked": 17,
+        "anatomy_lookups": 5
+      }
     }
   ],
   "daemon_usage": [],

+ 1 - 0
app/models.py

@@ -96,6 +96,7 @@ class AIModel(db.Model):
     model_name = db.Column(db.String(200), nullable=False) # e.g. "Qwen/Qwen3-Next-80B-A3B-Instruct"
     system_prompt = db.Column(db.Text)
     is_active = db.Column(db.Boolean, default=True)
+    is_default = db.Column(db.Boolean, default=False)
     total_tokens = db.Column(db.BigInteger, default=0)
     created_at = db.Column(db.DateTime, default=datetime.utcnow)
     updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

+ 30 - 2
app/routes/ai_routes.py

@@ -373,9 +373,18 @@ def analysis_chat():
     db.session.add(user_msg_db)
     db.session.commit()
 
-    # --- 2. Build History ---
+    # --- 2. Build History & Select Model ---
     past_messages = AIMessage.query.filter_by(conversation_id=conversation_id).order_by(AIMessage.created_at.asc()).all()
-    model = AIModel.query.filter_by(is_active=True).first()
+
+    # 模型选择优先级:前端指定 model_id > 默认模型 > 激活模型
+    model_id = data.get('model_id')
+    model = None
+    if model_id:
+        model = AIModel.query.get(int(model_id))
+    if not model:
+        model = AIModel.query.filter_by(is_default=True).first()
+    if not model:
+        model = AIModel.query.filter_by(is_active=True).first()
     if not model:
         model = AIModel.query.first()
         if not model:
@@ -594,6 +603,7 @@ def get_model_list():
             'model_name': m.model_name,
             'system_prompt': m.system_prompt,
             'is_active': m.is_active,
+            'is_default': m.is_default or False,
             'total_tokens': m.total_tokens,
             'created_at': m.created_at.strftime('%Y-%m-%d %H:%M:%S')
         } for m in models]
@@ -653,6 +663,24 @@ def toggle_model():
         db.session.rollback()
         return jsonify({'error': str(e)}), 500
 
+@bp.route('/api/set-default', methods=['POST'])
+@login_required
+def set_default_model():
+    data = request.json
+    try:
+        # 先取消所有默认模型
+        AIModel.query.update({'is_default': False})
+        # 设置新的默认模型
+        model = AIModel.query.get(int(data['id']))
+        if not model:
+            return jsonify({'error': '模型不存在'}), 404
+        model.is_default = True
+        db.session.commit()
+        return jsonify({'message': '设置成功', 'id': model.id})
+    except Exception as e:
+        db.session.rollback()
+        return jsonify({'error': str(e)}), 500
+
 @bp.route('/api/test', methods=['POST'])
 @login_required
 def test_model():

+ 35 - 2
app/templates/ai_analysis.html

@@ -14,6 +14,13 @@
             </button>
             <h1 class="text-lg md:text-2xl font-bold tech-title truncate ml-2">AI分析报告</h1>
             <div class="flex items-center space-x-4">
+                <!-- 模型选择器 -->
+                <div class="hidden md:flex items-center gap-2">
+                    <i class="fas fa-microchip text-blue-400 text-sm"></i>
+                    <select id="model-select" class="bg-gray-800 text-gray-300 text-sm rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none px-3 py-1.5 max-w-[200px]">
+                        <option value="">加载中...</option>
+                    </select>
+                </div>
                  <div class="flex items-center space-x-2">
                     <div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold border border-cyan-400 shadow-md">A</div>
                     <span class="hidden md:inline text-sm text-gray-300">{{ current_user.username }}</span>
@@ -99,6 +106,9 @@
         userInput = document.getElementById('user-input');
         chatContainer = document.getElementById('chat-container');
 
+        // 加载模型列表
+        loadModelOptions();
+
         // Auto-resize textarea
         if (userInput) {
             userInput.addEventListener('keydown', function(e) {
@@ -110,6 +120,28 @@
         }
     });
 
+    function loadModelOptions() {
+        $.get('/ai/api/list', function(response) {
+            const select = $('#model-select');
+            select.empty();
+            const models = response.items || [];
+            if (models.length === 0) {
+                select.append('<option value="">无可用模型</option>');
+                return;
+            }
+            models.forEach(function(m) {
+                if (!m.is_active) return;
+                const label = m.is_default ? m.name + ' (默认)' : m.name;
+                const selected = m.is_default ? ' selected' : '';
+                select.append(`<option value="${m.id}"${selected}>${label}</option>`);
+            });
+            // 如果没有默认模型,选中第一个
+            if (!select.val() && select.find('option').length > 0) {
+                select.find('option:first').prop('selected', true);
+            }
+        });
+    }
+
     function scrollToBottom() {
         if (chatContainer) {
             chatContainer.scrollTop = chatContainer.scrollHeight;
@@ -137,9 +169,10 @@
             const response = await fetch('/ai/api/analysis/chat', {
                 method: 'POST',
                 headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({ 
+                body: JSON.stringify({
                     message: message,
-                    history: conversationHistory
+                    history: conversationHistory,
+                    model_id: $('#model-select').val() || null
                 })
             });
 

+ 20 - 1
app/templates/ai_management.html

@@ -152,7 +152,7 @@
                         
                         <div class="flex justify-between items-start mb-4 relative z-10">
                             <div>
-                                <h3 class="text-lg font-bold text-white mb-1">${item.name}</h3>
+                                <h3 class="text-lg font-bold text-white mb-1">${item.name} ${item.is_default ? '<span class="text-xs bg-yellow-500/20 text-yellow-400 border border-yellow-500/30 px-2 py-0.5 rounded-full ml-1 font-normal">默认</span>' : ''}</h3>
                                 <p class="text-xs text-gray-400 font-mono bg-gray-900/50 px-2 py-1 rounded inline-block">${item.model_name}</p>
                             </div>
                             <div class="flex items-center">
@@ -185,6 +185,9 @@
                             <button onclick='openEditModal(${JSON.stringify(item)})' class="bg-gray-700 hover:bg-gray-600 text-gray-300 py-2 px-3 rounded transition-colors" title="编辑">
                                 <i class="fas fa-edit"></i>
                             </button>
+                            <button onclick="setDefault(${item.id})" class="${item.is_default ? 'bg-yellow-600/30 text-yellow-400' : 'bg-gray-700 hover:bg-yellow-600/30 text-gray-300 hover:text-yellow-400'} py-2 px-3 rounded transition-colors" title="${item.is_default ? '当前默认' : '设为默认'}">
+                                <i class="fas fa-star"></i>
+                            </button>
                             <button onclick="deleteModel(${item.id})" class="bg-red-900/20 hover:bg-red-600 text-red-400 hover:text-white py-2 px-3 rounded transition-colors" title="删除">
                                 <i class="fas fa-trash-alt"></i>
                             </button>
@@ -271,6 +274,22 @@
         });
     }
 
+    function setDefault(id) {
+        $.ajax({
+            url: '/ai/api/set-default',
+            method: 'POST',
+            contentType: 'application/json',
+            data: JSON.stringify({id: id}),
+            success: function() {
+                showToast('已设为默认模型', 'success');
+                loadModels();
+            },
+            error: function(xhr) {
+                showToast('设置失败: ' + (xhr.responseJSON?.error || 'Unknown'), 'error');
+            }
+        });
+    }
+
     // --- Test Chat Logic ---
     function openTestModal(id, name, totalTokens) {
         currentTestModelId = id;

+ 24 - 0
migrate_add_is_default.py

@@ -0,0 +1,24 @@
+"""Add is_default column to ai_model table if not exists."""
+from app import create_app, db
+from sqlalchemy import text, inspect
+
+
+def add_is_default_column():
+    app = create_app()
+    with app.app_context():
+        inspector = inspect(db.engine)
+        columns = [col['name'] for col in inspector.get_columns('ai_model')]
+
+        if 'is_default' not in columns:
+            print("Adding 'is_default' column to ai_model table...")
+            db.session.execute(
+                text("ALTER TABLE ai_model ADD COLUMN is_default BOOLEAN DEFAULT FALSE")
+            )
+            db.session.commit()
+            print("Done. Column added successfully.")
+        else:
+            print("Column 'is_default' already exists. No action needed.")
+
+
+if __name__ == '__main__':
+    add_is_default_column()