jfox-cli 0.3.2__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jfox_cli-0.4.1/.claude/settings.local.json +9 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/PKG-INFO +1 -1
- jfox_cli-0.4.1/docs/superpowers/plans/2026-04-15-session-summary-confirmation.md +135 -0
- jfox_cli-0.4.1/docs/superpowers/specs/2026-04-15-session-summary-confirmation-design.md +52 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/__init__.py +1 -1
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/cli.py +115 -4
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/config.py +6 -5
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/daemon/__init__.py +8 -1
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/daemon/process.py +116 -23
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/daemon/server.py +9 -2
- jfox_cli-0.4.1/jfox/embedding_backend.py +174 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/indexer.py +2 -2
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/vector_store.py +52 -1
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/pyproject.toml +1 -1
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/skills-recommend/claude-code/jfox-common/SKILL.md +1 -1
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/skills-recommend/claude-code/jfox-session-summary/SKILL.md +32 -9
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/conftest.py +11 -1
- jfox_cli-0.4.1/tests/test_config_set_unit.py +78 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_config_unit.py +4 -4
- jfox_cli-0.4.1/tests/test_embedding_device.py +107 -0
- jfox_cli-0.4.1/tests/unit/test_daemon_process.py +161 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_indexer_clear_before_rebuild.py +13 -13
- jfox_cli-0.4.1/tests/unit/test_vector_store_clear.py +213 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/uv.lock +1 -1
- jfox_cli-0.3.2/jfox/embedding_backend.py +0 -112
- jfox_cli-0.3.2/tests/unit/test_vector_store_clear.py +0 -76
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/.githooks/pre-push +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/.github/workflows/integration-test.yml +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/.github/workflows/publish.yml +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/.gitignore +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/.python-version +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/AGENTS.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/CHANGELOG.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/CLAUDE.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/DEVELOPMENT_PLAN.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/README.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/SESSION.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/SESSION_SUMMARY.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/installation.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-11-bulk-import-bm25-fix.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-11-edit-command.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-11-unify-format-option.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-ci-coverage-optimization.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-edit-content-file.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-fix-index-rebuild-clear.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-fix-index-verify-id-mismatch.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-fix-jfox-health-skill-kb-param.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-index-kb-param.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-lazy-import-perf.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-12-skill-redesign.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/plans/2026-04-14-sync-docs-daemon-show.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/specs/2026-04-03-bugfixes-design.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/specs/2026-04-12-skill-redesign-design.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/specs/2026-04-13-pr-auto-code-review-design.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/superpowers/specs/2026-04-14-show-command-design.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/docs/troubleshooting.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jessica-jones-static-cable.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/__main__.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/bm25_index.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/daemon/__main__.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/daemon/client.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/formatters.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/git_extractor.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/global_config.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/graph.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/kb_manager.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/models.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/note.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/performance.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/search_engine.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/template.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/jfox/template_cli.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/pytest.ini +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/run_full_test.ps1 +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/skills-recommend/README.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/skills-recommend/claude-code/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/skills-recommend/claude-code/jfox-organize/SKILL.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/skills-recommend/claude-code/jfox-search/SKILL.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/COVERAGE_PLAN.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/MIGRATION.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/TESTS.md +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/integration/__init__.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/integration/test_backlinks.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/performance/__init__.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/performance/test_performance.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_advanced_features.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_cli_format.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_core_workflow.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_hybrid_search.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_integration.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_kb_current.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/test_suggest_links.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/__init__.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_bm25_batch.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_edit.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_format_unify.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_formatters.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_git_extractor.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_global_config.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_index_kb_param.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_indexer_verify.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_kb_manager.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_lazy_import.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_logging_config.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_show.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_template.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/unit/test_template_cli.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/utils/__init__.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/utils/assertions.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/utils/jfox_cli.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/utils/note_generator.py +0 -0
- {jfox_cli-0.3.2 → jfox_cli-0.4.1}/tests/utils/temp_kb.py +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(cp \"C:/Users/zhuzh/.claude/skills/jfox-ingest/SKILL.md\" \"skills-recommend/claude-code/jfox-ingest/SKILL.md\")",
|
|
5
|
+
"Bash(cp \"C:/Users/zhuzh/.claude/skills/jfox-organize/SKILL.md\" \"skills-recommend/claude-code/jfox-organize/SKILL.md\")",
|
|
6
|
+
"Bash(cp \"C:/Users/zhuzh/.claude/skills/jfox-common/SKILL.md\" \"skills-recommend/claude-code/jfox-common/SKILL.md\")"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Session Summary Confirmation Flow Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add user confirmation and note type selection steps to the jfox-session-summary skill before writing to the knowledge base.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Insert two new steps (confirm content + select type) between summary generation and `jfox add`. The modification is purely in the skill Markdown document — no Python code changes.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Skill document (Markdown), AskUserQuestion tool
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-04-15-session-summary-confirmation-design.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Task 1: Rewrite SKILL.md workflow section
|
|
16
|
+
|
|
17
|
+
**Files:**
|
|
18
|
+
- Modify: `skills-recommend/claude-code/jfox-session-summary/SKILL.md`
|
|
19
|
+
|
|
20
|
+
This is the only task. The file is a single skill document — no decomposition needed.
|
|
21
|
+
|
|
22
|
+
- [ ] **Step 1: Read current SKILL.md**
|
|
23
|
+
|
|
24
|
+
Read `skills-recommend/claude-code/jfox-session-summary/SKILL.md` to confirm current content matches expectations (3-step workflow, Step 2 hardcodes `--type fleeting`).
|
|
25
|
+
|
|
26
|
+
- [ ] **Step 2: Replace Step 2 and add new Steps 2–3**
|
|
27
|
+
|
|
28
|
+
Replace the entire `### Step 2: 写入知识库` section and everything after it (through end of file) with the new 5-step workflow. The exact replacement content:
|
|
29
|
+
|
|
30
|
+
Replace the old `### Step 2` and `### Step 3` sections (lines 42–96) with:
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
### Step 2: 用户确认
|
|
34
|
+
|
|
35
|
+
将生成的总结用普通文本输出,供用户阅读。然后使用 `AskUserQuestion` 询问:
|
|
36
|
+
|
|
37
|
+
- 问题:`笔记内容是否 OK?`
|
|
38
|
+
- 选项:
|
|
39
|
+
- `内容没问题` → 继续 Step 3
|
|
40
|
+
- `需要修改` → 用户在 "Other" 中输入修改意见,根据意见调整总结后回到 Step 2 重新展示和确认
|
|
41
|
+
|
|
42
|
+
循环直到用户满意为止。
|
|
43
|
+
|
|
44
|
+
### Step 3: 选择笔记类型
|
|
45
|
+
|
|
46
|
+
用户确认内容后,使用 `AskUserQuestion` 询问笔记类型:
|
|
47
|
+
|
|
48
|
+
- 问题:`选择笔记类型`
|
|
49
|
+
- 选项:
|
|
50
|
+
- `fleeting`(推荐)— 会话记录是临时性笔记,后续可提炼为 permanent
|
|
51
|
+
- `literature` — 如果会话有明确的参考资料来源
|
|
52
|
+
- `permanent` — 如果总结已经是成熟的知识
|
|
53
|
+
|
|
54
|
+
### Step 4: 写入知识库
|
|
55
|
+
|
|
56
|
+
使用 Step 3 选定的笔记类型执行写入:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
jfox add "<markdown-escaped-summary>" \
|
|
60
|
+
--title "Session: <topic>" \
|
|
61
|
+
--type <Step 3 选定的类型> \
|
|
62
|
+
--tag session \
|
|
63
|
+
--kb <kb-name> \
|
|
64
|
+
--format json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**注意**:
|
|
68
|
+
- 标题格式统一为 `Session: <简短主题>`
|
|
69
|
+
- 类型使用 Step 3 的选择结果,不再硬编码 `fleeting`
|
|
70
|
+
- 标签统一使用 `session`
|
|
71
|
+
- 内容中的双引号需要转义,或使用 `--content-file` 从临时文件读取
|
|
72
|
+
|
|
73
|
+
### Step 5: 处理长内容
|
|
74
|
+
|
|
75
|
+
如果总结超过 500 字或包含特殊字符,优先使用 `--content-file`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 写入临时文件
|
|
79
|
+
cat > /tmp/session-summary.md << 'EOF'
|
|
80
|
+
<总结内容>
|
|
81
|
+
EOF
|
|
82
|
+
|
|
83
|
+
# 从文件导入
|
|
84
|
+
jfox add --content-file /tmp/session-summary.md \
|
|
85
|
+
--title "Session: <topic>" \
|
|
86
|
+
--type <Step 3 选定的类型> \
|
|
87
|
+
--tag session \
|
|
88
|
+
--kb <kb-name> \
|
|
89
|
+
--format json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 命令参考
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 直接添加(短内容)
|
|
96
|
+
jfox add "<summary>" --title "Session: <topic>" --type <type> --tag session --kb <name>
|
|
97
|
+
|
|
98
|
+
# 从文件添加(长内容或含特殊字符)
|
|
99
|
+
jfox add --content-file <path> --title "Session: <topic>" --type <type> --tag session --kb <name>
|
|
100
|
+
|
|
101
|
+
# 验证写入
|
|
102
|
+
jfox show <note_id> --format json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 错误处理
|
|
106
|
+
|
|
107
|
+
- **"Knowledge base not found"**: 提示用户先运行 `/jfox-common` 创建知识库
|
|
108
|
+
- **内容过长导致 shell 解析失败**: 切换到 `--content-file` 方式
|
|
109
|
+
- **特殊字符转义问题**: 使用单引号包裹内容,或写入临时文件
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- [ ] **Step 3: Verify the file reads correctly**
|
|
113
|
+
|
|
114
|
+
Read the full updated `SKILL.md` and confirm:
|
|
115
|
+
1. Step 1 (生成会话总结) is unchanged
|
|
116
|
+
2. Step 2 (用户确认) has AskUserQuestion with "内容没问题" and "需要修改" options, plus loop description
|
|
117
|
+
3. Step 3 (选择笔记类型) has AskUserQuestion with fleeting/literature/permanent options
|
|
118
|
+
4. Step 4 (写入知识库) uses `<Step 3 选定的类型>` not hardcoded `fleeting`
|
|
119
|
+
5. Step 5 (处理长内容) also uses `<Step 3 选定的类型>`
|
|
120
|
+
6. 命令参考 section shows `--type <type>` not `--type fleeting`
|
|
121
|
+
7. No leftover hardcoded `fleeting` in any `jfox add` example (except the Step 3 option description)
|
|
122
|
+
|
|
123
|
+
- [ ] **Step 4: Commit**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git add skills-recommend/claude-code/jfox-session-summary/SKILL.md
|
|
127
|
+
git commit -m "feat(skill): add user confirmation and note type selection to session-summary
|
|
128
|
+
|
|
129
|
+
Inserts Step 2 (content confirmation via AskUserQuestion) and Step 3
|
|
130
|
+
(note type selection) before writing to knowledge base. Users can now
|
|
131
|
+
review the generated summary, request modifications, and choose between
|
|
132
|
+
fleeting/literature/permanent note types.
|
|
133
|
+
|
|
134
|
+
Closes #154"
|
|
135
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# jfox-session-summary 用户确认流程设计
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-15
|
|
4
|
+
**Issue**: #154
|
|
5
|
+
**Status**: Draft
|
|
6
|
+
|
|
7
|
+
## 背景
|
|
8
|
+
|
|
9
|
+
当前 `jfox-session-summary` skill 生成总结后直接写入知识库,硬编码 `--type fleeting`。用户无法审查内容或选择笔记类型。
|
|
10
|
+
|
|
11
|
+
## 改动范围
|
|
12
|
+
|
|
13
|
+
仅修改 `skills-recommend/claude-code/jfox-session-summary/SKILL.md`。不涉及 CLI 命令或 Python 代码变更。
|
|
14
|
+
|
|
15
|
+
## 新流程
|
|
16
|
+
|
|
17
|
+
当前 3 步扩展为 5 步:
|
|
18
|
+
|
|
19
|
+
| Step | 内容 | 状态 |
|
|
20
|
+
|------|------|------|
|
|
21
|
+
| Step 1 | 生成会话总结 | 不变 |
|
|
22
|
+
| Step 2 | 展示总结 + 用户确认 | **新增** |
|
|
23
|
+
| Step 3 | 选择笔记类型 | **新增** |
|
|
24
|
+
| Step 4 | `jfox add --type <选择>` | 原 Step 2 |
|
|
25
|
+
| Step 5 | 处理长内容 | 原 Step 3,不变 |
|
|
26
|
+
|
|
27
|
+
### Step 2:展示总结 + 用户确认
|
|
28
|
+
|
|
29
|
+
1. 用普通文本输出完整总结内容,供用户阅读
|
|
30
|
+
2. 使用 `AskUserQuestion` 询问「笔记内容是否 OK?」
|
|
31
|
+
- **内容没问题** → 继续 Step 3
|
|
32
|
+
- **需要修改** → 用户在 Other 中输入修改意见 → 根据意见调整总结 → 回到 Step 2 重新展示和确认
|
|
33
|
+
3. 循环直到用户满意为止
|
|
34
|
+
|
|
35
|
+
### Step 3:选择笔记类型
|
|
36
|
+
|
|
37
|
+
使用 `AskUserQuestion` 询问「选择笔记类型」:
|
|
38
|
+
|
|
39
|
+
- **fleeting**(推荐)— 会话记录是临时性笔记,后续可提炼为 permanent
|
|
40
|
+
- **literature** — 会话有明确的参考资料来源
|
|
41
|
+
- **permanent** — 总结已经是成熟的知识
|
|
42
|
+
|
|
43
|
+
### Step 4:写入知识库
|
|
44
|
+
|
|
45
|
+
`jfox add` 的 `--type` 参数使用 Step 3 的选择结果,不再硬编码 `fleeting`。其余参数(`--title`、`--tag session`、`--kb`)不变。
|
|
46
|
+
|
|
47
|
+
## 不在范围内
|
|
48
|
+
|
|
49
|
+
- 不修改 `jfox add` CLI 命令本身
|
|
50
|
+
- 不增加新的笔记类型
|
|
51
|
+
- 不改变总结模板格式(Step 1 不变)
|
|
52
|
+
- 不做自动检测笔记类型的智能逻辑
|
|
@@ -8,6 +8,8 @@ from datetime import datetime
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import List, Optional
|
|
10
10
|
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
11
13
|
# Windows 下强制 UTF-8 输出,避免中文乱码
|
|
12
14
|
if sys.platform == "win32":
|
|
13
15
|
if hasattr(sys.stdout, "reconfigure"):
|
|
@@ -52,6 +54,9 @@ for _lib in (
|
|
|
52
54
|
|
|
53
55
|
logger = logging.getLogger(__name__)
|
|
54
56
|
|
|
57
|
+
# config set 支持的配置项
|
|
58
|
+
_VALID_CONFIG_KEYS = {"device", "embedding_model", "batch_size"}
|
|
59
|
+
|
|
55
60
|
# 创建应用
|
|
56
61
|
app = typer.Typer(
|
|
57
62
|
name="jfox",
|
|
@@ -569,6 +574,104 @@ def search(
|
|
|
569
574
|
raise typer.Exit(1)
|
|
570
575
|
|
|
571
576
|
|
|
577
|
+
def _warn_dimension_change(new_model: str):
|
|
578
|
+
"""切换 embedding 模型时警告用户重建索引"""
|
|
579
|
+
if new_model == "auto":
|
|
580
|
+
return # auto 模式不需要警告
|
|
581
|
+
try:
|
|
582
|
+
import chromadb
|
|
583
|
+
|
|
584
|
+
chroma_path = config.chroma_dir
|
|
585
|
+
if not chroma_path.exists():
|
|
586
|
+
return
|
|
587
|
+
client = chromadb.PersistentClient(path=str(chroma_path))
|
|
588
|
+
collection = client.get_collection("notes")
|
|
589
|
+
if collection.count() == 0:
|
|
590
|
+
return # 空索引,无需警告
|
|
591
|
+
except Exception:
|
|
592
|
+
return
|
|
593
|
+
|
|
594
|
+
# 有已有索引 + 换了模型 = 需要重建
|
|
595
|
+
console.print(
|
|
596
|
+
f"\n[yellow]⚠ 模型已更改为 {new_model}[/yellow]\n"
|
|
597
|
+
f"[yellow] 如果检索结果异常,请重建索引:[/yellow]\n"
|
|
598
|
+
f" jfox index rebuild\n"
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _config_set_impl(key: str, value: str):
|
|
603
|
+
"""设置知识库配置项"""
|
|
604
|
+
if key not in _VALID_CONFIG_KEYS:
|
|
605
|
+
raise ValueError(f"不支持的配置项: {key},可选值: {', '.join(sorted(_VALID_CONFIG_KEYS))}")
|
|
606
|
+
|
|
607
|
+
config_path = config.zk_dir / "config.yaml"
|
|
608
|
+
|
|
609
|
+
# 读取现有配置
|
|
610
|
+
if config_path.exists():
|
|
611
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
612
|
+
data = yaml.safe_load(f) or {}
|
|
613
|
+
else:
|
|
614
|
+
data = {}
|
|
615
|
+
|
|
616
|
+
# 类型转换
|
|
617
|
+
if key == "batch_size":
|
|
618
|
+
value = int(value)
|
|
619
|
+
|
|
620
|
+
# 写入配置
|
|
621
|
+
data[key] = value
|
|
622
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
623
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
624
|
+
yaml.dump(data, f, allow_unicode=True, sort_keys=False)
|
|
625
|
+
|
|
626
|
+
# 同步更新内存中的 config 对象
|
|
627
|
+
setattr(config, key, value if key != "batch_size" else int(value))
|
|
628
|
+
|
|
629
|
+
# 重置 backend 单例,让新配置在下次使用时生效
|
|
630
|
+
from .embedding_backend import reset_backend
|
|
631
|
+
|
|
632
|
+
reset_backend()
|
|
633
|
+
|
|
634
|
+
console.print(f"[green]✓ {key} = {value}[/green]")
|
|
635
|
+
|
|
636
|
+
# 切换模型时检查维度
|
|
637
|
+
if key == "embedding_model":
|
|
638
|
+
_warn_dimension_change(value)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
@app.command(name="config")
|
|
642
|
+
def config_cmd(
|
|
643
|
+
action: str = typer.Argument(..., help="操作: set"),
|
|
644
|
+
key: str = typer.Argument(None, help="配置项名称"),
|
|
645
|
+
value: str = typer.Argument(None, help="配置值"),
|
|
646
|
+
):
|
|
647
|
+
"""
|
|
648
|
+
查看/修改知识库配置
|
|
649
|
+
|
|
650
|
+
示例:
|
|
651
|
+
|
|
652
|
+
jfox config set device cuda
|
|
653
|
+
jfox config set device auto
|
|
654
|
+
jfox config set embedding_model BAAI/bge-m3
|
|
655
|
+
jfox config set embedding_model auto
|
|
656
|
+
"""
|
|
657
|
+
try:
|
|
658
|
+
if action == "set":
|
|
659
|
+
if key is None:
|
|
660
|
+
console.print("[red]✗ 缺少配置项名称[/red]")
|
|
661
|
+
raise typer.Exit(1)
|
|
662
|
+
if value is None:
|
|
663
|
+
console.print("[red]✗ 缺少配置值[/red]")
|
|
664
|
+
raise typer.Exit(1)
|
|
665
|
+
_config_set_impl(key, value)
|
|
666
|
+
else:
|
|
667
|
+
console.print(f"[red]✗ 未知操作: {action}[/red]")
|
|
668
|
+
console.print(" 可用操作: set")
|
|
669
|
+
raise typer.Exit(1)
|
|
670
|
+
except ValueError as e:
|
|
671
|
+
console.print(f"[red]✗ {e}[/red]")
|
|
672
|
+
raise typer.Exit(1)
|
|
673
|
+
|
|
674
|
+
|
|
572
675
|
def _status_impl(output_format: str, json_output: bool):
|
|
573
676
|
"""查看知识库状态的内部实现"""
|
|
574
677
|
from .formatters import OutputFormatter
|
|
@@ -587,8 +690,9 @@ def _status_impl(output_format: str, json_output: bool):
|
|
|
587
690
|
},
|
|
588
691
|
"stats": stats,
|
|
589
692
|
"backend": {
|
|
590
|
-
"type":
|
|
591
|
-
"model": backend.model_name
|
|
693
|
+
"type": backend.resolved_device,
|
|
694
|
+
"model": backend.model_name or "auto (未加载)",
|
|
695
|
+
"dimension": backend.dimension,
|
|
592
696
|
},
|
|
593
697
|
}
|
|
594
698
|
|
|
@@ -612,8 +716,9 @@ def _status_impl(output_format: str, json_output: bool):
|
|
|
612
716
|
table.add_row("Fleeting", str(stats["by_type"].get("fleeting", 0)))
|
|
613
717
|
table.add_row("Literature", str(stats["by_type"].get("literature", 0)))
|
|
614
718
|
table.add_row("Permanent", str(stats["by_type"].get("permanent", 0)))
|
|
615
|
-
table.add_row("Backend",
|
|
616
|
-
table.add_row("Model", backend.model_name)
|
|
719
|
+
table.add_row("Backend", backend.resolved_device)
|
|
720
|
+
table.add_row("Model", backend.model_name or "auto (未加载)")
|
|
721
|
+
table.add_row("Dimension", str(backend.dimension))
|
|
617
722
|
|
|
618
723
|
console.print(table)
|
|
619
724
|
else:
|
|
@@ -2507,7 +2612,10 @@ def daemon(
|
|
|
2507
2612
|
|
|
2508
2613
|
try:
|
|
2509
2614
|
if action == "start":
|
|
2615
|
+
from .daemon.process import DAEMON_LOG_FILE
|
|
2616
|
+
|
|
2510
2617
|
console.print("[yellow]正在启动 embedding daemon...[/yellow]")
|
|
2618
|
+
console.print(f"[dim]日志文件: {DAEMON_LOG_FILE}[/dim]")
|
|
2511
2619
|
ok = start_daemon(port=port)
|
|
2512
2620
|
if ok:
|
|
2513
2621
|
info = get_daemon_status()
|
|
@@ -2520,11 +2628,13 @@ def daemon(
|
|
|
2520
2628
|
table.add_row("端口", str(info["port"]))
|
|
2521
2629
|
table.add_row("模型", info["model"])
|
|
2522
2630
|
table.add_row("维度", str(info["dimension"]))
|
|
2631
|
+
table.add_row("设备", info.get("device", "unknown"))
|
|
2523
2632
|
console.print(table)
|
|
2524
2633
|
else:
|
|
2525
2634
|
console.print("[green]✓ Daemon 已启动[/green]")
|
|
2526
2635
|
else:
|
|
2527
2636
|
console.print("[red]✗ Daemon 启动失败[/red]")
|
|
2637
|
+
console.print(f"[dim]查看日志: {DAEMON_LOG_FILE}[/dim]")
|
|
2528
2638
|
raise typer.Exit(1)
|
|
2529
2639
|
|
|
2530
2640
|
elif action == "stop":
|
|
@@ -2546,6 +2656,7 @@ def daemon(
|
|
|
2546
2656
|
table.add_row("端口", str(info["port"]))
|
|
2547
2657
|
table.add_row("模型", info.get("model", "unknown"))
|
|
2548
2658
|
table.add_row("维度", str(info.get("dimension", "unknown")))
|
|
2659
|
+
table.add_row("设备", info.get("device", "unknown"))
|
|
2549
2660
|
console.print(table)
|
|
2550
2661
|
else:
|
|
2551
2662
|
console.print("[dim]Daemon 未运行[/dim]")
|
|
@@ -28,10 +28,10 @@ class ZKConfig:
|
|
|
28
28
|
zk_dir: Path = field(init=False)
|
|
29
29
|
chroma_dir: Path = field(init=False)
|
|
30
30
|
|
|
31
|
-
#
|
|
32
|
-
embedding_model: str = "
|
|
33
|
-
embedding_dimension: int =
|
|
34
|
-
device: str = "auto" # auto /
|
|
31
|
+
# Embedding 配置
|
|
32
|
+
embedding_model: str = "auto" # auto = 根据 device 自动选择模型
|
|
33
|
+
embedding_dimension: int = 0 # 0 = 动态,由模型决定
|
|
34
|
+
device: str = "auto" # auto / cuda / cpu
|
|
35
35
|
batch_size: int = 32
|
|
36
36
|
|
|
37
37
|
# 检索配置
|
|
@@ -122,13 +122,14 @@ config = get_config()
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
def _reset_singletons():
|
|
125
|
-
"""重置所有缓存的单例(搜索引擎、向量存储、BM25
|
|
125
|
+
"""重置所有缓存的单例(搜索引擎、向量存储、BM25 索引、embedding 后端)"""
|
|
126
126
|
import importlib
|
|
127
127
|
|
|
128
128
|
for module_name, fn_name in [
|
|
129
129
|
(".bm25_index", "reset_bm25_index"),
|
|
130
130
|
(".search_engine", "reset_search_engine"),
|
|
131
131
|
(".vector_store", "reset_vector_store"),
|
|
132
|
+
(".embedding_backend", "reset_backend"),
|
|
132
133
|
]:
|
|
133
134
|
try:
|
|
134
135
|
module = importlib.import_module(module_name, package="jfox")
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
DEFAULT_HOST = "127.0.0.1"
|
|
4
4
|
DEFAULT_PORT = 18700
|
|
5
5
|
|
|
6
|
-
from .process import
|
|
6
|
+
from .process import (
|
|
7
|
+
DAEMON_LOG_FILE,
|
|
8
|
+
get_daemon_status,
|
|
9
|
+
is_daemon_running,
|
|
10
|
+
start_daemon,
|
|
11
|
+
stop_daemon,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
__all__ = [
|
|
15
|
+
"DAEMON_LOG_FILE",
|
|
9
16
|
"DEFAULT_HOST",
|
|
10
17
|
"DEFAULT_PORT",
|
|
11
18
|
"start_daemon",
|
|
@@ -16,9 +16,28 @@ from typing import Optional
|
|
|
16
16
|
|
|
17
17
|
from . import DEFAULT_HOST, DEFAULT_PORT
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
def _get_pythonw_executable() -> str:
|
|
21
|
+
"""获取 pythonw.exe 路径(Windows 无控制台入口)
|
|
22
|
+
|
|
23
|
+
Windows 上优先使用 pythonw.exe 避免 daemon 子进程弹出控制台窗口。
|
|
24
|
+
如果 pythonw.exe 不存在则回退到 sys.executable。
|
|
25
|
+
非 Windows 平台直接返回 sys.executable。
|
|
26
|
+
"""
|
|
27
|
+
if sys.platform != "win32":
|
|
28
|
+
return sys.executable
|
|
29
|
+
|
|
30
|
+
pythonw = sys.executable.replace("python.exe", "pythonw.exe")
|
|
31
|
+
if pythonw != sys.executable and Path(pythonw).exists():
|
|
32
|
+
return pythonw
|
|
33
|
+
return sys.executable
|
|
34
|
+
|
|
35
|
+
|
|
19
36
|
logger = logging.getLogger(__name__)
|
|
20
37
|
|
|
21
38
|
STARTUP_TIMEOUT = 60 # 模型加载可能较慢
|
|
39
|
+
FIRST_RUN_TIMEOUT = 300 # 首次下载模型超时(秒)
|
|
40
|
+
DAEMON_LOG_FILE = Path.home() / ".jfox_daemon.log"
|
|
22
41
|
PID_FILE = Path.home() / ".jfox_daemon.pid"
|
|
23
42
|
|
|
24
43
|
|
|
@@ -86,6 +105,56 @@ def is_daemon_running() -> bool:
|
|
|
86
105
|
return False
|
|
87
106
|
|
|
88
107
|
|
|
108
|
+
def _check_model_cache() -> dict:
|
|
109
|
+
"""
|
|
110
|
+
检查当前模型是否已缓存
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
dict: {"needs_download": bool, "model_name": str, "size_hint": str}
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
from ..config import config as _cfg
|
|
117
|
+
from ..embedding_backend import _CPU_DEFAULT_MODEL, _GPU_DEFAULT_MODEL
|
|
118
|
+
|
|
119
|
+
# 确定目标模型名
|
|
120
|
+
model_name = _cfg.embedding_model
|
|
121
|
+
if model_name == "auto" or not model_name:
|
|
122
|
+
try:
|
|
123
|
+
import torch
|
|
124
|
+
|
|
125
|
+
if torch.cuda.is_available():
|
|
126
|
+
model_name = _GPU_DEFAULT_MODEL
|
|
127
|
+
else:
|
|
128
|
+
model_name = _CPU_DEFAULT_MODEL
|
|
129
|
+
except Exception:
|
|
130
|
+
model_name = _CPU_DEFAULT_MODEL
|
|
131
|
+
|
|
132
|
+
# 检查 HuggingFace 缓存
|
|
133
|
+
hf_home = os.environ.get("HF_HOME", str(Path.home() / ".cache" / "huggingface"))
|
|
134
|
+
hub_cache = os.environ.get("HUGGINGFACE_HUB_CACHE", str(Path(hf_home) / "hub"))
|
|
135
|
+
model_cache_dir = Path(hub_cache) / f"models--{model_name.replace('/', '--')}"
|
|
136
|
+
|
|
137
|
+
size_hint = "2GB" if "bge-m3" in model_name else "90MB"
|
|
138
|
+
|
|
139
|
+
if model_cache_dir.exists():
|
|
140
|
+
snapshots_dir = model_cache_dir / "snapshots"
|
|
141
|
+
has_files = snapshots_dir.exists() and any(snapshots_dir.iterdir())
|
|
142
|
+
return {
|
|
143
|
+
"needs_download": not has_files,
|
|
144
|
+
"model_name": model_name,
|
|
145
|
+
"size_hint": size_hint,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"needs_download": True,
|
|
150
|
+
"model_name": model_name,
|
|
151
|
+
"size_hint": size_hint,
|
|
152
|
+
}
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.debug(f"Model cache check failed, assuming download needed: {e}")
|
|
155
|
+
return {"needs_download": True, "model_name": "unknown", "size_hint": ""}
|
|
156
|
+
|
|
157
|
+
|
|
89
158
|
def start_daemon(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> bool:
|
|
90
159
|
"""
|
|
91
160
|
启动 daemon 后台进程
|
|
@@ -107,12 +176,28 @@ def start_daemon(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> bool:
|
|
|
107
176
|
)
|
|
108
177
|
return True
|
|
109
178
|
|
|
110
|
-
#
|
|
111
|
-
|
|
179
|
+
# 首次启动预检:检查模型缓存是否存在
|
|
180
|
+
timeout = STARTUP_TIMEOUT
|
|
181
|
+
cache_info = _check_model_cache()
|
|
182
|
+
if cache_info["needs_download"]:
|
|
183
|
+
logger.info(
|
|
184
|
+
f"首次启动需要下载模型 {cache_info['model_name']}" f"(约 {cache_info['size_hint']})"
|
|
185
|
+
)
|
|
186
|
+
timeout = FIRST_RUN_TIMEOUT
|
|
187
|
+
|
|
188
|
+
# 构建启动命令(Windows 使用 pythonw.exe 避免控制台窗口)
|
|
189
|
+
cmd = [
|
|
190
|
+
_get_pythonw_executable(),
|
|
191
|
+
"-m",
|
|
192
|
+
"jfox.daemon.server",
|
|
193
|
+
"--host",
|
|
194
|
+
host,
|
|
195
|
+
"--port",
|
|
196
|
+
str(port),
|
|
197
|
+
]
|
|
112
198
|
|
|
113
199
|
kwargs = {}
|
|
114
200
|
if sys.platform == "win32":
|
|
115
|
-
# Windows: 后台分离进程,不弹窗
|
|
116
201
|
CREATE_NEW_PROCESS_GROUP = 0x00000200
|
|
117
202
|
DETACHED_PROCESS = 0x00000008
|
|
118
203
|
CREATE_NO_WINDOW = 0x08000000
|
|
@@ -120,39 +205,46 @@ def start_daemon(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> bool:
|
|
|
120
205
|
else:
|
|
121
206
|
kwargs["start_new_session"] = True
|
|
122
207
|
|
|
208
|
+
# 子进程日志落盘(stdout/stderr → 日志文件)
|
|
209
|
+
log_file = open(DAEMON_LOG_FILE, "a", encoding="utf-8")
|
|
210
|
+
|
|
123
211
|
try:
|
|
124
212
|
proc = subprocess.Popen(
|
|
125
213
|
cmd,
|
|
126
|
-
stdout=
|
|
127
|
-
stderr=
|
|
214
|
+
stdout=log_file,
|
|
215
|
+
stderr=log_file,
|
|
128
216
|
stdin=subprocess.DEVNULL,
|
|
129
217
|
**kwargs,
|
|
130
218
|
)
|
|
131
219
|
logger.info(f"Daemon 进程已启动 (PID: {proc.pid})")
|
|
220
|
+
logger.info(f"Daemon 日志文件: {DAEMON_LOG_FILE}")
|
|
132
221
|
except Exception as e:
|
|
222
|
+
log_file.close()
|
|
133
223
|
logger.error(f"启动 daemon 失败: {e}")
|
|
134
224
|
return False
|
|
135
225
|
|
|
136
226
|
# 等待 daemon 就绪(用 HTTP 健康检查判断,不用 PID)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
227
|
+
try:
|
|
228
|
+
for i in range(timeout):
|
|
229
|
+
time.sleep(1)
|
|
230
|
+
health = _http_health_check(host, port)
|
|
231
|
+
if health is not None:
|
|
232
|
+
real_pid = health.get("pid", proc.pid)
|
|
233
|
+
_write_pid_file(
|
|
234
|
+
{
|
|
235
|
+
"pid": real_pid,
|
|
236
|
+
"host": host,
|
|
237
|
+
"port": port,
|
|
238
|
+
"started_at": time.time(),
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
logger.info(f"Daemon 已就绪 (PID: {real_pid}, port: {port})")
|
|
242
|
+
return True
|
|
153
243
|
|
|
154
|
-
|
|
155
|
-
|
|
244
|
+
logger.warning(f"Daemon 启动超时({timeout}秒),日志见 {DAEMON_LOG_FILE}")
|
|
245
|
+
return False
|
|
246
|
+
finally:
|
|
247
|
+
log_file.close()
|
|
156
248
|
|
|
157
249
|
|
|
158
250
|
def stop_daemon() -> bool:
|
|
@@ -222,5 +314,6 @@ def get_daemon_status() -> Optional[dict]:
|
|
|
222
314
|
"port": port,
|
|
223
315
|
"model": health.get("model", "unknown"),
|
|
224
316
|
"dimension": health.get("dimension", 384),
|
|
317
|
+
"device": health.get("device", "unknown"),
|
|
225
318
|
"started_at": data.get("started_at") if data else None,
|
|
226
319
|
}
|
|
@@ -26,12 +26,17 @@ def _load_model():
|
|
|
26
26
|
"""启动时加载模型(标记为 daemon 进程,防止自引用)"""
|
|
27
27
|
global _backend
|
|
28
28
|
os.environ["JFOX_DAEMON_PROCESS"] = "1"
|
|
29
|
+
from ..config import config
|
|
29
30
|
from ..embedding_backend import EmbeddingBackend
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
model_name = config.embedding_model if config.embedding_model != "auto" else None
|
|
33
|
+
_backend = EmbeddingBackend(device=config.device, model_name=model_name)
|
|
32
34
|
try:
|
|
33
35
|
_backend.load()
|
|
34
|
-
logger.info(
|
|
36
|
+
logger.info(
|
|
37
|
+
f"Daemon: 模型已加载 {_backend.model_name} "
|
|
38
|
+
f"(device={_backend._resolved_device}, dimension={_backend._resolved_dim})"
|
|
39
|
+
)
|
|
35
40
|
except Exception as e:
|
|
36
41
|
logger.error(f"Daemon: 模型加载失败,进程退出: {e}")
|
|
37
42
|
os._exit(1)
|
|
@@ -46,6 +51,7 @@ class HealthResponse(BaseModel):
|
|
|
46
51
|
status: str
|
|
47
52
|
model: str
|
|
48
53
|
dimension: int
|
|
54
|
+
device: str # 实际使用的设备
|
|
49
55
|
pid: int
|
|
50
56
|
|
|
51
57
|
|
|
@@ -80,6 +86,7 @@ def health():
|
|
|
80
86
|
status="ok",
|
|
81
87
|
model=_backend.model_name,
|
|
82
88
|
dimension=_backend.dimension,
|
|
89
|
+
device=_backend.resolved_device,
|
|
83
90
|
pid=os.getpid(),
|
|
84
91
|
)
|
|
85
92
|
|