diff --git a/API.md b/API.md new file mode 100644 index 0000000..020272c --- /dev/null +++ b/API.md @@ -0,0 +1,232 @@ +# 素材库系统 — 完整 API 文档 + +- 基础地址:`http://<主机>:16091` +- 数据格式:JSON(`Content-Type: application/json`) +- 文件上传:`multipart/form-data` +- 所有接口均为无鉴权访问(内网工具);如需鉴权可自行在前面加网关 + +--- + +## 一、项目 Projects + +### 1.1 项目列表 +`GET /api/projects` + +查询参数: +| 参数 | 说明 | +|------|------| +| `q` | 关键词(匹配名称/描述/标签) | +| `category` | 按类别过滤(精确) | +| `tags` | 按标签过滤(逗号分隔,多标签=同时满足 AND) | + +响应:项目数组,每项含 `id, name, description, category, tags, created_at, updated_at, material_count, analyzed_count, analysis`(最新项目摘要) + +### 1.2 新建项目 +`POST /api/projects` + +```json +{"name": "项目名", "description": "描述", "category": "市场资讯", "tags": "AI,行业"} +``` + +### 1.3 项目详情(含全部素材+分析) +`GET /api/projects/` + +响应:项目对象 + `materials` 数组(每个素材含抽取文本与最新 AI 分析) + +### 1.4 修改项目 +`PUT /api/projects/` +```json +{"name": "新名", "description": "新描述", "category": "新类别", "tags": "新标签"} +``` +(缺省字段保持原值) + +### 1.5 删除项目(级联删素材+分析+文件) +`DELETE /api/projects/` + +### 1.6 项目摘要历史 +`GET /api/projects//summaries` + +响应:该项目全部项目级 AI 摘要(按时间倒序),每份含 `id, summary, key_points[], keywords[], tags[], category, created_at, model` + +--- + +## 二、素材 Materials + +### 2.1 批量上传文件 +`POST /api/projects//materials/upload` +`multipart/form-data`,字段 `files`(可多个) + +支持类型:文本(txt/md/csv/json/xml/srt/log/html) · 文档(pdf/docx/pptx) · 图片 · 视频 · 音频 + +响应:`{"results": [素材对象], "count": n}`;不支持的扩展名返回 `{"ok":false,"error":"..."}` + +### 2.2 新增文本素材(粘贴内容) +`POST /api/projects//materials` +```json +{"name": "素材名", "content": "文本内容"} +``` + +### 2.3 素材详情 +`GET /api/materials/` + +响应含:`name, mtype, file_path, file_size, ext, meta(宽高/格式), extracted_text, text_status, status, created_at, analysis`(最新素材级分析) + +### 2.4 修改素材(改名 / 编辑文本内容) +`PUT /api/materials/` +```json +{"name": "新名字", "content": "新内容", "mtype": "text"} +``` +- `content` 有值时更新正文并**作废旧分析**(需重新分析) +- 缺省字段保持原值 + +### 2.5 删除素材 +`DELETE /api/materials/` + +### 2.6 下载/预览素材文件 +`GET /files/` +- 图片/音视频:浏览器内预览;其它:下载 + +--- + +## 三、AI 分析 Analysis + +### 3.1 分析单个素材 +`POST /api/materials//analyze` +→ `{"ok": true, "task": "material:"}` +后台执行,用任务接口轮询进度。 + +### 3.2 分析项目全部素材 +`POST /api/projects//analyze` +→ `{"ok": true, "task": "material:"}` + +### 3.3 生成项目级 AI 摘要 +`POST /api/projects//summary` +→ `{"ok": true, "task": "summary:"}` +每次生成都会保留为一条历史记录。 + +### 3.4 任务进度查询 +`GET /api/tasks/` +→ `{"running": bool, "done": n, "total": n, "msg": "已分析 2/4", "error": null}` + +--- + +## 四、搜索 Search + +### 4.1 全文搜索 +`GET /api/search?q=关键词` +- 项目匹配(名称/描述/标签 LIKE) +- 素材匹配(SQLite FTS5 + jieba 中文分词,索引含素材名/正文/AI摘要/关键词) + +响应:`{"projects": [...], "materials": [...]}` + +### 4.2 元数据/统计 +`GET /api/meta` +响应:`{"categories": [{name,count}], "tags": [{name,count}], "stats": {projects, materials, analyzed}}` + +--- + +## 五、设置 Settings + +### 5.1 读取设置 +`GET /api/settings` +响应(KV): +```json +{ + "llm_base_url": "https://api.deepseek.com", + "llm_api_key": "sk-...", + "llm_model": "deepseek-v4-flash", + "vision_base_url": "https://ark.cn-beijing.volces.com/api/plan/v3", + "vision_api_key": "ark-...", + "vision_model": "doubao-seed-evolving", + "backup_interval_hours": "24", + "backup_change_threshold": "50", + "backup_max_keep": "10", + "last_backup_time": "...", + "change_counter": "0" +} +``` + +### 5.2 保存设置 +`PUT /api/settings` +传需要修改的字段即可(部分更新)。大模型/视觉接口改动**立即生效**,无需重启。 + +### 5.3 测试接口连接 +`POST /api/settings/test` +```json +{"kind": "llm|vision", "base_url": "...", "api_key": "...", "model": "..."} +``` +→ `{"ok": true, "reply": "..."}` 或 `{"ok": false, "error": "..."}` + +--- + +## 六、备份 Backups + +### 6.1 备份列表 +`GET /api/backups` +→ `{"backups": [{name,size,time}], "last_backup_time": "...", "change_counter": "5"}` + +### 6.2 手动立即备份 +`POST /api/backup` +→ `{"ok": true, "name": "backup_20260827_100000_manual.zip"}` +备份包含完整数据库 + 上传目录,打包为 zip 存于 `data/backups/` + +### 6.3 下载备份 +`GET /api/backup/download/<文件名>` + +### 6.4 删除备份 +`DELETE /api/backup/<文件名>` + +### 6.5 导入恢复备份 +`POST /api/backup/restore` +`multipart/form-data`,字段 `file`(上传备份 zip) + +⚠️ 会**覆盖当前全部数据**(数据库 + 上传目录),后台有任务运行时返回 409 拒绝。 + +--- + +## 七、自动备份触发规则 + +后台调度器每 30 秒检查一次,满足以下任一条件即自动备份: +1. **时间间隔**:距上次备份 ≥ `backup_interval_hours` 小时(0=关闭) +2. **变更量**:`change_counter`(项目/素材增删改次数)≥ `backup_change_threshold`(0=关闭) + +备份后 `change_counter` 清零、`last_backup_time` 更新。保留份数 `backup_max_keep`,超出自动删除最旧备份。 + +--- + +## 八、错误码约定 + +| 情况 | 状态码 | +|------|--------| +| 参数/校验错误 | 400(`{"error": "..."}`) | +| 资源不存在 | 404 | +| 后台任务运行中(恢复备份) | 409 | +| 服务器内部错误 | 500 | + +## 九、调用示例(curl) + +```bash +# 新建项目 +curl -X POST http://127.0.0.1:16091/api/projects \ + -H 'Content-Type: application/json' \ + -d '{"name":"调研","category":"研究","tags":"AI"}' + +# 上传文件 +curl -X POST http://127.0.0.1:16091/api/projects/1/materials/upload \ + -F "files=@report.pdf" -F "files=@pic.png" + +# 新增文本素材 +curl -X POST http://127.0.0.1:16091/api/projects/1/materials \ + -H 'Content-Type: application/json' \ + -d '{"name":"笔记","content":"这是内容"}' + +# 全文搜索 +curl "http://127.0.0.1:16091/api/search?q=大模型" + +# 分析项目全部素材 → 轮询任务 +curl -X POST http://127.0.0.1:16091/api/projects/1/analyze +curl http://127.0.0.1:16091/api/tasks/material:1 + +# 立即备份 +curl -X POST http://127.0.0.1:16091/api/backup +``` diff --git a/README.md b/README.md index ca19881..60792fa 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,14 @@ Flask + SQLite + FTS5 全文检索 + DeepSeek/豆包大模型分析 的素材管 - **导入恢复**:上传备份 zip 一键还原(覆盖当前数据,有确认) ### 5. 其他便捷功能 +- **文本素材直接编辑**:查看详情时文本区域默认可直接编辑,点「💾 保存」才真正落库;有未保存修改时关闭弹窗/点外部区域会提示是否保存,防误点 +- **Markdown 切换**:文本查看支持「纯文本 / Markdown」切换,默认纯文本,Markdown 模式渲染预览(只读) - **粘贴图片上传**:在项目页直接 Ctrl+V 粘贴剪贴板截图/图片 -- **文本素材编辑**:新增的文本素材可再次编辑名称与内容(内容改动后需重新 AI 分析) - **一键复制**:文本素材可一键复制全文(表格行 + 详情弹窗均有入口) - **摘要历史**:每次生成的“项目 AI 摘要”全部保留,可展开查看历史版本 +- **素材列表**:显示类型/名称/大小/创建时间/状态/操作,点列头可按类型/名称/大小/创建时间升序降序排序 - **Logo 点击**:左上角 Logo 一键返回首页 +- **API 文档**:右上角 📖 按钮(或 `/api-doc`)查看完整接口文档,详见 `API.md` ## 技术栈 - 后端:Flask 3 (Python 3.12, openclaw conda 环境) @@ -47,6 +50,10 @@ Flask + SQLite + FTS5 全文检索 + DeepSeek/豆包大模型分析 的素材管 - 检索:SQLite FTS5 + jieba - LLM:DeepSeek `deepseek-v4-flash`(结构化 JSON 分析)+ 豆包 `doubao-seed-evolving`(图片视觉描述) +## API 文档 +- 网页版:`http://:16091/api-doc`(右上角 📖 按钮) +- 完整文档见 `API.md`(含全部接口、参数、响应、curl 示例) + ## 快速开始 ```bash ./start.sh # 启动 (默认 16091) diff --git a/app.py b/app.py index 098ef8e..83259f8 100644 --- a/app.py +++ b/app.py @@ -651,6 +651,125 @@ def index(): return send_file(str(STATIC_DIR / "index.html")) +# ---------------- API 文档页 ---------------- +def _md_to_html(md): + """极简 markdown 渲染(标题/表格/代码块/列表/行内样式)""" + import html as _html + lines = md.split("\n") + out = [] + in_code = False + in_table = False + buf = [] + + def flush_table(): + nonlocal buf + if not buf: + return + rows = buf + buf = [] + html = "" + for i, row in enumerate(rows): + cells = [c.strip() for c in row.strip().strip("|").split("|")] + tag = "th" if i == 0 else "td" + html += "" + "".join(f"<{tag}>{_inline(c)}" for c in cells) + "" + html += "
" + out.append(html) + + def _inline(t): + t = _html.escape(t) + t = re.sub(r"\*\*(.+?)\*\*", r"\1", t) + t = re.sub(r"`(.+?)`", r"\1", t) + return t + + i = 0 + while i < len(lines): + line = lines[i] + if line.strip().startswith("```"): + if not in_code: + in_code = True + out.append("
")
+            else:
+                in_code = False
+                out.append("
") + i += 1 + continue + if in_code: + out.append(_html.escape(line)) + i += 1 + continue + if line.startswith("|"): + if not in_table: + in_table = True + buf = [] + if re.fullmatch(r"\|?[\s|:—-]+\|?", line) and line.count("|") > 1: + # 分隔行,跳过 + i += 1 + continue + buf.append(line) + i += 1 + continue + if in_table: + flush_table() + in_table = False + s = line.strip() + if not s: + out.append("") + i += 1 + continue + if s.startswith("### "): + out.append(f"

{_inline(s[4:])}

") + elif s.startswith("## "): + out.append(f"

{_inline(s[3:])}

") + elif s.startswith("# "): + out.append(f"

{_inline(s[2:])}

") + elif s.startswith("- "): + out.append(f"
  • {_inline(s[2:])}
  • ") + elif re.match(r"^\d+\.\s", s): + out.append(f"
  • {_inline(re.sub(r'^\d+\.\s', '', s))}
  • ") + else: + out.append(f"

    {_inline(s)}

    ") + i += 1 + if in_code: + out.append("") + if in_table: + flush_table() + # 合并连续的
    • + merged = [] + for ln in out: + if ln.startswith("
    • ") and merged and merged[-1].startswith("
    • "): + merged[-1] = merged[-1] + ln + elif ln.startswith("
    • "): + merged.append(ln) + else: + merged.append(ln) + merged = [("
        " + ln + "
      ") if ln.startswith("
    • ") else ln for ln in merged] + return "\n".join(merged) + + +@app.route("/api-doc") +def api_doc(): + md = (BASE_DIR / "API.md").read_text(encoding="utf-8") + body = _md_to_html(md) + html = f""" + +素材库系统 API 文档 +{body}""" + return html + + @app.route("/files/") def serve_file(mid): db = get_db() diff --git a/static/index.html b/static/index.html index 9be5288..e9fd981 100644 --- a/static/index.html +++ b/static/index.html @@ -127,6 +127,21 @@ main{flex:1;padding:22px 26px;overflow-y:auto;max-height:calc(100vh - 59px)} .b-extracted{background:rgba(79,140,255,.12);color:#8ab4ff} .b-empty{color:var(--dim);background:var(--panel2)} .mat-name{font-weight:600;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.md-prev{background:#0b0f19;border:1px solid var(--line);border-radius:10px;padding:14px;max-height:380px;overflow-y:auto;font-size:13px;line-height:1.75;word-break:break-word} +.md-prev h1,.md-prev h2,.md-prev h3{color:var(--acc);margin:10px 0 6px} +.md-prev h1{font-size:20px}.md-prev h2{font-size:17px}.md-prev h3{font-size:15px} +.md-prev pre{background:#141a2c;border:1px solid var(--line);border-radius:8px;padding:10px;overflow-x:auto} +.md-prev code{background:#1e2740;padding:2px 5px;border-radius:5px;font-size:12px;color:#22d3a5} +.md-prev pre code{background:none;padding:0} +.md-prev table{border-collapse:collapse;margin:8px 0} +.md-prev th,.md-prev td{border:1px solid var(--line);padding:6px 10px} +.md-prev ul,.md-prev ol{padding-left:20px} +.md-prev li{margin:3px 0} +.btn.sm.active{background:rgba(79,140,255,.2);border-color:var(--acc);color:var(--acc)} +@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.06)}100%{transform:scale(1)}} +.btn.pulse{animation:pulse .8s ease infinite} +th.sortable{cursor:pointer;user-select:none;white-space:nowrap} +th.sortable:hover{color:var(--acc)} .mat-meta{font-size:11px;color:var(--dim)} /* 弹窗 */ @@ -165,9 +180,11 @@ pre.text-prev{white-space:pre-wrap;word-break:break-word;background:#0b0f19;bord +
      +
      0项目
      @@ -225,16 +242,15 @@ pre.text-prev{white-space:pre-wrap;word-break:break-word;background:#0b0f19;bord
      - +