GitHub Action commited on
Commit
59bd45e
·
0 Parent(s):

Deploy clean version of Nora

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +75 -0
  2. .env.example +33 -0
  3. .gitattributes +35 -0
  4. .github/workflows/sync.yml +38 -0
  5. .gitignore +61 -0
  6. .kiro/specs/voice-text-processor/design.md +514 -0
  7. .kiro/specs/voice-text-processor/requirements.md +139 -0
  8. .kiro/specs/voice-text-processor/tasks.md +204 -0
  9. Dockerfile +31 -0
  10. HOTFIX_DOCKER_BUILD.md +123 -0
  11. HOTFIX_NULL_ERROR.md +129 -0
  12. HUGGINGFACE_DEPLOY.md +176 -0
  13. HUGGINGFACE_FIX_SUMMARY.md +223 -0
  14. PRD.md +155 -0
  15. PROJECT_STRUCTURE.md +155 -0
  16. README.md +175 -0
  17. README_HF.md +131 -0
  18. app/__init__.py +1 -0
  19. app/asr_service.py +202 -0
  20. app/config.py +226 -0
  21. app/image_service.py +441 -0
  22. app/logging_config.py +196 -0
  23. app/main.py +1132 -0
  24. app/models.py +118 -0
  25. app/semantic_parser.py +326 -0
  26. app/storage.py +508 -0
  27. app/user_config.py +211 -0
  28. data/.gitkeep +1 -0
  29. deployment/DEPLOYMENT.md +133 -0
  30. deployment/DEPLOY_CHECKLIST.md +137 -0
  31. deployment/Dockerfile +31 -0
  32. deployment/README_HF.md +99 -0
  33. deployment/README_MODELSCOPE.md +126 -0
  34. deployment/app_modelscope.py +187 -0
  35. deployment/configuration.json +5 -0
  36. deployment/deploy_to_hf.bat +109 -0
  37. deployment/deploy_to_hf.sh +101 -0
  38. deployment/ms_deploy.json +29 -0
  39. deployment/requirements_hf.txt +17 -0
  40. deployment/requirements_modelscope.txt +17 -0
  41. docs/API_配置说明.md +113 -0
  42. docs/FEATURE_SUMMARY.md +368 -0
  43. docs/README.md +103 -0
  44. docs/ROADMAP.md +422 -0
  45. docs/功能架构图.md +375 -0
  46. docs/后端启动问题排查.md +368 -0
  47. docs/局域网访问修复完成.md +187 -0
  48. docs/局域网访问快速修复.md +157 -0
  49. docs/局域网访问指南.md +269 -0
  50. docs/局域网访问问题排查.md +195 -0
.dockerignore ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git
2
+ .git
3
+ .gitignore
4
+ .gitattributes
5
+
6
+ # Python
7
+ __pycache__
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ env/
13
+ venv/
14
+ ENV/
15
+ .venv
16
+
17
+ # Testing
18
+ .pytest_cache
19
+ .hypothesis
20
+ .coverage
21
+ htmlcov/
22
+ *.log
23
+
24
+ # IDE
25
+ .vscode
26
+ .idea
27
+ *.swp
28
+ *.swo
29
+ *~
30
+
31
+ # Documentation (keep only essential)
32
+ docs/
33
+ PRD.md
34
+ PROJECT_STRUCTURE.md
35
+ 局域网访问修复完成.md
36
+
37
+ # Deployment files (not needed in container)
38
+ deployment/
39
+ scripts/start_local.py
40
+ scripts/start_local.bat
41
+ scripts/test_lan_access.bat
42
+ scripts/build_and_deploy.sh
43
+ scripts/build_and_deploy.bat
44
+
45
+ # Frontend source (only need dist)
46
+ frontend/node_modules
47
+ frontend/src
48
+ frontend/components
49
+ frontend/services
50
+ frontend/utils
51
+ frontend/.env.local
52
+ frontend/package.json
53
+ frontend/package-lock.json
54
+ frontend/tsconfig.json
55
+ frontend/vite.config.ts
56
+ frontend/index.tsx
57
+ frontend/index.css
58
+ frontend/types.ts
59
+ frontend/App.tsx
60
+ frontend/test-*.html
61
+
62
+ # Tests
63
+ tests/
64
+
65
+ # Logs
66
+ logs/
67
+
68
+ # OS
69
+ .DS_Store
70
+ Thumbs.db
71
+
72
+ # Temporary files
73
+ *.tmp
74
+ *.bak
75
+ *.swp
.env.example ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Voice Text Processor Configuration
2
+ # Copy this file to .env and fill in your values
3
+
4
+ # Required: Zhipu AI API Key (for semantic parsing)
5
+ # 获取方式: https://open.bigmodel.cn/ -> API Keys
6
+ ZHIPU_API_KEY=your_zhipu_api_key_here
7
+
8
+ # Required: MiniMax API Key (for image generation)
9
+ # 获取方式: https://platform.minimax.io/ -> API Keys
10
+ # 格式示例: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
11
+ MINIMAX_API_KEY=your_minimax_api_key_here
12
+
13
+ # Optional: MiniMax Group ID (已废弃,保留用于兼容性)
14
+ MINIMAX_GROUP_ID=your_group_id_here
15
+
16
+ # Optional: Data storage directory (default: data/)
17
+ DATA_DIR=data
18
+
19
+ # Optional: Maximum audio file size in bytes (default: 10485760 = 10MB)
20
+ MAX_AUDIO_SIZE=10485760
21
+
22
+ # Optional: Logging level (default: INFO)
23
+ # Valid values: DEBUG, INFO, WARNING, ERROR, CRITICAL
24
+ LOG_LEVEL=INFO
25
+
26
+ # Optional: Log file path (default: logs/app.log)
27
+ LOG_FILE=logs/app.log
28
+
29
+ # Optional: Server host (default: 0.0.0.0)
30
+ HOST=0.0.0.0
31
+
32
+ # Optional: Server port (default: 8000)
33
+ PORT=8000
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.github/workflows/sync.yml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ workflow_dispatch:
6
+
7
+ jobs:
8
+ sync-to-hub:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ with:
13
+ fetch-depth: 0
14
+ lfs: true
15
+ - name: Push to hub
16
+ env:
17
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
18
+ run: |
19
+ # 1. 配置身份
20
+ git config --global user.email "bot@github.com"
21
+ git config --global user.name "GitHub Action"
22
+
23
+ # 2. 彻底移除二进制文件及其索引
24
+ rm -rf generated_images
25
+ git rm -r --cached generated_images || echo "Already removed"
26
+
27
+ # 3. 创建一个全新的、没有历史记录的临时分支
28
+ git checkout --orphan temp-branch
29
+
30
+ # 4. 只添加当前的代码文件
31
+ git add .
32
+ git commit -m "Deploy clean version of Nora"
33
+
34
+ # 5. 强制推送到 Hugging Face 的 main 分支
35
+ # 注意:这会覆盖 HF 上的所有历史,非常适合解决当前死锁
36
+ git push --force https://kernel14:$HF_TOKEN@huggingface.co/spaces/kernel14/Nora temp-branch:main
37
+
38
+
.gitignore ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+
22
+ # Virtual environments
23
+ venv/
24
+ env/
25
+ ENV/
26
+ .venv
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+ *.swo
33
+ *~
34
+
35
+ # Environment variables
36
+ .env
37
+
38
+ # Logs
39
+ logs/
40
+ *.log
41
+
42
+ # Data files
43
+ data/*.json
44
+
45
+ # Testing
46
+ .pytest_cache/
47
+ .coverage
48
+ htmlcov/
49
+ .hypothesis/
50
+
51
+ # OS
52
+ .DS_Store
53
+ Thumbs.db
54
+
55
+ # Frontend (开发时忽略,但部署时需要 dist)
56
+ frontend/node_modules/
57
+ # 注意:frontend/dist/ 不要忽略,部署需要它!
58
+
59
+ # Docker(不要忽略 Dockerfile)
60
+ # Dockerfile 需要提交
61
+
.kiro/specs/voice-text-processor/design.md ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Design Document: Voice Text Processor
2
+
3
+ ## Overview
4
+
5
+ 本系统是一个基于 FastAPI 的 REST API 服务,用于处理用户的语音录音或文字输入,通过智谱 API 进行语音识别和语义解析,提取情绪、灵感和待办事项等结构化数据,并持久化到本地 JSON 文件。
6
+
7
+ 系统采用分层架构设计:
8
+ - **API 层**:FastAPI 路由和请求处理
9
+ - **服务层**:业务逻辑处理(ASR、语义解析)
10
+ - **存储层**:JSON 文件持久化
11
+
12
+ 核心工作流程:
13
+ 1. 接收用户输入(音频文件或文本)
14
+ 2. 如果是音频,调用智谱 ASR API 转写为文本
15
+ 3. 调用 GLM-4-Flash API 进行语义解析
16
+ 4. 提取情绪、灵感、待办数据
17
+ 5. 持久化到对应的 JSON 文件
18
+ 6. 返回结构化响应
19
+
20
+ ## Architecture
21
+
22
+ 系统采用三层架构:
23
+
24
+ ```
25
+ ┌─────────────────────────────────────┐
26
+ │ API Layer (FastAPI) │
27
+ │ - POST /api/process │
28
+ │ - Request validation │
29
+ │ - Response formatting │
30
+ └──────────────┬──────────────────────┘
31
+
32
+ ┌──────────────▼──────────────────────┐
33
+ │ Service Layer │
34
+ │ - ASRService │
35
+ │ - SemanticParserService │
36
+ │ - StorageService │
37
+ └──────────────┬──────────────────────┘
38
+
39
+ ┌──────────────▼──────────────────────┐
40
+ │ External Services │
41
+ │ - Zhipu ASR API │
42
+ │ - GLM-4-Flash API │
43
+ │ - Local JSON Files │
44
+ └─────────────────────────────────────┘
45
+ ```
46
+
47
+ ### 模块职责
48
+
49
+ **API Layer**:
50
+ - 处理 HTTP 请求和响应
51
+ - 输入验证(文件格式、大小、文本编码)
52
+ - 错误处理和状态码映射
53
+ - 请求日志记录
54
+
55
+ **Service Layer**:
56
+ - `ASRService`: 封装智谱 ASR API 调用,处理音频转文字
57
+ - `SemanticParserService`: 封装 GLM-4-Flash API 调用,执行语义解析
58
+ - `StorageService`: 管理 JSON 文件读写,生成唯一 ID 和时间戳
59
+
60
+ **Configuration**:
61
+ - 环境变量管理(API 密钥、文件路径、大小限制)
62
+ - 启动时配置验证
63
+
64
+ ## Components and Interfaces
65
+
66
+ ### 1. API Endpoint
67
+
68
+ ```python
69
+ @app.post("/api/process")
70
+ async def process_input(
71
+ audio: Optional[UploadFile] = File(None),
72
+ text: Optional[str] = Body(None)
73
+ ) -> ProcessResponse
74
+ ```
75
+
76
+ **输入**:
77
+ - `audio`: 音频文件(multipart/form-data),支持 mp3, wav, m4a
78
+ - `text`: 文本内容(application/json),UTF-8 编码
79
+
80
+ **输出**:
81
+ ```python
82
+ class ProcessResponse(BaseModel):
83
+ record_id: str
84
+ timestamp: str
85
+ mood: Optional[MoodData]
86
+ inspirations: List[InspirationData]
87
+ todos: List[TodoData]
88
+ error: Optional[str]
89
+ ```
90
+
91
+ ### 2. ASRService
92
+
93
+ ```python
94
+ class ASRService:
95
+ def __init__(self, api_key: str):
96
+ self.api_key = api_key
97
+ self.client = httpx.AsyncClient()
98
+
99
+ async def transcribe(self, audio_file: bytes) -> str:
100
+ """
101
+ 调用智谱 ASR API 进行语音识别
102
+
103
+ 参数:
104
+ audio_file: 音频文件字节流
105
+
106
+ 返回:
107
+ 转写后的文本内容
108
+
109
+ 异常:
110
+ ASRServiceError: API 调用失败或识别失败
111
+ """
112
+ ```
113
+
114
+ ### 3. SemanticParserService
115
+
116
+ ```python
117
+ class SemanticParserService:
118
+ def __init__(self, api_key: str):
119
+ self.api_key = api_key
120
+ self.client = httpx.AsyncClient()
121
+ self.system_prompt = (
122
+ "你是一个数据转换器。请将文本解析为 JSON 格式。"
123
+ "维度包括:1.情绪(type,intensity,keywords); "
124
+ "2.灵感(core_idea,tags,category); "
125
+ "3.待办(task,time,location)。"
126
+ "必须严格遵循 JSON 格式返回。"
127
+ )
128
+
129
+ async def parse(self, text: str) -> ParsedData:
130
+ """
131
+ 调用 GLM-4-Flash API 进行语义解析
132
+
133
+ 参数:
134
+ text: 待解析的文本内容
135
+
136
+ 返回:
137
+ ParsedData 对象,包含 mood, inspirations, todos
138
+
139
+ 异常:
140
+ SemanticParserError: API 调用失败或解析失败
141
+ """
142
+ ```
143
+
144
+ ### 4. StorageService
145
+
146
+ ```python
147
+ class StorageService:
148
+ def __init__(self, data_dir: str):
149
+ self.data_dir = Path(data_dir)
150
+ self.records_file = self.data_dir / "records.json"
151
+ self.moods_file = self.data_dir / "moods.json"
152
+ self.inspirations_file = self.data_dir / "inspirations.json"
153
+ self.todos_file = self.data_dir / "todos.json"
154
+
155
+ def save_record(self, record: RecordData) -> str:
156
+ """
157
+ 保存完整记录到 records.json
158
+
159
+ 参数:
160
+ record: 记录数据对象
161
+
162
+ 返回:
163
+ 生成的唯一 record_id
164
+
165
+ 异常:
166
+ StorageError: 文件写入失败
167
+ """
168
+
169
+ def append_mood(self, mood: MoodData, record_id: str) -> None:
170
+ """追加情绪数据到 moods.json"""
171
+
172
+ def append_inspirations(self, inspirations: List[InspirationData], record_id: str) -> None:
173
+ """追加灵感数据到 inspirations.json"""
174
+
175
+ def append_todos(self, todos: List[TodoData], record_id: str) -> None:
176
+ """追加待办数据到 todos.json"""
177
+ ```
178
+
179
+ ## Data Models
180
+
181
+ ### 核心数据结构
182
+
183
+ ```python
184
+ class MoodData(BaseModel):
185
+ type: Optional[str] = None
186
+ intensity: Optional[int] = Field(None, ge=1, le=10)
187
+ keywords: List[str] = []
188
+
189
+ class InspirationData(BaseModel):
190
+ core_idea: str = Field(..., max_length=20)
191
+ tags: List[str] = Field(default_factory=list, max_items=5)
192
+ category: Literal["工作", "生活", "学习", "创意"]
193
+
194
+ class TodoData(BaseModel):
195
+ task: str
196
+ time: Optional[str] = None
197
+ location: Optional[str] = None
198
+ status: str = "pending"
199
+
200
+ class ParsedData(BaseModel):
201
+ mood: Optional[MoodData] = None
202
+ inspirations: List[InspirationData] = []
203
+ todos: List[TodoData] = []
204
+
205
+ class RecordData(BaseModel):
206
+ record_id: str
207
+ timestamp: str
208
+ input_type: Literal["audio", "text"]
209
+ original_text: str
210
+ parsed_data: ParsedData
211
+ ```
212
+
213
+ ### 存储格式
214
+
215
+ **records.json**:
216
+ ```json
217
+ [
218
+ {
219
+ "record_id": "uuid-string",
220
+ "timestamp": "2024-01-01T12:00:00Z",
221
+ "input_type": "audio",
222
+ "original_text": "转写后的文本",
223
+ "parsed_data": {
224
+ "mood": {...},
225
+ "inspirations": [...],
226
+ "todos": [...]
227
+ }
228
+ }
229
+ ]
230
+ ```
231
+
232
+ **moods.json**:
233
+ ```json
234
+ [
235
+ {
236
+ "record_id": "uuid-string",
237
+ "timestamp": "2024-01-01T12:00:00Z",
238
+ "type": "开心",
239
+ "intensity": 8,
240
+ "keywords": ["愉快", "放松"]
241
+ }
242
+ ]
243
+ ```
244
+
245
+ **inspirations.json**:
246
+ ```json
247
+ [
248
+ {
249
+ "record_id": "uuid-string",
250
+ "timestamp": "2024-01-01T12:00:00Z",
251
+ "core_idea": "新的项目想法",
252
+ "tags": ["创新", "技术"],
253
+ "category": "工作"
254
+ }
255
+ ]
256
+ ```
257
+
258
+ **todos.json**:
259
+ ```json
260
+ [
261
+ {
262
+ "record_id": "uuid-string",
263
+ "timestamp": "2024-01-01T12:00:00Z",
264
+ "task": "完成报告",
265
+ "time": "明天下午",
266
+ "location": "办公室",
267
+ "status": "pending"
268
+ }
269
+ ]
270
+ ```
271
+
272
+
273
+ ## Correctness Properties
274
+
275
+ 属性(Property)是关于系统行为的特征或规则,应该在所有有效执行中保持为真。属性是人类可读规范和机器可验证正确性保证之间的桥梁。
276
+
277
+ ### Property 1: 音频格式验证
278
+ *For any* 提交的文件,如果文件扩展名是 mp3、wav 或 m4a,系统应该接受该文件;如果是其他格式,系统应该拒绝并返回错误。
279
+ **Validates: Requirements 1.1**
280
+
281
+ ### Property 2: UTF-8 文本接受
282
+ *For any* UTF-8 编码的文本字符串(包括中文、emoji、特殊字符),系统应该正确接受并处理。
283
+ **Validates: Requirements 1.2**
284
+
285
+ ### Property 3: 无效输入错误处理
286
+ *For any* 空输入或格式无效的输入,系统应该返回包含 error 字段的 JSON 响应,而不是崩溃或返回成功状态。
287
+ **Validates: Requirements 1.3, 9.1**
288
+
289
+ ### Property 4: 解析结果结构完整性
290
+ *For any* 成功的语义解析结果,返回的 JSON 应该包含 mood、inspirations、todos 三个字段,即使某些字段为空值或空数组。
291
+ **Validates: Requirements 3.3**
292
+
293
+ ### Property 5: 缺失维度处理
294
+ *For any* 不包含特定维度信息的文本,解析结果中该维度应该返回 null(对于 mood)或空数组(对于 inspirations 和 todos)。
295
+ **Validates: Requirements 3.4**
296
+
297
+ ### Property 6: 情绪数据结构验证
298
+ *For any* 解析出的情绪数据,应该包含 type(字符串)、intensity(1-10 的整数)、keywords(字符串数组)三个字段,且 intensity 必须在有效范围内。
299
+ **Validates: Requirements 4.1, 4.2, 4.3**
300
+
301
+ ### Property 7: 灵感数据结构验证
302
+ *For any* 解析出的灵感数据,应该包含 core_idea(长度 ≤ 20)、tags(数组长度 ≤ 5)、category(枚举值:工作/生活/学习/创意)三个字段,且所有约束都被满足。
303
+ **Validates: Requirements 5.1, 5.2, 5.3**
304
+
305
+ ### Property 8: 待办数据结构验证
306
+ *For any* 解析出的待办数据,应该包含 task(必需)、time(可选)、location(可选)、status(默认为 "pending")四个字段。
307
+ **Validates: Requirements 6.1, 6.2, 6.3, 6.4**
308
+
309
+ ### Property 9: 数据持久化完整性
310
+ *For any* 成功处理的记录,应该在 records.json 中保存完整记录,并且如果包含情绪/灵感/待办数据,应该同时追加到对应的 moods.json、inspirations.json、todos.json 文件中。
311
+ **Validates: Requirements 7.1, 7.2, 7.3, 7.4**
312
+
313
+ ### Property 10: 文件初始化
314
+ *For any* 不存在的 JSON 文件,当首次写入时,系统应该创建该文件并初始化为空数组 `[]`。
315
+ **Validates: Requirements 7.5**
316
+
317
+ ### Property 11: 唯一 ID 生成
318
+ *For any* 两条不同的记录,生成的 record_id 应该是唯一的(不重复)。
319
+ **Validates: Requirements 7.7**
320
+
321
+ ### Property 12: 成功响应格式
322
+ *For any* 成功处理的请求,HTTP 响应应该返回 200 状态码,并且响应 JSON 包含 record_id、timestamp、mood、inspirations、todos 字段。
323
+ **Validates: Requirements 8.4, 8.6**
324
+
325
+ ### Property 13: 错误响应格式
326
+ *For any* 处理失败的请求,HTTP 响应应该返回适当的错误状态码(400 或 500),并且响应 JSON 包含 error 字段,描述具体错误信息。
327
+ **Validates: Requirements 8.5, 9.1, 9.3**
328
+
329
+ ### Property 14: 错误日志记录
330
+ *For any* 系统发生的错误,应该在日志文件中记录该错误,包含时间戳和错误堆栈信息。
331
+ **Validates: Requirements 9.5**
332
+
333
+ ### Property 15: 敏感信息保护
334
+ *For any* 日志输出,不应该包含敏感信息(如 API 密钥、用户密码等)。
335
+ **Validates: Requirements 10.5**
336
+
337
+ ## Error Handling
338
+
339
+ ### 错误分类
340
+
341
+ **1. 输入验证错误(HTTP 400)**:
342
+ - 音频文件格式不支持
343
+ - 音频文件大小超过限制
344
+ - 文本内容为空
345
+ - 请求格式错误(既没有 audio 也没有 text)
346
+
347
+ **2. 外部服务错误(HTTP 500)**:
348
+ - 智谱 ASR API 调用失败
349
+ - GLM-4-Flash API 调用失败
350
+ - API 返回非预期格式
351
+
352
+ **3. 存储错误(HTTP 500)**:
353
+ - JSON 文件写入失败
354
+ - 磁盘空间不足
355
+ - 文件权限错误
356
+
357
+ **4. 配置错误(启动时失败)**:
358
+ - API 密钥缺失
359
+ - 数据目录不可访问
360
+ - 必需配置项缺失
361
+
362
+ ### 错误处理策略
363
+
364
+ ```python
365
+ class APIError(Exception):
366
+ """API 层错误基类"""
367
+ def __init__(self, message: str, status_code: int):
368
+ self.message = message
369
+ self.status_code = status_code
370
+
371
+ class ASRServiceError(APIError):
372
+ """ASR 服务错误"""
373
+ def __init__(self, message: str = "语音识别服务不可用"):
374
+ super().__init__(message, 500)
375
+
376
+ class SemanticParserError(APIError):
377
+ """语义解析服务错误"""
378
+ def __init__(self, message: str = "语义解析服务不可用"):
379
+ super().__init__(message, 500)
380
+
381
+ class StorageError(APIError):
382
+ """存储错误"""
383
+ def __init__(self, message: str = "数据存储失败"):
384
+ super().__init__(message, 500)
385
+
386
+ class ValidationError(APIError):
387
+ """输入验证错误"""
388
+ def __init__(self, message: str):
389
+ super().__init__(message, 400)
390
+ ```
391
+
392
+ ### 错误响应格式
393
+
394
+ ```json
395
+ {
396
+ "error": "具体错误描述",
397
+ "detail": "详细错误信息(可选)",
398
+ "timestamp": "2024-01-01T12:00:00Z"
399
+ }
400
+ ```
401
+
402
+ ### 日志记录
403
+
404
+ 使用 Python logging 模块:
405
+ - **INFO**: 正常请求处理流程
406
+ - **WARNING**: 可恢复的异常情况(如 API 重试)
407
+ - **ERROR**: 错误情况,包含完整堆栈信息
408
+ - **DEBUG**: 详细调试信息(开发环境)
409
+
410
+ 日志格式:
411
+ ```
412
+ [2024-01-01 12:00:00] [ERROR] [request_id: xxx] ASR API call failed: Connection timeout
413
+ Traceback: ...
414
+ ```
415
+
416
+ ## Testing Strategy
417
+
418
+ 本系统采用双重测试策略:单元测试和基于属性的测试(Property-Based Testing)。
419
+
420
+ ### 单元测试
421
+
422
+ 单元测试用于验证特定示例、边缘情况和错误条件:
423
+
424
+ **测试范围**:
425
+ - API 端点的请求/响应处理
426
+ - 各服务类的 mock 测试(模拟外部 API)
427
+ - 数据模型的验证逻辑
428
+ - 错误处理流程
429
+ - 配置加载和验证
430
+
431
+ **示例测试用例**:
432
+ - 测试 POST /api/process 端点存在
433
+ - 测试接受 multipart/form-data 格式
434
+ - 测试接受 application/json 格式
435
+ - 测试 ASR API 调用失败时的错误处理
436
+ - 测试 GLM-4-Flash API 调用失败时的错误处理
437
+ - 测试文件写入失败时的错误处理
438
+ - 测试配置缺失时启动失败
439
+ - 测试空音频识别的边缘情况
440
+ - 测试无情绪信息文本的边缘情况
441
+ - 测试无灵感信息文本的边缘情况
442
+ - 测试无待办信息文本的边缘情况
443
+
444
+ ### 基于属性的测试(Property-Based Testing)
445
+
446
+ 基于属性的测试用于验证通用属性在所有输入下都成立。
447
+
448
+ **测试库**: 使用 `hypothesis` 库(Python 的 PBT 框架)
449
+
450
+ **配置**:
451
+ - 每个属性测试运行最少 100 次迭代
452
+ - 每个测试必须引用设计文档中的属性编号
453
+ - 标签格式:`# Feature: voice-text-processor, Property N: [property text]`
454
+
455
+ **属性测试覆盖**:
456
+ - Property 1: 音频格式验证
457
+ - Property 2: UTF-8 文本接受
458
+ - Property 3: 无效输入错误处理
459
+ - Property 4: 解析结果结构完整性
460
+ - Property 5: 缺失维度处理
461
+ - Property 6: 情绪数据结构验证
462
+ - Property 7: 灵感数据结构验证
463
+ - Property 8: 待办数据结构验证
464
+ - Property 9: 数据持久化完整性
465
+ - Property 10: 文件初始化
466
+ - Property 11: 唯一 ID 生成
467
+ - Property 12: 成功响应格式
468
+ - Property 13: 错误响应格式
469
+ - Property 14: 错误日志记录
470
+ - Property 15: 敏感信息保护
471
+
472
+ **测试策略**:
473
+ - 使用 hypothesis 生成随机输入(文件名、文本、数据结构)
474
+ - 使用 pytest-mock 模拟外部 API 调用
475
+ - 使用临时文件系统进行存储测试
476
+ - 验证所有属性在随机输入下都成立
477
+
478
+ **示例属性测试**:
479
+ ```python
480
+ from hypothesis import given, strategies as st
481
+ import pytest
482
+
483
+ @given(st.text(min_size=1))
484
+ def test_property_2_utf8_text_acceptance(text):
485
+ """
486
+ Feature: voice-text-processor, Property 2: UTF-8 文本接受
487
+ For any UTF-8 encoded text string, the system should accept and process it.
488
+ """
489
+ response = client.post("/api/process", json={"text": text})
490
+ assert response.status_code in [200, 500] # 接受输入,可能解析失败但不应拒绝
491
+
492
+ @given(st.lists(st.text(), min_size=1, max_size=10))
493
+ def test_property_11_unique_id_generation(texts):
494
+ """
495
+ Feature: voice-text-processor, Property 11: 唯一 ID 生成
496
+ For any two different records, the generated record_ids should be unique.
497
+ """
498
+ record_ids = []
499
+ for text in texts:
500
+ response = client.post("/api/process", json={"text": text})
501
+ if response.status_code == 200:
502
+ record_ids.append(response.json()["record_id"])
503
+
504
+ # 所有 ID 应该唯一
505
+ assert len(record_ids) == len(set(record_ids))
506
+ ```
507
+
508
+ ### 测试覆盖目标
509
+
510
+ - 代码覆盖率:≥ 80%
511
+ - 属性测试:覆盖所有 15 个正确性属性
512
+ - 单元测试:覆盖所有边缘情况和错误路径
513
+ - 集成测试:端到端流程测试(音频 → 转写 → 解析 → 存储)
514
+
.kiro/specs/voice-text-processor/requirements.md ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Requirements Document
2
+
3
+ ## Introduction
4
+
5
+ 这是一个治愈系记录助手的后端核心模块。系统接收语音录音或文字输入,通过智谱 API 进行语音转写和语义解析,输出包含情绪、灵感、待办的结构化 JSON 数据,并持久化到本地文件系统。
6
+
7
+ ## Glossary
8
+
9
+ - **System**: 治愈系记录助手后端系统
10
+ - **ASR_Service**: 智谱 API 语音识别服务
11
+ - **Semantic_Parser**: GLM-4-Flash 语义解析引擎
12
+ - **Storage_Manager**: 本地 JSON 文件存储管理器
13
+ - **Record**: 用户输入的单次记录(语音或文字)
14
+ - **Mood**: 情绪数据结构(type, intensity, keywords)
15
+ - **Inspiration**: 灵感数据结构(core_idea, tags, category)
16
+ - **Todo**: 待办事项数据结构(task, time, location, status)
17
+
18
+ ## Requirements
19
+
20
+ ### Requirement 1: 接收用户输入
21
+
22
+ **User Story:** 作为用户,我想要提交语音录音或文字内容,以便系统能够处理我的记录。
23
+
24
+ #### Acceptance Criteria
25
+
26
+ 1. WHEN 用户提交音频文件,THE System SHALL 接受常见音频格式(mp3, wav, m4a)
27
+ 2. WHEN 用户提交文字内容,THE System SHALL 接受 UTF-8 编码的文本字符串
28
+ 3. WHEN 输入数据为空或格式无效,THE System SHALL 返回明确的错误信息
29
+ 4. WHEN 音频文件大小超过 10MB,THE System SHALL 拒绝处理并返回文件过大错误
30
+
31
+ ### Requirement 2: 语音转文字
32
+
33
+ **User Story:** 作为用户,我想要系统将我的语音录音转换为文字,以便进行后续的语义分析。
34
+
35
+ #### Acceptance Criteria
36
+
37
+ 1. WHEN 接收到音频文件,THE ASR_Service SHALL 调用智谱 ASR API 进行语音识别
38
+ 2. WHEN 语音识别成功,THE ASR_Service SHALL 返回转写后的文本内容
39
+ 3. IF 智谱 API 调用失败,THEN THE System SHALL 记录错误日志并返回转写失败错误
40
+ 4. WHEN 音频内容无法识别,THE ASR_Service SHALL 返回空文本并标记为识别失败
41
+
42
+ ### Requirement 3: 语义解析
43
+
44
+ **User Story:** 作为用户,我想要系统从我的文本中提取情绪、灵感和待办事项,以便获得结构化的记录数据。
45
+
46
+ #### Acceptance Criteria
47
+
48
+ 1. WHEN 接收到文本内容,THE Semantic_Parser SHALL 调用 GLM-4-Flash API 进行语义解析
49
+ 2. WHEN 调用 GLM-4-Flash,THE System SHALL 使用指定的 System Prompt:"你是一个数据转换器。请将文本解析为 JSON 格式。维度包括:1.情绪(type,intensity,keywords); 2.灵感(core_idea,tags,category); 3.待办(task,time,location)。必须严格遵循 JSON 格式返回。"
50
+ 3. WHEN 解析成功,THE Semantic_Parser SHALL 返回包含 mood、inspirations、todos 的 JSON 结构
51
+ 4. WHEN 文本中不包含某个维度的信息,THE Semantic_Parser SHALL 返回该维度的空值或空数组
52
+ 5. IF GLM-4-Flash API 调用失败,THEN THE System SHALL 记录错误日志并返回解析失败错误
53
+
54
+ ### Requirement 4: 情绪数据提取
55
+
56
+ **User Story:** 作为用户,我想要系统识别我的情绪状态,以便追踪我的情绪变化。
57
+
58
+ #### Acceptance Criteria
59
+
60
+ 1. WHEN 解析情绪数据,THE Semantic_Parser SHALL 提取情绪类型(type)
61
+ 2. WHEN 解析情绪数据,THE Semantic_Parser SHALL 提取情绪强度(intensity),范围为 1-10 的整数
62
+ 3. WHEN 解析情绪数据,THE Semantic_Parser SHALL 提取情绪关键词(keywords),以字符串数组形式返回
63
+ 4. WHEN 文本中不包含明确的情绪信息,THE Semantic_Parser SHALL 返回 null 或默认值
64
+
65
+ ### Requirement 5: 灵感数据提取
66
+
67
+ **User Story:** 作为用户,我想要系统捕捉我的灵感想法,以便日后回顾和整理。
68
+
69
+ #### Acceptance Criteria
70
+
71
+ 1. WHEN 解析灵感数据,THE Semantic_Parser SHALL 提取核心观点(core_idea),长度不超过 20 个字符
72
+ 2. WHEN 解析灵感数据,THE Semantic_Parser SHALL 提取标签(tags),以字符串数组形式返回,最多 5 个标签
73
+ 3. WHEN 解析灵感数据,THE Semantic_Parser SHALL 提取分类(category),值为"工作"、"生活"、"学习"或"创意"之一
74
+ 4. WHEN 文本中包含多个灵感,THE Semantic_Parser SHALL 返回灵感数组
75
+ 5. WHEN 文本中不包含灵感信息,THE Semantic_Parser SHALL 返回空数组
76
+
77
+ ### Requirement 6: 待办事项提取
78
+
79
+ **User Story:** 作为用户,我想要系统识别我提到的待办事项,以便自动创建任务清单。
80
+
81
+ #### Acceptance Criteria
82
+
83
+ 1. WHEN 解析待办数据,THE Semantic_Parser SHALL 提取任务描述(task)
84
+ 2. WHEN 解析待办数据,THE Semantic_Parser SHALL 提取时间信息(time),保留原始表达(如"明晚"、"下周三")
85
+ 3. WHEN 解析待办数据,THE Semantic_Parser SHALL 提取地点信息(location)
86
+ 4. WHEN 创建新待办事项,THE System SHALL 设置状态(status)为"pending"
87
+ 5. WHEN 文本中包含多个待办事项,THE Semantic_Parser SHALL 返回待办数组
88
+ 6. WHEN 文本中不包含待办信息,THE Semantic_Parser SHALL 返回空数组
89
+
90
+ ### Requirement 7: 数据持久化
91
+
92
+ **User Story:** 作为用户,我想要系统保存我的���录数据,以便日后查询和分析。
93
+
94
+ #### Acceptance Criteria
95
+
96
+ 1. WHEN 解析完成后,THE Storage_Manager SHALL 将完整记录保存到 records.json 文件
97
+ 2. WHEN 提取到情绪数据,THE Storage_Manager SHALL 将情绪信息追加到 moods.json 文件
98
+ 3. WHEN 提取到灵感数据,THE Storage_Manager SHALL 将灵感信息追加到 inspirations.json 文件
99
+ 4. WHEN 提取到待办数据,THE Storage_Manager SHALL 将待办信息追加到 todos.json 文件
100
+ 5. WHEN JSON 文件不存在,THE Storage_Manager SHALL 创建新文件并初始化为空数组
101
+ 6. WHEN 写入文件失败,THE System SHALL 记录错误日志并返回存储失败错误
102
+ 7. WHEN 保存记录时,THE System SHALL 为每条记录生成唯一 ID 和时间戳
103
+
104
+ ### Requirement 8: API 接口设计
105
+
106
+ **User Story:** 作为前端开发者,我想要调用清晰的 REST API,以便集成后端功能。
107
+
108
+ #### Acceptance Criteria
109
+
110
+ 1. THE System SHALL 提供 POST /api/process 接口接收用户输入
111
+ 2. WHEN 请求包含音频文件,THE System SHALL 接受 multipart/form-data 格式
112
+ 3. WHEN 请求包含文字内容,THE System SHALL 接受 application/json 格式
113
+ 4. WHEN 处理成功,THE System SHALL 返回 HTTP 200 状态码和结构化 JSON 响应
114
+ 5. WHEN 处理失败,THE System SHALL 返回适当的 HTTP 错误状态码(400/500)和错误信息
115
+ 6. THE System SHALL 在响应中包含 record_id 和 timestamp 字段
116
+
117
+ ### Requirement 9: 错误处理
118
+
119
+ **User Story:** 作为用户,我想要在系统出错时获得清晰的错误提示,以便了解问题所在。
120
+
121
+ #### Acceptance Criteria
122
+
123
+ 1. WHEN 任何步骤发生错误,THE System SHALL 返回包含 error 字段的 JSON 响应
124
+ 2. WHEN 智谱 API 调用失败,THE System SHALL 返回"语音识别服务不可用"或"语义解析服务不可用"错误
125
+ 3. WHEN 输入验证失败,THE System SHALL 返回具体的验证错误信息
126
+ 4. WHEN 文件操作失败,THE System SHALL 返回"数据存储失败"错误
127
+ 5. THE System SHALL 记录所有错误到日志文件,包含时间戳和错误堆栈
128
+
129
+ ### Requirement 10: 配置管理
130
+
131
+ **User Story:** 作为系统管理员,我想要配置 API 密钥和系统参数,以便灵活部署系统。
132
+
133
+ #### Acceptance Criteria
134
+
135
+ 1. THE System SHALL 从环境变量或配置文件读取智谱 API 密钥
136
+ 2. THE System SHALL 支持配置数据文件存储路径
137
+ 3. THE System SHALL 支持配置音频文件大小限制
138
+ 4. WHEN 必需的配置项缺失,THE System SHALL 在启动时报错并拒绝启动
139
+ 5. THE System SHALL 不在日志中输出敏感信息(如 API 密钥)
.kiro/specs/voice-text-processor/tasks.md ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Implementation Plan: Voice Text Processor
2
+
3
+ ## Overview
4
+
5
+ 本实现计划将语音文本处理系统分解为离散的编码步骤。实现顺序遵循从核心基础设施到业务逻辑,再到集成测试的渐进式方法。每个任务都引用具体的需求条款,确保完整的需求覆盖。
6
+
7
+ ## Tasks
8
+
9
+ - [x] 1. 设置项目结构和核心配置
10
+ - 创建项目目录结构(app/, tests/, data/)
11
+ - 设置 FastAPI 应用和基础配置
12
+ - 实现配置管理模块(从环境变量读取 API 密钥、数据路径、文件大小限制)
13
+ - 配置日志系统(格式、级别、文件输出)
14
+ - 添加启动时配置验证(缺失必需配置时拒绝启动)
15
+ - _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5_
16
+
17
+ - [x] 2. 实现数据模型和验证
18
+ - [x] 2.1 创建 Pydantic 数据模型
19
+ - 实现 MoodData 模型(type, intensity 1-10, keywords)
20
+ - 实现 InspirationData 模型(core_idea ≤20 字符, tags ≤5, category 枚举)
21
+ - 实现 TodoData 模型(task, time, location, status 默认 "pending")
22
+ - 实现 ParsedData 模型(mood, inspirations, todos)
23
+ - 实现 RecordData 模型(record_id, timestamp, input_type, original_text, parsed_data)
24
+ - 实现 ProcessResponse 模型(record_id, timestamp, mood, inspirations, todos, error)
25
+ - _Requirements: 4.1, 4.2, 4.3, 5.1, 5.2, 5.3, 6.1, 6.2, 6.3, 6.4_
26
+
27
+ - [x] 2.2 编写数据模型属性测试
28
+ - **Property 6: 情绪数据结构验证**
29
+ - **Validates: Requirements 4.1, 4.2, 4.3**
30
+
31
+ - [x] 2.3 编写数据模型属性测试
32
+ - **Property 7: 灵感数据结构验证**
33
+ - **Validates: Requirements 5.1, 5.2, 5.3**
34
+
35
+ - [x] 2.4 编写数据模型属性测试
36
+ - **Property 8: 待办数据结构验证**
37
+ - **Validates: Requirements 6.1, 6.2, 6.3, 6.4**
38
+
39
+ - [x] 3. 实现存储服务(StorageService)
40
+ - [x] 3.1 实现 JSON 文件存储管理器
41
+ - 实现 save_record 方法(保存到 records.json,生成唯一 UUID)
42
+ - 实现 append_mood 方法(追加到 moods.json)
43
+ - 实现 append_inspirations 方法(追加到 inspirations.json)
44
+ - 实现 append_todos 方法(追加到 todos.json)
45
+ - 实现文件初始化逻辑(不存在时创建并初始化为空数组)
46
+ - 实现错误处理(文件写入失败时抛出 StorageError)
47
+ - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7_
48
+
49
+ - [x] 3.2 编写存储服务属性测试
50
+ - **Property 9: 数据持久化完整性**
51
+ - **Validates: Requirements 7.1, 7.2, 7.3, 7.4**
52
+
53
+ - [x] 3.3 编写存储服务属性测试
54
+ - **Property 10: 文件初始化**
55
+ - **Validates: Requirements 7.5**
56
+
57
+ - [x] 3.4 编写存储服务属性测试
58
+ - **Property 11: 唯一 ID 生成**
59
+ - **Validates: Requirements 7.7**
60
+
61
+ - [x] 3.5 编写存储服务单元测试
62
+ - 测试文件写入失败的错误处理
63
+ - 测试并发写入的安全性
64
+ - _Requirements: 7.6_
65
+
66
+ - [x] 4. 检查点 - 确保存储层测试通过
67
+ - 确保所有测试通过,如有问题请询问用户。
68
+
69
+ - [x] 5. 实现 ASR 服务(ASRService)
70
+ - [x] 5.1 实现语音识别服务
71
+ - 创建 ASRService 类,初始化 httpx.AsyncClient
72
+ - 实现 transcribe 方法(调用智谱 ASR API)
73
+ - 处理 API 响应,提取转写文本
74
+ - 实现错误处理(API 调用失败时抛出 ASRServiceError)
75
+ - 处理空识别结果(返回空字符串并标记)
76
+ - 记录错误日志(包含时间戳和堆栈)
77
+ - _Requirements: 2.1, 2.2, 2.3, 2.4, 9.2, 9.5_
78
+
79
+ - [x] 5.2 编写 ASR 服务单元测试
80
+ - 测试 API 调用成功场景(使用 mock)
81
+ - 测试 API 调用失败场景(使用 mock)
82
+ - 测试空识别结果的边缘情况
83
+ - _Requirements: 2.1, 2.2, 2.3, 2.4_
84
+
85
+ - [x] 6. 实现语义解析服务(SemanticParserService)
86
+ - [x] 6.1 实现语义解析服务
87
+ - 创建 SemanticParserService 类,初始化 httpx.AsyncClient
88
+ - 配置 System Prompt(数据转换器提示词)
89
+ - 实现 parse 方法(调用 GLM-4-Flash API)
90
+ - 解析 API 返回的 JSON 结构
91
+ - 处理缺失维度(返回 null 或空数组)
92
+ - 实现错误处理(API 调用失败时抛出 SemanticParserError)
93
+ - 记录错误日志(包含时间戳和堆栈)
94
+ - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 9.2, 9.5_
95
+
96
+ - [x] 6.2 编写语义解析服务属性测试
97
+ - **Property 4: 解析结果结构完整性**
98
+ - **Validates: Requirements 3.3**
99
+
100
+ - [x] 6.3 编写语义解析服务属性测试
101
+ - **Property 5: 缺失维度处理**
102
+ - **Validates: Requirements 3.4**
103
+
104
+ - [x] 6.4 编写语义解析服务单元测试
105
+ - 测试 API 调用成功场景(使用 mock)
106
+ - 测试 API 调用失败场景(使用 mock)
107
+ - 测试 System Prompt 正确使用
108
+ - 测试无情绪信息文本的边缘情况
109
+ - 测试无灵感信息文本的边缘情况
110
+ - 测试无待办信息文本的边缘情况
111
+ - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
112
+
113
+ - [x] 7. 检查点 - 确保服务层测试通过
114
+ - 确保所有测试通过,如有问题请询问用户。
115
+
116
+ - [x] 8. 实现 API 端点和请求处理
117
+ - [x] 8.1 实现 POST /api/process 端点
118
+ - 创建 FastAPI 路由处理器
119
+ - 实现输入验证(音频格式、文件大小、文本编码)
120
+ - 处理 multipart/form-data 格式(音频文件)
121
+ - 处理 application/json 格式(文本内容)
122
+ - 实现请求日志记录
123
+ - _Requirements: 1.1, 1.2, 8.1, 8.2, 8.3_
124
+
125
+ - [x] 8.2 实现业务逻辑编排
126
+ - 如果是音频输入,调用 ASRService.transcribe
127
+ - 调用 SemanticParserService.parse 进行语义解析
128
+ - 生成 record_id 和 timestamp
129
+ - 调用 StorageService 保存数据
130
+ - 构建成功响应(HTTP 200,包含 record_id, timestamp, mood, inspirations, todos)
131
+ - _Requirements: 7.7, 8.4, 8.6_
132
+
133
+ - [x] 8.3 实现错误处理和响应
134
+ - 捕获 ValidationError,返回 HTTP 400 和错误信息
135
+ - 捕获 ASRServiceError,返回 HTTP 500 和"语音识别服务不可用"
136
+ - 捕获 SemanticParserError,返回 HTTP 500 和"语义解析服务不可用"
137
+ - 捕获 StorageError,返回 HTTP 500 和"数据存储失败"
138
+ - 所有错误响应包含 error 字段和 timestamp
139
+ - 记录所有错误到日志文件
140
+ - _Requirements: 1.3, 8.5, 9.1, 9.2, 9.3, 9.4, 9.5_
141
+
142
+ - [x] 8.4 编写 API 端点属性测试
143
+ - **Property 1: 音频格式验证**
144
+ - **Validates: Requirements 1.1**
145
+
146
+ - [x] 8.5 编写 API 端点属性测试
147
+ - **Property 2: UTF-8 文本接受**
148
+ - **Validates: Requirements 1.2**
149
+
150
+ - [x] 8.6 编写 API 端点属性测试
151
+ - **Property 3: 无效输入错误处理**
152
+ - **Validates: Requirements 1.3, 9.1**
153
+
154
+ - [x] 8.7 编写 API 端点属性测试
155
+ - **Property 12: 成功响应格式**
156
+ - **Validates: Requirements 8.4, 8.6**
157
+
158
+ - [x] 8.8 编写 API 端点属性测试
159
+ - **Property 13: 错误响应格式**
160
+ - **Validates: Requirements 8.5, 9.1, 9.3**
161
+
162
+ - [x] 8.9 编写 API 端点单元测试
163
+ - 测试 POST /api/process 端点存在
164
+ - 测试接受 multipart/form-data 格式
165
+ - 测试接受 application/json 格式
166
+ - _Requirements: 8.1, 8.2, 8.3_
167
+
168
+ - [x] 9. 实现日志安全性和错误日志
169
+ - [x] 9.1 实现日志过滤器
170
+ - 创建日志过滤器,屏蔽敏感信息(API 密钥、密码等)
171
+ - 配置日志格式(包含 request_id, timestamp, level, message)
172
+ - 确保错误日志包含完整堆栈信息
173
+ - _Requirements: 9.5, 10.5_
174
+
175
+ - [x] 9.2 编写日志属性测试
176
+ - **Property 14: 错误日志记录**
177
+ - **Validates: Requirements 9.5**
178
+
179
+ - [-] 9.3 编写日志属性测试
180
+ - **Property 15: 敏感信息保护**
181
+ - **Validates: Requirements 10.5**
182
+
183
+ - [x] 10. 检查点 - 确保所有测试通过
184
+ - 确保所有测试通过,如有问题请询问用户。
185
+
186
+ - [x] 11. 集成测试
187
+ - [x] 11.1 编写端到端集成测试
188
+ - 测试完整流程:音频上传 → ASR → 语义解析 → 存储 → 响应
189
+ - 测试完整流程:文本提交 → 语义解析 → 存储 → 响应
190
+ - 测试错误场景的端到端处理
191
+ - _Requirements: 所有需求_
192
+
193
+ - [x] 12. 最终检查点
194
+ - 确保所有测试通过,代码覆盖率达到 80% 以上,如有问题请询问用户。
195
+
196
+ ## Notes
197
+
198
+ - 所有任务均为必需任务,确保全面的测试覆盖
199
+ - 每个任务都引用了具体的需求条款,确保可追溯性
200
+ - 检查点任务确保增量验证
201
+ - 属性测试验证通用正确性属性(使用 hypothesis 库,最少 100 次迭代)
202
+ - 单元测试验证特定示例和边缘情况
203
+ - 所有外部 API 调用使用 mock 进行测试
204
+
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # 安装系统依赖
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # 复制依赖文件
11
+ COPY requirements.txt .
12
+
13
+ # 安装 Python 依赖
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # 复制应用代码
17
+ COPY app/ ./app/
18
+ COPY data/ ./data/
19
+ COPY frontend/dist/ ./frontend/dist/
20
+
21
+ # 复制启动脚本
22
+ COPY start.py .
23
+
24
+ # 创建必要的目录
25
+ RUN mkdir -p generated_images logs
26
+
27
+ # 暴露端口
28
+ EXPOSE 7860
29
+
30
+ # 启动应用
31
+ CMD ["python", "start.py"]
HOTFIX_DOCKER_BUILD.md ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔧 紧急修复:Docker 构建失败
2
+
3
+ ## 🐛 问题描述
4
+ Hugging Face Space 构建失败:
5
+ ```
6
+ ERROR: failed to calculate checksum of ref: "/generated_images": not found
7
+ ```
8
+
9
+ ## 🔍 问题原因
10
+ 1. `Dockerfile` 尝试复制 `generated_images/` 目录
11
+ 2. 但该目录在 GitHub 仓库中被 `.github/workflows/sync.yml` 删除了
12
+ 3. Docker 构建时找不到该目录,导致失败
13
+
14
+ ## ✅ 已修复
15
+
16
+ ### 1. 简化 Dockerfile
17
+ **文件**:`Dockerfile`
18
+
19
+ **修改前**:
20
+ ```dockerfile
21
+ COPY generated_images/ ./generated_images/
22
+ ```
23
+
24
+ **修改后**:
25
+ ```dockerfile
26
+ # 只创建空目录,不复制文件
27
+ RUN mkdir -p generated_images logs
28
+ ```
29
+
30
+ ### 2. 修改默认配置
31
+ **文件**:`app/user_config.py` 和 `app/storage.py`
32
+
33
+ **修改前**:
34
+ ```python
35
+ "image_url": "generated_images/default_character.jpeg",
36
+ ```
37
+
38
+ **修改后**:
39
+ ```python
40
+ "image_url": "", # 空字符串,前端会显示占位符
41
+ ```
42
+
43
+ **原因**:
44
+ - 不依赖 Git 仓库中的图片文件
45
+ - 用户首次使用时可以生成自己的 AI 形象
46
+ - 或者前端显示一个默认占位符
47
+
48
+ ## 🚀 部署步骤
49
+
50
+ ### 1. 提交修复
51
+ ```bash
52
+ git add Dockerfile app/user_config.py app/storage.py
53
+ git commit -m "Fix: Remove dependency on generated_images directory in Docker build"
54
+ git push origin main
55
+ ```
56
+
57
+ ### 2. 同步到 Hugging Face
58
+ 1. 访问:https://huggingface.co/spaces/kernel14/Nora
59
+ 2. Settings → Sync from GitHub → **Sync now**
60
+
61
+ ### 3. 等待重新构建
62
+ - 查看 **Logs** 标签页
63
+ - 应该能看到构建成功
64
+
65
+ ## ✅ 验证修复
66
+
67
+ 构建成功后,访问:
68
+ ```
69
+ https://kernel14-nora.hf.space/
70
+ ```
71
+
72
+ 应该能看到:
73
+ - ✅ 前端正常加载
74
+ - ✅ AI 形象位置显示占位符(或默认图标)
75
+ - ✅ 可以点击 ✨ 按钮生成自定义形象
76
+ - ✅ 所有功能正常工作
77
+
78
+ ## 📝 技术说明
79
+
80
+ ### 为什么不在 Docker 镜像中包含默认图片?
81
+
82
+ 1. **Git 仓库限制**:
83
+ - 图片文件较大(几百 KB)
84
+ - 会增加仓库体积
85
+ - 被 `.github/workflows/sync.yml` 清理
86
+
87
+ 2. **更好的方案**:
88
+ - 用户首次使用时生成个性化形象
89
+ - 或者使用 CDN 托管的默认图片
90
+ - 或者前端显示 SVG 占位符
91
+
92
+ 3. **运行时生成**:
93
+ - 用户可以随时生成新形象
94
+ - 图片保存在容器的 `generated_images/` 目录
95
+ - 重启容器后会丢失(可以接受)
96
+
97
+ ### 未来改进方向
98
+
99
+ 1. **使用对象存储**:
100
+ - 将生成的图片上传到 S3/OSS
101
+ - 持久化存储,不会丢失
102
+ - 支持多实例共享
103
+
104
+ 2. **内嵌默认图片**:
105
+ - 将默认图片转为 Base64
106
+ - 直接写在代码中
107
+ - 或者使用 SVG 矢量图
108
+
109
+ 3. **CDN 托管**:
110
+ - 将默认图片放在 CDN
111
+ - 配置 URL 指向 CDN
112
+ - 加载更快
113
+
114
+ ## 🎉 修复完成
115
+
116
+ 修复后,Docker 构建应该能成功,Space 可以正常运行。
117
+
118
+ ---
119
+
120
+ **修复时间**:2026-01-18
121
+ **影响范围**:Hugging Face Space Docker 构建
122
+ **严重程度**:高(导致构建失败)
123
+ **修复状态**:✅ 已完成
HOTFIX_NULL_ERROR.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔧 紧急修复:Python null 错误
2
+
3
+ ## 🐛 问题描述
4
+ Hugging Face Space 部署后出现错误:
5
+ ```
6
+ NameError: name 'null' is not defined
7
+ ```
8
+
9
+ ## 🔍 问题原因
10
+ 在 `app/storage.py` 中使用了 JavaScript 语法的 `null`,但 Python 中应该使用 `None`。
11
+
12
+ ## ✅ 已修复
13
+
14
+ ### 1. 修复 storage.py 中的 null
15
+ **文件**:`app/storage.py`
16
+
17
+ **修改位置**:
18
+ - 第 173-175 行:`_get_default_records()` 方法
19
+ - 第 315-317 行:`_get_default_todos()` 方法
20
+
21
+ **修改内容**:
22
+ ```python
23
+ # 错误 ❌
24
+ "time": null,
25
+ "location": null,
26
+
27
+ # 正确 ✅
28
+ "time": None,
29
+ "location": None,
30
+ ```
31
+
32
+ ### 2. 修复 Dockerfile
33
+ **文件**:`Dockerfile`
34
+
35
+ **问题**:未复制 `generated_images/` 目录,导致默认角色图片 404
36
+
37
+ **修改**:
38
+ ```dockerfile
39
+ # 添加这行
40
+ COPY generated_images/ ./generated_images/
41
+ ```
42
+
43
+ ## 🚀 部署步骤
44
+
45
+ ### 1. 提交修复
46
+ ```bash
47
+ git add app/storage.py Dockerfile
48
+ git commit -m "Fix: Replace null with None in Python code"
49
+ git push origin main
50
+ ```
51
+
52
+ ### 2. 同步到 Hugging Face
53
+ 1. 访问:https://huggingface.co/spaces/kernel14/Nora
54
+ 2. Settings → Sync from GitHub → **Sync now**
55
+
56
+ ### 3. 等待重新构建
57
+ - 查看 **Logs** 标签页
58
+ - 等待构建完成
59
+
60
+ ## ✅ 验证修复
61
+
62
+ 访问以下 API 端点,应该都能正常返回:
63
+
64
+ 1. **健康检查**:
65
+ ```
66
+ https://kernel14-nora.hf.space/health
67
+ ```
68
+
69
+ 2. **获取记录**:
70
+ ```
71
+ https://kernel14-nora.hf.space/api/records
72
+ ```
73
+
74
+ 3. **获取心情**:
75
+ ```
76
+ https://kernel14-nora.hf.space/api/moods
77
+ ```
78
+
79
+ 4. **获取待办**:
80
+ ```
81
+ https://kernel14-nora.hf.space/api/todos
82
+ ```
83
+
84
+ 5. **默认角色图片**:
85
+ ```
86
+ https://kernel14-nora.hf.space/generated_images/default_character.jpeg
87
+ ```
88
+
89
+ ## 📝 技术说明
90
+
91
+ ### Python vs JavaScript 的 null/None
92
+
93
+ | 语言 | 空值表示 |
94
+ |------|---------|
95
+ | JavaScript | `null` |
96
+ | Python | `None` |
97
+ | JSON | `null` |
98
+
99
+ 在 Python 代码中:
100
+ - ✅ 使用 `None`
101
+ - ❌ 不要使用 `null`
102
+
103
+ 在 JSON 字符串中(如 AI 提示):
104
+ - ✅ 使用 `"null"`(字符串形式)
105
+ - ✅ 这是正确的,因为是 JSON 格式
106
+
107
+ ### 为什么会出现这个错误?
108
+
109
+ 1. **复制粘贴错误**:可能从 JSON 示例中复制了代码
110
+ 2. **语言混淆**:在多语言项目中容易混淆语法
111
+ 3. **IDE 未检测**:某些 IDE 可能不会立即标记这个错误
112
+
113
+ ### 如何避免?
114
+
115
+ 1. **使用 Linter**:配置 pylint 或 flake8
116
+ 2. **类型检查**:使用 mypy 进行类型检查
117
+ 3. **单元测试**:编写测试覆盖默认数据生成
118
+ 4. **代码审查**:提交前仔细检查
119
+
120
+ ## 🎉 修复完成
121
+
122
+ 修复后,Space 应该能正常运行,所有 API 端点都能正常响应。
123
+
124
+ ---
125
+
126
+ **修复时间**:2026-01-18
127
+ **影响范围**:Hugging Face Space 部署
128
+ **严重程度**:高(导致服务无法启动)
129
+ **修复状态**:✅ 已完成
HUGGINGFACE_DEPLOY.md ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Hugging Face Spaces 部署指南
2
+
3
+ ## ✅ 部署前检查清单
4
+
5
+ ### 1. 根目录必需文件
6
+
7
+ 确保以下文件在**根目录**(不是子目录):
8
+
9
+ - ✅ `Dockerfile` - Docker 构建配置
10
+ - ✅ `start.py` - 应用启动脚本
11
+ - ✅ `requirements.txt` - Python 依赖
12
+ - ✅ `README_HF.md` - Hugging Face 专用 README(带 frontmatter)
13
+
14
+ ### 2. 前端构建文件
15
+
16
+ 确保前端已构建:
17
+
18
+ ```bash
19
+ cd frontend
20
+ npm install
21
+ npm run build
22
+ ```
23
+
24
+ 检查 `frontend/dist/` 目录是否存在且包含:
25
+ - ✅ `index.html`
26
+ - ✅ `assets/` 目录(包含 JS 和 CSS 文件)
27
+
28
+ ### 3. 环境变量配置
29
+
30
+ 在 Hugging Face Space 的 **Settings → Variables and secrets** 中配置:
31
+
32
+ **必需:**
33
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
34
+
35
+ **可选:**
36
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
37
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
38
+
39
+ ### 4. README 配置
40
+
41
+ 确保 `README_HF.md` 包含正确的 frontmatter:
42
+
43
+ ```yaml
44
+ ---
45
+ title: Nora - 治愈系记录助手
46
+ emoji: 🌟
47
+ colorFrom: purple
48
+ colorTo: pink
49
+ sdk: docker
50
+ pinned: false
51
+ license: mit
52
+ ---
53
+ ```
54
+
55
+ ## 🔧 部署步骤
56
+
57
+ ### 方法 1:通过 GitHub 同步(推荐)
58
+
59
+ 1. **提交所有更改到 GitHub**:
60
+ ```bash
61
+ git add .
62
+ git commit -m "Fix: Add required files to root directory for HF deployment"
63
+ git push origin main
64
+ ```
65
+
66
+ 2. **在 Hugging Face Space 中同步**:
67
+ - 进入你的 Space:https://huggingface.co/spaces/kernel14/Nora
68
+ - 点击 **Settings**
69
+ - 找到 **Sync from GitHub** 部分
70
+ - 点击 **Sync now**
71
+
72
+ 3. **等待构建完成**:
73
+ - 查看 **Logs** 标签页
74
+ - 等待 Docker 构建完成(可能需要 5-10 分钟)
75
+
76
+ ### 方法 2:直接上传文件
77
+
78
+ 1. **在 Hugging Face Space 中上传文件**:
79
+ - 进入 **Files** 标签页
80
+ - 上传以下文件到根目录:
81
+ - `Dockerfile`
82
+ - `start.py`
83
+ - `requirements.txt`
84
+ - `README_HF.md`(重命名为 `README.md`)
85
+
86
+ 2. **上传应用代码**:
87
+ - 上传 `app/` 目录
88
+ - 上传 `data/` 目录
89
+ - 上传 `frontend/dist/` 目录
90
+
91
+ 3. **触发重新构建**:
92
+ - 点击 **Factory reboot**
93
+
94
+ ## 🐛 常见问题
95
+
96
+ ### 问题 1:Space 显示 "Missing app file"
97
+
98
+ **原因**:根目录缺少 `Dockerfile` 或 `start.py`
99
+
100
+ **解决方案**:
101
+ 1. 确认根目录有 `Dockerfile` 和 `start.py`
102
+ 2. 如果使用 GitHub 同步,确保这些文件已提交并推送
103
+ 3. Factory reboot 重启 Space
104
+
105
+ ### 问题 2:Docker 构建失败
106
+
107
+ **原因**:依赖安装失败或文件路径错误
108
+
109
+ **解决方案**:
110
+ 1. 查看 **Logs** 标签页的详细错误信息
111
+ 2. 检查 `requirements.txt` 是否正确
112
+ 3. 检查 `Dockerfile` 中的路径是否正确
113
+
114
+ ### 问题 3:前端无法加载
115
+
116
+ **原因**:`frontend/dist/` 目录不存在或未包含在 Docker 镜像中
117
+
118
+ **解决方案**:
119
+ 1. 本地运行 `cd frontend && npm run build`
120
+ 2. 确认 `frontend/dist/` 目录存在
121
+ 3. 提交并推送到 GitHub
122
+ 4. 重新同步 Space
123
+
124
+ ### 问题 4:API 调用失败
125
+
126
+ **原因**:未配置环境变量
127
+
128
+ **解决方案**:
129
+ 1. 在 Space Settings 中配置 `ZHIPU_API_KEY`
130
+ 2. Factory reboot 重启 Space
131
+ 3. 检查 Logs 确认环境变量已加载
132
+
133
+ ## 📊 验证部署
134
+
135
+ 部署成功后,访问你的 Space URL,应该能看到:
136
+
137
+ 1. ✅ 前端页面正常加载
138
+ 2. ✅ AI 角色形象显示
139
+ 3. ✅ 可以进行文本输入
140
+ 4. ✅ 可以查看心情、灵感、待办数据
141
+
142
+ 测试 API 端点:
143
+ - `https://你的space.hf.space/health` - 应该返回健康状态
144
+ - `https://你的space.hf.space/docs` - 应该显示 API 文档
145
+
146
+ ## 🔄 更新部署
147
+
148
+ 当你更新代码后:
149
+
150
+ 1. **提交到 GitHub**:
151
+ ```bash
152
+ git add .
153
+ git commit -m "Update: 描述你的更改"
154
+ git push origin main
155
+ ```
156
+
157
+ 2. **同步到 Hugging Face**:
158
+ - 在 Space Settings 中点击 **Sync now**
159
+ - 或者等待自动同步(如果已配置)
160
+
161
+ 3. **重启 Space**(如果需要):
162
+ - 点击 **Factory reboot**
163
+
164
+ ## 📚 相关文档
165
+
166
+ - [Hugging Face Spaces 文档](https://huggingface.co/docs/hub/spaces)
167
+ - [Docker SDK 文档](https://huggingface.co/docs/hub/spaces-sdks-docker)
168
+ - [项目完整文档](README.md)
169
+
170
+ ## 🆘 需要帮助?
171
+
172
+ 如果遇到问题:
173
+
174
+ 1. 查看 Space 的 **Logs** 标签页
175
+ 2. 检查 **Community** 标签页的讨论
176
+ 3. 在 GitHub 仓库提 Issue
HUGGINGFACE_FIX_SUMMARY.md ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✅ Hugging Face Spaces 部署问题已修复
2
+
3
+ ## 🎯 问题描述
4
+ Hugging Face Space 显示错误:
5
+ ```
6
+ This Space is missing an app file. An app file is required for the Space to build and run properly.
7
+ ```
8
+
9
+ ## 🔍 问题原因
10
+ 之前为了整理项目结构,将部署文件移到了 `deployment/` 目录,但 Hugging Face Spaces 要求关键文件必须在**根目录**。
11
+
12
+ ## 🔧 已完成的修复
13
+
14
+ ### 1. 复制关键文件到根目录
15
+ - ✅ `Dockerfile` - 从 `deployment/Dockerfile` 复制到根目录
16
+ - ✅ `start.py` - 从 `scripts/start.py` 复制到根目录
17
+ - ✅ `README_HF.md` - 创建了带 frontmatter 的 Hugging Face 专用 README
18
+
19
+ ### 2. 创建部署工具
20
+ - ✅ `.dockerignore` - 优化 Docker 构建,排除不必要的文件
21
+ - ✅ `HUGGINGFACE_DEPLOY.md` - 完整的部署指南
22
+ - ✅ `scripts/prepare_hf_deploy.bat` - 自动化部署准备脚本
23
+
24
+ ### 3. 验证文件结构
25
+ 根目录现在包含所有必需文件:
26
+ ```
27
+ 项目根目录/
28
+ ├── Dockerfile ✅ Docker 构建配置
29
+ ├── start.py ✅ 应用启动脚本
30
+ ├── requirements.txt ✅ Python 依赖
31
+ ├── README_HF.md ✅ HF 专用 README(带 frontmatter)
32
+ ├── app/ ✅ 应用代码
33
+ ├── data/ ✅ 数据目录
34
+ ├── frontend/dist/ ✅ 前端构建文件
35
+ └── generated_images/ ✅ 图片目录
36
+ ```
37
+
38
+ ## 🚀 立即部署
39
+
40
+ ### 方法 1:使用自动化脚本(推荐)
41
+
42
+ 运行准备脚本:
43
+ ```bash
44
+ scripts\prepare_hf_deploy.bat
45
+ ```
46
+
47
+ 这会自动:
48
+ - ✅ 检查所有必需文件
49
+ - ✅ 构建前端(如果需要)
50
+ - ✅ 生成部署清单
51
+ - ✅ 显示下一步操作
52
+
53
+ ### 方法 2:手动操作
54
+
55
+ #### 步骤 1:确认文件存在
56
+ ```bash
57
+ # 检查根目录文件
58
+ dir Dockerfile
59
+ dir start.py
60
+ dir requirements.txt
61
+ dir README_HF.md
62
+
63
+ # 检查前端构建
64
+ dir frontend\dist\index.html
65
+ ```
66
+
67
+ #### 步骤 2:提交到 GitHub
68
+ ```bash
69
+ git add .
70
+ git commit -m "Fix: Add required files to root directory for HF deployment"
71
+ git push origin main
72
+ ```
73
+
74
+ #### 步骤 3:同步到 Hugging Face
75
+ 1. 访问:https://huggingface.co/spaces/kernel14/Nora
76
+ 2. 点击 **Settings** 标签
77
+ 3. 找到 **Sync from GitHub** 部分
78
+ 4. 点击 **Sync now** 按钮
79
+
80
+ #### 步骤 4:配置环境变量
81
+ 1. 在 Settings 中找到 **Variables and secrets**
82
+ 2. 添加环境变量:
83
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥(必需)
84
+ - `MINIMAX_API_KEY` - MiniMax API 密钥(可选)
85
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID(可选)
86
+ 3. 点击 **Factory reboot** 重启 Space
87
+
88
+ #### 步骤 5:等待构建完成
89
+ 1. 切换到 **Logs** 标签页
90
+ 2. 观察 Docker 构建过程
91
+ 3. 等待显示 "Running on http://0.0.0.0:7860"
92
+
93
+ ## ✅ 验证部署
94
+
95
+ 部署成功后,测试以下功能:
96
+
97
+ ### 1. 访问主页
98
+ ```
99
+ https://kernel14-nora.hf.space/
100
+ ```
101
+ 应该看到:
102
+ - ✅ 前端页面正常加载
103
+ - ✅ AI 角色形象显示
104
+ - ✅ 输入框可用
105
+
106
+ ### 2. 测试 API
107
+ ```
108
+ https://kernel14-nora.hf.space/health
109
+ ```
110
+ 应该返回:
111
+ ```json
112
+ {
113
+ "status": "healthy",
114
+ "data_dir": "data",
115
+ "max_audio_size": 10485760
116
+ }
117
+ ```
118
+
119
+ ### 3. 查看 API 文档
120
+ ```
121
+ https://kernel14-nora.hf.space/docs
122
+ ```
123
+ 应该显示完整的 API 文档
124
+
125
+ ### 4. 测试功能
126
+ - ✅ 文本输入和处理
127
+ - ✅ 查看心情、灵感、待办
128
+ - ✅ AI 对话功能
129
+ - ✅ 心情气泡池
130
+
131
+ ## 🐛 故障排查
132
+
133
+ ### 问题 1:仍然显示 "Missing app file"
134
+
135
+ **可能原因**:
136
+ - 文件未正确提交到 GitHub
137
+ - GitHub 同步未完成
138
+
139
+ **解决方案**:
140
+ 1. 检查 GitHub 仓库根目录是否有 `Dockerfile` 和 `start.py`
141
+ 2. 在 HF Space 中手动触发同步
142
+ 3. 查看 Logs 标签页的详细错误
143
+
144
+ ### 问题 2:Docker 构建失败
145
+
146
+ **可能原因**:
147
+ - 依赖安装失败
148
+ - 文件路径错误
149
+
150
+ **解决方案**:
151
+ 1. 查看 Logs 标签页的详细错误信息
152
+ 2. 检查 `requirements.txt` 是否正确
153
+ 3. 确认 `frontend/dist/` 目录存在
154
+
155
+ ### 问题 3:前端无法加载
156
+
157
+ **可能原因**:
158
+ - `frontend/dist/` 目录不存在或为空
159
+ - 前端构建文件未提交
160
+
161
+ **解决方案**:
162
+ 1. 本地运行:`cd frontend && npm run build`
163
+ 2. 确认 `frontend/dist/` 包含 `index.html` 和 `assets/`
164
+ 3. 提交并推送到 GitHub
165
+ 4. 重新同步 Space
166
+
167
+ ### 问题 4:API 调用失败
168
+
169
+ **可能原因**:
170
+ - 未配置 `ZHIPU_API_KEY`
171
+ - API 密钥无效或配额不足
172
+
173
+ **解决方案**:
174
+ 1. 在 Space Settings 中配置环境变量
175
+ 2. 访问 https://open.bigmodel.cn/ 检查 API 密钥和配额
176
+ 3. Factory reboot 重启 Space
177
+
178
+ ## 📊 部署状态检查
179
+
180
+ 运行以下命令检查本地准备情况:
181
+ ```bash
182
+ scripts\prepare_hf_deploy.bat
183
+ ```
184
+
185
+ 查看生成的 `deploy_checklist.txt` 文件。
186
+
187
+ ## 📚 相关文档
188
+
189
+ - [HUGGINGFACE_DEPLOY.md](HUGGINGFACE_DEPLOY.md) - 完整部署指南
190
+ - [README_HF.md](README_HF.md) - Hugging Face Space 的 README
191
+ - [deployment/DEPLOYMENT.md](deployment/DEPLOYMENT.md) - 通用部署文档
192
+
193
+ ## 🎉 成功标志
194
+
195
+ 当看到以下内容时,说明部署成功:
196
+
197
+ 1. ✅ Space 状态显示为 "Running"
198
+ 2. ✅ 可以访问主页并看到 UI
199
+ 3. ✅ API 端点正常响应
200
+ 4. ✅ 可以进行文本输入和查看数据
201
+ 5. ✅ Logs 中没有错误信息
202
+
203
+ ---
204
+
205
+ ## 📝 技术说明
206
+
207
+ ### 为什么需要文件在根目录?
208
+
209
+ Hugging Face Spaces 的构建系统会在根目录查找以下文件:
210
+
211
+ 1. **Dockerfile** - 用于 Docker SDK 的 Space
212
+ 2. **app.py** - 用于 Gradio/Streamlit SDK 的 Space
213
+ 3. **README.md** - 带 frontmatter 的配置文件
214
+
215
+ 如果这些文件不在根目录,构建系统会报错 "Missing app file"。
216
+
217
+ ### 我们的解决方案
218
+
219
+ - 保留 `deployment/` 目录用于备份和文档
220
+ - 在根目录创建必需文件的副本
221
+ - 使用 `.dockerignore` 优化构建,避免包含不必要的文件
222
+
223
+ 这样既保持了项目结构的整洁,又满足了 Hugging Face 的要求。
PRD.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+
3
+ # 产品概述
4
+
5
+ 一款通过 **iOS 原生 (SwiftUI)** 构建,结合 **BLE 蓝牙硬件** 震动提醒与 **AI 语义解析** 的治愈系记录助手。用户通过 APP 或配套硬件录音,系统自动将内容拆解为灵感、心情与待办,并通过 RAG 技术实现历史记忆的回溯。
6
+
7
+ # 核心交互逻辑
8
+
9
+ ## 硬件交互:蓝牙协议
10
+
11
+ 由于使用 iOS 原生开发,手机充当“网关”角色,负责硬件与云端的中转。
12
+
13
+ - **连接流程 (Local Only)**:
14
+ - **无需 API 接口**。iOS APP 使用 `CBCentralManager` 扫描硬件 UUID。
15
+ - 硬件作为外设 (Peripheral) 被手机连接。
16
+ - **指令交互**:
17
+ - **录音阶段**:硬件按下录音键,通过蓝牙特征值 (Characteristic) 将音频数据包流式传输或发送结束信号至 iOS。
18
+ - **震动反馈**:
19
+ - **轻微短振(心跳感)**:iOS 检测到录音启动,向蓝牙写入 `0x01` 指令。
20
+ - **急促振动(提醒感)**:iOS 的待办逻辑触发,向蓝牙写入 `0x02` 指令。
21
+
22
+ ## AI:调用智谱原生api
23
+
24
+ - **语音转写**:iOS 使用 `URLSession` 调用智谱 **ASR API** 上传音频,实时获取转写文字。
25
+ - **语义理解**:iOS 调用 **GLM-4-Flash API**,通过 Prompt 约束 AI 返回标准 JSON(包含情绪、灵感、待办)。
26
+ - **形象定制**:登录时调用 **CogView API** 生成固定形象,图片下载后由 iOS 进行本地持久化存储。
27
+
28
+ # **技术架构 (iOS Native)**
29
+
30
+ ## **前端:SwiftUI**
31
+
32
+ - **状态管理**:使用 `@Observable` (iOS 17+) 实时同步 AI 解析出的心情颜色和形象气泡。
33
+ - **持久化**:使用 **SwiftData** 存储本地 JSON 结构的记录(`records`, `moods`, `todos`, `inspirations`)。
34
+ - **安全性**:智谱 API Key 存储在 **Keychain** 中,避免硬编码。
35
+
36
+ ## **AI 引擎 (智谱 API 集成)**
37
+
38
+ | **模块** | **API 模型** | **职责** |
39
+ | --- | --- | --- |
40
+ | **ASR** | 智谱语音识别 | 硬件原始音频转文字 |
41
+ | **NLP** | GLM-4-Flash | 解析 JSON 结构、RAG 历史回溯对话 |
42
+ | **图像** | CogView-3 | 登录时一次性生成固定猫咪形象 |
43
+
44
+ # AI形象生成
45
+
46
+ ## 设置
47
+
48
+ - **初始化生成**:用户注册/首次登录时,系统引导用户输入关键词(或默认随机),调用 **GLM-Image (CogView)** 生成 1-3 张插画。
49
+ - **持久化存储**:生成的图片 URL 存储在用户配置中,不再随每次录音改变。
50
+ - **按需修改**:在“设置”提供修改接口,用户可以消耗积分或次数重新生成。
51
+
52
+ ## 生成逻辑
53
+
54
+ 为了保证品牌统一性,系统预设为”**治愈系插画猫咪**”,通过映射逻辑处理用户输入。
55
+
56
+ - **提示词生成逻辑 (Prompt Engineering)**
57
+
58
+ | **用户输入维度** | **映射逻辑 (Internal Tags)** | **示例** |
59
+ | --- | --- | --- |
60
+ | **颜色** | 主色调 & 环境色 | 温暖粉 -> `soft pastel pink fur, rose-colored aesthetic` |
61
+ | **性格** | 构图 & 眼神光 | 活泼 -> `big curious eyes, dynamic paw gesture, energetic aura` |
62
+ | **形象** | 配饰 & 特征 | 戴眼镜 -> `wearing tiny round glasses, scholarly look` |
63
+
64
+ 【陪伴式朋友】【温柔照顾型长辈】【引导型 老师】
65
+
66
+ **系统底座提示词 (System Base Prompt):**
67
+
68
+ > "A masterpiece cute stylized cat illustration, [Color] theme, [Personality] facial expression and posture, [Description]. Japanese watercolor style, clean minimalist background, high quality, soft studio lighting, 4k."
69
+ >
70
+
71
+ ## 技术架构
72
+
73
+ ### 前端:iOS Native (SwiftUI)
74
+
75
+ - **UI 渲染**:利用 `SwiftUI` 实现毛玻璃效果与治愈系猫咪插画的流畅加载。
76
+ - **状态管理**:使用 `Combine` 或 `Observation` 框架同步心情颜色变化。
77
+ - **硬件接口**:`CoreBluetooth`。
78
+
79
+ ### 后端:FastAPI (Python)
80
+
81
+ - **API 核心**:处理 ASR、NLP、RAG 和 Image Generation。
82
+ - **存储**:本地 JSON 文件系统(`records.json`, `moods.json`, `todos.json`, `inspirations.json`)。
83
+
84
+ ### AI 引擎 (智谱全家桶)
85
+
86
+ - **ASR**:语音转文字。
87
+ - **GLM-4-Flash**:语义解析与 RAG 问答。
88
+ - **GLM-Image (CogView)**:基于情绪映射生成的静态形象。
89
+
90
+ # 核心功能模块
91
+
92
+ ### 首页 - 录音与实时处理
93
+
94
+ - **功能描述:**
95
+ - 支持语音录音(5-30 秒)或文字直接输入。
96
+ - **静态形象展示**:页面中心展示常驻形象。
97
+ - 实时处理:完成录音后自动触发后端 ASR 与 NLP 流程。
98
+ - **结果速览**:展示最近一次分析的**原文及摘要**(提取出的情绪、灵感标签或待办任务)。
99
+ - **数据存储:** * 音频文件:`data/audio/{timestamp}.wav`
100
+ - 完整记录索引:`data/records.json`(包含关联的 JSON ID 和音频路径)。
101
+
102
+ ### 灵感看板页面
103
+
104
+ - **功能描述:**
105
+ - **瀑布流展示**:以卡片形式展示所有灵感。
106
+ - **核心要素**:显示 AI 总结的核心观点、自动生成的标签、所属分类(工作/生活/学习/创意)。
107
+ - **筛选排序**:支持按分类筛选及时间顺序/倒序排列。
108
+ - **数据结构:** `inspirations.json` 存储核心观点、关键字及原文引用。
109
+
110
+ ### 心情日记页面
111
+
112
+ - **功能描述:**
113
+ - **情绪可视化**:展示情绪分布柱状图(如:本周 60% 平静,20% 喜悦)。
114
+ - **记录列表**:显示每条记录的情绪类型、强度(1-10)及当时的心情关键词。
115
+ - **筛选**:可单独查看“喜”或“哀”等特定情绪的历史。
116
+ - **数据结构:** `moods.json` 记录 `type`, `intensity`, `keywords` 等字段。
117
+
118
+ ### 待办清单页面
119
+
120
+ - **功能描述:**
121
+ - **任务管理**:从输入中自动提取出的任务(包含时间、地点、内容)。
122
+ - **状态切换**:支持手动勾选“已完成”。
123
+ - **统计**:显示待办/已完成的数量对比。
124
+ - **数据结构:** `todos.json` 包含任务描述、时间实体及完成状态。
125
+
126
+ ### AI 对话页面
127
+
128
+ - **功能描述:**
129
+ - **智能检索**:用户询问“我上周关于论文有什么灵感?”时,系统通过 RAG 技术检索 `records.json` 并回答。
130
+ - **快捷指令**:提供“总结今日心情”、“还有哪些待办”等快捷按钮。
131
+ - **技术实现:** 基于 **GLM-4-Flash** 进行上下文理解与 RAG 检索。
132
+
133
+ ---
134
+
135
+ # 业务流程与数据流
136
+
137
+ iOS 端在请求 GLM-4 时,使用以下 System Prompt 确保数据可被解析:
138
+
139
+ > "你是一个数据转换器。请将文本解析为 JSON 格式。维度包括:1.情绪(type,intensity); 2.灵感(core_idea,tags); 3.待办(task,time,location)。必须严格遵循 JSON 格式返回。"
140
+ >
141
+
142
+ ### NLP 语义解析策略
143
+
144
+ | **提取维度** | **逻辑** | **去向** |
145
+ | --- | --- | --- |
146
+ | **情绪** | 识别情感极性与 1-10 的强度值 | `moods.json` |
147
+ | **灵感** | 提炼 20 字以内的核心观点 + 3个标签 | `inspirations.json` |
148
+ | **待办** | 识别时间词(如“明晚”)、地点与动词短语 | `todos.json` |
149
+
150
+ # 技术栈总结
151
+
152
+ - **开发语言**:Swift 6.0 / SwiftUI
153
+ - **核心框架**:CoreBluetooth (硬件), SwiftData (存储), CoreHaptics (震动)
154
+ - **AI 接口**:智谱 API (HTTP/HTTPS 请求)
155
+ - **数据存储**:iOS Local SandBox (音频文件 + 结构化数据)
PROJECT_STRUCTURE.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 项目目录结构
2
+
3
+ ```
4
+ Inspiration-Record-APP/
5
+ ├── app/ # 后端应用代码
6
+ │ ├── __init__.py
7
+ │ ├── main.py # FastAPI 主应用
8
+ │ ├── config.py # 配置管理
9
+ │ ├── models.py # 数据模型
10
+ │ ├── storage.py # 数据存储
11
+ │ ├── asr_service.py # 语音识别服务
12
+ │ ├── semantic_parser.py # 语义解析服务
13
+ │ ├── image_service.py # 图像生成服务
14
+ │ ├── user_config.py # 用户配置管理
15
+ │ └── logging_config.py # 日志配置
16
+
17
+ ├── frontend/ # 前端应用
18
+ │ ├── components/ # React 组件
19
+ │ ├── services/ # API 服务
20
+ │ ├── utils/ # 工具函数
21
+ │ ├── dist/ # 构建产物(部署需要)
22
+ │ ├── App.tsx # 主应用组件
23
+ │ ├── index.tsx # 入口文件
24
+ │ ├── types.ts # TypeScript 类型定义
25
+ │ ├── package.json # 前端依赖
26
+ │ └── vite.config.ts # Vite 配置
27
+
28
+ ├── data/ # 数据存储目录
29
+ │ ├── moods.json # 心情数据
30
+ │ ├── inspirations.json # 灵感数据
31
+ │ ├── todos.json # 待办数据
32
+ │ ├── records.json # 记录数据
33
+ │ └── user_config.json # 用户配置
34
+
35
+ ├── generated_images/ # AI 生成的图片
36
+ │ └── default_character.jpeg # 默认形象
37
+
38
+ ├── logs/ # 日志文件
39
+ │ └── app.log
40
+
41
+ ├── tests/ # 测试文件
42
+ │ ├── test_*.py # 单元测试
43
+ │ ├── test_api.html # API 测试页面
44
+ │ ├── test_chat_api.py # 聊天 API 测试
45
+ │ └── test_default_character.py # 默认形象测试
46
+
47
+ ├── scripts/ # 脚本文件
48
+ │ ├── start_local.py # 本地启动脚本(8000端口)
49
+ │ ├── start_local.bat # Windows 启动脚本
50
+ │ ├── start.py # 通用启动脚本(7860端口)
51
+ │ ├── build_and_deploy.bat # 构建并部署脚本
52
+ │ └── build_and_deploy.sh # Linux/Mac 部署脚本
53
+
54
+ ├── deployment/ # 部署配置文件
55
+ │ ├── Dockerfile # Docker 配置
56
+ │ ├── app_modelscope.py # ModelScope 入口
57
+ │ ├── configuration.json # ModelScope 配置
58
+ │ ├── ms_deploy.json # ModelScope 部署配置
59
+ │ ├── requirements_hf.txt # Hugging Face 依赖
60
+ │ ├── requirements_modelscope.txt # ModelScope 依赖
61
+ │ ├── README_HF.md # Hugging Face 说明
62
+ │ ├── README_MODELSCOPE.md # ModelScope 说明
63
+ │ ├── DEPLOY_CHECKLIST.md # 部署检查清单
64
+ │ ├── DEPLOYMENT.md # 部署指南
65
+ │ ├── deploy_to_hf.bat # 部署到 HF 脚本
66
+ │ └── deploy_to_hf.sh # 部署到 HF 脚本
67
+
68
+ ├── docs/ # 文档目录
69
+ │ ├── README.md # 项目文档
70
+ │ ├── FEATURE_SUMMARY.md # 功能总结
71
+ │ ├── API_配置说明.md # API 配置说明
72
+ │ ├── 局域网访问指南.md # 局域网访问指南
73
+ │ ├── 功能架构图.md # 架构图
74
+ │ ├── 后端启动问题排查.md # 故障排查
75
+ │ ├── 心情气泡池功能说明.md
76
+ │ ├── 心情气泡池快速开始.md
77
+ │ └── 语音录制问题排查.md
78
+
79
+ ├── .github/ # GitHub 配置
80
+ │ └── workflows/
81
+ │ └── sync.yml # 自动同步工作流
82
+
83
+ ├── .env # 环境变量(本地)
84
+ ├── .env.example # 环境变量示例
85
+ ├── .gitignore # Git 忽略文件
86
+ ├── requirements.txt # Python 依赖(开发环境)
87
+ ├── pytest.ini # Pytest 配置
88
+ ├── PRD.md # 产品需求文档
89
+ └── README.md # 项目说明
90
+ ```
91
+
92
+ ## 目录说明
93
+
94
+ ### 核心目录
95
+
96
+ - **app/** - 后端 FastAPI 应用,包含所有业务逻辑
97
+ - **frontend/** - 前端 React 应用,使用 TypeScript + Vite
98
+ - **data/** - 运行时数据存储,JSON 格式
99
+ - **generated_images/** - AI 生成的角色图片
100
+
101
+ ### 开发目录
102
+
103
+ - **tests/** - 所有测试文件,包括单元测试和集成测试
104
+ - **scripts/** - 开发和部署脚本
105
+ - **logs/** - 应用日志文件
106
+
107
+ ### 部署目录
108
+
109
+ - **deployment/** - 所有部署相关的配置文件
110
+ - Hugging Face Spaces 部署
111
+ - ModelScope 部署
112
+ - Docker 部署
113
+
114
+ ### 文档目录
115
+
116
+ - **docs/** - 项目文档和使用指南
117
+
118
+ ## 快速开始
119
+
120
+ ### 本地开发
121
+
122
+ ```bash
123
+ # 1. 安装依赖
124
+ pip install -r requirements.txt
125
+ cd frontend && npm install && cd ..
126
+
127
+ # 2. 构建前端
128
+ cd frontend && npm run build && cd ..
129
+
130
+ # 3. 启动服务器
131
+ python scripts/start_local.py
132
+ ```
133
+
134
+ ### 部署
135
+
136
+ **Hugging Face:**
137
+ ```bash
138
+ cd deployment
139
+ ./deploy_to_hf.sh
140
+ ```
141
+
142
+ **ModelScope:**
143
+ - 上传所有文件到 ModelScope
144
+ - 确保 `ms_deploy.json` 在根目录
145
+
146
+ ## 文件清理说明
147
+
148
+ 已删除的冗余文件:
149
+ - `app_gradio_old.py.bak` - 旧的 Gradio 备份文件
150
+ - `packages.txt` - 不再使用的包列表
151
+
152
+ 已整理的文件:
153
+ - 脚本文件 → `scripts/`
154
+ - 部署文件 → `deployment/`
155
+ - 测试文件 → `tests/`
README.md ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Nora - 治愈系记录助手
3
+ emoji: 🌟
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # 🌟 治愈系记录助手 - SoulMate AI Companion
12
+
13
+ 一个温暖、治愈的 AI 陪伴应用,帮助你记录心情、捕捉灵感、管理待办。
14
+
15
+ 目前已上线huggingface,体验链接:https://huggingface.co/spaces/kernel14/Nora
16
+
17
+ ## ✨ 核心特性
18
+
19
+ - 🎤 **语音/文字快速记录** - 自动分类保存
20
+ - 🤖 **AI 语义解析** - 智能提取情绪、灵感和待办
21
+ - 💬 **AI 对话陪伴(RAG)** - 基于历史记录的个性化对话
22
+ - 🖼️ **AI 形象定制** - 生成专属治愈系角色(720 种组合)
23
+ - 🫧 **物理引擎心情池** - 基于 Matter.js 的动态气泡可视化
24
+
25
+ ## 🚀 快速开始
26
+
27
+ ### 在线使用
28
+
29
+ 直接访问本 Space 即可使用完整功能!
30
+
31
+ ### 配置 API 密钥
32
+
33
+ 在 Space 的 **Settings → Repository secrets** 中配置:
34
+
35
+ **必需:**
36
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
37
+ - 获取地址:https://open.bigmodel.cn/
38
+ - 用途:语音识别、语义解析、AI 对话
39
+
40
+ **可选:**
41
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
42
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
43
+ - 获取地址:https://platform.minimaxi.com/
44
+ - 用途:AI 形象生成
45
+
46
+ ## 📖 使用说明
47
+
48
+ 1. **首页快速记录**
49
+ - 点击麦克风录音或在输入框输入文字
50
+ - AI 自动分析并分类保存
51
+
52
+ 2. **查看分类数据**
53
+ - 点击顶部心情、灵感、待办图标
54
+ - 查看不同类型的记录
55
+
56
+ 3. **与 AI 对话**
57
+ - 点击 AI 形象显示问候对话框
58
+ - 点击对话框中的聊天图标进入完整对话
59
+ - AI 基于你的历史记录提供个性化回复
60
+
61
+ 4. **定制 AI 形象**
62
+ - 点击右下角 ✨ 按钮
63
+ - 选择颜色、性格、外观、角色
64
+ - 生成专属形象(需要 MiniMax API)
65
+
66
+ 5. **心情气泡池**
67
+ - 点击顶部心情图标
68
+ - 左右滑动查看不同日期的心情卡片
69
+ - 点击卡片展开查看当天的气泡池
70
+ - 可以拖拽气泡,感受物理引擎效果
71
+
72
+ ## 📊 API 端点
73
+
74
+ - `POST /api/process` - 处理文本/语音输入
75
+ - `POST /api/chat` - 与 AI 对话(RAG)
76
+ - `GET /api/records` - 获取所有记录
77
+ - `GET /api/moods` - 获取情绪数据
78
+ - `GET /api/inspirations` - 获取灵感
79
+ - `GET /api/todos` - 获取待办事项
80
+ - `POST /api/character/generate` - 生成角色形象
81
+ - `GET /health` - 健康检查
82
+ - `GET /docs` - API 文档
83
+
84
+ ## 🔗 相关链接
85
+
86
+ - [GitHub 仓库](https://github.com/kernel-14/Nora)
87
+ - [详细文档](https://github.com/kernel-14/Nora/blob/main/README.md)
88
+ - [智谱 AI](https://open.bigmodel.cn/)
89
+ - [MiniMax](https://platform.minimaxi.com/)
90
+ - [Huggingface](https://huggingface.co/spaces/kernel14/Nora)
91
+
92
+ ## 📝 技术栈
93
+
94
+ - **后端**: FastAPI + Python 3.11
95
+ - **前端**: React + TypeScript + Vite
96
+ - **物理引擎**: Matter.js
97
+ - **AI 服务**: 智谱 AI (GLM-4) + MiniMax
98
+ - **部署**: Hugging Face Spaces (Docker)
99
+
100
+ ## 🔧 本地开发
101
+
102
+ ### 启动后端服务
103
+
104
+ ```bash
105
+ # 安装依赖
106
+ pip install -r requirements.txt
107
+
108
+ # 配置环境变量(复制 .env.example 为 .env 并填写)
109
+ cp .env.example .env
110
+
111
+ # 启动服务(端口 8000)
112
+ python scripts/start_local.py
113
+ ```
114
+
115
+ ### 构建前端
116
+
117
+ ```bash
118
+ cd frontend
119
+ npm install
120
+ npm run build
121
+ ```
122
+
123
+ ### 局域网访问
124
+
125
+ 1. 启动后端后,会显示局域网访问地址(如 `http://192.168.1.100:8000/`)
126
+ 2. 其他设备连接同一 WiFi 后,使用该地址访问
127
+ 3. 如果无法访问,请参考 [局域网访问快速修复指南](docs/局域网访问快速修复.md)
128
+
129
+ **快速诊断**:
130
+ ```bash
131
+ # Windows
132
+ scripts\test_lan_access.bat
133
+
134
+ # 或访问诊断页面
135
+ http://你的IP:8000/test-connection.html
136
+ ```
137
+
138
+ ## 🐛 故障排查
139
+
140
+ ### 问题:其他设备访问显示 "Load failed"
141
+
142
+ **原因**:防火墙阻止、网络隔离或 API 地址配置错误
143
+
144
+ **解决方案**:
145
+ 1. 运行诊断工具:`scripts\test_lan_access.bat`
146
+ 2. 访问诊断页面:`http://你的IP:8000/test-connection.html`
147
+ 3. 查看详细指南:[局域网访问快速修复](docs/局域网访问快速修复.md)
148
+
149
+ ### 问题:语音识别失败
150
+
151
+ **原因**:未配置 ZHIPU_API_KEY 或 API 配额不足
152
+
153
+ **解决方案**:
154
+ 1. 检查 `.env` 文件中的 `ZHIPU_API_KEY`
155
+ 2. 访问 https://open.bigmodel.cn/ 检查配额
156
+
157
+ ### 问题:AI 形象生成失败
158
+
159
+ **原因**:未配置 MINIMAX_API_KEY 或 API 配额不足
160
+
161
+ **解决方案**:
162
+ 1. 检查 `.env` 文件中的 `MINIMAX_API_KEY` 和 `MINIMAX_GROUP_ID`
163
+ 2. 访问 https://platform.minimaxi.com/ 检查配额
164
+
165
+ ## 📚 文档
166
+
167
+ - [功能架构图](docs/功能架构图.md)
168
+ - [API 配置说明](docs/API_配置说明.md)
169
+ - [局域网访问指南](docs/局域网访问指南.md)
170
+ - [局域网访问快速修复](docs/局域网访问快速修复.md)
171
+ - [心情气泡池功能说明](docs/心情气泡池功能说明.md)
172
+
173
+ ## 📄 License
174
+
175
+ MIT License
README_HF.md ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Nora - 治愈系记录助手
3
+ emoji: 🌟
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # 🌟 治愈系记录助手 - SoulMate AI Companion
12
+
13
+ 一个温暖、治愈的 AI 陪伴应用,帮助你记录心情、捕捉灵感、管理待办。
14
+
15
+ ## ✨ 核心特性
16
+
17
+ - 🎤 **语音/文字快速记录** - 自动分类保存
18
+ - 🤖 **AI 语义解析** - 智能提取情绪、灵感和待办
19
+ - 💬 **AI 对话陪伴(RAG)** - 基于历史记录的个性化对话
20
+ - 🖼️ **AI 形象定制** - 生成专属治愈系角色(720 种组合)
21
+ - 🫧 **物理引擎心情池** - 基于 Matter.js 的动态气泡可视化
22
+
23
+ ## 🚀 快速开始
24
+
25
+ ### 在线使用
26
+
27
+ 直接访问本 Space 即可使用完整功能!
28
+
29
+ ### ⚙️ 配置 API 密钥
30
+
31
+ 在 Space 的 **Settings → Variables and secrets** 中配置:
32
+
33
+ **必需:**
34
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
35
+ - 获取地址:https://open.bigmodel.cn/
36
+ - 用途:语音识别、语义解析、AI 对话
37
+
38
+ **可选:**
39
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
40
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
41
+ - 获取地址:https://platform.minimaxi.com/
42
+ - 用途:AI 形象生成
43
+
44
+ 配置后,点击 **Factory reboot** 重启 Space 使配置生效。
45
+
46
+ ## 📖 使用说明
47
+
48
+ 1. **首页快速记录**
49
+ - 点击麦克风录音或在输入框输入文字
50
+ - AI 自动分析并分类保存
51
+
52
+ 2. **查看分类数据**
53
+ - 点击顶部心情、灵感、待办图标
54
+ - 查看不同类型的记录
55
+
56
+ 3. **与 AI 对话**
57
+ - 点击 AI 形象显示问候对话框
58
+ - 点击对话框中的聊天图标进入完整对话
59
+ - AI 基于你的历史记录提供个性化回复
60
+
61
+ 4. **定制 AI 形象**
62
+ - 点击右下角 ✨ 按钮
63
+ - 选择颜色、性格、外观、角色
64
+ - 生成专属形象(需要 MiniMax API)
65
+
66
+ 5. **心情气泡池**
67
+ - 点击顶部心情图标
68
+ - 左右滑动查看不同日期的心情卡片
69
+ - 点击卡片展开查看当天的气泡池
70
+ - 可以拖拽气泡,感受物理引擎效果
71
+
72
+ ## 📊 API 端点
73
+
74
+ - `POST /api/process` - 处理文本/语音输入
75
+ - `POST /api/chat` - 与 AI 对话(RAG)
76
+ - `GET /api/records` - 获取所有记录
77
+ - `GET /api/moods` - 获取情绪数据
78
+ - `GET /api/inspirations` - 获取灵感
79
+ - `GET /api/todos` - 获取待办事项
80
+ - `POST /api/character/generate` - 生成角色形象
81
+ - `GET /health` - 健康检查
82
+ - `GET /docs` - API 文档
83
+
84
+ ## 🔗 相关链接
85
+
86
+ - [GitHub 仓库](https://github.com/kernel-14/Nora)
87
+ - [完整文档](https://github.com/kernel-14/Nora/blob/main/README.md)
88
+ - [智谱 AI](https://open.bigmodel.cn/)
89
+ - [MiniMax](https://platform.minimaxi.com/)
90
+
91
+ ## 📝 技术栈
92
+
93
+ - **后端**: FastAPI + Python 3.11
94
+ - **前端**: React + TypeScript + Vite
95
+ - **物理引擎**: Matter.js
96
+ - **AI 服务**: 智谱 AI (GLM-4) + MiniMax
97
+ - **部署**: Hugging Face Spaces (Docker)
98
+
99
+ ## 🐛 故障排查
100
+
101
+ ### 问题:语音识别失败
102
+
103
+ **原因**:未配置 ZHIPU_API_KEY 或 API 配额不足
104
+
105
+ **解决方案**:
106
+ 1. 在 Space Settings 中配置 `ZHIPU_API_KEY`
107
+ 2. 访问 https://open.bigmodel.cn/ 检查配额
108
+ 3. Factory reboot 重启 Space
109
+
110
+ ### 问题:AI 形象生成失败
111
+
112
+ **原因**:未配置 MINIMAX_API_KEY 或 API 配额不足
113
+
114
+ **解决方案**:
115
+ 1. 在 Space Settings 中配置 `MINIMAX_API_KEY` 和 `MINIMAX_GROUP_ID`
116
+ 2. 访问 https://platform.minimaxi.com/ 检查配额
117
+ 3. Factory reboot 重启 Space
118
+
119
+ ### 问题:Space 构建失败
120
+
121
+ **原因**:缺少必要的文件或配置
122
+
123
+ **检查清单**:
124
+ - ✅ 根目录有 `Dockerfile`
125
+ - ✅ 根目录有 `start.py`
126
+ - ✅ 根目录有 `requirements.txt`
127
+ - ✅ `frontend/dist/` 目录存在且包含构建文件
128
+
129
+ ## 📄 License
130
+
131
+ MIT License
app/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Voice Text Processor Application"""
app/asr_service.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ASR (Automatic Speech Recognition) service for Voice Text Processor.
2
+
3
+ This module implements the ASRService class for transcribing audio files
4
+ to text using the Zhipu AI GLM-ASR-2512 API.
5
+
6
+ Requirements: 2.1, 2.2, 2.3, 2.4, 9.2, 9.5
7
+ """
8
+
9
+ import logging
10
+ from typing import Optional
11
+ import httpx
12
+
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ASRServiceError(Exception):
18
+ """Exception raised when ASR service operations fail.
19
+
20
+ This exception is raised when the Zhipu ASR API call fails,
21
+ such as due to network issues, API errors, or invalid responses.
22
+
23
+ Requirements: 2.3
24
+ """
25
+
26
+ def __init__(self, message: str = "语音识别服务不可用"):
27
+ """Initialize ASRServiceError.
28
+
29
+ Args:
30
+ message: Error message describing the failure
31
+ """
32
+ super().__init__(message)
33
+ self.message = message
34
+
35
+
36
+ class ASRService:
37
+ """Service for transcribing audio files using Zhipu AI ASR API.
38
+
39
+ This service handles audio file transcription by calling the Zhipu AI
40
+ GLM-ASR-2512 API. It manages API authentication, request formatting,
41
+ response parsing, and error handling.
42
+
43
+ Attributes:
44
+ api_key: Zhipu AI API key for authentication
45
+ client: Async HTTP client for making API requests
46
+ api_url: Zhipu AI ASR API endpoint URL
47
+ model: ASR model identifier
48
+
49
+ Requirements: 2.1, 2.2, 2.3, 2.4, 9.2, 9.5
50
+ """
51
+
52
+ def __init__(self, api_key: str):
53
+ """Initialize the ASR service.
54
+
55
+ Args:
56
+ api_key: Zhipu AI API key for authentication
57
+ """
58
+ self.api_key = api_key
59
+ self.client = httpx.AsyncClient(timeout=30.0)
60
+ self.api_url = "https://api.z.ai/api/paas/v4/audio/transcriptions"
61
+ self.model = "glm-asr-2512"
62
+
63
+ async def close(self):
64
+ """Close the HTTP client.
65
+
66
+ This should be called when the service is no longer needed
67
+ to properly clean up resources.
68
+ """
69
+ await self.client.aclose()
70
+
71
+ async def transcribe(self, audio_file: bytes, filename: str = "audio.mp3") -> str:
72
+ """Transcribe audio file to text using Zhipu ASR API.
73
+
74
+ This method sends the audio file to the Zhipu AI ASR API and returns
75
+ the transcribed text. It handles API errors, empty recognition results,
76
+ and logs all errors with timestamps and stack traces.
77
+
78
+ Args:
79
+ audio_file: Audio file content as bytes
80
+ filename: Name of the audio file (for API request)
81
+
82
+ Returns:
83
+ Transcribed text content. Returns empty string if audio cannot
84
+ be recognized (empty recognition result).
85
+
86
+ Raises:
87
+ ASRServiceError: If API call fails or returns invalid response
88
+
89
+ Requirements: 2.1, 2.2, 2.3, 2.4, 9.2, 9.5
90
+ """
91
+ try:
92
+ # Prepare request headers
93
+ headers = {
94
+ "Authorization": f"Bearer {self.api_key}"
95
+ }
96
+
97
+ # Prepare multipart form data
98
+ files = {
99
+ "file": (filename, audio_file, "audio/mpeg")
100
+ }
101
+
102
+ data = {
103
+ "model": self.model,
104
+ "stream": "false"
105
+ }
106
+
107
+ logger.info(f"Calling Zhipu ASR API for file: {filename}")
108
+
109
+ # Make API request
110
+ response = await self.client.post(
111
+ self.api_url,
112
+ headers=headers,
113
+ files=files,
114
+ data=data
115
+ )
116
+
117
+ # Check response status
118
+ if response.status_code != 200:
119
+ error_msg = f"ASR API returned status {response.status_code}"
120
+ try:
121
+ error_detail = response.json()
122
+ error_msg += f": {error_detail}"
123
+ except Exception:
124
+ error_msg += f": {response.text}"
125
+
126
+ logger.error(
127
+ f"ASR API call failed: {error_msg}",
128
+ exc_info=True,
129
+ extra={"timestamp": logger.makeRecord(
130
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
131
+ ).created}
132
+ )
133
+ raise ASRServiceError(f"语音识别服务不可用: {error_msg}")
134
+
135
+ # Parse response
136
+ try:
137
+ result = response.json()
138
+ except Exception as e:
139
+ error_msg = f"Failed to parse ASR API response: {str(e)}"
140
+ logger.error(
141
+ error_msg,
142
+ exc_info=True,
143
+ extra={"timestamp": logger.makeRecord(
144
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
145
+ ).created}
146
+ )
147
+ raise ASRServiceError(f"语音识别服务不可用: 响应格式无效")
148
+
149
+ # Extract transcribed text
150
+ text = result.get("text", "")
151
+
152
+ # Handle empty recognition result
153
+ if not text or text.strip() == "":
154
+ logger.warning(
155
+ f"ASR returned empty text for file: {filename}. "
156
+ "Audio content may be unrecognizable."
157
+ )
158
+ return ""
159
+
160
+ logger.info(
161
+ f"ASR transcription successful for {filename}. "
162
+ f"Text length: {len(text)} characters"
163
+ )
164
+
165
+ return text
166
+
167
+ except ASRServiceError:
168
+ # Re-raise ASRServiceError as-is
169
+ raise
170
+
171
+ except httpx.TimeoutException as e:
172
+ error_msg = f"ASR API request timeout: {str(e)}"
173
+ logger.error(
174
+ error_msg,
175
+ exc_info=True,
176
+ extra={"timestamp": logger.makeRecord(
177
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
178
+ ).created}
179
+ )
180
+ raise ASRServiceError("语音识别服务不可用: 请求超时")
181
+
182
+ except httpx.RequestError as e:
183
+ error_msg = f"ASR API request failed: {str(e)}"
184
+ logger.error(
185
+ error_msg,
186
+ exc_info=True,
187
+ extra={"timestamp": logger.makeRecord(
188
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
189
+ ).created}
190
+ )
191
+ raise ASRServiceError(f"语音识别服务不可用: 网络错误")
192
+
193
+ except Exception as e:
194
+ error_msg = f"Unexpected error in ASR service: {str(e)}"
195
+ logger.error(
196
+ error_msg,
197
+ exc_info=True,
198
+ extra={"timestamp": logger.makeRecord(
199
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
200
+ ).created}
201
+ )
202
+ raise ASRServiceError(f"语音识别服务不可用: {str(e)}")
app/config.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration management module for Voice Text Processor.
2
+
3
+ This module handles loading configuration from environment variables,
4
+ validating required settings, and providing configuration access throughout
5
+ the application.
6
+
7
+ Requirements: 10.1, 10.2, 10.3, 10.4, 10.5
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Optional
13
+ from pydantic import BaseModel, Field, field_validator
14
+ from dotenv import load_dotenv
15
+
16
+
17
+ class Config(BaseModel):
18
+ """Application configuration loaded from environment variables."""
19
+
20
+ # API Keys
21
+ zhipu_api_key: str = Field(
22
+ ...,
23
+ description="Zhipu AI API key for ASR and GLM-4-Flash services"
24
+ )
25
+
26
+ minimax_api_key: Optional[str] = Field(
27
+ default=None,
28
+ description="MiniMax API key for image generation (optional)"
29
+ )
30
+
31
+ minimax_group_id: Optional[str] = Field(
32
+ default=None,
33
+ description="MiniMax Group ID (optional)"
34
+ )
35
+
36
+ # Data storage paths
37
+ data_dir: Path = Field(
38
+ default=Path("data"),
39
+ description="Directory for storing JSON data files"
40
+ )
41
+
42
+ # File size limits (in bytes)
43
+ max_audio_size: int = Field(
44
+ default=10 * 1024 * 1024, # 10 MB default
45
+ description="Maximum audio file size in bytes"
46
+ )
47
+
48
+ # Logging configuration
49
+ log_level: str = Field(
50
+ default="INFO",
51
+ description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)"
52
+ )
53
+
54
+ log_file: Optional[Path] = Field(
55
+ default=Path("logs/app.log"),
56
+ description="Log file path"
57
+ )
58
+
59
+ # Server configuration
60
+ host: str = Field(
61
+ default="0.0.0.0",
62
+ description="Server host"
63
+ )
64
+
65
+ port: int = Field(
66
+ default=8000,
67
+ description="Server port"
68
+ )
69
+
70
+ @field_validator("log_level")
71
+ @classmethod
72
+ def validate_log_level(cls, v: str) -> str:
73
+ """Validate log level is one of the standard levels."""
74
+ valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
75
+ v_upper = v.upper()
76
+ if v_upper not in valid_levels:
77
+ raise ValueError(f"log_level must be one of {valid_levels}")
78
+ return v_upper
79
+
80
+ @field_validator("max_audio_size")
81
+ @classmethod
82
+ def validate_max_audio_size(cls, v: int) -> int:
83
+ """Validate max audio size is positive."""
84
+ if v <= 0:
85
+ raise ValueError("max_audio_size must be positive")
86
+ return v
87
+
88
+ @field_validator("data_dir", "log_file")
89
+ @classmethod
90
+ def convert_to_path(cls, v) -> Path:
91
+ """Convert string paths to Path objects."""
92
+ if isinstance(v, str):
93
+ return Path(v)
94
+ return v
95
+
96
+ class Config:
97
+ """Pydantic configuration."""
98
+ frozen = True # Make config immutable
99
+
100
+
101
+ def load_config() -> Config:
102
+ """Load configuration from environment variables.
103
+
104
+ Returns:
105
+ Config: Validated configuration object
106
+
107
+ Raises:
108
+ ValueError: If required configuration is missing or invalid
109
+
110
+ Environment Variables:
111
+ ZHIPU_API_KEY: Required. API key for Zhipu AI services
112
+ MINIMAX_API_KEY: Optional. API key for MiniMax image generation
113
+ MINIMAX_GROUP_ID: Optional. MiniMax Group ID
114
+ DATA_DIR: Optional. Directory for data storage (default: data/)
115
+ MAX_AUDIO_SIZE: Optional. Max audio file size in bytes (default: 10MB)
116
+ LOG_LEVEL: Optional. Logging level (default: INFO)
117
+ LOG_FILE: Optional. Log file path (default: logs/app.log)
118
+ HOST: Optional. Server host (default: 0.0.0.0)
119
+ PORT: Optional. Server port (default: 8000)
120
+ """
121
+ # Load environment variables from .env file
122
+ load_dotenv()
123
+
124
+ # Load from environment variables
125
+ config_dict = {
126
+ "zhipu_api_key": os.getenv("ZHIPU_API_KEY"),
127
+ "minimax_api_key": os.getenv("MINIMAX_API_KEY"),
128
+ "minimax_group_id": os.getenv("MINIMAX_GROUP_ID"),
129
+ "data_dir": os.getenv("DATA_DIR", "data"),
130
+ "max_audio_size": int(os.getenv("MAX_AUDIO_SIZE", str(10 * 1024 * 1024))),
131
+ "log_level": os.getenv("LOG_LEVEL", "INFO"),
132
+ "log_file": os.getenv("LOG_FILE", "logs/app.log"),
133
+ "host": os.getenv("HOST", "0.0.0.0"),
134
+ "port": int(os.getenv("PORT", "8000")),
135
+ }
136
+
137
+ # Validate required fields
138
+ if not config_dict["zhipu_api_key"]:
139
+ raise ValueError(
140
+ "ZHIPU_API_KEY environment variable is required. "
141
+ "Please set it before starting the application."
142
+ )
143
+
144
+ # Create and validate config
145
+ try:
146
+ config = Config(**config_dict)
147
+ except Exception as e:
148
+ raise ValueError(f"Configuration validation failed: {e}")
149
+
150
+ # Ensure data directory exists
151
+ config.data_dir.mkdir(parents=True, exist_ok=True)
152
+
153
+ # Ensure log directory exists
154
+ if config.log_file:
155
+ config.log_file.parent.mkdir(parents=True, exist_ok=True)
156
+
157
+ return config
158
+
159
+
160
+ def validate_config(config: Config) -> None:
161
+ """Validate configuration at startup.
162
+
163
+ Args:
164
+ config: Configuration object to validate
165
+
166
+ Raises:
167
+ ValueError: If configuration is invalid or required resources are unavailable
168
+ """
169
+ # Check data directory is writable
170
+ if not os.access(config.data_dir, os.W_OK):
171
+ raise ValueError(
172
+ f"Data directory {config.data_dir} is not writable. "
173
+ "Please check permissions."
174
+ )
175
+
176
+ # Check log directory is writable
177
+ if config.log_file and not os.access(config.log_file.parent, os.W_OK):
178
+ raise ValueError(
179
+ f"Log directory {config.log_file.parent} is not writable. "
180
+ "Please check permissions."
181
+ )
182
+
183
+ # Validate API key format (basic check)
184
+ if len(config.zhipu_api_key) < 10:
185
+ raise ValueError(
186
+ "ZHIPU_API_KEY appears to be invalid (too short). "
187
+ "Please check your API key."
188
+ )
189
+
190
+
191
+ # Global config instance (loaded on import)
192
+ _config: Optional[Config] = None
193
+
194
+
195
+ def get_config() -> Config:
196
+ """Get the global configuration instance.
197
+
198
+ Returns:
199
+ Config: The application configuration
200
+
201
+ Raises:
202
+ RuntimeError: If configuration has not been initialized
203
+ """
204
+ global _config
205
+ if _config is None:
206
+ raise RuntimeError(
207
+ "Configuration not initialized. Call init_config() first."
208
+ )
209
+ return _config
210
+
211
+
212
+ def init_config() -> Config:
213
+ """Initialize the global configuration.
214
+
215
+ This should be called once at application startup.
216
+
217
+ Returns:
218
+ Config: The initialized configuration
219
+
220
+ Raises:
221
+ ValueError: If configuration is invalid
222
+ """
223
+ global _config
224
+ _config = load_config()
225
+ validate_config(_config)
226
+ return _config
app/image_service.py ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Image Generation service for Voice Text Processor.
2
+
3
+ This module implements the ImageGenerationService class for generating
4
+ cat character images using the MiniMax Text-to-Image API.
5
+
6
+ Requirements: PRD - AI形象生成模块
7
+ """
8
+
9
+ import logging
10
+ import httpx
11
+ from typing import Optional, Dict, List
12
+ import time
13
+ import json
14
+ from pathlib import Path
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ImageGenerationError(Exception):
20
+ """Exception raised when image generation operations fail.
21
+
22
+ This exception is raised when the MiniMax API call fails,
23
+ such as due to network issues, API errors, or invalid responses.
24
+ """
25
+
26
+ def __init__(self, message: str = "图像生成服务不可用"):
27
+ """Initialize ImageGenerationError.
28
+
29
+ Args:
30
+ message: Error message describing the failure
31
+ """
32
+ super().__init__(message)
33
+ self.message = message
34
+
35
+
36
+ class ImageGenerationService:
37
+ """Service for generating cat character images using MiniMax API.
38
+
39
+ This service handles image generation by calling the MiniMax Text-to-Image API
40
+ to create healing-style cat illustrations based on user preferences
41
+ (color, personality, appearance).
42
+
43
+ Attributes:
44
+ api_key: MiniMax API key for authentication
45
+ group_id: MiniMax group ID for authentication
46
+ client: Async HTTP client for making API requests
47
+ api_url: MiniMax API endpoint URL
48
+ model: Model identifier (text-to-image-v2)
49
+
50
+ Requirements: PRD - AI形象生成模块
51
+ """
52
+
53
+ # 颜色映射
54
+ COLOR_MAPPING = {
55
+ "温暖粉": "soft pastel pink fur, rose-colored aesthetic",
56
+ "天空蓝": "light sky blue fur, serene blue atmosphere",
57
+ "薄荷绿": "mint green fur, fresh green ambiance",
58
+ "奶油黄": "cream yellow fur, warm golden glow",
59
+ "薰衣草紫": "lavender purple fur, gentle purple tones",
60
+ "珊瑚橙": "coral orange fur, warm peachy atmosphere",
61
+ "纯白": "pure white fur, clean minimalist aesthetic",
62
+ "浅灰": "light gray fur, soft neutral tones"
63
+ }
64
+
65
+ # 性格映射
66
+ PERSONALITY_MAPPING = {
67
+ "活泼": "big curious eyes, dynamic paw gesture, energetic aura, playful expression",
68
+ "温柔": "soft gentle eyes, calm posture, peaceful expression, caring demeanor",
69
+ "聪明": "intelligent eyes, thoughtful expression, wise appearance, attentive look",
70
+ "慵懒": "relaxed eyes, lounging posture, comfortable expression, laid-back vibe",
71
+ "勇敢": "confident eyes, strong posture, determined expression, courageous stance",
72
+ "害羞": "shy eyes, timid posture, gentle expression, reserved demeanor"
73
+ }
74
+
75
+ # 形象特征映射
76
+ APPEARANCE_MAPPING = {
77
+ "戴眼镜": "wearing tiny round glasses, scholarly look",
78
+ "戴帽子": "wearing a cute small hat, fashionable style",
79
+ "戴围巾": "wearing a cozy scarf, warm appearance",
80
+ "戴蝴蝶结": "wearing a cute bow tie, elegant look",
81
+ "无配饰": "natural appearance, simple and pure"
82
+ }
83
+
84
+ # 角色类型映射
85
+ ROLE_MAPPING = {
86
+ "陪伴式朋友": "friendly companion, approachable and warm",
87
+ "温柔照顾型长辈": "caring elder figure, nurturing and protective",
88
+ "引导型老师": "wise teacher figure, knowledgeable and patient"
89
+ }
90
+
91
+ # 系统底座提示词
92
+ BASE_PROMPT = (
93
+ "A masterpiece cute stylized cat illustration, {color} theme, "
94
+ "{personality} facial expression and posture, {appearance}. "
95
+ "{role}. Japanese watercolor style, clean minimalist background, "
96
+ "high quality, soft studio lighting, 4k, healing aesthetic, "
97
+ "adorable and heartwarming"
98
+ )
99
+
100
+ def __init__(self, api_key: str, group_id: Optional[str] = None):
101
+ """Initialize the image generation service.
102
+
103
+ Args:
104
+ api_key: MiniMax API key for authentication
105
+ group_id: MiniMax group ID (optional, for compatibility)
106
+ """
107
+ self.api_key = api_key
108
+ self.group_id = group_id # 保留但不使用
109
+ self.client = httpx.AsyncClient(timeout=120.0) # 图像生成需要更长时间
110
+ self.api_url = "https://api.minimaxi.com/v1/image_generation"
111
+ self.model = "image-01"
112
+
113
+ async def close(self):
114
+ """Close the HTTP client.
115
+
116
+ This should be called when the service is no longer needed
117
+ to properly clean up resources.
118
+ """
119
+ await self.client.aclose()
120
+
121
+ async def download_image(self, url: str, save_path: str) -> str:
122
+ """Download image from URL and save to local file.
123
+
124
+ Args:
125
+ url: Image URL to download
126
+ save_path: Local file path to save the image
127
+
128
+ Returns:
129
+ Absolute path to the saved image file
130
+
131
+ Raises:
132
+ ImageGenerationError: If download fails
133
+ """
134
+ try:
135
+ logger.info(f"Downloading image from: {url}")
136
+
137
+ # 创建保存目录(如果不存在)
138
+ save_path_obj = Path(save_path)
139
+ save_path_obj.parent.mkdir(parents=True, exist_ok=True)
140
+
141
+ # 下载图像
142
+ response = await self.client.get(url, timeout=60.0)
143
+
144
+ if response.status_code != 200:
145
+ error_msg = f"Failed to download image: HTTP {response.status_code}"
146
+ logger.error(error_msg)
147
+ raise ImageGenerationError(error_msg)
148
+
149
+ # 保存到文件
150
+ with open(save_path, 'wb') as f:
151
+ f.write(response.content)
152
+
153
+ abs_path = str(save_path_obj.absolute())
154
+ logger.info(f"Image saved to: {abs_path}")
155
+
156
+ return abs_path
157
+
158
+ except ImageGenerationError:
159
+ raise
160
+ except Exception as e:
161
+ error_msg = f"Failed to download image: {str(e)}"
162
+ logger.error(error_msg)
163
+ raise ImageGenerationError(error_msg)
164
+
165
+ def build_prompt(
166
+ self,
167
+ color: str = "温暖粉",
168
+ personality: str = "温柔",
169
+ appearance: str = "无配饰",
170
+ role: str = "陪伴式朋友"
171
+ ) -> str:
172
+ """Build the complete prompt for image generation.
173
+
174
+ Args:
175
+ color: Color preference (温暖粉/天空蓝/薄荷绿等)
176
+ personality: Personality trait (活泼/温柔/聪明等)
177
+ appearance: Appearance feature (戴眼镜/戴帽子等)
178
+ role: Character role (陪伴式朋友/温柔照顾型长辈等)
179
+
180
+ Returns:
181
+ Complete prompt string for CogView API
182
+ """
183
+ # 获取映射值,如果没有则使用默认值
184
+ color_desc = self.COLOR_MAPPING.get(color, self.COLOR_MAPPING["温暖粉"])
185
+ personality_desc = self.PERSONALITY_MAPPING.get(
186
+ personality,
187
+ self.PERSONALITY_MAPPING["温柔"]
188
+ )
189
+ appearance_desc = self.APPEARANCE_MAPPING.get(
190
+ appearance,
191
+ self.APPEARANCE_MAPPING["无配饰"]
192
+ )
193
+ role_desc = self.ROLE_MAPPING.get(
194
+ role,
195
+ self.ROLE_MAPPING["陪伴式朋友"]
196
+ )
197
+
198
+ # 构建完整提示词
199
+ prompt = self.BASE_PROMPT.format(
200
+ color=color_desc,
201
+ personality=personality_desc,
202
+ appearance=appearance_desc,
203
+ role=role_desc
204
+ )
205
+
206
+ logger.info(f"Generated prompt: {prompt[:100]}...")
207
+ return prompt
208
+
209
+ async def generate_image(
210
+ self,
211
+ color: str = "温暖粉",
212
+ personality: str = "温柔",
213
+ appearance: str = "无配饰",
214
+ role: str = "陪伴式朋友",
215
+ aspect_ratio: str = "1:1",
216
+ n: int = 1,
217
+ response_format: str = "url"
218
+ ) -> Dict[str, str]:
219
+ """Generate a cat character image using MiniMax API.
220
+
221
+ This method sends a request to the MiniMax API with the constructed
222
+ prompt and returns the generated image URL or base64 data.
223
+
224
+ Args:
225
+ color: Color preference
226
+ personality: Personality trait
227
+ appearance: Appearance feature
228
+ role: Character role
229
+ aspect_ratio: Image aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)
230
+ n: Number of images to generate (1-4)
231
+ response_format: Response format ("url" or "base64")
232
+
233
+ Returns:
234
+ Dictionary containing:
235
+ - url: Image URL (if response_format="url")
236
+ - data: Base64 image data (if response_format="base64")
237
+ - prompt: Used prompt
238
+ - task_id: Task ID from MiniMax
239
+
240
+ Raises:
241
+ ImageGenerationError: If API call fails or returns invalid response
242
+ """
243
+ try:
244
+ # 构建提示词
245
+ prompt = self.build_prompt(color, personality, appearance, role)
246
+
247
+ # 准备请求
248
+ headers = {
249
+ "Authorization": f"Bearer {self.api_key.strip()}",
250
+ "Content-Type": "application/json"
251
+ }
252
+
253
+ payload = {
254
+ "model": self.model,
255
+ "prompt": prompt,
256
+ "aspect_ratio": aspect_ratio,
257
+ "response_format": "url",
258
+ "n": n,
259
+ "prompt_optimizer": True
260
+ }
261
+
262
+ logger.info(
263
+ f"Calling MiniMax API for image generation. "
264
+ f"Aspect ratio: {aspect_ratio}, Count: {n}"
265
+ )
266
+ logger.debug(f"API URL: {self.api_url}")
267
+ logger.debug(f"API Key (first 20 chars): {self.api_key[:20]}...")
268
+ logger.debug(f"Payload: {json.dumps(payload, ensure_ascii=False)}")
269
+
270
+ # 发送请求
271
+ response = await self.client.post(
272
+ self.api_url,
273
+ headers=headers,
274
+ json=payload
275
+ )
276
+
277
+ # 检查响应状态
278
+ if response.status_code != 200:
279
+ error_msg = f"MiniMax API returned status {response.status_code}"
280
+ try:
281
+ error_detail = response.json()
282
+ error_msg += f": {json.dumps(error_detail, ensure_ascii=False)}"
283
+ except Exception:
284
+ error_msg += f": {response.text}"
285
+
286
+ logger.error(f"Image generation API call failed: {error_msg}")
287
+ logger.error(f"Request URL: {self.api_url}")
288
+ logger.error(f"Request headers: Authorization=Bearer {self.api_key[:20]}..., Content-Type=application/json")
289
+ logger.error(f"Request payload: {json.dumps(payload, ensure_ascii=False)}")
290
+ raise ImageGenerationError(f"图像生成服务不可用: {error_msg}")
291
+
292
+ # 解析响应
293
+ try:
294
+ result = response.json()
295
+ logger.info(f"API Response (full): {json.dumps(result, indent=2, ensure_ascii=False)}")
296
+ except Exception as e:
297
+ error_msg = f"Failed to parse MiniMax API response: {str(e)}"
298
+ logger.error(error_msg)
299
+ logger.error(f"Raw response text: {response.text}")
300
+ raise ImageGenerationError(f"图像生成服务不可用: 响应格式无效")
301
+
302
+ # 提取图像 URL
303
+ try:
304
+ # MiniMax 实际返回格式:
305
+ # {
306
+ # "id": "task_id",
307
+ # "data": {"image_urls": [...]},
308
+ # "metadata": {...},
309
+ # "base_resp": {"status_code": 0, "status_msg": "success"}
310
+ # }
311
+
312
+ # 先检查是否有 base_resp
313
+ if "base_resp" in result:
314
+ base_resp = result.get("base_resp", {})
315
+ status_code = base_resp.get("status_code", -1)
316
+ error_msg = base_resp.get("status_msg", "Unknown error")
317
+
318
+ # status_code = 0 表示成功
319
+ if status_code != 0:
320
+ logger.error(f"MiniMax API error: {status_code} - {error_msg}")
321
+ raise ImageGenerationError(f"图像生成失败: {error_msg}")
322
+
323
+ logger.info(f"MiniMax API success: {status_code} - {error_msg}")
324
+
325
+ # 提取 task_id(可能在 id 或 task_id 字段)
326
+ task_id = result.get("id") or result.get("task_id", "")
327
+
328
+ # 提取图像数据
329
+ if "data" in result:
330
+ data = result["data"]
331
+ logger.info(f"Data field keys: {list(data.keys()) if isinstance(data, dict) else 'not a dict'}")
332
+
333
+ if isinstance(data, dict):
334
+ # 尝试多个可能的字段名
335
+ urls = None
336
+ if "image_urls" in data:
337
+ urls = data["image_urls"]
338
+ logger.info("Found image_urls field")
339
+ elif "url" in data:
340
+ urls = data["url"]
341
+ logger.info("Found url field")
342
+
343
+ if urls:
344
+ # 如果只生成一张,返回单个 URL
345
+ image_url = urls[0] if n == 1 else urls
346
+ logger.info(f"Image generation successful. URLs: {urls}")
347
+
348
+ return {
349
+ "url": image_url,
350
+ "prompt": prompt,
351
+ "task_id": task_id,
352
+ "metadata": result.get("metadata", {})
353
+ }
354
+
355
+ # 如果到这里还没有返回,说明响应格式不符合预期
356
+ logger.error(f"Could not extract image URLs from response: {json.dumps(result, ensure_ascii=False)}")
357
+ raise ImageGenerationError("API 响应格式错误: 无法提取图像 URL")
358
+
359
+ except (KeyError, IndexError) as e:
360
+ error_msg = f"Invalid API response structure: {str(e)}, Response: {json.dumps(result, ensure_ascii=False)}"
361
+ logger.error(error_msg)
362
+ raise ImageGenerationError(f"图像生成服务不可用: 响应结构无效")
363
+
364
+ except ImageGenerationError:
365
+ # Re-raise ImageGenerationError as-is
366
+ raise
367
+
368
+ except httpx.TimeoutException as e:
369
+ error_msg = f"MiniMax API request timeout: {str(e)}"
370
+ logger.error(error_msg)
371
+ raise ImageGenerationError("图像生成服务不可用: 请求超时")
372
+
373
+ except httpx.RequestError as e:
374
+ error_msg = f"MiniMax API request failed: {str(e)}"
375
+ logger.error(error_msg)
376
+ raise ImageGenerationError(f"图像生成服务不可用: 网络错误")
377
+
378
+ except Exception as e:
379
+ error_msg = f"Unexpected error in image generation service: {str(e)}"
380
+ logger.error(error_msg, exc_info=True)
381
+ raise ImageGenerationError(f"图像生成服务不可用: {str(e)}")
382
+
383
+ async def generate_multiple_images(
384
+ self,
385
+ color: str = "温暖粉",
386
+ personality: str = "温柔",
387
+ appearance: str = "无配饰",
388
+ role: str = "陪伴式朋友",
389
+ count: int = 3,
390
+ aspect_ratio: str = "1:1"
391
+ ) -> List[Dict[str, str]]:
392
+ """Generate multiple cat character images.
393
+
394
+ This method generates multiple images with the same parameters,
395
+ allowing users to choose their favorite one.
396
+
397
+ Args:
398
+ color: Color preference
399
+ personality: Personality trait
400
+ appearance: Appearance feature
401
+ role: Character role
402
+ count: Number of images to generate (1-4)
403
+ aspect_ratio: Image aspect ratio
404
+
405
+ Returns:
406
+ List of dictionaries, each containing url, prompt, and task_id
407
+
408
+ Raises:
409
+ ImageGenerationError: If any API call fails
410
+ """
411
+ if count < 1 or count > 4:
412
+ raise ValueError("Count must be between 1 and 4")
413
+
414
+ try:
415
+ # MiniMax 支持一次生成多张图像
416
+ result = await self.generate_image(
417
+ color=color,
418
+ personality=personality,
419
+ appearance=appearance,
420
+ role=role,
421
+ aspect_ratio=aspect_ratio,
422
+ n=count
423
+ )
424
+
425
+ # 将结果转换为列表格式
426
+ urls = result['url'] if isinstance(result['url'], list) else [result['url']]
427
+
428
+ images = []
429
+ for i, url in enumerate(urls):
430
+ images.append({
431
+ "url": url,
432
+ "prompt": result['prompt'],
433
+ "task_id": result['task_id'],
434
+ "index": i
435
+ })
436
+
437
+ return images
438
+
439
+ except ImageGenerationError as e:
440
+ logger.error(f"Failed to generate images: {e.message}")
441
+ raise
app/logging_config.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Logging configuration for Voice Text Processor.
2
+
3
+ This module sets up the logging system with proper formatting, levels,
4
+ and file output. It also includes a filter to prevent sensitive information
5
+ from being logged.
6
+
7
+ Requirements: 10.5, 9.5
8
+ """
9
+
10
+ import logging
11
+ import re
12
+ from typing import Optional
13
+ from pathlib import Path
14
+ from contextvars import ContextVar
15
+
16
+
17
+ # Context variable to store request_id across async calls
18
+ request_id_var: ContextVar[Optional[str]] = ContextVar('request_id', default=None)
19
+
20
+
21
+ class RequestIdFilter(logging.Filter):
22
+ """Filter to add request_id to log records.
23
+
24
+ This filter adds the request_id from context to each log record,
25
+ making it available in the log format.
26
+
27
+ Requirements: 9.5
28
+ """
29
+
30
+ def filter(self, record: logging.LogRecord) -> bool:
31
+ """Add request_id to log record.
32
+
33
+ Args:
34
+ record: Log record to enhance
35
+
36
+ Returns:
37
+ bool: Always True (we modify but don't reject records)
38
+ """
39
+ # Get request_id from context, default to empty string if not set
40
+ record.request_id = request_id_var.get() or '-'
41
+ return True
42
+
43
+
44
+ class SensitiveDataFilter(logging.Filter):
45
+ """Filter to remove sensitive information from log records.
46
+
47
+ This filter masks API keys, passwords, and other sensitive data
48
+ to prevent them from appearing in logs.
49
+
50
+ Requirements: 10.5
51
+ """
52
+
53
+ # Patterns to detect and mask sensitive data
54
+ SENSITIVE_PATTERNS = [
55
+ # API keys (various formats)
56
+ (re.compile(r'(api[_-]?key["\s:=]+)([a-zA-Z0-9_-]{10,})', re.IGNORECASE), r'\1***REDACTED***'),
57
+ (re.compile(r'(zhipu[_-]?api[_-]?key["\s:=]+)([a-zA-Z0-9_-]{10,})', re.IGNORECASE), r'\1***REDACTED***'),
58
+ # Bearer tokens
59
+ (re.compile(r'(bearer\s+)([a-zA-Z0-9_-]{10,})', re.IGNORECASE), r'\1***REDACTED***'),
60
+ # Passwords
61
+ (re.compile(r'(password["\s:=]+)([^\s"]+)', re.IGNORECASE), r'\1***REDACTED***'),
62
+ # Authorization headers (capture the whole value)
63
+ (re.compile(r'(authorization["\s:=]+)([^\s"]+)', re.IGNORECASE), r'\1***REDACTED***'),
64
+ ]
65
+
66
+ def filter(self, record: logging.LogRecord) -> bool:
67
+ """Filter log record to mask sensitive data.
68
+
69
+ Args:
70
+ record: Log record to filter
71
+
72
+ Returns:
73
+ bool: Always True (we modify but don't reject records)
74
+ """
75
+ # Mask sensitive data in the message
76
+ if hasattr(record, 'msg') and isinstance(record.msg, str):
77
+ record.msg = self._mask_sensitive_data(record.msg)
78
+
79
+ # Mask sensitive data in arguments
80
+ if hasattr(record, 'args') and record.args:
81
+ if isinstance(record.args, dict):
82
+ record.args = {
83
+ k: self._mask_sensitive_data(str(v)) if isinstance(v, str) else v
84
+ for k, v in record.args.items()
85
+ }
86
+ elif isinstance(record.args, tuple):
87
+ record.args = tuple(
88
+ self._mask_sensitive_data(str(arg)) if isinstance(arg, str) else arg
89
+ for arg in record.args
90
+ )
91
+
92
+ return True
93
+
94
+ def _mask_sensitive_data(self, text: str) -> str:
95
+ """Mask sensitive data in text using regex patterns.
96
+
97
+ Args:
98
+ text: Text to mask
99
+
100
+ Returns:
101
+ str: Text with sensitive data masked
102
+ """
103
+ for pattern, replacement in self.SENSITIVE_PATTERNS:
104
+ text = pattern.sub(replacement, text)
105
+ return text
106
+
107
+
108
+ def setup_logging(
109
+ log_level: str = "INFO",
110
+ log_file: Optional[Path] = None,
111
+ log_format: Optional[str] = None
112
+ ) -> None:
113
+ """Set up logging configuration for the application.
114
+
115
+ Args:
116
+ log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
117
+ log_file: Optional path to log file. If None, logs only to console.
118
+ log_format: Optional custom log format string
119
+
120
+ Requirements: 10.5, 9.5
121
+ """
122
+ # Default log format with request_id, timestamp, level, and message
123
+ if log_format is None:
124
+ log_format = "[%(asctime)s] [%(levelname)s] [%(request_id)s] [%(name)s] %(message)s"
125
+
126
+ # Date format
127
+ date_format = "%Y-%m-%d %H:%M:%S"
128
+
129
+ # Create formatter
130
+ formatter = logging.Formatter(log_format, datefmt=date_format)
131
+
132
+ # Get root logger
133
+ root_logger = logging.getLogger()
134
+ root_logger.setLevel(getattr(logging, log_level.upper()))
135
+
136
+ # Remove existing handlers
137
+ root_logger.handlers.clear()
138
+
139
+ # Add filters
140
+ request_id_filter = RequestIdFilter()
141
+ sensitive_filter = SensitiveDataFilter()
142
+
143
+ # Console handler
144
+ console_handler = logging.StreamHandler()
145
+ console_handler.setFormatter(formatter)
146
+ console_handler.addFilter(request_id_filter)
147
+ console_handler.addFilter(sensitive_filter)
148
+ root_logger.addHandler(console_handler)
149
+
150
+ # File handler (if log file specified)
151
+ if log_file:
152
+ file_handler = logging.FileHandler(log_file, encoding="utf-8")
153
+ file_handler.setFormatter(formatter)
154
+ file_handler.addFilter(request_id_filter)
155
+ file_handler.addFilter(sensitive_filter)
156
+ root_logger.addHandler(file_handler)
157
+
158
+ # Log startup message
159
+ logger = logging.getLogger(__name__)
160
+ logger.info(f"Logging initialized at level {log_level}")
161
+ if log_file:
162
+ logger.info(f"Logging to file: {log_file}")
163
+
164
+
165
+ def get_logger(name: str) -> logging.Logger:
166
+ """Get a logger instance for a module.
167
+
168
+ Args:
169
+ name: Logger name (typically __name__)
170
+
171
+ Returns:
172
+ logging.Logger: Logger instance
173
+ """
174
+ return logging.getLogger(name)
175
+
176
+
177
+ def set_request_id(request_id: str) -> None:
178
+ """Set the request_id in the current context.
179
+
180
+ This should be called at the beginning of each request to ensure
181
+ all log messages include the request_id.
182
+
183
+ Args:
184
+ request_id: Unique identifier for the request
185
+
186
+ Requirements: 9.5
187
+ """
188
+ request_id_var.set(request_id)
189
+
190
+
191
+ def clear_request_id() -> None:
192
+ """Clear the request_id from the current context.
193
+
194
+ This should be called at the end of each request to clean up.
195
+ """
196
+ request_id_var.set(None)
app/main.py ADDED
@@ -0,0 +1,1132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Main FastAPI application for Voice Text Processor.
2
+
3
+ This module initializes the FastAPI application, sets up configuration,
4
+ logging, and defines the application lifecycle.
5
+
6
+ Requirements: 10.1, 10.2, 10.3, 10.4, 10.5
7
+ """
8
+
9
+ import logging
10
+ import uuid
11
+ from contextlib import asynccontextmanager
12
+ from datetime import datetime
13
+ from typing import Optional
14
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
15
+ from fastapi.responses import JSONResponse
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.staticfiles import StaticFiles
18
+
19
+ from app.config import init_config, get_config
20
+ from app.logging_config import setup_logging, set_request_id, clear_request_id
21
+ from app.models import ProcessResponse, RecordData, ParsedData
22
+ from app.storage import StorageService, StorageError
23
+ from app.asr_service import ASRService, ASRServiceError
24
+ from app.semantic_parser import SemanticParserService, SemanticParserError
25
+
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ @asynccontextmanager
31
+ async def lifespan(app: FastAPI):
32
+ """Application lifespan manager.
33
+
34
+ This handles startup and shutdown events for the application.
35
+ On startup, it initializes configuration and logging.
36
+
37
+ Requirements: 10.4 - Startup configuration validation
38
+ """
39
+ # Startup
40
+ logger.info("Starting Voice Text Processor application...")
41
+
42
+ try:
43
+ # Initialize configuration (will raise ValueError if invalid)
44
+ config = init_config()
45
+ logger.info("Configuration loaded and validated successfully")
46
+
47
+ # Setup logging with config values
48
+ setup_logging(
49
+ log_level=config.log_level,
50
+ log_file=config.log_file
51
+ )
52
+ logger.info("Logging system configured")
53
+
54
+ # Log configuration (without sensitive data)
55
+ logger.info(f"Data directory: {config.data_dir}")
56
+ logger.info(f"Max audio size: {config.max_audio_size} bytes")
57
+ logger.info(f"Log level: {config.log_level}")
58
+
59
+ except ValueError as e:
60
+ # Configuration validation failed - refuse to start
61
+ logger.error(f"Configuration validation failed: {e}")
62
+ logger.error("Application startup aborted due to configuration errors")
63
+ raise RuntimeError(f"Configuration error: {e}") from e
64
+ except Exception as e:
65
+ logger.error(f"Unexpected error during startup: {e}", exc_info=True)
66
+ raise RuntimeError(f"Startup error: {e}") from e
67
+
68
+ logger.info("Application startup complete")
69
+
70
+ yield
71
+
72
+ # Shutdown
73
+ logger.info("Shutting down Voice Text Processor application...")
74
+ logger.info("Application shutdown complete")
75
+
76
+
77
+ # Create FastAPI application
78
+ app = FastAPI(
79
+ title="Voice Text Processor",
80
+ description="治愈系记录助手后端核心模块 - 语音和文本处理服务",
81
+ version="1.0.0",
82
+ lifespan=lifespan
83
+ )
84
+
85
+ # Add CORS middleware
86
+ app.add_middleware(
87
+ CORSMiddleware,
88
+ allow_origins=[
89
+ "http://localhost:5173",
90
+ "http://localhost:3000",
91
+ "http://172.18.16.245:5173", # 允许从电脑 IP 访问
92
+ "*" # 开发环境允许所有来源(生产环境应该限制)
93
+ ],
94
+ allow_credentials=True,
95
+ allow_methods=["*"],
96
+ allow_headers=["*"],
97
+ )
98
+
99
+ # Mount static files for generated images
100
+ from pathlib import Path
101
+ from fastapi import Request
102
+
103
+ generated_images_dir = Path("generated_images")
104
+ generated_images_dir.mkdir(exist_ok=True)
105
+ app.mount("/generated_images", StaticFiles(directory="generated_images"), name="generated_images")
106
+
107
+
108
+ def get_base_url(request: Request) -> str:
109
+ """获取请求的基础 URL(支持局域网访问)"""
110
+ # 使用请求的 host 来构建 URL
111
+ scheme = request.url.scheme # http 或 https
112
+ host = request.headers.get("host", "localhost:8000")
113
+ return f"{scheme}://{host}"
114
+
115
+
116
+ @app.get("/api/status")
117
+ async def root():
118
+ """API status endpoint."""
119
+ return {
120
+ "service": "Voice Text Processor",
121
+ "status": "running",
122
+ "version": "1.0.0"
123
+ }
124
+
125
+
126
+ @app.get("/health")
127
+ async def health_check():
128
+ """Health check endpoint."""
129
+ try:
130
+ config = get_config()
131
+ return {
132
+ "status": "healthy",
133
+ "data_dir": str(config.data_dir),
134
+ "max_audio_size": config.max_audio_size
135
+ }
136
+ except Exception as e:
137
+ logger.error(f"Health check failed: {e}")
138
+ return JSONResponse(
139
+ status_code=503,
140
+ content={
141
+ "status": "unhealthy",
142
+ "error": str(e)
143
+ }
144
+ )
145
+
146
+
147
+ # Validation error class
148
+ class ValidationError(Exception):
149
+ """Exception raised when input validation fails.
150
+
151
+ Requirements: 1.3, 8.5, 9.1
152
+ """
153
+ def __init__(self, message: str):
154
+ super().__init__(message)
155
+ self.message = message
156
+
157
+
158
+ # Supported audio formats
159
+ SUPPORTED_AUDIO_FORMATS = {".mp3", ".wav", ".m4a", ".webm"}
160
+
161
+
162
+ @app.post("/api/process", response_model=ProcessResponse)
163
+ async def process_input(
164
+ audio: Optional[UploadFile] = File(None),
165
+ text: Optional[str] = Form(None)
166
+ ) -> ProcessResponse:
167
+ """Process user input (audio or text) and extract structured data.
168
+
169
+ This endpoint accepts either an audio file or text content, performs
170
+ speech recognition (if audio), semantic parsing, and stores the results.
171
+
172
+ Args:
173
+ audio: Audio file (multipart/form-data) in mp3, wav, or m4a format
174
+ text: Text content (application/json) in UTF-8 encoding
175
+
176
+ Returns:
177
+ ProcessResponse containing record_id, timestamp, mood, inspirations, todos
178
+
179
+ Raises:
180
+ HTTPException: With appropriate status code and error message
181
+
182
+ Requirements: 1.1, 1.2, 1.3, 7.7, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 9.1, 9.2, 9.3, 9.4, 9.5
183
+ """
184
+ request_id = str(uuid.uuid4())
185
+ timestamp = datetime.utcnow().isoformat() + "Z"
186
+
187
+ # Set request_id in logging context
188
+ set_request_id(request_id)
189
+
190
+ logger.info(f"Processing request - audio: {audio is not None}, text: {text is not None}")
191
+
192
+ try:
193
+ # Input validation
194
+ if audio is None and text is None:
195
+ raise ValidationError("请提供音频文件或文本内容")
196
+
197
+ if audio is not None and text is not None:
198
+ raise ValidationError("请只提供音频文件或文本内容中的一种")
199
+
200
+ # Get configuration
201
+ config = get_config()
202
+
203
+ # Initialize services
204
+ storage_service = StorageService(str(config.data_dir))
205
+ asr_service = ASRService(config.zhipu_api_key)
206
+ parser_service = SemanticParserService(config.zhipu_api_key)
207
+
208
+ original_text = ""
209
+ input_type = "text"
210
+
211
+ try:
212
+ # Handle audio input
213
+ if audio is not None:
214
+ input_type = "audio"
215
+
216
+ # Validate audio format
217
+ filename = audio.filename or "audio"
218
+ file_ext = "." + filename.split(".")[-1].lower() if "." in filename else ""
219
+
220
+ if file_ext not in SUPPORTED_AUDIO_FORMATS:
221
+ raise ValidationError(
222
+ f"不支持的音频格式: {file_ext}. "
223
+ f"支持的格式: {', '.join(SUPPORTED_AUDIO_FORMATS)}"
224
+ )
225
+
226
+ # Read audio file
227
+ audio_content = await audio.read()
228
+
229
+ # Validate audio file size
230
+ if len(audio_content) > config.max_audio_size:
231
+ raise ValidationError(
232
+ f"音频文件过大: {len(audio_content)} bytes. "
233
+ f"最大允许: {config.max_audio_size} bytes"
234
+ )
235
+
236
+ logger.info(
237
+ f"Audio file received: {filename}, "
238
+ f"size: {len(audio_content)} bytes"
239
+ )
240
+
241
+ # Transcribe audio to text
242
+ try:
243
+ original_text = await asr_service.transcribe(audio_content, filename)
244
+ logger.info(
245
+ f"ASR transcription successful. "
246
+ f"Text length: {len(original_text)}"
247
+ )
248
+ except ASRServiceError as e:
249
+ logger.error(
250
+ f"ASR service error: {e.message}",
251
+ exc_info=True
252
+ )
253
+ raise
254
+
255
+ # Handle text input
256
+ else:
257
+ # Validate text encoding (UTF-8)
258
+ # Accept whitespace-only text as valid UTF-8, but reject None or empty string
259
+ if text is None or text == "":
260
+ raise ValidationError("文本内容不能为空")
261
+
262
+ original_text = text
263
+ logger.info(
264
+ f"Text input received. "
265
+ f"Length: {len(original_text)}"
266
+ )
267
+
268
+ # Perform semantic parsing
269
+ try:
270
+ parsed_data = await parser_service.parse(original_text)
271
+ logger.info(
272
+ f"Semantic parsing successful. "
273
+ f"Mood: {'present' if parsed_data.mood else 'none'}, "
274
+ f"Inspirations: {len(parsed_data.inspirations)}, "
275
+ f"Todos: {len(parsed_data.todos)}"
276
+ )
277
+ except SemanticParserError as e:
278
+ logger.error(
279
+ f"Semantic parser error: {e.message}",
280
+ exc_info=True
281
+ )
282
+ raise
283
+
284
+ # Generate record ID and timestamp
285
+ record_id = str(uuid.uuid4())
286
+ record_timestamp = datetime.utcnow().isoformat() + "Z"
287
+
288
+ # Create record data
289
+ record = RecordData(
290
+ record_id=record_id,
291
+ timestamp=record_timestamp,
292
+ input_type=input_type,
293
+ original_text=original_text,
294
+ parsed_data=parsed_data
295
+ )
296
+
297
+ # Save to storage
298
+ try:
299
+ storage_service.save_record(record)
300
+ logger.info(f"Record saved: {record_id}")
301
+
302
+ # Save mood if present
303
+ if parsed_data.mood:
304
+ storage_service.append_mood(
305
+ parsed_data.mood,
306
+ record_id,
307
+ record_timestamp
308
+ )
309
+ logger.info(f"Mood data saved")
310
+
311
+ # Save inspirations if present
312
+ if parsed_data.inspirations:
313
+ storage_service.append_inspirations(
314
+ parsed_data.inspirations,
315
+ record_id,
316
+ record_timestamp
317
+ )
318
+ logger.info(
319
+ f"{len(parsed_data.inspirations)} "
320
+ f"inspiration(s) saved"
321
+ )
322
+
323
+ # Save todos if present
324
+ if parsed_data.todos:
325
+ storage_service.append_todos(
326
+ parsed_data.todos,
327
+ record_id,
328
+ record_timestamp
329
+ )
330
+ logger.info(
331
+ f"{len(parsed_data.todos)} "
332
+ f"todo(s) saved"
333
+ )
334
+
335
+ except StorageError as e:
336
+ logger.error(
337
+ f"Storage error: {str(e)}",
338
+ exc_info=True
339
+ )
340
+ raise
341
+
342
+ # Build success response
343
+ response = ProcessResponse(
344
+ record_id=record_id,
345
+ timestamp=record_timestamp,
346
+ mood=parsed_data.mood,
347
+ inspirations=parsed_data.inspirations,
348
+ todos=parsed_data.todos
349
+ )
350
+
351
+ logger.info(f"Request processed successfully")
352
+
353
+ return response
354
+
355
+ finally:
356
+ # Clean up services
357
+ await asr_service.close()
358
+ await parser_service.close()
359
+ # Clear request_id from context
360
+ clear_request_id()
361
+
362
+ except ValidationError as e:
363
+ # Input validation error - HTTP 400
364
+ logger.warning(
365
+ f"Validation error: {e.message}",
366
+ exc_info=True
367
+ )
368
+ clear_request_id()
369
+ return JSONResponse(
370
+ status_code=400,
371
+ content={
372
+ "error": e.message,
373
+ "timestamp": timestamp
374
+ }
375
+ )
376
+
377
+ except ASRServiceError as e:
378
+ # ASR service error - HTTP 500
379
+ logger.error(
380
+ f"ASR service unavailable: {e.message}",
381
+ exc_info=True
382
+ )
383
+ clear_request_id()
384
+ return JSONResponse(
385
+ status_code=500,
386
+ content={
387
+ "error": "语音识别服务不可用",
388
+ "detail": e.message,
389
+ "timestamp": timestamp
390
+ }
391
+ )
392
+
393
+ except SemanticParserError as e:
394
+ # Semantic parser error - HTTP 500
395
+ logger.error(
396
+ f"Semantic parser unavailable: {e.message}",
397
+ exc_info=True
398
+ )
399
+ clear_request_id()
400
+ return JSONResponse(
401
+ status_code=500,
402
+ content={
403
+ "error": "语义解析服务不可用",
404
+ "detail": e.message,
405
+ "timestamp": timestamp
406
+ }
407
+ )
408
+
409
+ except StorageError as e:
410
+ # Storage error - HTTP 500
411
+ logger.error(
412
+ f"Storage error: {str(e)}",
413
+ exc_info=True
414
+ )
415
+ clear_request_id()
416
+ return JSONResponse(
417
+ status_code=500,
418
+ content={
419
+ "error": "数据存储失败",
420
+ "detail": str(e),
421
+ "timestamp": timestamp
422
+ }
423
+ )
424
+
425
+ except Exception as e:
426
+ # Unexpected error - HTTP 500
427
+ logger.error(
428
+ f"Unexpected error: {str(e)}",
429
+ exc_info=True
430
+ )
431
+ clear_request_id()
432
+ return JSONResponse(
433
+ status_code=500,
434
+ content={
435
+ "error": "服务器内部错误",
436
+ "detail": str(e),
437
+ "timestamp": timestamp
438
+ }
439
+ )
440
+
441
+
442
+ @app.get("/api/records")
443
+ async def get_records():
444
+ """Get all records."""
445
+ try:
446
+ config = get_config()
447
+ storage_service = StorageService(str(config.data_dir))
448
+ records = storage_service._read_json_file(storage_service.records_file)
449
+ return {"records": records}
450
+ except Exception as e:
451
+ logger.error(f"Failed to get records: {e}")
452
+ return JSONResponse(
453
+ status_code=500,
454
+ content={"error": str(e)}
455
+ )
456
+
457
+
458
+ @app.get("/api/moods")
459
+ async def get_moods():
460
+ """Get all moods from both moods.json and records.json."""
461
+ try:
462
+ config = get_config()
463
+ storage_service = StorageService(str(config.data_dir))
464
+
465
+ # 1. 读取 moods.json
466
+ moods_from_file = storage_service._read_json_file(storage_service.moods_file)
467
+ logger.info(f"Loaded {len(moods_from_file)} moods from moods.json")
468
+
469
+ # 2. 从 records.json 中提取心情数据
470
+ records = storage_service._read_json_file(storage_service.records_file)
471
+ moods_from_records = []
472
+
473
+ for record in records:
474
+ # 检查 parsed_data 中是否有 mood
475
+ parsed_data = record.get("parsed_data", {})
476
+ mood_data = parsed_data.get("mood")
477
+
478
+ if mood_data and mood_data.get("type"):
479
+ # 构造心情对象
480
+ mood_obj = {
481
+ "record_id": record["record_id"],
482
+ "timestamp": record["timestamp"],
483
+ "type": mood_data.get("type"),
484
+ "intensity": mood_data.get("intensity", 5),
485
+ "keywords": mood_data.get("keywords", []),
486
+ "original_text": record.get("original_text", "") # 添加原文
487
+ }
488
+ moods_from_records.append(mood_obj)
489
+
490
+ logger.info(f"Extracted {len(moods_from_records)} moods from records.json")
491
+
492
+ # 3. 合并两个来源的心情数据(去重,优先使用 records 中的数据)
493
+ # 同时需要补充 moods.json 中缺失的 original_text
494
+ mood_dict = {}
495
+
496
+ # 先添加 moods.json 中的数据
497
+ for mood in moods_from_file:
498
+ mood_dict[mood["record_id"]] = mood
499
+ # 如果没有 original_text,设置为空字符串
500
+ if "original_text" not in mood:
501
+ mood["original_text"] = ""
502
+
503
+ # 再添加/覆盖 records.json 中的数据(包含 original_text)
504
+ for mood in moods_from_records:
505
+ mood_dict[mood["record_id"]] = mood
506
+
507
+ # 转换为列表并按时间排序(最新的在前)
508
+ all_moods = list(mood_dict.values())
509
+ all_moods.sort(key=lambda x: x["timestamp"], reverse=True)
510
+
511
+ logger.info(f"Total unique moods: {len(all_moods)}")
512
+
513
+ return {"moods": all_moods}
514
+ except Exception as e:
515
+ logger.error(f"Failed to get moods: {e}", exc_info=True)
516
+ return JSONResponse(
517
+ status_code=500,
518
+ content={"error": str(e)}
519
+ )
520
+
521
+
522
+ @app.get("/api/inspirations")
523
+ async def get_inspirations():
524
+ """Get all inspirations."""
525
+ try:
526
+ config = get_config()
527
+ storage_service = StorageService(str(config.data_dir))
528
+ inspirations = storage_service._read_json_file(storage_service.inspirations_file)
529
+ return {"inspirations": inspirations}
530
+ except Exception as e:
531
+ logger.error(f"Failed to get inspirations: {e}")
532
+ return JSONResponse(
533
+ status_code=500,
534
+ content={"error": str(e)}
535
+ )
536
+
537
+
538
+ @app.get("/api/todos")
539
+ async def get_todos():
540
+ """Get all todos."""
541
+ try:
542
+ config = get_config()
543
+ storage_service = StorageService(str(config.data_dir))
544
+ todos = storage_service._read_json_file(storage_service.todos_file)
545
+ return {"todos": todos}
546
+ except Exception as e:
547
+ logger.error(f"Failed to get todos: {e}")
548
+ return JSONResponse(
549
+ status_code=500,
550
+ content={"error": str(e)}
551
+ )
552
+
553
+
554
+ @app.patch("/api/todos/{todo_id}")
555
+ async def update_todo(todo_id: str, status: str = Form(...)):
556
+ """Update todo status."""
557
+ try:
558
+ config = get_config()
559
+ storage_service = StorageService(str(config.data_dir))
560
+ todos = storage_service._read_json_file(storage_service.todos_file)
561
+
562
+ # Find and update todo
563
+ updated = False
564
+ for todo in todos:
565
+ if todo.get("record_id") == todo_id or str(hash(todo.get("task", ""))) == todo_id:
566
+ todo["status"] = status
567
+ updated = True
568
+ break
569
+
570
+ if not updated:
571
+ return JSONResponse(
572
+ status_code=404,
573
+ content={"error": "Todo not found"}
574
+ )
575
+
576
+ storage_service._write_json_file(storage_service.todos_file, todos)
577
+ return {"success": True}
578
+ except Exception as e:
579
+ logger.error(f"Failed to update todo: {e}")
580
+ return JSONResponse(
581
+ status_code=500,
582
+ content={"error": str(e)}
583
+ )
584
+
585
+
586
+ @app.post("/api/chat")
587
+ async def chat_with_ai(text: str = Form(...)):
588
+ """Chat with AI assistant using RAG with records.json as knowledge base.
589
+
590
+ This endpoint provides conversational AI that has context about the user's
591
+ previous records, moods, inspirations, and todos.
592
+ """
593
+ try:
594
+ config = get_config()
595
+ storage_service = StorageService(str(config.data_dir))
596
+
597
+ # Load user's records as RAG knowledge base
598
+ records = storage_service._read_json_file(storage_service.records_file)
599
+
600
+ # Build context from recent records (last 10)
601
+ recent_records = records[-10:] if len(records) > 10 else records
602
+ context_parts = []
603
+
604
+ for record in recent_records:
605
+ original_text = record.get('original_text', '')
606
+ timestamp = record.get('timestamp', '')
607
+
608
+ # Add parsed data context
609
+ parsed_data = record.get('parsed_data', {})
610
+ mood = parsed_data.get('mood')
611
+ inspirations = parsed_data.get('inspirations', [])
612
+ todos = parsed_data.get('todos', [])
613
+
614
+ context_entry = f"[{timestamp}] 用户说: {original_text}"
615
+
616
+ if mood:
617
+ context_entry += f"\n情绪: {mood.get('type')} (强度: {mood.get('intensity')})"
618
+
619
+ if inspirations:
620
+ ideas = [insp.get('core_idea') for insp in inspirations]
621
+ context_entry += f"\n灵感: {', '.join(ideas)}"
622
+
623
+ if todos:
624
+ tasks = [todo.get('task') for todo in todos]
625
+ context_entry += f"\n待办: {', '.join(tasks)}"
626
+
627
+ context_parts.append(context_entry)
628
+
629
+ # Build system prompt with context
630
+ context_text = "\n\n".join(context_parts) if context_parts else "暂无历史记录"
631
+
632
+ system_prompt = f"""你是一个温柔、善解人意的AI陪伴助手。你的名字叫小喵。
633
+ 你会用温暖、治愈的语气和用户聊天,给予他们情感支持和陪伴。
634
+ 回复要简短、自然、有温度。
635
+
636
+ 你可以参考用户的历史记录来提供更贴心的回复:
637
+
638
+ {context_text}
639
+
640
+ 请基于这些背景信息,用温暖、理解的语气回复用户。如果用户提到之前的事情,你可以自然地关联起来。"""
641
+
642
+ try:
643
+ import httpx
644
+
645
+ # 增加超时时间,添加重试逻辑
646
+ async with httpx.AsyncClient(timeout=60.0) as client:
647
+ response = await client.post(
648
+ "https://open.bigmodel.cn/api/paas/v4/chat/completions",
649
+ headers={
650
+ "Authorization": f"Bearer {config.zhipu_api_key}",
651
+ "Content-Type": "application/json"
652
+ },
653
+ json={
654
+ "model": "glm-4-flash",
655
+ "messages": [
656
+ {
657
+ "role": "system",
658
+ "content": system_prompt
659
+ },
660
+ {
661
+ "role": "user",
662
+ "content": text
663
+ }
664
+ ],
665
+ "temperature": 0.8,
666
+ "top_p": 0.9
667
+ }
668
+ )
669
+
670
+ if response.status_code == 200:
671
+ result = response.json()
672
+ ai_response = result.get("choices", [{}])[0].get("message", {}).get("content", "")
673
+ logger.info(f"AI chat successful with RAG context")
674
+ return {"response": ai_response}
675
+ else:
676
+ logger.error(f"AI chat failed: {response.status_code} {response.text}")
677
+ return {"response": "抱歉,我现在有点累了,稍后再聊好吗?"}
678
+
679
+ except httpx.TimeoutException:
680
+ logger.error(f"AI API timeout")
681
+ return {"response": "抱歉,网络有点慢,请稍后再试~"}
682
+ except httpx.ConnectError:
683
+ logger.error(f"AI API connection error")
684
+ return {"response": "抱歉,无法连接到AI服务,请检查网络连接~"}
685
+ except Exception as e:
686
+ logger.error(f"AI API call error: {e}")
687
+ return {"response": "抱歉,我现在有点累了,稍后再聊好吗?"}
688
+
689
+ except Exception as e:
690
+ logger.error(f"Chat error: {e}")
691
+ return {"response": "抱歉,我现在有点累了,稍后再聊好吗?"}
692
+
693
+
694
+ @app.get("/api/user/config")
695
+ async def get_user_config(request: Request):
696
+ """Get user configuration including character image."""
697
+ try:
698
+ from app.user_config import UserConfig
699
+ from pathlib import Path
700
+ import os
701
+
702
+ config = get_config()
703
+ user_config = UserConfig(str(config.data_dir))
704
+ user_data = user_config.load_config()
705
+
706
+ base_url = get_base_url(request)
707
+
708
+ # 如果没有保存的图片,尝试加载默认形象或最新的本地图���
709
+ if not user_data.get('character', {}).get('image_url'):
710
+ generated_images_dir = Path("generated_images")
711
+ default_image = generated_images_dir / "default_character.jpeg"
712
+
713
+ # 优先使用默认形象
714
+ if default_image.exists():
715
+ logger.info("Loading default character image")
716
+ user_config.save_character_image(
717
+ image_url=str(default_image),
718
+ prompt="默认治愈系小猫形象",
719
+ preferences={
720
+ "color": "薰衣草紫",
721
+ "personality": "温柔",
722
+ "appearance": "无配饰",
723
+ "role": "陪伴式朋友"
724
+ }
725
+ )
726
+ user_data = user_config.load_config()
727
+ logger.info("Default character image loaded successfully")
728
+
729
+ # 如果没有默认形象,尝试加载最新的本地图片
730
+ elif generated_images_dir.exists():
731
+ # 获取所有图片文件
732
+ image_files = list(generated_images_dir.glob("character_*.jpeg"))
733
+ if image_files:
734
+ # 按修改时间排序,获取最新的
735
+ latest_image = max(image_files, key=lambda p: p.stat().st_mtime)
736
+
737
+ # 构建 URL 路径(使用动态 base_url)
738
+ image_url = f"{base_url}/generated_images/{latest_image.name}"
739
+
740
+ # 从文件名提取偏好设置
741
+ # 格式: character_颜色_性格_时间戳.jpeg
742
+ parts = latest_image.stem.split('_')
743
+ if len(parts) >= 3:
744
+ color = parts[1]
745
+ personality = parts[2]
746
+
747
+ # 更新配置
748
+ user_config.save_character_image(
749
+ image_url=str(latest_image),
750
+ prompt=f"Character with {color} and {personality}",
751
+ preferences={
752
+ "color": color,
753
+ "personality": personality,
754
+ "appearance": "无配饰",
755
+ "role": "陪伴式朋友"
756
+ }
757
+ )
758
+
759
+ # 重新加载配置
760
+ user_data = user_config.load_config()
761
+
762
+ logger.info(f"Loaded latest local image: {latest_image.name}")
763
+
764
+ # 如果 image_url 是本地路径,转换为 URL
765
+ image_url = user_data.get('character', {}).get('image_url')
766
+ if image_url and not image_url.startswith('http'):
767
+ # 本地路径,转换为 URL(处理 Windows 和 Unix 路径)
768
+ image_path = Path(image_url)
769
+ if image_path.exists():
770
+ # 使用正斜杠构建 URL(使用动态 base_url)
771
+ user_data['character']['image_url'] = f"{base_url}/generated_images/{image_path.name}"
772
+ else:
773
+ # 如果路径不存在,尝试只使用文件名
774
+ filename = image_path.name
775
+ full_path = Path("generated_images") / filename
776
+ if full_path.exists():
777
+ user_data['character']['image_url'] = f"{base_url}/generated_images/{filename}"
778
+ logger.info(f"Converted path to URL: {filename}")
779
+
780
+ return user_data
781
+ except Exception as e:
782
+ logger.error(f"Failed to get user config: {e}")
783
+ return JSONResponse(
784
+ status_code=500,
785
+ content={"error": str(e)}
786
+ )
787
+
788
+
789
+ @app.post("/api/character/generate")
790
+ async def generate_character(
791
+ request: Request,
792
+ color: str = Form(...),
793
+ personality: str = Form(...),
794
+ appearance: str = Form(...),
795
+ role: str = Form(...)
796
+ ):
797
+ """Generate AI character image based on preferences.
798
+
799
+ Args:
800
+ color: Color preference (温暖粉/天空蓝/薄荷绿等)
801
+ personality: Personality trait (活泼/温柔/聪明等)
802
+ appearance: Appearance feature (戴眼镜/戴帽子等)
803
+ role: Character role (陪伴式朋友/温柔照顾型长辈等)
804
+
805
+ Returns:
806
+ JSON with image_url, prompt, and preferences
807
+ """
808
+ try:
809
+ from app.image_service import ImageGenerationService, ImageGenerationError
810
+ from app.user_config import UserConfig
811
+ from datetime import datetime
812
+ from pathlib import Path
813
+ import httpx
814
+
815
+ config = get_config()
816
+
817
+ # 检查是否配置了 MiniMax API
818
+ minimax_api_key = getattr(config, 'minimax_api_key', None)
819
+
820
+ if not minimax_api_key:
821
+ logger.warning("MiniMax API key not configured")
822
+ return JSONResponse(
823
+ status_code=400,
824
+ content={
825
+ "error": "MiniMax API 未配置",
826
+ "detail": "请在 .env 文件中配置 MINIMAX_API_KEY。访问 https://platform.minimaxi.com/ 获取 API 密钥。"
827
+ }
828
+ )
829
+
830
+ # 初始化服务
831
+ image_service = ImageGenerationService(
832
+ api_key=minimax_api_key,
833
+ group_id=getattr(config, 'minimax_group_id', None)
834
+ )
835
+ user_config = UserConfig(str(config.data_dir))
836
+
837
+ try:
838
+ logger.info(
839
+ f"Generating character image: "
840
+ f"color={color}, personality={personality}, "
841
+ f"appearance={appearance}, role={role}"
842
+ )
843
+
844
+ # 生成图像
845
+ result = await image_service.generate_image(
846
+ color=color,
847
+ personality=personality,
848
+ appearance=appearance,
849
+ role=role,
850
+ aspect_ratio="1:1",
851
+ n=1
852
+ )
853
+
854
+ # 下载图片到本地
855
+ generated_images_dir = Path("generated_images")
856
+ generated_images_dir.mkdir(exist_ok=True)
857
+
858
+ # 生成文件名:character_颜色_性格_时间戳.jpeg
859
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
860
+ filename = f"character_{color}_{personality}_{timestamp}.jpeg"
861
+ local_path = generated_images_dir / filename
862
+
863
+ logger.info(f"Downloading image to: {local_path}")
864
+
865
+ # 下载图片
866
+ async with httpx.AsyncClient(timeout=60.0) as client:
867
+ response = await client.get(result['url'])
868
+ if response.status_code == 200:
869
+ with open(local_path, 'wb') as f:
870
+ f.write(response.content)
871
+ logger.info(f"Image saved to: {local_path}")
872
+ else:
873
+ logger.error(f"Failed to download image: HTTP {response.status_code}")
874
+ # 如果下载失败,仍然使用远程 URL
875
+ local_path = None
876
+
877
+ # 保存到用户配置
878
+ preferences = {
879
+ "color": color,
880
+ "personality": personality,
881
+ "appearance": appearance,
882
+ "role": role
883
+ }
884
+
885
+ # 使用本地路径(如果下载成功)
886
+ image_url = str(local_path) if local_path else result['url']
887
+
888
+ user_config.save_character_image(
889
+ image_url=image_url,
890
+ prompt=result['prompt'],
891
+ revised_prompt=result.get('metadata', {}).get('revised_prompt'),
892
+ preferences=preferences
893
+ )
894
+
895
+ logger.info(f"Character image generated and saved: {image_url}")
896
+
897
+ # 返回 HTTP URL(使用动态 base_url)
898
+ base_url = get_base_url(request)
899
+ if local_path:
900
+ http_url = f"{base_url}/generated_images/{local_path.name}"
901
+ else:
902
+ http_url = image_url
903
+
904
+ return {
905
+ "success": True,
906
+ "image_url": http_url,
907
+ "prompt": result['prompt'],
908
+ "preferences": preferences,
909
+ "task_id": result.get('task_id')
910
+ }
911
+
912
+ finally:
913
+ await image_service.close()
914
+
915
+ except ImageGenerationError as e:
916
+ logger.error(f"Image generation error: {e.message}")
917
+
918
+ # 提供更友好的错误信息
919
+ error_detail = e.message
920
+ if "invalid api key" in e.message.lower():
921
+ error_detail = "API 密钥无效,请检查 MINIMAX_API_KEY 配置是否正确"
922
+ elif "quota" in e.message.lower() or "配额" in e.message:
923
+ error_detail = "API 配额不足,请充值或等待配额恢复"
924
+ elif "timeout" in e.message.lower() or "超时" in e.message:
925
+ error_detail = "请求超时,请检查网络连接后重试"
926
+
927
+ return JSONResponse(
928
+ status_code=500,
929
+ content={
930
+ "error": "图像生成失败",
931
+ "detail": error_detail
932
+ }
933
+ )
934
+
935
+ except Exception as e:
936
+ logger.error(f"Failed to generate character: {e}", exc_info=True)
937
+ return JSONResponse(
938
+ status_code=500,
939
+ content={
940
+ "error": "生成角色形象失败",
941
+ "detail": str(e)
942
+ }
943
+ )
944
+
945
+
946
+ @app.get("/api/character/history")
947
+ async def get_character_history(request: Request):
948
+ """Get list of all generated character images.
949
+
950
+ Returns:
951
+ JSON with list of historical character images
952
+ """
953
+ try:
954
+ from pathlib import Path
955
+ import os
956
+
957
+ base_url = get_base_url(request)
958
+ generated_images_dir = Path("generated_images")
959
+
960
+ if not generated_images_dir.exists():
961
+ return {"images": []}
962
+
963
+ # 获取所有图片文件
964
+ image_files = []
965
+ for file in generated_images_dir.glob("character_*.jpeg"):
966
+ # 解析文件名:character_颜色_性格_时间戳.jpeg
967
+ parts = file.stem.split("_")
968
+ if len(parts) >= 4:
969
+ color = parts[1]
970
+ personality = parts[2]
971
+ timestamp = "_".join(parts[3:])
972
+
973
+ # 获取文件信息
974
+ stat = file.stat()
975
+
976
+ image_files.append({
977
+ "filename": file.name,
978
+ "url": f"{base_url}/generated_images/{file.name}",
979
+ "color": color,
980
+ "personality": personality,
981
+ "timestamp": timestamp,
982
+ "created_at": stat.st_ctime,
983
+ "size": stat.st_size
984
+ })
985
+
986
+ # 按创建时间倒序排列(最新的在前)
987
+ image_files.sort(key=lambda x: x["created_at"], reverse=True)
988
+
989
+ logger.info(f"Found {len(image_files)} historical character images")
990
+
991
+ return {"images": image_files}
992
+
993
+ except Exception as e:
994
+ logger.error(f"Error getting character history: {e}", exc_info=True)
995
+ raise HTTPException(status_code=500, detail=str(e))
996
+
997
+
998
+ @app.post("/api/character/select")
999
+ async def select_character(
1000
+ request: Request,
1001
+ filename: str = Form(...)
1002
+ ):
1003
+ """Select a historical character image as current.
1004
+
1005
+ Args:
1006
+ filename: Filename of the character image to select
1007
+
1008
+ Returns:
1009
+ JSON with success status and image URL
1010
+ """
1011
+ try:
1012
+ from app.user_config import UserConfig
1013
+ from pathlib import Path
1014
+
1015
+ config = get_config()
1016
+ user_config = UserConfig(str(config.data_dir))
1017
+
1018
+ # 验证文件存在
1019
+ image_path = Path("generated_images") / filename
1020
+ if not image_path.exists():
1021
+ raise HTTPException(status_code=404, detail="图片文件不存在")
1022
+
1023
+ # 解析文件名获取偏好设置
1024
+ parts = filename.replace(".jpeg", "").split("_")
1025
+ if len(parts) >= 4:
1026
+ color = parts[1]
1027
+ personality = parts[2]
1028
+
1029
+ preferences = {
1030
+ "color": color,
1031
+ "personality": personality,
1032
+ "appearance": "未知",
1033
+ "role": "未知"
1034
+ }
1035
+ else:
1036
+ preferences = {}
1037
+
1038
+ # 更新用户配置
1039
+ image_url = str(image_path)
1040
+ user_config.save_character_image(
1041
+ image_url=image_url,
1042
+ prompt=f"历史形象: {filename}",
1043
+ preferences=preferences
1044
+ )
1045
+
1046
+ logger.info(f"Selected historical character: {filename}")
1047
+
1048
+ # 返回 HTTP URL(使用动态 base_url)
1049
+ base_url = get_base_url(request)
1050
+ http_url = f"{base_url}/generated_images/{filename}"
1051
+
1052
+ return {
1053
+ "success": True,
1054
+ "image_url": http_url,
1055
+ "filename": filename,
1056
+ "preferences": preferences
1057
+ }
1058
+
1059
+ except HTTPException:
1060
+ raise
1061
+ except Exception as e:
1062
+ logger.error(f"Error selecting character: {e}", exc_info=True)
1063
+ raise HTTPException(status_code=500, detail=str(e))
1064
+
1065
+
1066
+ @app.post("/api/character/preferences")
1067
+ async def update_character_preferences(
1068
+ color: Optional[str] = Form(None),
1069
+ personality: Optional[str] = Form(None),
1070
+ appearance: Optional[str] = Form(None),
1071
+ role: Optional[str] = Form(None)
1072
+ ):
1073
+ """Update character preferences without generating new image.
1074
+
1075
+ Args:
1076
+ color: Color preference (optional)
1077
+ personality: Personality trait (optional)
1078
+ appearance: Appearance feature (optional)
1079
+ role: Character role (optional)
1080
+
1081
+ Returns:
1082
+ JSON with updated preferences
1083
+ """
1084
+ try:
1085
+ from app.user_config import UserConfig
1086
+
1087
+ config = get_config()
1088
+ user_config = UserConfig(str(config.data_dir))
1089
+
1090
+ # 更新偏好设置
1091
+ user_config.update_character_preferences(
1092
+ color=color,
1093
+ personality=personality,
1094
+ appearance=appearance,
1095
+ role=role
1096
+ )
1097
+
1098
+ # 返回更新后的配置
1099
+ updated_config = user_config.load_config()
1100
+
1101
+ return {
1102
+ "success": True,
1103
+ "preferences": updated_config['character']['preferences']
1104
+ }
1105
+
1106
+ except Exception as e:
1107
+ logger.error(f"Failed to update preferences: {e}")
1108
+ return JSONResponse(
1109
+ status_code=500,
1110
+ content={"error": str(e)}
1111
+ )
1112
+
1113
+
1114
+ if __name__ == "__main__":
1115
+ import uvicorn
1116
+
1117
+ # Load config for server settings
1118
+ try:
1119
+ config = init_config()
1120
+ setup_logging(log_level=config.log_level, log_file=config.log_file)
1121
+
1122
+ # Run server
1123
+ uvicorn.run(
1124
+ "app.main:app",
1125
+ host=config.host,
1126
+ port=config.port,
1127
+ reload=False,
1128
+ log_level=config.log_level.lower()
1129
+ )
1130
+ except Exception as e:
1131
+ print(f"Failed to start application: {e}")
1132
+ exit(1)
app/models.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Data models for Voice Text Processor.
2
+
3
+ This module defines all Pydantic data models used throughout the application
4
+ for data validation, serialization, and API request/response handling.
5
+
6
+ Requirements: 4.1, 4.2, 4.3, 5.1, 5.2, 5.3, 6.1, 6.2, 6.3, 6.4
7
+ """
8
+
9
+ from typing import Optional, List, Literal
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class MoodData(BaseModel):
14
+ """Mood data structure.
15
+
16
+ Represents the emotional state extracted from user input.
17
+
18
+ Attributes:
19
+ type: The type/name of the emotion (e.g., "开心", "焦虑")
20
+ intensity: Emotion intensity on a scale of 1-10
21
+ keywords: List of keywords associated with the emotion
22
+
23
+ Requirements: 4.1, 4.2, 4.3
24
+ """
25
+ type: Optional[str] = None
26
+ intensity: Optional[int] = Field(None, ge=1, le=10)
27
+ keywords: List[str] = Field(default_factory=list)
28
+
29
+
30
+ class InspirationData(BaseModel):
31
+ """Inspiration data structure.
32
+
33
+ Represents an idea or inspiration extracted from user input.
34
+
35
+ Attributes:
36
+ core_idea: The core idea/concept (max 20 characters)
37
+ tags: List of tags for categorization (max 5 tags)
38
+ category: Category of the inspiration
39
+
40
+ Requirements: 5.1, 5.2, 5.3
41
+ """
42
+ core_idea: str = Field(..., max_length=20)
43
+ tags: List[str] = Field(default_factory=list, max_length=5)
44
+ category: Literal["工作", "生活", "学习", "创意"]
45
+
46
+
47
+ class TodoData(BaseModel):
48
+ """Todo item data structure.
49
+
50
+ Represents a task/todo item extracted from user input.
51
+
52
+ Attributes:
53
+ task: Description of the task
54
+ time: Time information (preserved as original expression)
55
+ location: Location information
56
+ status: Task status (defaults to "pending")
57
+
58
+ Requirements: 6.1, 6.2, 6.3, 6.4
59
+ """
60
+ task: str
61
+ time: Optional[str] = None
62
+ location: Optional[str] = None
63
+ status: str = "pending"
64
+
65
+
66
+ class ParsedData(BaseModel):
67
+ """Parsed data structure.
68
+
69
+ Contains all structured data extracted from semantic parsing.
70
+
71
+ Attributes:
72
+ mood: Extracted mood data (optional)
73
+ inspirations: List of extracted inspirations
74
+ todos: List of extracted todo items
75
+ """
76
+ mood: Optional[MoodData] = None
77
+ inspirations: List[InspirationData] = Field(default_factory=list)
78
+ todos: List[TodoData] = Field(default_factory=list)
79
+
80
+
81
+ class RecordData(BaseModel):
82
+ """Complete record data structure.
83
+
84
+ Represents a complete user input record with all metadata and parsed data.
85
+
86
+ Attributes:
87
+ record_id: Unique identifier for the record
88
+ timestamp: ISO 8601 timestamp of when the record was created
89
+ input_type: Type of input (audio or text)
90
+ original_text: The original or transcribed text
91
+ parsed_data: Structured data extracted from the text
92
+ """
93
+ record_id: str
94
+ timestamp: str
95
+ input_type: Literal["audio", "text"]
96
+ original_text: str
97
+ parsed_data: ParsedData
98
+
99
+
100
+ class ProcessResponse(BaseModel):
101
+ """API response model for /api/process endpoint.
102
+
103
+ Represents the response returned to clients after processing input.
104
+
105
+ Attributes:
106
+ record_id: Unique identifier for the processed record
107
+ timestamp: ISO 8601 timestamp of when processing completed
108
+ mood: Extracted mood data (optional)
109
+ inspirations: List of extracted inspirations
110
+ todos: List of extracted todo items
111
+ error: Error message if processing failed (optional)
112
+ """
113
+ record_id: str
114
+ timestamp: str
115
+ mood: Optional[MoodData] = None
116
+ inspirations: List[InspirationData] = Field(default_factory=list)
117
+ todos: List[TodoData] = Field(default_factory=list)
118
+ error: Optional[str] = None
app/semantic_parser.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Semantic Parser service for Voice Text Processor.
2
+
3
+ This module implements the SemanticParserService class for parsing text
4
+ into structured data (mood, inspirations, todos) using the GLM-4-Flash API.
5
+
6
+ Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 9.2, 9.5
7
+ """
8
+
9
+ import logging
10
+ import json
11
+ from typing import Optional
12
+ import httpx
13
+
14
+ from app.models import ParsedData, MoodData, InspirationData, TodoData
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class SemanticParserError(Exception):
21
+ """Exception raised when semantic parsing operations fail.
22
+
23
+ This exception is raised when the GLM-4-Flash API call fails,
24
+ such as due to network issues, API errors, or invalid responses.
25
+
26
+ Requirements: 3.5
27
+ """
28
+
29
+ def __init__(self, message: str = "语义解析服务不可用"):
30
+ """Initialize SemanticParserError.
31
+
32
+ Args:
33
+ message: Error message describing the failure
34
+ """
35
+ super().__init__(message)
36
+ self.message = message
37
+
38
+
39
+ class SemanticParserService:
40
+ """Service for parsing text into structured data using GLM-4-Flash API.
41
+
42
+ This service handles semantic parsing by calling the GLM-4-Flash API
43
+ to extract mood, inspirations, and todos from text. It manages API
44
+ authentication, request formatting, response parsing, and error handling.
45
+
46
+ Attributes:
47
+ api_key: Zhipu AI API key for authentication
48
+ client: Async HTTP client for making API requests
49
+ api_url: GLM-4-Flash API endpoint URL
50
+ model: Model identifier
51
+ system_prompt: System prompt for data conversion
52
+
53
+ Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 9.2, 9.5
54
+ """
55
+
56
+ def __init__(self, api_key: str):
57
+ """Initialize the semantic parser service.
58
+
59
+ Args:
60
+ api_key: Zhipu AI API key for authentication
61
+
62
+ Requirements: 3.1, 3.2
63
+ """
64
+ self.api_key = api_key
65
+ self.client = httpx.AsyncClient(timeout=30.0)
66
+ self.api_url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
67
+ self.model = "glm-4-flash"
68
+
69
+ # System prompt as specified in requirements
70
+ self.system_prompt = (
71
+ "你是一个专业的文本语义分析助手。请将用户输入的文本解析为结构化的 JSON 数据。\n\n"
72
+ "你需要提取以下三个维度的信息:\n\n"
73
+ "1. **情绪 (mood)**:\n"
74
+ " - type: 情绪类型(如:喜悦、焦虑、平静、忧虑、兴奋、悲伤等中文词汇)\n"
75
+ " - intensity: 情绪强度(1-10的整数,10表示最强烈)\n"
76
+ " - keywords: 情绪关键词列表(3-5个中文词)\n\n"
77
+ "2. **灵感 (inspirations)**:数组,每个元素包含:\n"
78
+ " - core_idea: 核心观点或想法(20字以内的中文)\n"
79
+ " - tags: 相关标签列表(3-5个中文词)\n"
80
+ " - category: 所属分类(必须是:工作、生活、学习、创意 之一)\n\n"
81
+ "3. **待办 (todos)**:数组,每个元素包含:\n"
82
+ " - task: 任务描述(中文)\n"
83
+ " - time: 时间信息(如:明天、下周、周五等,如果没有则为null)\n"
84
+ " - location: 地点信息(如果没有则为null)\n"
85
+ " - status: 状态(默认为\"pending\")\n\n"
86
+ "**重要规则**:\n"
87
+ "- 如果文本中没有某个维度的信息,mood 返回 null,inspirations 和 todos 返回空数组 []\n"
88
+ "- 必须返回有效的 JSON 格式,不要添加任何其他说明文字\n"
89
+ "- 所有字段名使用英文,内容使用中文\n"
90
+ "- 直接返回 JSON,不要用 markdown 代码块包裹\n\n"
91
+ "返回格式示例:\n"
92
+ "{\n"
93
+ " \"mood\": {\"type\": \"焦虑\", \"intensity\": 7, \"keywords\": [\"压力\", \"疲惫\", \"放松\"]},\n"
94
+ " \"inspirations\": [{\"core_idea\": \"晚霞可以缓解压力\", \"tags\": [\"自然\", \"治愈\"], \"category\": \"生活\"}],\n"
95
+ " \"todos\": [{\"task\": \"整理文档\", \"time\": \"明天\", \"location\": null, \"status\": \"pending\"}]\n"
96
+ "}"
97
+ )
98
+
99
+ async def close(self):
100
+ """Close the HTTP client.
101
+
102
+ This should be called when the service is no longer needed
103
+ to properly clean up resources.
104
+ """
105
+ await self.client.aclose()
106
+
107
+ async def parse(self, text: str) -> ParsedData:
108
+ """Parse text into structured data using GLM-4-Flash API.
109
+
110
+ This method sends the text to the GLM-4-Flash API with the configured
111
+ system prompt and returns structured data containing mood, inspirations,
112
+ and todos. It handles API errors, missing dimensions, and logs all errors
113
+ with timestamps and stack traces.
114
+
115
+ Args:
116
+ text: Text content to parse
117
+
118
+ Returns:
119
+ ParsedData object containing mood (optional), inspirations (list),
120
+ and todos (list). Missing dimensions return null or empty arrays.
121
+
122
+ Raises:
123
+ SemanticParserError: If API call fails or returns invalid response
124
+
125
+ Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 9.2, 9.5
126
+ """
127
+ try:
128
+ # Prepare request headers
129
+ headers = {
130
+ "Authorization": f"Bearer {self.api_key}",
131
+ "Content-Type": "application/json"
132
+ }
133
+
134
+ # Prepare request payload
135
+ payload = {
136
+ "model": self.model,
137
+ "messages": [
138
+ {
139
+ "role": "system",
140
+ "content": self.system_prompt
141
+ },
142
+ {
143
+ "role": "user",
144
+ "content": text
145
+ }
146
+ ],
147
+ "temperature": 0.7,
148
+ "top_p": 0.9
149
+ }
150
+
151
+ logger.info(f"Calling GLM-4-Flash API for semantic parsing. Text length: {len(text)}")
152
+
153
+ # Make API request
154
+ response = await self.client.post(
155
+ self.api_url,
156
+ headers=headers,
157
+ json=payload
158
+ )
159
+
160
+ # Check response status
161
+ if response.status_code != 200:
162
+ error_msg = f"GLM-4-Flash API returned status {response.status_code}"
163
+ try:
164
+ error_detail = response.json()
165
+ error_msg += f": {error_detail}"
166
+ except Exception:
167
+ error_msg += f": {response.text}"
168
+
169
+ logger.error(
170
+ f"Semantic parsing API call failed: {error_msg}",
171
+ exc_info=True,
172
+ extra={"timestamp": logger.makeRecord(
173
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
174
+ ).created}
175
+ )
176
+ raise SemanticParserError(f"语义解析服务不可用: {error_msg}")
177
+
178
+ # Parse response
179
+ try:
180
+ result = response.json()
181
+ except Exception as e:
182
+ error_msg = f"Failed to parse GLM-4-Flash API response: {str(e)}"
183
+ logger.error(
184
+ error_msg,
185
+ exc_info=True,
186
+ extra={"timestamp": logger.makeRecord(
187
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
188
+ ).created}
189
+ )
190
+ raise SemanticParserError(f"语义解析服务不可用: 响应格式无效")
191
+
192
+ # Extract content from response
193
+ try:
194
+ content = result["choices"][0]["message"]["content"]
195
+ except (KeyError, IndexError) as e:
196
+ error_msg = f"Invalid API response structure: {str(e)}"
197
+ logger.error(
198
+ error_msg,
199
+ exc_info=True,
200
+ extra={"timestamp": logger.makeRecord(
201
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
202
+ ).created}
203
+ )
204
+ raise SemanticParserError(f"语义解析服务不可用: 响应结构无效")
205
+
206
+ # Parse JSON from content
207
+ try:
208
+ # Try to extract JSON from markdown code blocks if present
209
+ if "```json" in content:
210
+ json_start = content.find("```json") + 7
211
+ json_end = content.find("```", json_start)
212
+ content = content[json_start:json_end].strip()
213
+ elif "```" in content:
214
+ json_start = content.find("```") + 3
215
+ json_end = content.find("```", json_start)
216
+ content = content[json_start:json_end].strip()
217
+
218
+ parsed_json = json.loads(content)
219
+ except json.JSONDecodeError as e:
220
+ error_msg = f"Failed to parse JSON from API response: {str(e)}"
221
+ logger.error(
222
+ error_msg,
223
+ exc_info=True,
224
+ extra={"timestamp": logger.makeRecord(
225
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
226
+ ).created}
227
+ )
228
+ raise SemanticParserError(f"语义解析服务不可用: JSON 解析失败")
229
+
230
+ # Extract and validate mood data
231
+ mood = None
232
+ if "mood" in parsed_json and parsed_json["mood"]:
233
+ try:
234
+ mood_data = parsed_json["mood"]
235
+ if isinstance(mood_data, dict):
236
+ mood = MoodData(
237
+ type=mood_data.get("type"),
238
+ intensity=mood_data.get("intensity"),
239
+ keywords=mood_data.get("keywords", [])
240
+ )
241
+ except Exception as e:
242
+ logger.warning(f"Failed to parse mood data: {str(e)}")
243
+ mood = None
244
+
245
+ # Extract and validate inspirations
246
+ inspirations = []
247
+ if "inspirations" in parsed_json and parsed_json["inspirations"]:
248
+ for insp_data in parsed_json["inspirations"]:
249
+ try:
250
+ if isinstance(insp_data, dict):
251
+ inspiration = InspirationData(
252
+ core_idea=insp_data.get("core_idea", ""),
253
+ tags=insp_data.get("tags", []),
254
+ category=insp_data.get("category", "生活")
255
+ )
256
+ inspirations.append(inspiration)
257
+ except Exception as e:
258
+ logger.warning(f"Failed to parse inspiration data: {str(e)}")
259
+ continue
260
+
261
+ # Extract and validate todos
262
+ todos = []
263
+ if "todos" in parsed_json and parsed_json["todos"]:
264
+ for todo_data in parsed_json["todos"]:
265
+ try:
266
+ if isinstance(todo_data, dict):
267
+ todo = TodoData(
268
+ task=todo_data.get("task", ""),
269
+ time=todo_data.get("time"),
270
+ location=todo_data.get("location"),
271
+ status=todo_data.get("status", "pending")
272
+ )
273
+ todos.append(todo)
274
+ except Exception as e:
275
+ logger.warning(f"Failed to parse todo data: {str(e)}")
276
+ continue
277
+
278
+ logger.info(
279
+ f"Semantic parsing successful. "
280
+ f"Mood: {'present' if mood else 'none'}, "
281
+ f"Inspirations: {len(inspirations)}, "
282
+ f"Todos: {len(todos)}"
283
+ )
284
+
285
+ return ParsedData(
286
+ mood=mood,
287
+ inspirations=inspirations,
288
+ todos=todos
289
+ )
290
+
291
+ except SemanticParserError:
292
+ # Re-raise SemanticParserError as-is
293
+ raise
294
+
295
+ except httpx.TimeoutException as e:
296
+ error_msg = f"GLM-4-Flash API request timeout: {str(e)}"
297
+ logger.error(
298
+ error_msg,
299
+ exc_info=True,
300
+ extra={"timestamp": logger.makeRecord(
301
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
302
+ ).created}
303
+ )
304
+ raise SemanticParserError("语义解析服务不可用: 请求超时")
305
+
306
+ except httpx.RequestError as e:
307
+ error_msg = f"GLM-4-Flash API request failed: {str(e)}"
308
+ logger.error(
309
+ error_msg,
310
+ exc_info=True,
311
+ extra={"timestamp": logger.makeRecord(
312
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
313
+ ).created}
314
+ )
315
+ raise SemanticParserError(f"语义解析服务不可用: 网络错误")
316
+
317
+ except Exception as e:
318
+ error_msg = f"Unexpected error in semantic parser service: {str(e)}"
319
+ logger.error(
320
+ error_msg,
321
+ exc_info=True,
322
+ extra={"timestamp": logger.makeRecord(
323
+ logger.name, logging.ERROR, "", 0, error_msg, (), None
324
+ ).created}
325
+ )
326
+ raise SemanticParserError(f"语义解析服务不可用: {str(e)}")
app/storage.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Storage service for JSON file persistence.
2
+
3
+ This module implements the StorageService class for managing JSON file storage
4
+ of records, moods, inspirations, and todos.
5
+
6
+ Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7
7
+ """
8
+
9
+ import json
10
+ import uuid
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ from datetime import datetime
14
+
15
+ from app.models import RecordData, MoodData, InspirationData, TodoData
16
+
17
+
18
+ class StorageError(Exception):
19
+ """Exception raised when storage operations fail.
20
+
21
+ This exception is raised when file operations (read/write) fail,
22
+ such as due to permission issues, disk space, or I/O errors.
23
+
24
+ Requirements: 7.6
25
+ """
26
+ pass
27
+
28
+
29
+ class StorageService:
30
+ """Service for managing JSON file storage.
31
+
32
+ This service handles persistence of records, moods, inspirations, and todos
33
+ to separate JSON files. It ensures file initialization, generates unique IDs,
34
+ and handles errors appropriately.
35
+
36
+ Attributes:
37
+ data_dir: Directory path for storing JSON files
38
+ records_file: Path to records.json
39
+ moods_file: Path to moods.json
40
+ inspirations_file: Path to inspirations.json
41
+ todos_file: Path to todos.json
42
+
43
+ Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7
44
+ """
45
+
46
+ def __init__(self, data_dir: str):
47
+ """Initialize the storage service.
48
+
49
+ Args:
50
+ data_dir: Directory path for storing JSON files
51
+ """
52
+ self.data_dir = Path(data_dir)
53
+ self.records_file = self.data_dir / "records.json"
54
+ self.moods_file = self.data_dir / "moods.json"
55
+ self.inspirations_file = self.data_dir / "inspirations.json"
56
+ self.todos_file = self.data_dir / "todos.json"
57
+
58
+ # Ensure data directory exists
59
+ self.data_dir.mkdir(parents=True, exist_ok=True)
60
+
61
+ def _ensure_file_exists(self, file_path: Path) -> None:
62
+ """Ensure a JSON file exists and is initialized with default data.
63
+
64
+ If the file doesn't exist, creates it with sample Chinese data.
65
+
66
+ Args:
67
+ file_path: Path to the JSON file
68
+
69
+ Raises:
70
+ StorageError: If file creation fails
71
+
72
+ Requirements: 7.5
73
+ """
74
+ if not file_path.exists():
75
+ try:
76
+ # 根据文件类型提供不同的默认数据
77
+ default_data = []
78
+
79
+ if file_path.name == 'records.json':
80
+ default_data = self._get_default_records()
81
+ elif file_path.name == 'moods.json':
82
+ default_data = self._get_default_moods()
83
+ elif file_path.name == 'inspirations.json':
84
+ default_data = self._get_default_inspirations()
85
+ elif file_path.name == 'todos.json':
86
+ default_data = self._get_default_todos()
87
+ elif file_path.name == 'user_config.json':
88
+ default_data = self._get_default_user_config()
89
+
90
+ with open(file_path, 'w', encoding='utf-8') as f:
91
+ json.dump(default_data, f, ensure_ascii=False, indent=2)
92
+ except Exception as e:
93
+ raise StorageError(
94
+ f"Failed to initialize file {file_path}: {str(e)}"
95
+ )
96
+
97
+ def _get_default_records(self) -> list:
98
+ """获取默认的记录数据"""
99
+ from datetime import datetime, timedelta
100
+ now = datetime.now()
101
+
102
+ return [
103
+ {
104
+ "record_id": "welcome-1",
105
+ "timestamp": (now - timedelta(hours=2)).isoformat() + "Z",
106
+ "input_type": "text",
107
+ "original_text": "今天天气真好,阳光洒在窗台上,心情也跟着明朗起来。决定下午去公园散散步,感受一下大自然的美好。",
108
+ "parsed_data": {
109
+ "mood": {
110
+ "type": "喜悦",
111
+ "intensity": 8,
112
+ "keywords": ["阳光", "明朗", "美好"]
113
+ },
114
+ "inspirations": [
115
+ {
116
+ "core_idea": "享受自然的美好时光",
117
+ "tags": ["自然", "散步", "放松"],
118
+ "category": "生活"
119
+ }
120
+ ],
121
+ "todos": [
122
+ {
123
+ "task": "去公园散步",
124
+ "time": "下午",
125
+ "location": "公园",
126
+ "status": "pending"
127
+ }
128
+ ]
129
+ }
130
+ },
131
+ {
132
+ "record_id": "welcome-2",
133
+ "timestamp": (now - timedelta(hours=5)).isoformat() + "Z",
134
+ "input_type": "text",
135
+ "original_text": "刚看完���本很棒的书,书中的一句话让我印象深刻:'生活不是等待暴风雨过去,而是学会在雨中跳舞。'这句话给了我很多启发。",
136
+ "parsed_data": {
137
+ "mood": {
138
+ "type": "平静",
139
+ "intensity": 7,
140
+ "keywords": ["启发", "思考", "感悟"]
141
+ },
142
+ "inspirations": [
143
+ {
144
+ "core_idea": "学会在困难中保持积极",
145
+ "tags": ["人生哲理", "积极心态", "成长"],
146
+ "category": "学习"
147
+ }
148
+ ],
149
+ "todos": []
150
+ }
151
+ },
152
+ {
153
+ "record_id": "welcome-3",
154
+ "timestamp": (now - timedelta(days=1, hours=3)).isoformat() + "Z",
155
+ "input_type": "text",
156
+ "original_text": "和好朋友聊了很久,她分享了最近的生活和工作。虽然大家都很忙,但能抽时间见面真的很珍贵。友谊需要用心维护。",
157
+ "parsed_data": {
158
+ "mood": {
159
+ "type": "温暖",
160
+ "intensity": 9,
161
+ "keywords": ["友谊", "珍贵", "陪伴"]
162
+ },
163
+ "inspirations": [
164
+ {
165
+ "core_idea": "珍惜身边的朋友",
166
+ "tags": ["友情", "陪伴", "珍惜"],
167
+ "category": "生活"
168
+ }
169
+ ],
170
+ "todos": [
171
+ {
172
+ "task": "定期和朋友联系",
173
+ "time": None,
174
+ "location": None,
175
+ "status": "pending"
176
+ }
177
+ ]
178
+ }
179
+ },
180
+ {
181
+ "record_id": "welcome-4",
182
+ "timestamp": (now - timedelta(days=2)).isoformat() + "Z",
183
+ "input_type": "text",
184
+ "original_text": "今天完成了一个困扰我很久的项目,虽然过程很辛苦,但看到成果的那一刻,所有的付出都值得了。成就感满满!",
185
+ "parsed_data": {
186
+ "mood": {
187
+ "type": "兴奋",
188
+ "intensity": 10,
189
+ "keywords": ["成就感", "完成", "满足"]
190
+ },
191
+ "inspirations": [],
192
+ "todos": []
193
+ }
194
+ },
195
+ {
196
+ "record_id": "welcome-5",
197
+ "timestamp": (now - timedelta(days=3)).isoformat() + "Z",
198
+ "input_type": "text",
199
+ "original_text": "最近工作压力有点大,总是担心做不好。但转念一想,每个人都会遇到困难,重要的是保持积极的心态,一步一步来。",
200
+ "parsed_data": {
201
+ "mood": {
202
+ "type": "焦虑",
203
+ "intensity": 6,
204
+ "keywords": ["压力", "担心", "积极"]
205
+ },
206
+ "inspirations": [
207
+ {
208
+ "core_idea": "保持积极心态面对压力",
209
+ "tags": ["心态", "压力管理", "成长"],
210
+ "category": "工作"
211
+ }
212
+ ],
213
+ "todos": []
214
+ }
215
+ }
216
+ ]
217
+
218
+ def _get_default_moods(self) -> list:
219
+ """获取默认的心情数据"""
220
+ from datetime import datetime, timedelta
221
+ now = datetime.now()
222
+
223
+ return [
224
+ {
225
+ "record_id": "welcome-1",
226
+ "timestamp": (now - timedelta(hours=2)).isoformat() + "Z",
227
+ "type": "喜悦",
228
+ "intensity": 8,
229
+ "keywords": ["阳光", "明朗", "美好"]
230
+ },
231
+ {
232
+ "record_id": "welcome-2",
233
+ "timestamp": (now - timedelta(hours=5)).isoformat() + "Z",
234
+ "type": "平静",
235
+ "intensity": 7,
236
+ "keywords": ["启发", "思考", "感悟"]
237
+ },
238
+ {
239
+ "record_id": "welcome-3",
240
+ "timestamp": (now - timedelta(days=1, hours=3)).isoformat() + "Z",
241
+ "type": "温暖",
242
+ "intensity": 9,
243
+ "keywords": ["友谊", "珍贵", "陪伴"]
244
+ },
245
+ {
246
+ "record_id": "welcome-4",
247
+ "timestamp": (now - timedelta(days=2)).isoformat() + "Z",
248
+ "type": "兴奋",
249
+ "intensity": 10,
250
+ "keywords": ["成就感", "完成", "满足"]
251
+ },
252
+ {
253
+ "record_id": "welcome-5",
254
+ "timestamp": (now - timedelta(days=3)).isoformat() + "Z",
255
+ "type": "焦虑",
256
+ "intensity": 6,
257
+ "keywords": ["压力", "担心", "积极"]
258
+ }
259
+ ]
260
+
261
+ def _get_default_inspirations(self) -> list:
262
+ """获取默认的灵感数据"""
263
+ from datetime import datetime, timedelta
264
+ now = datetime.now()
265
+
266
+ return [
267
+ {
268
+ "record_id": "welcome-1",
269
+ "timestamp": (now - timedelta(hours=2)).isoformat() + "Z",
270
+ "core_idea": "享受自然的美好时光",
271
+ "tags": ["自然", "散步", "放松"],
272
+ "category": "生活"
273
+ },
274
+ {
275
+ "record_id": "welcome-2",
276
+ "timestamp": (now - timedelta(hours=5)).isoformat() + "Z",
277
+ "core_idea": "学会在困难中保持积极",
278
+ "tags": ["人生哲理", "积极心态", "成长"],
279
+ "category": "学习"
280
+ },
281
+ {
282
+ "record_id": "welcome-3",
283
+ "timestamp": (now - timedelta(days=1, hours=3)).isoformat() + "Z",
284
+ "core_idea": "珍惜身边的朋友",
285
+ "tags": ["友情", "陪伴", "珍惜"],
286
+ "category": "生活"
287
+ },
288
+ {
289
+ "record_id": "welcome-5",
290
+ "timestamp": (now - timedelta(days=3)).isoformat() + "Z",
291
+ "core_idea": "保持积极心态面对压力",
292
+ "tags": ["心态", "压力管理", "成长"],
293
+ "category": "工作"
294
+ }
295
+ ]
296
+
297
+ def _get_default_todos(self) -> list:
298
+ """获取默认的待办数据"""
299
+ from datetime import datetime, timedelta
300
+ now = datetime.now()
301
+
302
+ return [
303
+ {
304
+ "record_id": "welcome-1",
305
+ "timestamp": (now - timedelta(hours=2)).isoformat() + "Z",
306
+ "task": "去公园散步",
307
+ "time": "下午",
308
+ "location": "公园",
309
+ "status": "pending"
310
+ },
311
+ {
312
+ "record_id": "welcome-3",
313
+ "timestamp": (now - timedelta(days=1, hours=3)).isoformat() + "Z",
314
+ "task": "定期和朋友联系",
315
+ "time": None,
316
+ "location": None,
317
+ "status": "pending"
318
+ }
319
+ ]
320
+
321
+ def _get_default_user_config(self) -> dict:
322
+ """获取默认的用户配置"""
323
+ return {
324
+ "character": {
325
+ "image_url": "", # 空字符串,前端会显示占位符
326
+ "prompt": "默认形象:薰衣草紫色温柔猫咪",
327
+ "preferences": {
328
+ "color": "薰衣草紫",
329
+ "personality": "温柔",
330
+ "appearance": "无配饰",
331
+ "role": "陪伴式朋友"
332
+ }
333
+ }
334
+ }
335
+
336
+ def _read_json_file(self, file_path: Path) -> List:
337
+ """Read and parse a JSON file.
338
+
339
+ Args:
340
+ file_path: Path to the JSON file
341
+
342
+ Returns:
343
+ List of records from the JSON file
344
+
345
+ Raises:
346
+ StorageError: If file reading or parsing fails
347
+ """
348
+ self._ensure_file_exists(file_path)
349
+ try:
350
+ with open(file_path, 'r', encoding='utf-8') as f:
351
+ return json.load(f)
352
+ except Exception as e:
353
+ raise StorageError(
354
+ f"Failed to read file {file_path}: {str(e)}"
355
+ )
356
+
357
+ def _write_json_file(self, file_path: Path, data: List) -> None:
358
+ """Write data to a JSON file.
359
+
360
+ Args:
361
+ file_path: Path to the JSON file
362
+ data: List of records to write
363
+
364
+ Raises:
365
+ StorageError: If file writing fails
366
+
367
+ Requirements: 7.6
368
+ """
369
+ try:
370
+ with open(file_path, 'w', encoding='utf-8') as f:
371
+ json.dump(data, f, ensure_ascii=False, indent=2)
372
+ except Exception as e:
373
+ raise StorageError(
374
+ f"Failed to write file {file_path}: {str(e)}"
375
+ )
376
+
377
+ def save_record(self, record: RecordData) -> str:
378
+ """Save a complete record to records.json.
379
+
380
+ Generates a unique UUID for the record if not already set,
381
+ and appends the record to the records.json file.
382
+
383
+ Args:
384
+ record: RecordData object to save
385
+
386
+ Returns:
387
+ The unique record_id (UUID string)
388
+
389
+ Raises:
390
+ StorageError: If file writing fails
391
+
392
+ Requirements: 7.1, 7.7
393
+ """
394
+ # Generate unique UUID if not set
395
+ if not record.record_id:
396
+ record.record_id = str(uuid.uuid4())
397
+
398
+ # Read existing records
399
+ records = self._read_json_file(self.records_file)
400
+
401
+ # Append new record
402
+ records.append(record.model_dump())
403
+
404
+ # Write back to file
405
+ self._write_json_file(self.records_file, records)
406
+
407
+ return record.record_id
408
+
409
+ def append_mood(self, mood: MoodData, record_id: str, timestamp: str) -> None:
410
+ """Append mood data to moods.json.
411
+
412
+ Args:
413
+ mood: MoodData object to append
414
+ record_id: Associated record ID
415
+ timestamp: ISO 8601 timestamp
416
+
417
+ Raises:
418
+ StorageError: If file writing fails
419
+
420
+ Requirements: 7.2
421
+ """
422
+ # Read existing moods
423
+ moods = self._read_json_file(self.moods_file)
424
+
425
+ # Create mood entry with metadata
426
+ mood_entry = {
427
+ "record_id": record_id,
428
+ "timestamp": timestamp,
429
+ **mood.model_dump()
430
+ }
431
+
432
+ # Append new mood
433
+ moods.append(mood_entry)
434
+
435
+ # Write back to file
436
+ self._write_json_file(self.moods_file, moods)
437
+
438
+ def append_inspirations(
439
+ self,
440
+ inspirations: List[InspirationData],
441
+ record_id: str,
442
+ timestamp: str
443
+ ) -> None:
444
+ """Append inspiration data to inspirations.json.
445
+
446
+ Args:
447
+ inspirations: List of InspirationData objects to append
448
+ record_id: Associated record ID
449
+ timestamp: ISO 8601 timestamp
450
+
451
+ Raises:
452
+ StorageError: If file writing fails
453
+
454
+ Requirements: 7.3
455
+ """
456
+ if not inspirations:
457
+ return
458
+
459
+ # Read existing inspirations
460
+ all_inspirations = self._read_json_file(self.inspirations_file)
461
+
462
+ # Create inspiration entries with metadata
463
+ for inspiration in inspirations:
464
+ inspiration_entry = {
465
+ "record_id": record_id,
466
+ "timestamp": timestamp,
467
+ **inspiration.model_dump()
468
+ }
469
+ all_inspirations.append(inspiration_entry)
470
+
471
+ # Write back to file
472
+ self._write_json_file(self.inspirations_file, all_inspirations)
473
+
474
+ def append_todos(
475
+ self,
476
+ todos: List[TodoData],
477
+ record_id: str,
478
+ timestamp: str
479
+ ) -> None:
480
+ """Append todo data to todos.json.
481
+
482
+ Args:
483
+ todos: List of TodoData objects to append
484
+ record_id: Associated record ID
485
+ timestamp: ISO 8601 timestamp
486
+
487
+ Raises:
488
+ StorageError: If file writing fails
489
+
490
+ Requirements: 7.4
491
+ """
492
+ if not todos:
493
+ return
494
+
495
+ # Read existing todos
496
+ all_todos = self._read_json_file(self.todos_file)
497
+
498
+ # Create todo entries with metadata
499
+ for todo in todos:
500
+ todo_entry = {
501
+ "record_id": record_id,
502
+ "timestamp": timestamp,
503
+ **todo.model_dump()
504
+ }
505
+ all_todos.append(todo_entry)
506
+
507
+ # Write back to file
508
+ self._write_json_file(self.todos_file, all_todos)
app/user_config.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """User configuration management for Voice Text Processor.
2
+
3
+ This module handles user-specific configurations, including
4
+ the generated cat character image settings.
5
+
6
+ Requirements: PRD - AI形象生成模块
7
+ """
8
+
9
+ import json
10
+ import os
11
+ from typing import Optional, Dict, List
12
+ from datetime import datetime
13
+ import logging
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class UserConfig:
19
+ """User configuration manager.
20
+
21
+ This class manages user-specific settings, particularly
22
+ the generated cat character image configuration.
23
+
24
+ Attributes:
25
+ config_dir: Directory for storing user configurations
26
+ config_file: Path to the user config JSON file
27
+ """
28
+
29
+ def __init__(self, config_dir: str = "data"):
30
+ """Initialize user configuration manager.
31
+
32
+ Args:
33
+ config_dir: Directory for storing configurations
34
+ """
35
+ self.config_dir = config_dir
36
+ self.config_file = os.path.join(config_dir, "user_config.json")
37
+
38
+ # 确保目录存在
39
+ os.makedirs(config_dir, exist_ok=True)
40
+
41
+ # 初始化配置文件
42
+ if not os.path.exists(self.config_file):
43
+ self._init_config_file()
44
+
45
+ def _init_config_file(self):
46
+ """Initialize the configuration file with default values."""
47
+ default_config = {
48
+ "user_id": "default_user",
49
+ "created_at": datetime.utcnow().isoformat() + "Z",
50
+ "character": {
51
+ "image_url": "", # 空字符串,前端会显示占位符
52
+ "prompt": "默认治愈系小猫形象",
53
+ "revised_prompt": "一只薰衣草紫色的温柔猫咪,治愈系风格,温暖的陪伴者",
54
+ "preferences": {
55
+ "color": "薰衣草紫",
56
+ "personality": "温柔",
57
+ "appearance": "无配饰",
58
+ "role": "陪伴式朋友"
59
+ },
60
+ "generated_at": datetime.utcnow().isoformat() + "Z",
61
+ "generation_count": 0
62
+ },
63
+ "settings": {
64
+ "theme": "light",
65
+ "language": "zh-CN"
66
+ }
67
+ }
68
+
69
+ with open(self.config_file, 'w', encoding='utf-8') as f:
70
+ json.dump(default_config, f, ensure_ascii=False, indent=2)
71
+
72
+ logger.info(f"Initialized user config file: {self.config_file}")
73
+
74
+ def load_config(self) -> Dict:
75
+ """Load user configuration from file.
76
+
77
+ Returns:
78
+ Dictionary containing user configuration
79
+ """
80
+ try:
81
+ with open(self.config_file, 'r', encoding='utf-8') as f:
82
+ config = json.load(f)
83
+ return config
84
+ except Exception as e:
85
+ logger.error(f"Failed to load user config: {str(e)}")
86
+ # 返回默认配置
87
+ self._init_config_file()
88
+ return self.load_config()
89
+
90
+ def save_config(self, config: Dict):
91
+ """Save user configuration to file.
92
+
93
+ Args:
94
+ config: Configuration dictionary to save
95
+ """
96
+ try:
97
+ with open(self.config_file, 'w', encoding='utf-8') as f:
98
+ json.dump(config, f, ensure_ascii=False, indent=2)
99
+ logger.info("User config saved successfully")
100
+ except Exception as e:
101
+ logger.error(f"Failed to save user config: {str(e)}")
102
+ raise
103
+
104
+ def get_character_config(self) -> Dict:
105
+ """Get character configuration.
106
+
107
+ Returns:
108
+ Dictionary containing character settings
109
+ """
110
+ config = self.load_config()
111
+ return config.get("character", {})
112
+
113
+ def save_character_image(
114
+ self,
115
+ image_url: str,
116
+ prompt: str,
117
+ revised_prompt: Optional[str] = None,
118
+ preferences: Optional[Dict] = None
119
+ ):
120
+ """Save generated character image configuration.
121
+
122
+ Args:
123
+ image_url: URL of the generated image
124
+ prompt: Prompt used for generation
125
+ revised_prompt: AI-revised prompt (optional)
126
+ preferences: User preferences used (optional)
127
+ """
128
+ config = self.load_config()
129
+
130
+ # 更新角色配置
131
+ config["character"]["image_url"] = image_url
132
+ config["character"]["prompt"] = prompt
133
+ config["character"]["revised_prompt"] = revised_prompt or prompt
134
+ config["character"]["generated_at"] = datetime.utcnow().isoformat() + "Z"
135
+ config["character"]["generation_count"] += 1
136
+
137
+ if preferences:
138
+ config["character"]["preferences"] = preferences
139
+
140
+ self.save_config(config)
141
+ logger.info(f"Character image saved: {image_url[:50]}...")
142
+
143
+ def get_character_image_url(self) -> Optional[str]:
144
+ """Get the current character image URL.
145
+
146
+ Returns:
147
+ Image URL or None if not set
148
+ """
149
+ character = self.get_character_config()
150
+ return character.get("image_url")
151
+
152
+ def get_character_preferences(self) -> Dict:
153
+ """Get character generation preferences.
154
+
155
+ Returns:
156
+ Dictionary containing color, personality, appearance, role
157
+ """
158
+ character = self.get_character_config()
159
+ return character.get("preferences", {
160
+ "color": "温暖粉",
161
+ "personality": "温柔",
162
+ "appearance": "无配饰",
163
+ "role": "陪伴式朋友"
164
+ })
165
+
166
+ def update_character_preferences(
167
+ self,
168
+ color: Optional[str] = None,
169
+ personality: Optional[str] = None,
170
+ appearance: Optional[str] = None,
171
+ role: Optional[str] = None
172
+ ):
173
+ """Update character generation preferences.
174
+
175
+ Args:
176
+ color: Color preference (optional)
177
+ personality: Personality trait (optional)
178
+ appearance: Appearance feature (optional)
179
+ role: Character role (optional)
180
+ """
181
+ config = self.load_config()
182
+ preferences = config["character"]["preferences"]
183
+
184
+ if color:
185
+ preferences["color"] = color
186
+ if personality:
187
+ preferences["personality"] = personality
188
+ if appearance:
189
+ preferences["appearance"] = appearance
190
+ if role:
191
+ preferences["role"] = role
192
+
193
+ self.save_config(config)
194
+ logger.info("Character preferences updated")
195
+
196
+ def get_generation_count(self) -> int:
197
+ """Get the number of times character has been generated.
198
+
199
+ Returns:
200
+ Generation count
201
+ """
202
+ character = self.get_character_config()
203
+ return character.get("generation_count", 0)
204
+
205
+ def has_character_image(self) -> bool:
206
+ """Check if user has a character image set.
207
+
208
+ Returns:
209
+ True if character image exists, False otherwise
210
+ """
211
+ return self.get_character_image_url() is not None
data/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+ # This file ensures the data directory is tracked by git
deployment/DEPLOYMENT.md ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 部署指南
2
+
3
+ ## 部署到 Hugging Face Spaces
4
+
5
+ ### 前置准备
6
+
7
+ 1. **构建前端**
8
+ ```bash
9
+ cd frontend
10
+ npm install
11
+ npm run build
12
+ ```
13
+
14
+ 2. **验证构建产物**
15
+ - 确保 `frontend/dist/` 目录存在
16
+ - 包含 `index.html` 和 `assets/` 文件夹
17
+
18
+ ### 自动部署(推荐)
19
+
20
+ **Windows:**
21
+ ```bash
22
+ build_and_deploy.bat
23
+ ```
24
+
25
+ **Linux/Mac:**
26
+ ```bash
27
+ chmod +x build_and_deploy.sh
28
+ ./build_and_deploy.sh
29
+ ```
30
+
31
+ ### 手动部署
32
+
33
+ 1. **构建前端**
34
+ ```bash
35
+ cd frontend
36
+ npm run build
37
+ cd ..
38
+ ```
39
+
40
+ 2. **提交更改**
41
+ ```bash
42
+ git add .
43
+ git commit -m "Deploy: Update frontend build"
44
+ ```
45
+
46
+ 3. **推送到 Hugging Face**
47
+ ```bash
48
+ git push hf main
49
+ ```
50
+
51
+ ### 配置 Hugging Face Secrets
52
+
53
+ 在 Space 的 Settings → Repository secrets 中添加:
54
+
55
+ **必需:**
56
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
57
+ - 获取:https://open.bigmodel.cn/
58
+
59
+ **可选:**
60
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
61
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
62
+ - 获取:https://platform.minimaxi.com/
63
+
64
+ ### 访问应用
65
+
66
+ 部署成功后,访问:
67
+ - **前端应用**: `https://your-space.hf.space/app`
68
+ - **Gradio 界面**: `https://your-space.hf.space/gradio`
69
+ - **API 文档**: `https://your-space.hf.space/docs`
70
+
71
+ ### 文件结构
72
+
73
+ ```
74
+ .
75
+ ├── app.py # Hugging Face 入口文件
76
+ ├── app/ # FastAPI 后端
77
+ │ ├── main.py # 主应用
78
+ │ └── ...
79
+ ├── frontend/
80
+ │ ├── dist/ # 构建产物(需要提交)
81
+ │ │ ├── index.html
82
+ │ │ └── assets/
83
+ │ └── ...
84
+ ├── requirements_hf.txt # Python 依赖
85
+ └── README_HF.md # Hugging Face 说明
86
+ ```
87
+
88
+ ### 故障排查
89
+
90
+ **问题:前端 404**
91
+ - 检查 `frontend/dist/` 是否存在
92
+ - 确认已运行 `npm run build`
93
+ - 查看 Space 日志确认文件已上传
94
+
95
+ **问题:API 调用失败**
96
+ - 检查 Secrets 是否正确配置
97
+ - 查看 Space 日志中的错误信息
98
+ - 确认 API 密钥有效
99
+
100
+ **问题:静态资源加载失败**
101
+ - 检查 `frontend/dist/assets/` 是否存在
102
+ - 确认 CSS 和 JS 文件已生成
103
+ - 查看浏览器控制台的网络请求
104
+
105
+ ### 本地测试
106
+
107
+ 在部署前本地测试:
108
+
109
+ ```bash
110
+ # 构建前端
111
+ cd frontend && npm run build && cd ..
112
+
113
+ # 运行应用
114
+ python app.py
115
+ ```
116
+
117
+ 访问 `http://localhost:7860/app` 测试前端应用。
118
+
119
+ ### 更新部署
120
+
121
+ 每次修改前端代码后:
122
+
123
+ 1. 重新构建:`cd frontend && npm run build && cd ..`
124
+ 2. 提交更改:`git add . && git commit -m "Update"`
125
+ 3. 推送:`git push hf main`
126
+
127
+ ### 注意事项
128
+
129
+ - ✅ `frontend/dist/` 必须提交到 Git(不要在 .gitignore 中忽略)
130
+ - ✅ 每次修改前端代码都需要重新构建
131
+ - ✅ Hugging Face Spaces 会自动重启应用
132
+ - ⚠️ 首次部署可能需要 5-10 分钟
133
+ - ⚠️ 免费 Space 可能会在不活跃时休眠
deployment/DEPLOY_CHECKLIST.md ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces 部署检查清单
2
+
3
+ ## 📋 部署前检查
4
+
5
+ ### 1. 依赖版本确认
6
+ - [ ] `requirements_hf.txt` 中 `huggingface-hub==0.23.5`
7
+ - [ ] `requirements_hf.txt` 中 `gradio==4.44.0`
8
+ - [ ] `README_HF.md` frontmatter 中 `sdk_version: "4.44.0"`
9
+
10
+ ### 2. 文件结构确认
11
+ - [ ] `app.py` 存在且正确
12
+ - [ ] `frontend/dist/` 已构建(运行 `cd frontend && npm run build`)
13
+ - [ ] `data/` 目录存在
14
+ - [ ] `generated_images/` 目录存在
15
+
16
+ ### 3. 环境变量配置
17
+ 在 Space Settings → Repository secrets 中配置:
18
+ - [ ] `ZHIPU_API_KEY` - 必需
19
+ - [ ] `MINIMAX_API_KEY` - 可选
20
+ - [ ] `MINIMAX_GROUP_ID` - 可选
21
+
22
+ ## 🚀 部署步骤
23
+
24
+ ### 方法 1: 使用 deploy_to_hf.sh (推荐)
25
+
26
+ ```bash
27
+ # 1. 确保脚本可执行
28
+ chmod +x deploy_to_hf.sh
29
+
30
+ # 2. 运行部署脚本
31
+ ./deploy_to_hf.sh
32
+ ```
33
+
34
+ ### 方法 2: 手动部署
35
+
36
+ ```bash
37
+ # 1. 构建前端
38
+ cd frontend
39
+ npm install
40
+ npm run build
41
+ cd ..
42
+
43
+ # 2. 提交到 Git
44
+ git add .
45
+ git commit -m "Deploy to Hugging Face Spaces"
46
+
47
+ # 3. 推送到 Hugging Face
48
+ git push hf main
49
+ ```
50
+
51
+ ## 🐛 常见问题
52
+
53
+ ### ImportError: cannot import name 'HfFolder'
54
+
55
+ **原因:** `gradio` 和 `huggingface_hub` 版本不兼容
56
+
57
+ **解决方法:**
58
+ 1. 确认 `requirements_hf.txt` 版本正确
59
+ 2. 在 Space Settings 中点击 "Factory reboot"
60
+ 3. 查看 Container logs 确认安装的版本
61
+
62
+ ### 前端 404 错误
63
+
64
+ **原因:** 前端未构建或未正确挂载
65
+
66
+ **解决方法:**
67
+ 1. 本地运行 `cd frontend && npm run build`
68
+ 2. 确认 `frontend/dist/` 目录存在且有内容
69
+ 3. 提交并推送 `frontend/dist/` 到仓库
70
+
71
+ ### API 调用失败
72
+
73
+ **原因:** 环境变量未配置
74
+
75
+ **解决方法:**
76
+ 1. 在 Space Settings → Repository secrets 添加 `ZHIPU_API_KEY`
77
+ 2. 重启 Space
78
+ 3. 查看 Logs 确认 API 密钥已加载
79
+
80
+ ## 📊 部署后验证
81
+
82
+ ### 1. 健康检查
83
+ 访问 `https://your-space.hf.space/health` 应返回:
84
+ ```json
85
+ {
86
+ "status": "healthy",
87
+ "timestamp": "..."
88
+ }
89
+ ```
90
+
91
+ ### 2. API 文档
92
+ 访问 `https://your-space.hf.space/docs` 查看 API 文档
93
+
94
+ ### 3. 前端访问
95
+ 访问 `https://your-space.hf.space/` 应显示应用界面
96
+
97
+ ### 4. 功能测试
98
+ - [ ] 首页输入框可以输入文字
99
+ - [ ] 点击麦克风可以录音(需要浏览器权限)
100
+ - [ ] 点击 AI 形象显示对话框
101
+ - [ ] 底部导航可以切换页面
102
+
103
+ ## 🔄 更新部署
104
+
105
+ ### 代码更新
106
+ ```bash
107
+ git add .
108
+ git commit -m "Update: description"
109
+ git push hf main
110
+ ```
111
+
112
+ ### 强制重建
113
+ 如果遇到缓存问题:
114
+ 1. 进入 Space Settings
115
+ 2. 点击 "Factory reboot"
116
+ 3. 等待重新构建完成
117
+
118
+ ## 📝 版本兼容性
119
+
120
+ ### 已测试的稳定组合
121
+
122
+ | gradio | huggingface-hub | Python | 状态 |
123
+ |--------|----------------|--------|------|
124
+ | 4.44.0 | 0.23.5 | 3.11 | ✅ 推荐 |
125
+ | 4.36.1 | 0.23.0 | 3.11 | ✅ 可用 |
126
+ | 5.x | latest | 3.11 | ❌ 不兼容 |
127
+
128
+ ### 不兼容的组合
129
+
130
+ - `gradio==4.x` + `huggingface-hub>=0.24.0` → HfFolder 错误
131
+ - `gradio==5.x` + `huggingface-hub<0.24.0` → 版本冲突
132
+
133
+ ## 🔗 相关资源
134
+
135
+ - [Hugging Face Spaces 文档](https://huggingface.co/docs/hub/spaces)
136
+ - [Gradio 文档](https://www.gradio.app/docs)
137
+ - [项目 README](./README.md)
deployment/Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # 安装系统依赖
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # 复制依赖文件
11
+ COPY requirements.txt .
12
+
13
+ # 安装 Python 依赖
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # 复制应用代码
17
+ COPY app/ ./app/
18
+ COPY data/ ./data/
19
+ COPY frontend/dist/ ./frontend/dist/
20
+
21
+ # 复制启动脚本
22
+ COPY start.py .
23
+
24
+ # 创建必要的目录
25
+ RUN mkdir -p generated_images logs
26
+
27
+ # 暴露端口
28
+ EXPOSE 7860
29
+
30
+ # 启动应用
31
+ CMD ["python", "start.py"]
deployment/README_HF.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Nora - 治愈系记录助手
3
+ emoji: 🌟
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # 🌟 治愈系记录助手 - SoulMate AI Companion
12
+
13
+ 一个温暖、治愈的 AI 陪伴应用,帮助你记录心情、捕捉灵感、管理待办。
14
+
15
+ ## ✨ 核心特性
16
+
17
+ - 🎤 **语音/文字快速记录** - 自动分类保存
18
+ - 🤖 **AI 语义解析** - 智能提取情绪、灵感和待办
19
+ - 💬 **AI 对话陪伴(RAG)** - 基于历史记录的个性化对话
20
+ - 🖼️ **AI 形象定制** - 生成专属治愈系角色(720 种组合)
21
+ - 🫧 **物理引擎心情池** - 基于 Matter.js 的动态气泡可视化
22
+
23
+ ## 🚀 快速开始
24
+
25
+ ### 在线使用
26
+
27
+ 直接访问本 Space 即可使用完整功能!
28
+
29
+ ### 配置 API 密钥
30
+
31
+ 在 Space 的 **Settings → Repository secrets** 中配置:
32
+
33
+ **必需:**
34
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
35
+ - 获取地址:https://open.bigmodel.cn/
36
+ - 用途:语音识别、语义解析、AI 对话
37
+
38
+ **可选:**
39
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
40
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
41
+ - 获取地址:https://platform.minimaxi.com/
42
+ - 用途:AI 形象生成
43
+
44
+ ## 📖 使用说明
45
+
46
+ 1. **首页快速记录**
47
+ - 点击麦克风录音或在输入框输入文字
48
+ - AI 自动分析并分类保存
49
+
50
+ 2. **查看分类数据**
51
+ - 点击顶部心情、灵感、待办图标
52
+ - 查看不同类型的记录
53
+
54
+ 3. **与 AI 对话**
55
+ - 点击 AI 形象显示问候对话框
56
+ - 点击对话框中的聊天图标进入完整对话
57
+ - AI 基于你的历史记录提供个性化回复
58
+
59
+ 4. **定制 AI 形象**
60
+ - 点击右下角 ✨ 按钮
61
+ - 选择颜色、性格、外观、角色
62
+ - 生成专属形象(需要 MiniMax API)
63
+
64
+ 5. **心情气泡池**
65
+ - 点击顶部心情图标
66
+ - 左右滑动查看不同日期的心情卡片
67
+ - 点击卡片展开查看当天的气泡池
68
+ - 可以拖拽气泡,感受物理引擎效果
69
+
70
+ ## 📊 API 端点
71
+
72
+ - `POST /api/process` - 处理文本/语音输入
73
+ - `POST /api/chat` - 与 AI 对话(RAG)
74
+ - `GET /api/records` - 获取所有记录
75
+ - `GET /api/moods` - 获取情绪数据
76
+ - `GET /api/inspirations` - 获取灵感
77
+ - `GET /api/todos` - 获取待办事项
78
+ - `POST /api/character/generate` - 生成角色形象
79
+ - `GET /health` - 健康检查
80
+ - `GET /docs` - API 文档
81
+
82
+ ## 🔗 相关链接
83
+
84
+ - [GitHub 仓库](https://github.com/kernel-14/Nora)
85
+ - [详细文档](https://github.com/kernel-14/Nora/blob/main/README.md)
86
+ - [智谱 AI](https://open.bigmodel.cn/)
87
+ - [MiniMax](https://platform.minimaxi.com/)
88
+
89
+ ## 📝 技术栈
90
+
91
+ - **后端**: FastAPI + Python 3.11
92
+ - **前端**: React + TypeScript + Vite
93
+ - **物理引擎**: Matter.js
94
+ - **AI 服务**: 智谱 AI (GLM-4) + MiniMax
95
+ - **部署**: Hugging Face Spaces (Docker)
96
+
97
+ ## 📄 License
98
+
99
+ MIT License
deployment/README_MODELSCOPE.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🌟 治愈系记录助手 - SoulMate AI Companion
2
+
3
+ 一个温暖、治愈的 AI 陪伴应用,帮助你记录心情、捕捉灵感、管理待办。
4
+
5
+ ## ✨ 核心特性
6
+
7
+ - 🎤 **语音/文字快速记录** - 自动分类保存
8
+ - 🤖 **AI 语义解析** - 智能提取情绪、灵感和待办
9
+ - 💬 **AI 对话陪伴(RAG)** - 基于历史记录的个性化对话
10
+ - 🖼️ **AI 形象定制** - 生成专属治愈系角色(720 种组合)
11
+ - 🫧 **物理引擎心情池** - 基于 Matter.js 的动态气泡可视化
12
+
13
+ ## 🚀 快速开始
14
+
15
+ ### 在线使用
16
+
17
+ 直接访问本应用即可使用完整功能!
18
+
19
+ ### 配置 API 密钥
20
+
21
+ 在 ModelScope 的环境变量中配置:
22
+
23
+ **必需:**
24
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
25
+ - 获取地址:https://open.bigmodel.cn/
26
+ - 用途:语音识别、语义解析、AI 对话
27
+
28
+ **可选:**
29
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
30
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
31
+ - 获取地址:https://platform.minimaxi.com/
32
+ - 用途:AI 形象生成
33
+
34
+ ## 📖 使用说明
35
+
36
+ 1. **首页快速记录**
37
+ - 点击麦克风录音或在输入框输入文字
38
+ - AI 自动分析并分类保存
39
+
40
+ 2. **查看分类数据**
41
+ - 点击顶部心情、灵感、待办图标
42
+ - 查看不同类型的记录
43
+
44
+ 3. **与 AI 对话**
45
+ - 点击 AI 形象显示问候对话框
46
+ - 点击对话框中的聊天图标进入完整对话
47
+ - AI 基于你的历史记录提供个性化回复
48
+
49
+ 4. **定制 AI 形象**
50
+ - 点击右下角 ✨ 按钮
51
+ - 选择颜色、性格、外观、角色
52
+ - 生成专属形象(需要 MiniMax API)
53
+
54
+ 5. **心情气泡池**
55
+ - 点击顶部心情图标
56
+ - 左右滑动查看不同日期的心情卡片
57
+ - 点击卡片展开查看当天的气泡池
58
+ - 可以拖拽气泡,感受物理引擎效果
59
+
60
+ ## 📊 API 端点
61
+
62
+ - `POST /api/process` - 处理文本/语音输入
63
+ - `POST /api/chat` - 与 AI 对话(RAG)
64
+ - `GET /api/records` - 获取所有记录
65
+ - `GET /api/moods` - 获取情绪数据
66
+ - `GET /api/inspirations` - 获取灵感
67
+ - `GET /api/todos` - 获取待办事项
68
+ - `POST /api/character/generate` - 生成角色形象
69
+ - `GET /health` - 健康检查
70
+ - `GET /docs` - API 文档
71
+
72
+ ## 🔗 相关链接
73
+
74
+ - [GitHub 仓库](https://github.com/kernel-14/Nora)
75
+ - [详细文档](https://github.com/kernel-14/Nora/blob/main/README.md)
76
+ - [智谱 AI](https://open.bigmodel.cn/)
77
+ - [MiniMax](https://platform.minimaxi.com/)
78
+
79
+ ## 📝 技术栈
80
+
81
+ - **后端**: FastAPI + Python 3.11
82
+ - **前端**: React + TypeScript + Vite
83
+ - **物理引擎**: Matter.js
84
+ - **AI 服务**: 智谱 AI (GLM-4) + MiniMax
85
+ - **部署**: ModelScope (Gradio)
86
+
87
+ ## 📄 License
88
+
89
+ MIT License
90
+
91
+ ---
92
+
93
+ ## 🚀 部署到 ModelScope
94
+
95
+ ### 方法一:通过 Git 导入
96
+
97
+ 1. 在 ModelScope 创建新的应用空间
98
+ 2. 选择 "从 Git 导入"
99
+ 3. 输入仓库地址:`https://github.com/kernel-14/Nora.git`
100
+ 4. 选择 Gradio SDK
101
+ 5. 配置环境变量(见上方配置说明)
102
+ 6. 点击创建
103
+
104
+ ### 方法二:手动上传
105
+
106
+ 1. 克隆本仓库到本地
107
+ 2. 在 ModelScope 创建新的应用空间
108
+ 3. 上传所有文件
109
+ 4. 确保 `configuration.json` 和 `app_modelscope.py` 在根目录
110
+ 5. 配置环境变量
111
+ 6. 启动应用
112
+
113
+ ### 文件说明
114
+
115
+ - `app_modelscope.py` - ModelScope 入口文件
116
+ - `configuration.json` - ModelScope 配置文件
117
+ - `requirements_modelscope.txt` - Python 依赖(使用兼容的 Gradio 版本)
118
+ - `app/` - FastAPI 后端代码
119
+ - `frontend/dist/` - 前端构建产物
120
+ - `data/` - 数据存储目录
121
+
122
+ ### 注意事项
123
+
124
+ - 确保 `frontend/dist/` 目录已包含构建好的前端文件
125
+ - 环境变量必须正确配置才能使用 AI 功能
126
+ - ModelScope 使用 Gradio 4.44.1 版本以避免依赖冲突
deployment/app_modelscope.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ModelScope 部署入口文件
3
+ 使用 Gradio 包装 FastAPI 应用
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+ import gradio as gr
10
+
11
+ # 添加项目根目录到 Python 路径
12
+ sys.path.insert(0, str(Path(__file__).parent))
13
+
14
+ # 设置环境变量
15
+ os.environ.setdefault("DATA_DIR", "data")
16
+ os.environ.setdefault("LOG_LEVEL", "INFO")
17
+
18
+ # 确保数据目录存在
19
+ data_dir = Path("data")
20
+ data_dir.mkdir(exist_ok=True)
21
+
22
+ generated_images_dir = Path("generated_images")
23
+ generated_images_dir.mkdir(exist_ok=True)
24
+
25
+ # 导入 FastAPI 应用
26
+ from app.main import app as fastapi_app
27
+ from fastapi.staticfiles import StaticFiles
28
+ from fastapi.responses import FileResponse
29
+
30
+ # 挂载前端静态文件
31
+ frontend_dist = Path(__file__).parent / "frontend" / "dist"
32
+ if frontend_dist.exists():
33
+ # 挂载静态资源(CSS, JS)
34
+ assets_dir = frontend_dist / "assets"
35
+ if assets_dir.exists():
36
+ fastapi_app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
37
+ print(f"✅ 前端资源文件已挂载: {assets_dir}")
38
+
39
+ print(f"✅ 前端应用已挂载: {frontend_dist}")
40
+ else:
41
+ print(f"⚠️ 前端构建目录不存在: {frontend_dist}")
42
+
43
+ # 重写根路由以服务前端
44
+ @fastapi_app.get("/", include_in_schema=False)
45
+ async def serve_root():
46
+ """服务前端应用首页"""
47
+ if frontend_dist.exists():
48
+ index_file = frontend_dist / "index.html"
49
+ if index_file.exists():
50
+ return FileResponse(index_file)
51
+ return {
52
+ "service": "SoulMate AI Companion",
53
+ "status": "running",
54
+ "version": "1.0.0",
55
+ "message": "Welcome! Visit /docs for API documentation."
56
+ }
57
+
58
+ # 添加 catch-all 路由用于 SPA
59
+ @fastapi_app.get("/{full_path:path}", include_in_schema=False)
60
+ async def serve_spa(full_path: str):
61
+ """服务前端应用(SPA 路由支持)"""
62
+ # 如果是 API 路径,跳过
63
+ if full_path.startswith("api/") or full_path == "docs" or full_path == "openapi.json" or full_path == "health":
64
+ from fastapi import HTTPException
65
+ raise HTTPException(status_code=404, detail="Not found")
66
+
67
+ # 返回前端 index.html
68
+ if frontend_dist.exists():
69
+ index_file = frontend_dist / "index.html"
70
+ if index_file.exists():
71
+ return FileResponse(index_file)
72
+
73
+ return {"error": "Frontend not found"}
74
+
75
+ # 创建 Gradio 界面(用于 ModelScope 的展示)
76
+ with gr.Blocks(
77
+ title="治愈系记录助手 - SoulMate AI Companion",
78
+ theme=gr.themes.Soft(
79
+ primary_hue="purple",
80
+ secondary_hue="pink",
81
+ ),
82
+ ) as demo:
83
+
84
+ gr.Markdown("""
85
+ # 🌟 治愈系记录助手 - SoulMate AI Companion
86
+
87
+ 一个温暖、治愈的 AI 陪伴应用,帮助你记录心情、捕捉灵感、管理待办。
88
+
89
+ ### ✨ 核心特性
90
+ - 🎤 **语音/文字快速记录** - 自动分类保存
91
+ - 🤖 **AI 语义解析** - 智能提取情绪、灵感和待办
92
+ - 💬 **AI 对话陪伴(RAG)** - 基于历史记录的个性化对话
93
+ - 🖼️ **AI 形象定制** - 生成专属治愈系角色(720 种组合)
94
+ - 🫧 **物理引擎心情池** - 基于 Matter.js 的动态气泡可视化
95
+
96
+ ---
97
+
98
+ ### 🚀 开始使用
99
+
100
+ **🎯 前端应用地址:** 点击上方的 "App" 标签页访问完整应用
101
+
102
+ **📚 API 文档:** [FastAPI Swagger Docs →](/docs)
103
+
104
+ ---
105
+
106
+ ### 📖 使用说明
107
+
108
+ 1. **首页快速记录**
109
+ - 点击麦克风录音或在输入框输入文字
110
+ - AI 自动分析并分类保存
111
+
112
+ 2. **查看分类数据**
113
+ - 点击顶部心情、灵感、待办图标
114
+ - 查看不同类型的记录
115
+
116
+ 3. **与 AI 对话**
117
+ - 点击 AI 形象显示问候对话框
118
+ - 点击对话框中的聊天图标进入完整对话
119
+ - AI 基于你的历史记录提供个性化回复
120
+
121
+ 4. **定制 AI 形象**
122
+ - 点击右下角 ✨ 按钮
123
+ - 选择颜色、性格、外观、角色
124
+ - 生成专属形象(需要 MiniMax API)
125
+
126
+ 5. **心情气泡池**
127
+ - 点击顶部心情图标
128
+ - 左右滑动查看不同日期的心情卡片
129
+ - 点击卡片展开查看当天的气泡池
130
+ - 可以拖拽气泡,感受物理引擎效果
131
+
132
+ ---
133
+
134
+ ### ⚙️ 配置说明
135
+
136
+ 需要在 ModelScope 的环境变量中配置:
137
+
138
+ **必需:**
139
+ - `ZHIPU_API_KEY` - 智谱 AI API 密钥
140
+ - 获取地址:https://open.bigmodel.cn/
141
+ - 用途:语音识别、语义解析、AI 对话
142
+
143
+ **可选:**
144
+ - `MINIMAX_API_KEY` - MiniMax API 密钥
145
+ - `MINIMAX_GROUP_ID` - MiniMax Group ID
146
+ - 获取地址:https://platform.minimaxi.com/
147
+ - 用途:AI 形象生成
148
+
149
+ ---
150
+
151
+ ### 🔗 相关链接
152
+ - [GitHub 仓库](https://github.com/kernel-14/Nora)
153
+ - [详细文档](https://github.com/kernel-14/Nora/blob/main/README.md)
154
+ - [智谱 AI](https://open.bigmodel.cn/)
155
+ - [MiniMax](https://platform.minimaxi.com/)
156
+
157
+ ---
158
+
159
+ ### 📊 API 端点
160
+
161
+ - `POST /api/process` - 处理文本/语音输入
162
+ - `POST /api/chat` - 与 AI 对话(RAG)
163
+ - `GET /api/records` - 获取所有记录
164
+ - `GET /api/moods` - 获取情绪数据
165
+ - `GET /api/inspirations` - 获取灵感
166
+ - `GET /api/todos` - 获取待办事项
167
+ - `POST /api/character/generate` - 生成角色形象
168
+ - `GET /health` - 健康检查
169
+ - `GET /docs` - API 文档
170
+ """)
171
+
172
+ # 挂载 FastAPI 到 Gradio
173
+ app = gr.mount_gradio_app(fastapi_app, demo, path="/gradio")
174
+
175
+ # 如果直接运行此文件
176
+ if __name__ == "__main__":
177
+ import uvicorn
178
+ print("=" * 50)
179
+ print("🌟 治愈系记录助手 - SoulMate AI Companion")
180
+ print("=" * 50)
181
+ print(f"📍 前端应用: http://0.0.0.0:7860/")
182
+ print(f"📚 Gradio 界面: http://0.0.0.0:7860/gradio")
183
+ print(f"📖 API 文档: http://0.0.0.0:7860/docs")
184
+ print(f"🔍 健康检查: http://0.0.0.0:7860/health")
185
+ print("=" * 50)
186
+
187
+ uvicorn.run(app, host="0.0.0.0", port=7860)
deployment/configuration.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "framework": "Gradio",
3
+ "task": "chat",
4
+ "allow_remote_code": true
5
+ }
deployment/deploy_to_hf.bat ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ chcp 65001 >nul
3
+ echo 🚀 开始部署到 Hugging Face Spaces...
4
+ echo.
5
+
6
+ REM 检查是否已登录
7
+ huggingface-cli whoami >nul 2>&1
8
+ if errorlevel 1 (
9
+ echo ❌ 请先登录 Hugging Face CLI
10
+ echo 运行: huggingface-cli login
11
+ pause
12
+ exit /b 1
13
+ )
14
+
15
+ REM 获取用户名
16
+ for /f "tokens=2" %%i in ('huggingface-cli whoami ^| findstr "username:"') do set USERNAME=%%i
17
+ echo ✅ 已登录为: %USERNAME%
18
+ echo.
19
+
20
+ REM 询问 Space 名称
21
+ set /p SPACE_NAME="请输入 Space 名称 (默认: soulmate-ai-companion): "
22
+ if "%SPACE_NAME%"=="" set SPACE_NAME=soulmate-ai-companion
23
+
24
+ echo.
25
+ echo 📦 准备文件...
26
+
27
+ REM 构建前端
28
+ echo 🔨 构建前端...
29
+ cd frontend
30
+ call npm install
31
+ call npm run build
32
+ cd ..
33
+
34
+ if not exist "frontend\dist" (
35
+ echo ❌ 前端构建失败
36
+ pause
37
+ exit /b 1
38
+ )
39
+
40
+ echo ✅ 前端构建完成
41
+ echo.
42
+
43
+ REM 创建临时目录
44
+ set TEMP_DIR=temp_hf_deploy
45
+ if exist %TEMP_DIR% rmdir /s /q %TEMP_DIR%
46
+ mkdir %TEMP_DIR%
47
+
48
+ REM 复制文件
49
+ echo 📋 复制文件...
50
+ copy app.py %TEMP_DIR%\
51
+ copy requirements_hf.txt %TEMP_DIR%\requirements.txt
52
+ copy README_HF.md %TEMP_DIR%\README.md
53
+ copy .gitattributes %TEMP_DIR%\
54
+ xcopy /E /I /Y app %TEMP_DIR%\app
55
+ xcopy /E /I /Y frontend\dist %TEMP_DIR%\frontend
56
+ mkdir %TEMP_DIR%\data
57
+ mkdir %TEMP_DIR%\generated_images
58
+
59
+ REM 创建或克隆 Space
60
+ echo 🌐 准备 Space...
61
+ set SPACE_URL=https://huggingface.co/spaces/%USERNAME%/%SPACE_NAME%
62
+
63
+ huggingface-cli repo info spaces/%USERNAME%/%SPACE_NAME% >nul 2>&1
64
+ if errorlevel 1 (
65
+ echo 🆕 创建新 Space...
66
+ huggingface-cli repo create %SPACE_NAME% --type space --space_sdk gradio
67
+ ) else (
68
+ echo ✅ Space 已存在
69
+ )
70
+
71
+ cd %TEMP_DIR%
72
+ git clone %SPACE_URL% .
73
+
74
+ REM 复制文件到仓库
75
+ echo 📤 准备上传...
76
+ copy ..\app.py .
77
+ copy ..\requirements_hf.txt requirements.txt
78
+ copy ..\README_HF.md README.md
79
+ copy ..\.gitattributes .
80
+ xcopy /E /I /Y ..\app app
81
+ xcopy /E /I /Y ..\frontend\dist frontend
82
+ if not exist data mkdir data
83
+ if not exist generated_images mkdir generated_images
84
+
85
+ REM 提交并推送
86
+ echo 🚀 上传到 Hugging Face...
87
+ git add .
88
+ git commit -m "Deploy to Hugging Face Spaces"
89
+ git push
90
+
91
+ cd ..
92
+ rmdir /s /q %TEMP_DIR%
93
+
94
+ echo.
95
+ echo ✅ 部署完成!
96
+ echo.
97
+ echo 📍 Space URL: %SPACE_URL%
98
+ echo.
99
+ echo ⚙️ 下一步:
100
+ echo 1. 访问 %SPACE_URL%
101
+ echo 2. 点击 Settings → Repository secrets
102
+ echo 3. 添加环境变量:
103
+ echo - ZHIPU_API_KEY (必需)
104
+ echo - MINIMAX_API_KEY (可选)
105
+ echo - MINIMAX_GROUP_ID (可选)
106
+ echo.
107
+ echo 🎉 完成后即可使用!
108
+ echo.
109
+ pause
deployment/deploy_to_hf.sh ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Hugging Face Spaces 快速部署脚本
4
+
5
+ echo "🚀 开始部署到 Hugging Face Spaces..."
6
+
7
+ # 检查是否已登录
8
+ if ! huggingface-cli whoami &> /dev/null; then
9
+ echo "❌ 请先登录 Hugging Face CLI"
10
+ echo "运行: huggingface-cli login"
11
+ exit 1
12
+ fi
13
+
14
+ # 获取用户名
15
+ USERNAME=$(huggingface-cli whoami | grep "username:" | awk '{print $2}')
16
+ echo "✅ 已登录为: $USERNAME"
17
+
18
+ # 询问 Space 名称
19
+ read -p "请输入 Space 名称 (默认: soulmate-ai-companion): " SPACE_NAME
20
+ SPACE_NAME=${SPACE_NAME:-soulmate-ai-companion}
21
+
22
+ echo "📦 准备文件..."
23
+
24
+ # 构建前端
25
+ echo "🔨 构建前端..."
26
+ cd frontend
27
+ npm install
28
+ npm run build
29
+ cd ..
30
+
31
+ if [ ! -d "frontend/dist" ]; then
32
+ echo "❌ 前端构建失败"
33
+ exit 1
34
+ fi
35
+
36
+ echo "✅ 前端构建完成"
37
+
38
+ # 创建临时目录
39
+ TEMP_DIR="temp_hf_deploy"
40
+ rm -rf $TEMP_DIR
41
+ mkdir -p $TEMP_DIR
42
+
43
+ # 复制文件
44
+ echo "📋 复制文件..."
45
+ cp app.py $TEMP_DIR/
46
+ cp requirements_hf.txt $TEMP_DIR/requirements.txt
47
+ cp README_HF.md $TEMP_DIR/README.md
48
+ cp .gitattributes $TEMP_DIR/
49
+ cp -r app $TEMP_DIR/
50
+ cp -r frontend/dist $TEMP_DIR/frontend/
51
+ mkdir -p $TEMP_DIR/data
52
+ mkdir -p $TEMP_DIR/generated_images
53
+
54
+ # 创建或克隆 Space
55
+ echo "🌐 准备 Space..."
56
+ SPACE_URL="https://huggingface.co/spaces/$USERNAME/$SPACE_NAME"
57
+
58
+ if huggingface-cli repo info "spaces/$USERNAME/$SPACE_NAME" &> /dev/null; then
59
+ echo "✅ Space 已存在,克隆中..."
60
+ cd $TEMP_DIR
61
+ git clone $SPACE_URL .
62
+ else
63
+ echo "🆕 创建新 Space..."
64
+ huggingface-cli repo create $SPACE_NAME --type space --space_sdk gradio
65
+ cd $TEMP_DIR
66
+ git clone $SPACE_URL .
67
+ fi
68
+
69
+ # 复制文件到仓库
70
+ echo "📤 准备上传..."
71
+ cp ../app.py .
72
+ cp ../requirements_hf.txt ./requirements.txt
73
+ cp ../README_HF.md ./README.md
74
+ cp ../.gitattributes .
75
+ cp -r ../app .
76
+ cp -r ../frontend/dist ./frontend/
77
+ mkdir -p data generated_images
78
+
79
+ # 提交并推送
80
+ echo "🚀 上传到 Hugging Face..."
81
+ git add .
82
+ git commit -m "Deploy to Hugging Face Spaces"
83
+ git push
84
+
85
+ cd ..
86
+ rm -rf $TEMP_DIR
87
+
88
+ echo ""
89
+ echo "✅ 部署完成!"
90
+ echo ""
91
+ echo "📍 Space URL: $SPACE_URL"
92
+ echo ""
93
+ echo "⚙️ 下一步:"
94
+ echo "1. 访问 $SPACE_URL"
95
+ echo "2. 点击 Settings → Repository secrets"
96
+ echo "3. 添加环境变量:"
97
+ echo " - ZHIPU_API_KEY (必需)"
98
+ echo " - MINIMAX_API_KEY (可选)"
99
+ echo " - MINIMAX_GROUP_ID (可选)"
100
+ echo ""
101
+ echo "🎉 完成后即可使用!"
deployment/ms_deploy.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://modelscope.cn/api/v1/studios/deploy_schema.json",
3
+ "sdk_type": "gradio",
4
+ "sdk_version": "4.44.1",
5
+ "resource_configuration": "platform/2v-cpu-16g-mem",
6
+ "base_image": "ubuntu22.04-py311-torch2.3.1-modelscope1.31.0",
7
+ "environment_variables": [
8
+ {
9
+ "name": "ZHIPU_API_KEY",
10
+ "value": ""
11
+ },
12
+ {
13
+ "name": "MINIMAX_API_KEY",
14
+ "value": ""
15
+ },
16
+ {
17
+ "name": "MINIMAX_GROUP_ID",
18
+ "value": ""
19
+ },
20
+ {
21
+ "name": "DATA_DIR",
22
+ "value": "data"
23
+ },
24
+ {
25
+ "name": "LOG_LEVEL",
26
+ "value": "INFO"
27
+ }
28
+ ]
29
+ }
deployment/requirements_hf.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Requirements
2
+ # Using latest stable versions
3
+
4
+ # Core Gradio - use latest version which is compatible with new huggingface-hub
5
+ gradio==5.9.1
6
+
7
+ # Core dependencies (compatible with Python 3.11+)
8
+ fastapi==0.115.0
9
+ uvicorn[standard]==0.32.0
10
+ pydantic==2.10.0
11
+ pydantic-settings==2.6.0
12
+ httpx==0.27.0
13
+ python-multipart==0.0.12
14
+ python-dotenv==1.0.1
15
+
16
+ # Additional dependencies
17
+ aiofiles==24.1.0
deployment/requirements_modelscope.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ModelScope 部署依赖
2
+ # 使用兼容的 Gradio 版本
3
+
4
+ # Gradio - 使用稳定版本
5
+ gradio==4.44.1
6
+
7
+ # Core dependencies (compatible with Python 3.11+)
8
+ fastapi==0.115.0
9
+ uvicorn[standard]==0.32.0
10
+ pydantic==2.10.0
11
+ pydantic-settings==2.6.0
12
+ httpx==0.27.0
13
+ python-multipart==0.0.12
14
+ python-dotenv==1.0.1
15
+
16
+ # Additional dependencies
17
+ aiofiles==24.1.0
docs/API_配置说明.md ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API 配置说明
2
+
3
+ ## 自动检测 API 地址
4
+
5
+ 前端应用会自动检测运行环境并配置正确的 API 地址。
6
+
7
+ ### 支持的环境
8
+
9
+ #### 1. 生产环境(自动检测)
10
+
11
+ **Hugging Face Spaces:**
12
+ - 域名包含:`hf.space`, `huggingface.co`, `gradio.live`
13
+ - API 地址:使用相同的协议和域名
14
+ - 示例:`https://huggingface.co/spaces/kernel14/Nora`
15
+ - 前端:`https://huggingface.co/spaces/kernel14/Nora`
16
+ - API:`https://huggingface.co/spaces/kernel14/Nora/api/...`
17
+
18
+ **ModelScope:**
19
+ - 域名包含:`modelscope.cn`
20
+ - API 地址:使用相同的协议和域名
21
+ - 示例:`https://modelscope.cn/studios/xxx/yyy`
22
+ - 前端:`https://modelscope.cn/studios/xxx/yyy`
23
+ - API:`https://modelscope.cn/studios/xxx/yyy/api/...`
24
+
25
+ #### 2. 局域网访问
26
+
27
+ **通过 IP 地址访问:**
28
+ - 前端:`http://192.168.1.100:5173`
29
+ - API:`http://192.168.1.100:8000`
30
+
31
+ **通过主机名访问:**
32
+ - 前端:`http://mycomputer.local:5173`
33
+ - API:`http://mycomputer.local:8000`
34
+
35
+ #### 3. 本地开发
36
+
37
+ **默认配置:**
38
+ - 前端:`http://localhost:5173`
39
+ - API:`http://localhost:8000`
40
+
41
+ ### 环境变量配置(可选)
42
+
43
+ 如果需要手动指定 API 地址,可以在前端项目中创建 `.env.local` 文件:
44
+
45
+ ```env
46
+ VITE_API_URL=https://your-custom-api-url.com
47
+ ```
48
+
49
+ ### 检测逻辑
50
+
51
+ ```typescript
52
+ const getApiBaseUrl = () => {
53
+ // 1. 优先使用环境变量
54
+ if (import.meta.env.VITE_API_URL) {
55
+ return import.meta.env.VITE_API_URL;
56
+ }
57
+
58
+ // 2. 检测生产环境(Hugging Face, ModelScope)
59
+ if (hostname.includes('hf.space') ||
60
+ hostname.includes('huggingface.co') ||
61
+ hostname.includes('modelscope.cn')) {
62
+ return `${protocol}//${hostname}`;
63
+ }
64
+
65
+ // 3. 检测局域网访问
66
+ if (hostname !== 'localhost' && hostname !== '127.0.0.1') {
67
+ return `${protocol}//${hostname}:8000`;
68
+ }
69
+
70
+ // 4. 默认本地开发
71
+ return 'http://localhost:8000';
72
+ };
73
+ ```
74
+
75
+ ### 调试
76
+
77
+ 打开浏览器控制台,查看 API 地址:
78
+
79
+ ```
80
+ 🔗 API Base URL: https://huggingface.co/spaces/kernel14/Nora
81
+ ```
82
+
83
+ ### 常见问题
84
+
85
+ **Q: 为什么其他设备无法访问?**
86
+
87
+ A: 确保:
88
+ 1. 后端服务器绑定到 `0.0.0.0` 而不是 `127.0.0.1`
89
+ 2. 防火墙允许端口 8000
90
+ 3. 使用正确的 IP 地址访问
91
+
92
+ **Q: Hugging Face 上 API 调用失败?**
93
+
94
+ A: 检查:
95
+ 1. 浏览器控制台的 API 地址是否正确
96
+ 2. 是否配置了必需的环境变量(`ZHIPU_API_KEY`)
97
+ 3. 查看 Space 的日志是否有错误
98
+
99
+ **Q: 如何测试 API 连接?**
100
+
101
+ A: 访问以下地址:
102
+ - 健康检查:`/health`
103
+ - API 文档:`/docs`
104
+ - 测试页面:`/test_api.html`
105
+
106
+ ### 部署检查清单
107
+
108
+ - [ ] 前端已重新构建(`npm run build`)
109
+ - [ ] `frontend/dist/` 已提交到 Git
110
+ - [ ] 环境变量已配置(Hugging Face Secrets / ModelScope 环境变量)
111
+ - [ ] Space 已重启
112
+ - [ ] 浏览器控制台显示正确的 API 地址
113
+ - [ ] 测试 API 调用是否成功
docs/FEATURE_SUMMARY.md ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Home Interaction Feature - Implementation Summary
2
+
3
+ ## Overview
4
+
5
+ This document summarizes the implementation of the home page interaction feature for the SoulMate AI Companion application. The feature includes two complementary functionalities:
6
+
7
+ 1. **Quick Recording** - Fast capture of thoughts, inspirations, and todos
8
+ 2. **AI Chat (RAG-Enhanced)** - Intelligent conversation with context awareness
9
+
10
+ ## Key Features
11
+
12
+ ### 1. Home Page Quick Recording
13
+
14
+ **Purpose:** Enable users to quickly record their thoughts through voice or text input.
15
+
16
+ **Workflow:**
17
+ ```
18
+ User Input (Voice/Text)
19
+
20
+ Call /api/process
21
+
22
+ AI Semantic Analysis
23
+
24
+ Save to records.json
25
+
26
+ Auto-split to:
27
+ - moods.json (emotions)
28
+ - inspirations.json (ideas)
29
+ - todos.json (tasks)
30
+ ```
31
+
32
+ **Characteristics:**
33
+ - ✅ One-time processing
34
+ - ✅ Automatic categorization
35
+ - ✅ Structured data output
36
+ - ✅ No conversation context needed
37
+
38
+ ### 2. AI Chat with RAG Enhancement
39
+
40
+ **Purpose:** Provide intelligent, warm companionship through context-aware conversations.
41
+
42
+ **Workflow:**
43
+ ```
44
+ User Message
45
+
46
+ Call /api/chat
47
+
48
+ Load Recent Records (last 10)
49
+
50
+ Build RAG Context
51
+
52
+ AI Generates Personalized Response
53
+
54
+ Return to User
55
+ ```
56
+
57
+ **Characteristics:**
58
+ - ✅ Each message calls API
59
+ - ✅ Uses RAG (Retrieval-Augmented Generation)
60
+ - ✅ Context from records.json
61
+ - ✅ Personalized, warm responses
62
+ - ✅ Conversation not saved
63
+
64
+ ## Technical Implementation
65
+
66
+ ### Backend Changes
67
+
68
+ #### File: `app/main.py`
69
+
70
+ **Updated `/api/chat` endpoint with RAG:**
71
+
72
+ ```python
73
+ @app.post("/api/chat")
74
+ async def chat_with_ai(text: str = Form(...)):
75
+ # Load user's records as RAG knowledge base
76
+ records = storage_service._read_json_file(storage_service.records_file)
77
+ recent_records = records[-10:] # Last 10 records
78
+
79
+ # Build context from records
80
+ context_parts = []
81
+ for record in recent_records:
82
+ context_entry = f"[{timestamp}] User said: {original_text}"
83
+ if mood:
84
+ context_entry += f"\nMood: {mood['type']}"
85
+ if inspirations:
86
+ context_entry += f"\nInspirations: {ideas}"
87
+ if todos:
88
+ context_entry += f"\nTodos: {tasks}"
89
+ context_parts.append(context_entry)
90
+
91
+ # Build system prompt with context
92
+ system_prompt = f"""You are a warm, empathetic AI companion.
93
+ You can reference the user's history to provide more caring responses:
94
+
95
+ {context_text}
96
+
97
+ Please respond with warmth and understanding based on this background."""
98
+
99
+ # Call AI API with context
100
+ response = await client.post(
101
+ "https://open.bigmodel.cn/api/paas/v4/chat/completions",
102
+ json={
103
+ "model": "glm-4-flash",
104
+ "messages": [
105
+ {"role": "system", "content": system_prompt},
106
+ {"role": "user", "content": text}
107
+ ]
108
+ }
109
+ )
110
+ ```
111
+
112
+ ### Frontend Changes
113
+
114
+ #### New Component: `frontend/components/HomeInput.tsx`
115
+
116
+ **Features:**
117
+ - Large circular microphone button with gradient
118
+ - Text input field
119
+ - Real-time processing status
120
+ - Success/error animations
121
+ - Auto-refresh data on completion
122
+
123
+ **Key Functions:**
124
+
125
+ ```typescript
126
+ // Voice recording
127
+ const startRecording = async () => {
128
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
129
+ const mediaRecorder = new MediaRecorder(stream);
130
+ // Recording logic...
131
+ };
132
+
133
+ // Process audio
134
+ const processAudio = async (audioBlob: Blob) => {
135
+ const file = new File([audioBlob], 'recording.webm');
136
+ await apiService.processInput(file);
137
+ setShowSuccess(true);
138
+ onRecordComplete();
139
+ };
140
+
141
+ // Process text
142
+ const processText = async () => {
143
+ await apiService.processInput(undefined, textInput);
144
+ setTextInput('');
145
+ setShowSuccess(true);
146
+ onRecordComplete();
147
+ };
148
+ ```
149
+
150
+ #### Updated: `frontend/App.tsx`
151
+
152
+ Integrated HomeInput component into the home page:
153
+
154
+ ```typescript
155
+ <div className="flex-1 flex flex-col items-center justify-center">
156
+ <AIEntity imageUrl={characterImageUrl} />
157
+
158
+ {/* Home Input Component */}
159
+ <div className="mt-8 w-full">
160
+ <HomeInput onRecordComplete={loadAllData} />
161
+ </div>
162
+ </div>
163
+ ```
164
+
165
+ ## Feature Comparison
166
+
167
+ | Feature | Quick Recording | AI Chat |
168
+ |---------|----------------|---------|
169
+ | **Purpose** | Record thoughts | Intelligent companionship |
170
+ | **API Endpoint** | `/api/process` | `/api/chat` |
171
+ | **Call Frequency** | One-time | Per message |
172
+ | **Knowledge Base** | Not used | Uses RAG |
173
+ | **Output** | Structured data | Natural language |
174
+ | **Storage** | Auto-save to files | Not saved |
175
+ | **Context** | No context needed | Based on history |
176
+
177
+ ## Files Modified/Created
178
+
179
+ ### New Files
180
+
181
+ 1. **frontend/components/HomeInput.tsx** - Home input component
182
+ 2. **test_home_input.py** - Feature test script
183
+ 3. **首页交互功能说明.md** - Detailed documentation (Chinese)
184
+ 4. **新功能实现总结.md** - Implementation summary (Chinese)
185
+ 5. **快速开始-新功能.md** - Quick start guide (Chinese)
186
+ 6. **功能架构图.md** - Architecture diagrams (Chinese)
187
+ 7. **FEATURE_SUMMARY.md** - This file
188
+
189
+ ### Modified Files
190
+
191
+ 1. **app/main.py** - Updated `/api/chat` with RAG
192
+ 2. **frontend/App.tsx** - Integrated HomeInput component
193
+ 3. **README.md** - Updated documentation
194
+
195
+ ## Usage Examples
196
+
197
+ ### Example 1: Quick Recording
198
+
199
+ ```
200
+ User Input:
201
+ "Today I'm feeling great. Had a new idea for an app. Need to buy books tomorrow."
202
+
203
+ System Processing:
204
+ ✓ Call /api/process
205
+ ✓ Semantic analysis
206
+ ✓ Save to records.json
207
+ ✓ Split to:
208
+ - moods.json: feeling great
209
+ - inspirations.json: new app idea
210
+ - todos.json: buy books tomorrow
211
+ ✓ Show "Record Successful"
212
+ ```
213
+
214
+ ### Example 2: AI Chat with RAG
215
+
216
+ ```
217
+ User: "What have I been doing lately?"
218
+
219
+ AI (based on history):
220
+ "From your records, you've been working on a project. Although work
221
+ has been tiring, you felt accomplished after completing it. You also
222
+ plan to wake up early tomorrow for a run. Great plans!"
223
+
224
+ User: "How's my mood been?"
225
+
226
+ AI:
227
+ "Your mood has had ups and downs. You felt tired during work, but
228
+ happy after completing tasks. Overall, you're a positive person who
229
+ finds joy in achievements even when tired. Keep it up!"
230
+ ```
231
+
232
+ ## Testing
233
+
234
+ ### Run Test Script
235
+
236
+ ```bash
237
+ # Ensure backend is running
238
+ python -m uvicorn app.main:app --reload
239
+
240
+ # Run tests in another terminal
241
+ python test_home_input.py
242
+ ```
243
+
244
+ ### Test Coverage
245
+
246
+ 1. ✅ Home text input recording
247
+ 2. ✅ AI chat without history
248
+ 3. ✅ AI chat with RAG enhancement
249
+ 4. ✅ Retrieve records
250
+
251
+ ## Performance Considerations
252
+
253
+ ### Frontend Optimizations
254
+
255
+ - Debounce input handling
256
+ - Optimistic updates
257
+ - Component lazy loading
258
+ - Result caching
259
+
260
+ ### Backend Optimizations
261
+
262
+ - Async processing (async/await)
263
+ - Connection pool reuse
264
+ - Limit history records (10 items)
265
+ - Response compression
266
+
267
+ ### RAG Optimizations
268
+
269
+ - Load only recent records
270
+ - Streamline context information
271
+ - Cache common queries
272
+ - Vector database (future enhancement)
273
+
274
+ ## Security & Privacy
275
+
276
+ ### API Key Protection
277
+
278
+ - Stored in `.env` file
279
+ - Not committed to version control
280
+ - Auto-filtered in logs
281
+
282
+ ### Input Validation
283
+
284
+ - Frontend basic format validation
285
+ - Backend Pydantic model validation
286
+ - File size and format restrictions
287
+
288
+ ### Data Privacy
289
+
290
+ - Local storage only
291
+ - No external data sharing
292
+ - Consider encryption for sensitive data
293
+
294
+ ## Future Enhancements
295
+
296
+ ### Short-term
297
+
298
+ - [ ] Multi-turn conversation history
299
+ - [ ] Voice synthesis (AI voice response)
300
+ - [ ] Emotion analysis visualization
301
+ - [ ] Smart recommendations
302
+
303
+ ### Long-term
304
+
305
+ - [ ] Vector database for better RAG
306
+ - [ ] Semantic similarity search
307
+ - [ ] Knowledge graph
308
+ - [ ] Multi-modal support (images, video)
309
+ - [ ] User profiling
310
+ - [ ] Personalization engine
311
+
312
+ ## Deployment
313
+
314
+ ### Frontend
315
+
316
+ No additional configuration needed. HomeInput component is integrated into App.tsx.
317
+
318
+ ### Backend
319
+
320
+ No additional configuration needed. RAG functionality is integrated into existing `/api/chat` endpoint.
321
+
322
+ ### Requirements
323
+
324
+ - Python 3.8+
325
+ - Node.js 16+
326
+ - Zhipu AI API Key (required)
327
+
328
+ ## Troubleshooting
329
+
330
+ ### Issue: Voice recording not working
331
+
332
+ **Solution:**
333
+ - Check browser support (Chrome/Edge recommended)
334
+ - Allow microphone permissions
335
+ - Use HTTPS or localhost
336
+
337
+ ### Issue: Records not saving
338
+
339
+ **Solution:**
340
+ - Check if backend is running: `curl http://localhost:8000/health`
341
+ - Check browser console for errors
342
+ - Check backend logs: `tail -f logs/app.log`
343
+
344
+ ### Issue: AI chat not using history
345
+
346
+ **Solution:**
347
+ - Ensure records exist in `data/records.json`
348
+ - Ask more specific questions like "What did I do yesterday?"
349
+ - Check backend logs for "AI chat successful with RAG context"
350
+
351
+ ## Conclusion
352
+
353
+ This implementation successfully adds two complementary features:
354
+
355
+ 1. **Quick Recording** - Simple, direct, efficient thought capture
356
+ 2. **AI Chat** - Intelligent, warm, personalized companionship
357
+
358
+ Through RAG technology, the AI chat can provide context-aware responses based on user history, creating a truly "understanding" companion experience.
359
+
360
+ The features work together to provide a complete recording and companionship experience:
361
+ - Quick recording for capturing thoughts
362
+ - AI chat for intelligent companionship
363
+
364
+ ---
365
+
366
+ **Implementation Complete!** 🎉
367
+
368
+ For questions or further optimization needs, please refer to the detailed documentation or contact the development team.
docs/README.md ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 文档目录
2
+
3
+ 本目录包含项目的详细技术文档。
4
+
5
+ ## 📚 文档列表
6
+
7
+ ### 核心文档
8
+
9
+ - **[功能架构图.md](功能架构图.md)** - 系统架构、数据流向、组件关系图
10
+ - **[FEATURE_SUMMARY.md](FEATURE_SUMMARY.md)** - 功能实现总结(英文)
11
+
12
+ ### 故障排查
13
+
14
+ - **[后端启动问题排查.md](后端启动问题排查.md)** - 后端启动常见问题和解决方案
15
+ - **[语音录制问题排查.md](语音录制问题排查.md)** - 语音录制功能的使用和故障排查
16
+
17
+ ## 🔗 相关文档
18
+
19
+ ### 根目录文档
20
+
21
+ - **[README.md](../README.md)** - 项目主文档
22
+ - **[PRD.md](../PRD.md)** - 产品需求文档
23
+
24
+ ### 测试文件
25
+
26
+ - **[test_home_input.py](../test_home_input.py)** - 首页输入功能测试
27
+ - **[test_audio_recording.html](../test_audio_recording.html)** - 音频录制测试页面
28
+ - **[诊断环境.py](../诊断环境.py)** - 环境诊断脚本
29
+
30
+ ## 📖 快速导航
31
+
32
+ ### 我想...
33
+
34
+ - **启动应用** → 查看 [README.md](../README.md) 的"快速开始"部分
35
+ - **解决启动问题** → 查看 [后端启动问题排查.md](后端启动问题排查.md)
36
+ - **了解语音录制** → 查看 [语音录制问题排查.md](语音录制问题排查.md)
37
+ - **了解系统架构** → 查看 [功能架构图.md](功能架构图.md)
38
+ - **查看功能实现** → 查看 [FEATURE_SUMMARY.md](FEATURE_SUMMARY.md)
39
+
40
+ ## 🛠️ 工具和脚本
41
+
42
+ ### 诊断工具
43
+
44
+ ```bash
45
+ # 环境诊断
46
+ python 诊断环境.py
47
+
48
+ # 功能测试
49
+ python test_home_input.py
50
+ ```
51
+
52
+ ### 启动脚本
53
+
54
+ ```bash
55
+ # Windows CMD
56
+ 启动后端.bat
57
+
58
+ # PowerShell
59
+ .\启动后端.ps1
60
+ ```
61
+
62
+ ### 测试页面
63
+
64
+ - 打开 `test_audio_recording.html` 测试音频录制功能
65
+
66
+ ## 📝 文档维护
67
+
68
+ ### 文档结构
69
+
70
+ ```
71
+ 项目根目录/
72
+ ├── README.md # 主文档
73
+ ├── PRD.md # 产品需求文档
74
+ ├── docs/ # 详细文档目录
75
+ │ ├── README.md # 本文件
76
+ │ ├── 功能架构图.md # 架构文档
77
+ │ ├── 后端启动问题排查.md # 启动问题
78
+ │ ├── 语音录制问题排查.md # 录音问题
79
+ │ └── FEATURE_SUMMARY.md # 功能总结
80
+ ├── test_home_input.py # 测试脚本
81
+ ├── test_audio_recording.html # 测试页面
82
+ └── 诊断环境.py # 诊断脚本
83
+ ```
84
+
85
+ ### 更新文档
86
+
87
+ 如需更新文档,请:
88
+ 1. 修改对应的 Markdown 文件
89
+ 2. 确保链接正确
90
+ 3. 更新本 README 的文档列表
91
+
92
+ ## 🤝 贡献
93
+
94
+ 欢迎改进文档!如果你发现:
95
+ - 文档有错误
96
+ - 说明不清楚
97
+ - 缺少重要信息
98
+
99
+ 请提交 Issue 或 Pull Request。
100
+
101
+ ---
102
+
103
+ **最后更新:** 2024-01-17
docs/ROADMAP.md ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🗺️ 未来迭代计划 - Roadmap
2
+
3
+ ## 📋 版本规划
4
+
5
+ ### 当前版本:v1.0.0 ✅
6
+ **发布日期**:2026-01-18
7
+
8
+ **核心功能**:
9
+ - ✅ 语音/文本快速记录
10
+ - ✅ AI 语义解析(心情、灵感、待办)
11
+ - ✅ AI 对话陪伴(RAG)
12
+ - ✅ AI 形象定制(720 种组合)
13
+ - ✅ 物理引擎心情气泡池
14
+ - ✅ 多平台部署(Hugging Face, ModelScope, 本地)
15
+
16
+ ---
17
+
18
+ ## 🚀 v1.1.0 - 数据增强与可视化
19
+ **预计发布**:2026-02
20
+
21
+ ### 核心目标
22
+ 增强数据分析和可视化能力,让用户更好地了解自己的情绪变化和成长轨迹。
23
+
24
+ ### 新功能
25
+
26
+ #### 1. 情绪趋势分析 📊
27
+ - [ ] **情绪时间线**
28
+ - 按日/周/月查看情绪变化曲线
29
+ - 识别情绪周期和模式
30
+ - 情绪高峰和低谷标注
31
+
32
+ - [ ] **情绪统计报告**
33
+ - 情绪类型分布饼图
34
+ - 情绪强度热力图
35
+ - 每周/每月情绪总结
36
+
37
+ - [ ] **情绪触发因素分析**
38
+ - 关键词云图
39
+ - 高频触发场景识别
40
+ - 情绪关联事件分析
41
+
42
+ #### 2. 灵感知识图谱 🕸️
43
+ - [ ] **灵感关联网络**
44
+ - 基于标签的灵感连接
45
+ - 可视化灵感演化路径
46
+ - 发现灵感之间的隐藏联系
47
+
48
+ - [ ] **灵感分类优化**
49
+ - 自动分类(工作/生活/学习/创意)
50
+ - 自定义标签系统
51
+ - 灵感收藏夹
52
+
53
+ - [ ] **灵感搜索增强**
54
+ - 全文搜索
55
+ - 标签筛选
56
+ - 时间范围筛选
57
+
58
+ #### 3. 待办智能管理 ✅
59
+ - [ ] **待办优先级**
60
+ - AI 自动评估紧急程度
61
+ - 重要性标记
62
+ - 智能排序
63
+
64
+ - [ ] **待办提醒**
65
+ - 时间提醒
66
+ - 地点提醒(基于位置)
67
+ - 智能推荐最佳执行时间
68
+
69
+ - [ ] **待办统计**
70
+ - 完成率统计
71
+ - 拖延分析
72
+ - 效率趋势图
73
+
74
+ ### 技术改进
75
+ - [ ] 数据导出功能(JSON/CSV)
76
+ - [ ] 数据备份与恢复
77
+ - [ ] 性能优化(大数据量处理)
78
+
79
+ ---
80
+
81
+ ## 🎨 v1.2.0 - 社交与分享
82
+ **预计发布**:2026-03
83
+
84
+ ### 核心目标
85
+ 构建温暖的社区氛围,让用户可以安全地分享和交流。
86
+
87
+ ### 新功能
88
+
89
+ #### 1. 匿名社区 🌐
90
+ - [ ] **心情广场**
91
+ - 匿名分享心情
92
+ - 点赞和评论
93
+ - 情绪共鸣标记
94
+
95
+ - [ ] **灵感市集**
96
+ - 分享创意灵感
97
+ - 灵感收藏
98
+ - 灵感协作
99
+
100
+ - [ ] **治愈树洞**
101
+ - 完全匿名倾诉
102
+ - AI 温暖回复
103
+ - 用户互助支持
104
+
105
+ #### 2. 好友系统 👥
106
+ - [ ] **添加好友**
107
+ - 邀请码机制
108
+ - 好友申请
109
+ - 好友列表
110
+
111
+ - [ ] **私密分享**
112
+ - 向好友分享特定记录
113
+ - 好友可见的心情动态
114
+ - 互相鼓励和支持
115
+
116
+ - [ ] **小组功能**
117
+ - 创建兴趣小组
118
+ - 小组话题讨论
119
+ - 小组活动
120
+
121
+ #### 3. 成就系统 🏆
122
+ - [ ] **记录成就**
123
+ - 连续记录天数
124
+ - 记录总数里程碑
125
+ - 特殊成就徽章
126
+
127
+ - [ ] **成长勋章**
128
+ - 情绪管理大师
129
+ - 灵感收集家
130
+ - 行动派达人
131
+
132
+ - [ ] **每日打卡**
133
+ - 打卡日历
134
+ - 打卡奖励
135
+ - 打卡提醒
136
+
137
+ ### 技术改进
138
+ - [ ] 用户认证系统
139
+ - [ ] 数据隐私保护
140
+ - [ ] 内容审核机制
141
+
142
+ ---
143
+
144
+ ## 🧠 v1.3.0 - AI 能力升级
145
+ **预计发布**:2026-04
146
+
147
+ ### 核心目标
148
+ 提升 AI 的智能化水平,提供更个性化、更深入的陪伴体验。
149
+
150
+ ### 新功能
151
+
152
+ #### 1. 智能对话增强 💬
153
+ - [ ] **多轮对话记忆**
154
+ - 记住对话上下文
155
+ - 长期记忆用户偏好
156
+ - 个性化对话风格
157
+
158
+ - [ ] **情感识别**
159
+ - 识别用户情绪状态
160
+ - 根据情绪调整回复风格
161
+ - 主动关怀和安慰
162
+
163
+ - [ ] **主动对话**
164
+ - AI 主动发起问候
165
+ - 定期情绪检查
166
+ - 特殊日期提醒
167
+
168
+ #### 2. 个性化推荐 🎯
169
+ - [ ] **内容推荐**
170
+ - 推荐相关灵感
171
+ - 推荐治愈内容
172
+ - 推荐行动建议
173
+
174
+ - [ ] **习惯分析**
175
+ - 识别用户习惯模式
176
+ - 提供改善建议
177
+ - 个性化目标设定
178
+
179
+ - [ ] **智能提醒**
180
+ - 基于历史数据的智能提醒
181
+ - 最佳记录时间推荐
182
+ - 情绪调节建议
183
+
184
+ #### 3. AI 形象进化 🎭
185
+ - [ ] **动态表情**
186
+ - 根据对话内容变化表情
187
+ - 情绪同步动画
188
+ - 互动动作
189
+
190
+ - [ ] **语音对话**
191
+ - AI 语音回复
192
+ - 语音情感表达
193
+ - 多种声音选择
194
+
195
+ - [ ] **3D 形象**
196
+ - 3D 角色模型
197
+ - 更丰富的动画
198
+ - 场景互动
199
+
200
+ ### 技术改进
201
+ - [ ] 升级到更强大的 AI 模型
202
+ - [ ] 本地 AI 模型支持(隐私保护)
203
+ - [ ] 多模态输入(图片、视频)
204
+
205
+ ---
206
+
207
+ ## 📱 v1.4.0 - 移动端与硬件
208
+ **预计发布**:2026-05
209
+
210
+ ### 核心目标
211
+ 扩展到移动端和智能硬件,提供无处不在的陪伴体验。
212
+
213
+ ### 新功能
214
+
215
+ #### 1. 移动端应用 📱
216
+ - [ ] **原生 App**
217
+ - iOS 应用
218
+ - Android 应用
219
+ - 离线功能
220
+
221
+ - [ ] **移动端优化**
222
+ - 触摸手势优化
223
+ - 移动端专属 UI
224
+ - 省电模式
225
+
226
+ - [ ] **快捷记录**
227
+ - 桌面小组件
228
+ - 快捷指令
229
+ - 语音唤醒
230
+
231
+ #### 2. 智能硬件集成 ⌚
232
+ - [ ] **可穿戴设备**
233
+ - 智能手表集成
234
+ - 心率监测
235
+ - 情绪预警
236
+
237
+ - [ ] **智能音箱**
238
+ - 语音交互
239
+ - 定时播报
240
+ - 环境音乐
241
+
242
+ - [ ] **IoT 设备**
243
+ - 智能灯光(情绪灯)
244
+ - 智能香薰
245
+ - 环境传感器
246
+
247
+ #### 3. 跨平台同步 ☁️
248
+ - [ ] **云端同步**
249
+ - 实时数据同步
250
+ - 多设备无缝切换
251
+ - 冲突解决
252
+
253
+ - [ ] **离线模式**
254
+ - 离线记录
255
+ - 离线 AI 对话
256
+ - 自动同步
257
+
258
+ ### 技术改进
259
+ - [ ] React Native / Flutter 移动端开发
260
+ - [ ] 蓝牙/WiFi 硬件通信
261
+ - [ ] 云端数据库
262
+
263
+ ---
264
+
265
+ ## 🌟 v2.0.0 - 生态系统
266
+ **预计发布**:2026-Q3
267
+
268
+ ### 核心目标
269
+ 构建完整的心理健康生态系统,提供全方位的支持。
270
+
271
+ ### 新功能
272
+
273
+ #### 1. 专业服务对接 🏥
274
+ - [ ] **心理咨询师入驻**
275
+ - 在线预约
276
+ - 视频咨询
277
+ - 专业评估
278
+
279
+ - [ ] **心理测评**
280
+ - 标准化量表
281
+ - AI 辅助评估
282
+ - 报告生成
283
+
284
+ - [ ] **危机干预**
285
+ - 自动识别危机信号
286
+ - 紧急联系人通知
287
+ - 专业资源推荐
288
+
289
+ #### 2. 内容生态 📚
290
+ - [ ] **治愈内容库**
291
+ - 冥想音频
292
+ - 正念练习
293
+ - 心理学文章
294
+
295
+ - [ ] **课程体系**
296
+ - 情绪管理课程
297
+ - 压力应对课程
298
+ - 自我成长课程
299
+
300
+ - [ ] **创作者平台**
301
+ - 内容创作工具
302
+ - 创作者激励
303
+ - 内容分发
304
+
305
+ #### 3. 企业版 🏢
306
+ - [ ] **团队版功能**
307
+ - 团队情绪监测
308
+ - 团队氛围分析
309
+ - 管理者仪表盘
310
+
311
+ - [ ] **企业服务**
312
+ - 员工关怀计划
313
+ - 心理健康培训
314
+ - 数据报告
315
+
316
+ ### 技术改进
317
+ - [ ] 微服务架构
318
+ - [ ] 大数据分析平台
319
+ - [ ] AI 模型训练平台
320
+
321
+ ---
322
+
323
+ ## 🔮 未来展望
324
+
325
+ ### 长期愿景
326
+ 打造一个温暖、智能、专业的心理健康陪伴平台,让每个人都能:
327
+ - 🌈 更好地理解和管理自己的情绪
328
+ - 💡 记录和实现自己的灵感与目标
329
+ - 🤝 在安全的环境中获得支持和陪伴
330
+ - 🌱 持续成长,成为更好的自己
331
+
332
+ ### 技术方向
333
+ - **AI 技术**:更智能的情感理解和对话能力
334
+ - **隐私保护**:端到端加密、本地 AI 模型
335
+ - **多模态**:支持图片、视频、音频等多种输入
336
+ - **个性化**:深度学习用户偏好,提供定制化体验
337
+ - **开放生态**:API 开放、插件系统、第三方集成
338
+
339
+ ### 研究方向
340
+ - 情绪识别算法优化
341
+ - 个性化推荐系统
342
+ - 心理健康预警模型
343
+ - 人机交互体验研究
344
+
345
+ ---
346
+
347
+ ## 📊 迭代原则
348
+
349
+ ### 1. 用户优先
350
+ - 所有功能基于用户真实需求
351
+ - 持续收集用户反馈
352
+ - 快速迭代优化
353
+
354
+ ### 2. 隐私安全
355
+ - 数据加密存储
356
+ - 用户数据自主权
357
+ - 透明的隐私政策
358
+
359
+ ### 3. 温暖治愈
360
+ - 保持温暖的设计风格
361
+ - 避免过度商业化
362
+ - 关注用户心理健康
363
+
364
+ ### 4. 技术创新
365
+ - 采用前沿 AI 技术
366
+ - 优化用户体验
367
+ - 保持技术领先
368
+
369
+ ### 5. 可持续发展
370
+ - 合理的商业模式
371
+ - 社会责任
372
+ - 长期价值创造
373
+
374
+ ---
375
+
376
+ ## 🤝 参与贡献
377
+
378
+ 我们欢迎社区贡献!你可以通过以下方式参与:
379
+
380
+ ### 功能建议
381
+ - 在 GitHub Issues 提交功能建议
382
+ - 参与功能讨论和投票
383
+ - 分享你的使用体验
384
+
385
+ ### 代码贡献
386
+ - Fork 项目并提交 PR
387
+ - 修复 Bug
388
+ - 优化性能
389
+ - 添加新功能
390
+
391
+ ### 内容贡献
392
+ - 分享治愈内容
393
+ - 编写使用教程
394
+ - 翻译文档
395
+
396
+ ### 测试反馈
397
+ - 参与 Beta 测试
398
+ - 报告 Bug
399
+ - 提供改进建议
400
+
401
+ ---
402
+
403
+ ## 📞 联系我们
404
+
405
+ - **GitHub**:https://github.com/kernel-14/Nora
406
+ - **Issues**:https://github.com/kernel-14/Nora/issues
407
+ - **Discussions**:https://github.com/kernel-14/Nora/discussions
408
+
409
+ ---
410
+
411
+ ## 📝 更新日志
412
+
413
+ ### v1.0.0 (2026-01-18)
414
+ - ✅ 初始版本发布
415
+ - ✅ 核心功能实现
416
+ - ✅ 多平台部署支持
417
+
418
+ ---
419
+
420
+ **注意**:本路线图会根据实际情况和用户反馈进行调整。具体功能和发布时间可能会有变化。
421
+
422
+ **最后更新**:2026-01-18
docs/功能架构图.md ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 首页交互功能架构图
2
+
3
+ ## 整体架构
4
+
5
+ ```
6
+ ┌─────────────────────────────────────────────────────────────┐
7
+ │ 用户界面 │
8
+ │ │
9
+ │ ┌──────────────┐ ┌──────────────┐ │
10
+ │ │ 首页记录 │ │ AI 对话 │ │
11
+ │ │ │ │ │ │
12
+ │ │ 🎤 语音输入 │ │ 💬 聊天界面 │ │
13
+ │ │ ⌨️ 文字输入 │ │ 📝 消息输入 │ │
14
+ │ └──────────────┘ └──────────────┘ │
15
+ │ │ │ │
16
+ └─────────┼──────────────────────────────┼─────────────────────┘
17
+ │ │
18
+ ▼ ▼
19
+ ┌─────────────────────┐ ┌─────────────────────┐
20
+ │ /api/process │ │ /api/chat │
21
+ │ │ │ │
22
+ │ 1. 接收输入 │ │ 1. 接收消息 │
23
+ │ 2. ASR 转文字 │ │ 2. 加载历史记录 │
24
+ │ 3. 语义分析 │ │ 3. 构建 RAG 上下文 │
25
+ │ 4. 保存记录 │ │ 4. 调用 AI API │
26
+ │ 5. 拆分分类 │ │ 5. 返回回复 │
27
+ └─────────────────────┘ └─────────────────────┘
28
+ │ │
29
+ ▼ │
30
+ ┌─────────────────────┐ │
31
+ │ 数据存储 │◄─────────────────┘
32
+ │ │ (读取历史)
33
+ │ 📄 records.json │
34
+ │ 😊 moods.json │
35
+ │ 💡 inspirations.json│
36
+ │ ✅ todos.json │
37
+ └─────────────────────┘
38
+ ```
39
+
40
+ ## 首页记录流程
41
+
42
+ ```
43
+ 用户输入
44
+
45
+ ├─ 语音 ──► MediaRecorder ──► Blob ──► File
46
+ │ │
47
+ └─ 文字 ─────────────────────────────────┤
48
+
49
+
50
+ FormData (audio/text)
51
+
52
+
53
+ POST /api/process
54
+
55
+ ┌───────────────────────┴───────────────────────┐
56
+ │ │
57
+ ▼ ▼
58
+ audio != null? text != null?
59
+ │ │
60
+ ▼ │
61
+ ASR Service (智谱 AI) │
62
+ │ │
63
+ └───────────────────┬───────────────────────────┘
64
+
65
+
66
+ original_text
67
+
68
+
69
+ Semantic Parser (GLM-4-Flash)
70
+
71
+ ┌───────────────────┼───────────────────┐
72
+ │ │ │
73
+ ▼ ▼ ▼
74
+ mood inspirations todos
75
+ │ │ │
76
+ └───────────────────┴───────────────────┘
77
+
78
+
79
+ Storage Service
80
+
81
+ ┌───────────────────┼───────────────────┐
82
+ │ │ │
83
+ ▼ ▼ ▼
84
+ moods.json inspirations.json todos.json
85
+ │ │ │
86
+ └───────────────────┴───────────────────┘
87
+
88
+
89
+ records.json (完整记录)
90
+
91
+
92
+ 返回 ProcessResponse
93
+
94
+
95
+ 前端显示"记录成功"
96
+ ```
97
+
98
+ ## AI 对话流程(RAG 增强)
99
+
100
+ ```
101
+ 用户消息
102
+
103
+
104
+ POST /api/chat
105
+
106
+ ├─ text: "我最近在做什么?"
107
+
108
+
109
+ Storage Service
110
+
111
+ ├─ 读取 records.json
112
+
113
+
114
+ recent_records = records[-10:] (最近 10 条)
115
+
116
+
117
+ 构建上下文
118
+
119
+ ├─ for each record:
120
+ │ ├─ 提取 original_text
121
+ │ ├─ 提取 mood (type, intensity)
122
+ │ ├─ 提取 inspirations (core_idea)
123
+ │ └─ 提取 todos (task)
124
+
125
+
126
+ context_text = """
127
+ [2024-01-17T10:00:00Z] 用户说: 今天工作很累
128
+ 情绪: 疲惫 (强度: 7)
129
+ 待办: 明天早起跑步
130
+
131
+ [2024-01-17T14:00:00Z] 用户说: 完成了项目很开心
132
+ 情绪: 开心 (强度: 8)
133
+ 灵感: 项目完成的成就感
134
+ ...
135
+ """
136
+
137
+
138
+ system_prompt = f"""
139
+ 你是一个温柔、善解人意的AI陪伴助手。
140
+ 你可以参考用户的历史记录来提供更贴心的回复:
141
+
142
+ {context_text}
143
+
144
+ 请基于这些背景信息,用温暖、理解的语气回复用户。
145
+ """
146
+
147
+
148
+ GLM-4-Flash API
149
+
150
+ ├─ model: "glm-4-flash"
151
+ ├─ messages: [
152
+ │ {role: "system", content: system_prompt},
153
+ │ {role: "user", content: "我最近在做什么?"}
154
+ │ ]
155
+ ├─ temperature: 0.8
156
+ └─ top_p: 0.9
157
+
158
+
159
+ AI 生成回复
160
+
161
+ ├─ "从你的记录来看,你最近在忙一个项目,
162
+ │ 虽然工作很累,但完成后很有成就感呢!
163
+ │ 你还计划明天早起去跑步,保持健康的习惯真棒!"
164
+
165
+
166
+ 返回 {response: "..."}
167
+
168
+
169
+ 前端显示 AI 回复
170
+ ```
171
+
172
+ ## 数据流向
173
+
174
+ ```
175
+ ┌─────────────┐
176
+ │ 用户输入 │
177
+ └──────┬──────┘
178
+
179
+
180
+ ┌─────────────┐
181
+ │ HomeInput │ (前端组件)
182
+ │ Component │
183
+ └──────┬──────┘
184
+
185
+
186
+ ┌─────────────┐
187
+ │ API Service │ (前端服务层)
188
+ └──────┬──────┘
189
+
190
+
191
+ ┌─────────────┐
192
+ │ FastAPI │ (后端 API)
193
+ │ /api/process│
194
+ └──────┬──────┘
195
+
196
+ ├─► ASR Service ──► 智谱 AI (语音转文字)
197
+
198
+ ├─► Semantic Parser ──► GLM-4-Flash (语义分析)
199
+
200
+ └─► Storage Service ──► JSON 文件 (数据存储)
201
+
202
+ ├─► records.json
203
+ ├─► moods.json
204
+ ├─► inspirations.json
205
+ └─► todos.json
206
+ ```
207
+
208
+ ## RAG 知识库结构
209
+
210
+ ```
211
+ records.json (知识库)
212
+
213
+ ├─ Record 1
214
+ │ ├─ record_id: "uuid-1"
215
+ │ ├─ timestamp: "2024-01-17T10:00:00Z"
216
+ │ ├─ original_text: "今天工作很累"
217
+ │ └─ parsed_data:
218
+ │ ├─ mood: {type: "疲惫", intensity: 7}
219
+ │ ├─ inspirations: []
220
+ │ └─ todos: []
221
+
222
+ ├─ Record 2
223
+ │ ├─ record_id: "uuid-2"
224
+ │ ├─ timestamp: "2024-01-17T14:00:00Z"
225
+ │ ├─ original_text: "完成了项目很开心"
226
+ │ └─ parsed_data:
227
+ │ ├─ mood: {type: "开心", intensity: 8}
228
+ │ ├─ inspirations: [{core_idea: "项目完成"}]
229
+ │ └─ todos: []
230
+
231
+ └─ Record 3
232
+ ├─ record_id: "uuid-3"
233
+ ├─ timestamp: "2024-01-17T18:00:00Z"
234
+ ├─ original_text: "明天要早起跑步"
235
+ └─ parsed_data:
236
+ ├─ mood: null
237
+ ├─ inspirations: []
238
+ └─ todos: [{task: "早起跑步", time: "明天"}]
239
+
240
+ ↓ (RAG 提取)
241
+
242
+ AI 对话上下文:
243
+ """
244
+ [2024-01-17T10:00:00Z] 用户说: 今天工作很累
245
+ 情绪: 疲惫 (强度: 7)
246
+
247
+ [2024-01-17T14:00:00Z] 用户说: 完成了项目很开心
248
+ 情绪: 开心 (强度: 8)
249
+ 灵感: 项目完成
250
+
251
+ [2024-01-17T18:00:00Z] 用户说: 明天要早起跑步
252
+ 待办: 早起跑步
253
+ """
254
+ ```
255
+
256
+ ## 组件关系图
257
+
258
+ ```
259
+ App.tsx
260
+
261
+ ├─► HomeInput.tsx (首页输入)
262
+ │ │
263
+ │ ├─► VoiceRecording (语音录制)
264
+ │ │ └─► MediaRecorder API
265
+ │ │
266
+ │ ├─► TextInput (文字输入)
267
+ │ │ └─► Input Element
268
+ │ │
269
+ │ └─► apiService.processInput()
270
+ │ └─► POST /api/process
271
+
272
+ ├─► MoodView.tsx (心情页面)
273
+ │ └─► ChatDialog.tsx
274
+ │ └─► apiService.chatWithAI()
275
+ │ └─► POST /api/chat (RAG)
276
+
277
+ ├─► InspirationView.tsx (灵感页面)
278
+ │ └─► ChatDialog.tsx
279
+ │ └─► apiService.chatWithAI()
280
+ │ └─► POST /api/chat (RAG)
281
+
282
+ └─► TodoView.tsx (待办页面)
283
+ └─► ChatDialog.tsx
284
+ └─► apiService.chatWithAI()
285
+ └─► POST /api/chat (RAG)
286
+ ```
287
+
288
+ ## API 端点对比
289
+
290
+ ```
291
+ ┌─────────────────────────────────────────────────────────────┐
292
+ │ /api/process │
293
+ ├─────────────────────────────────────────────────────────────┤
294
+ │ 输入: audio (File) 或 text (string) │
295
+ │ 处理: │
296
+ │ 1. ASR 转文字 (如果是音频) │
297
+ │ 2. 语义分析 (GLM-4-Flash) │
298
+ │ 3. 保存到 records.json │
299
+ │ 4. 拆分到 moods/inspirations/todos.json │
300
+ │ 输出: ProcessResponse { │
301
+ │ record_id, timestamp, mood, inspirations, todos │
302
+ │ } │
303
+ └─────────────────────────────────────────────────────────────┘
304
+
305
+ ┌─────────────────────────────────────────────────────────────┐
306
+ │ /api/chat │
307
+ ├─────────────────────────────────────────────────────────────┤
308
+ │ 输入: text (string) │
309
+ │ 处理: │
310
+ │ 1. 加载 records.json (最近 10 条) │
311
+ │ 2. 提取情绪、灵感、待办信息 │
312
+ │ 3. 构建 RAG 上下文 │
313
+ │ 4. 调用 GLM-4-Flash API │
314
+ │ 5. 生成个性化回复 │
315
+ │ 输出: { │
316
+ │ response: "AI 的回复内容" │
317
+ │ } │
318
+ └─────────────────────────────────────────────────────────────┘
319
+ ```
320
+
321
+ ## 技术栈
322
+
323
+ ```
324
+ 前端
325
+ ├─ React 19
326
+ ├─ TypeScript
327
+ ├─ Vite
328
+ ├─ Tailwind CSS
329
+ └─ Lucide Icons
330
+
331
+ 后端
332
+ ├─ FastAPI
333
+ ├─ Pydantic
334
+ ├─ Uvicorn
335
+ ├─ httpx (异步 HTTP)
336
+ └─ Python 3.8+
337
+
338
+ AI 服务
339
+ ├─ 智谱 AI (ASR)
340
+ ├─ GLM-4-Flash (语义分析)
341
+ └─ GLM-4-Flash (对话生成)
342
+
343
+ 数据存储
344
+ └─ JSON 文件
345
+ ├─ records.json
346
+ ├─ moods.json
347
+ ├─ inspirations.json
348
+ └─ todos.json
349
+ ```
350
+
351
+ ## 性能优化点
352
+
353
+ ```
354
+ 前端优化
355
+ ├─ 防抖处理 (输入延迟)
356
+ ├─ 乐观更新 (立即反馈)
357
+ ├─ 组件懒加载
358
+ └─ 缓存机制
359
+
360
+ 后端优化
361
+ ├─ 异步处理 (async/await)
362
+ ├─ 连接池复用
363
+ ├─ 限制历史记录数量 (10 条)
364
+ └─ 响应压缩
365
+
366
+ RAG 优化
367
+ ├─ 只加载最近记录
368
+ ├─ 精简上下文信息
369
+ ├─ 缓存常见问题
370
+ └─ 向量数据库 (未来)
371
+ ```
372
+
373
+ ---
374
+
375
+ 这个架构图展示了整个系统的工作流程和数据流向,帮助理解两种功能的区别和联系。
docs/后端启动问题排查.md ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 后端启动问题排查指南
2
+
3
+ ## 问题:ModuleNotFoundError: No module named 'app'
4
+
5
+ ### 错误信息
6
+ ```
7
+ ModuleNotFoundError: No module named 'app'
8
+ ```
9
+
10
+ ### 原因分析
11
+
12
+ 这个错误通常由以下原因引起:
13
+
14
+ 1. **在错误的目录运行命令**
15
+ - 必须在项目根目录运行
16
+ - 不能在 `app/` 目录内运行
17
+
18
+ 2. **Python 路径问题**
19
+ - Python 找不到 `app` 模块
20
+ - PYTHONPATH 未正确设置
21
+
22
+ 3. **虚拟环境问题**
23
+ - 未激活正确的虚拟环境
24
+ - 依赖未安装
25
+
26
+ ## 解决方案
27
+
28
+ ### 方案 1:使用启动脚本(推荐)
29
+
30
+ **Windows CMD:**
31
+ ```bash
32
+ 启动后端.bat
33
+ ```
34
+
35
+ **PowerShell:**
36
+ ```bash
37
+ .\启动后端.ps1
38
+ ```
39
+
40
+ 这些脚本会:
41
+ - ✅ 检查当前目录是否正确
42
+ - ✅ 自动激活虚拟环境
43
+ - ✅ 使用正确的命令启动
44
+
45
+ ### 方案 2:手动启动
46
+
47
+ #### 步骤 1:确认在项目根目录
48
+
49
+ ```bash
50
+ # 检查当前目录
51
+ pwd
52
+
53
+ # 应该看到这些文件/目录
54
+ ls
55
+ # app/
56
+ # frontend/
57
+ # data/
58
+ # requirements.txt
59
+ # README.md
60
+ ```
61
+
62
+ #### 步骤 2:激活虚拟环境(如果有)
63
+
64
+ **Windows:**
65
+ ```bash
66
+ # CMD
67
+ venv\Scripts\activate.bat
68
+
69
+ # PowerShell
70
+ venv\Scripts\Activate.ps1
71
+ ```
72
+
73
+ **Linux/Mac:**
74
+ ```bash
75
+ source venv/bin/activate
76
+ ```
77
+
78
+ #### 步骤 3:启动服务器
79
+
80
+ **使用 python -m(推荐):**
81
+ ```bash
82
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
83
+ ```
84
+
85
+ **或者直接使用 uvicorn:**
86
+ ```bash
87
+ uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
88
+ ```
89
+
90
+ ### 方案 3:不使用 reload 模式
91
+
92
+ 如果 `--reload` 参数导致问题,可以不使用:
93
+
94
+ ```bash
95
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
96
+ ```
97
+
98
+ **注意:** 不使用 reload 模式时,修改代码后需要手动重启服务器。
99
+
100
+ ### 方案 4:设置 PYTHONPATH
101
+
102
+ 如果上述方法都不行,手动设置 PYTHONPATH:
103
+
104
+ **Windows CMD:**
105
+ ```bash
106
+ set PYTHONPATH=%CD%
107
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
108
+ ```
109
+
110
+ **PowerShell:**
111
+ ```powershell
112
+ $env:PYTHONPATH = $PWD
113
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
114
+ ```
115
+
116
+ **Linux/Mac:**
117
+ ```bash
118
+ export PYTHONPATH=$PWD
119
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
120
+ ```
121
+
122
+ ## 验证步骤
123
+
124
+ ### 1. 检查目录结构
125
+
126
+ ```bash
127
+ # 应该看到这个结构
128
+ 项目根目录/
129
+ ├── app/
130
+ │ ├── __init__.py
131
+ │ ├── main.py
132
+ │ ├── config.py
133
+ │ └── ...
134
+ ├── frontend/
135
+ ├── data/
136
+ └── requirements.txt
137
+ ```
138
+
139
+ ### 2. 检查 Python 环境
140
+
141
+ ```bash
142
+ # 检查 Python 版本
143
+ python --version
144
+ # 应该是 Python 3.8+
145
+
146
+ # 检查 uvicorn 是否安装
147
+ python -c "import uvicorn; print(uvicorn.__version__)"
148
+ # 应该显示版本号
149
+
150
+ # 检查 FastAPI 是否安装
151
+ python -c "import fastapi; print(fastapi.__version__)"
152
+ # 应该显示版本号
153
+ ```
154
+
155
+ ### 3. 测试导入
156
+
157
+ ```bash
158
+ # 测试能否导入 app 模块
159
+ python -c "import app.main; print('OK')"
160
+ # 应该显示 OK
161
+ ```
162
+
163
+ 如果这一步失败,说明模块路径有问题。
164
+
165
+ ### 4. 检查依赖
166
+
167
+ ```bash
168
+ # 安装/更新依赖
169
+ pip install -r requirements.txt
170
+
171
+ # 或者单独安装
172
+ pip install fastapi uvicorn python-multipart httpx pydantic
173
+ ```
174
+
175
+ ## 常见错误和解决方法
176
+
177
+ ### 错误 1:在 app/ 目录内运行
178
+
179
+ **错误操作:**
180
+ ```bash
181
+ cd app
182
+ python -m uvicorn main:app --reload
183
+ ```
184
+
185
+ **正确操作:**
186
+ ```bash
187
+ # 回到项目根目录
188
+ cd ..
189
+ python -m uvicorn app.main:app --reload
190
+ ```
191
+
192
+ ### 错误 2:虚拟环境未激活
193
+
194
+ **症状:**
195
+ - 提示找不到 uvicorn
196
+ - 提示找不到 fastapi
197
+
198
+ **解决:**
199
+ ```bash
200
+ # 激活虚拟环境
201
+ venv\Scripts\activate.bat # Windows CMD
202
+ venv\Scripts\Activate.ps1 # PowerShell
203
+ source venv/bin/activate # Linux/Mac
204
+
205
+ # 然后重新启动
206
+ python -m uvicorn app.main:app --reload
207
+ ```
208
+
209
+ ### 错误 3:端口被占用
210
+
211
+ **错误信息:**
212
+ ```
213
+ OSError: [Errno 48] Address already in use
214
+ ```
215
+
216
+ **解决方法:**
217
+
218
+ **方法 1:使用其他端口**
219
+ ```bash
220
+ python -m uvicorn app.main:app --port 8001
221
+ ```
222
+
223
+ **方法 2:关闭占用端口的进程**
224
+
225
+ Windows:
226
+ ```bash
227
+ # 查找占用 8000 端口的进程
228
+ netstat -ano | findstr :8000
229
+
230
+ # 关闭进程(替换 PID)
231
+ taskkill /PID <PID> /F
232
+ ```
233
+
234
+ Linux/Mac:
235
+ ```bash
236
+ # 查找并关闭
237
+ lsof -ti:8000 | xargs kill -9
238
+ ```
239
+
240
+ ### 错误 4:权限问题
241
+
242
+ **错误信息:**
243
+ ```
244
+ PermissionError: [Errno 13] Permission denied
245
+ ```
246
+
247
+ **解决方法:**
248
+
249
+ 1. 以管理员身份运行
250
+ 2. 检查文件权限
251
+ 3. 使用其他端口(> 1024)
252
+
253
+ ## 推荐的启动方式
254
+
255
+ ### 开发环境
256
+
257
+ **方式 1:使用启动脚本**
258
+ ```bash
259
+ # Windows
260
+ 启动后端.bat
261
+
262
+ # PowerShell
263
+ .\启动后端.ps1
264
+ ```
265
+
266
+ **方式 2:手动启动(带 reload)**
267
+ ```bash
268
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
269
+ ```
270
+
271
+ ### 生产环境
272
+
273
+ **使用 gunicorn(Linux):**
274
+ ```bash
275
+ gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
276
+ ```
277
+
278
+ **使用 uvicorn(Windows):**
279
+ ```bash
280
+ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
281
+ ```
282
+
283
+ ## 调试技巧
284
+
285
+ ### 1. 查看详细日志
286
+
287
+ ```bash
288
+ python -m uvicorn app.main:app --log-level debug
289
+ ```
290
+
291
+ ### 2. 检查配置
292
+
293
+ ```bash
294
+ # 查看环境变量
295
+ python -c "from app.config import get_config; print(get_config())"
296
+ ```
297
+
298
+ ### 3. 测试 API
299
+
300
+ ```bash
301
+ # 启动后测试
302
+ curl http://localhost:8000/health
303
+
304
+ # 或在浏览器访问
305
+ http://localhost:8000/docs
306
+ ```
307
+
308
+ ## 完整的启动检查清单
309
+
310
+ - [ ] 在项目根目录(不是 app/ 目录)
311
+ - [ ] 虚拟环境已激活(如果使用)
312
+ - [ ] 依赖已安装(pip install -r requirements.txt)
313
+ - [ ] .env 文件已配置
314
+ - [ ] 端口 8000 未被占用
315
+ - [ ] Python 版本 >= 3.8
316
+ - [ ] 可以导入 app 模块(python -c "import app.main")
317
+
318
+ ## 快速诊断命令
319
+
320
+ 运行这个命令进行快速诊断:
321
+
322
+ ```bash
323
+ python -c "
324
+ import sys
325
+ import os
326
+ print('Python 版本:', sys.version)
327
+ print('当前目录:', os.getcwd())
328
+ print('app 目录存在:', os.path.exists('app'))
329
+ print('main.py 存在:', os.path.exists('app/main.py'))
330
+ try:
331
+ import uvicorn
332
+ print('uvicorn 已安装:', uvicorn.__version__)
333
+ except:
334
+ print('uvicorn 未安装')
335
+ try:
336
+ import fastapi
337
+ print('fastapi 已安装:', fastapi.__version__)
338
+ except:
339
+ print('fastapi 未安装')
340
+ try:
341
+ import app.main
342
+ print('app.main 可导入: OK')
343
+ except Exception as e:
344
+ print('app.main 导入失败:', e)
345
+ "
346
+ ```
347
+
348
+ ## 总结
349
+
350
+ 最常见的问题是**在错误的目录运行命令**。
351
+
352
+ **解决方法:**
353
+ 1. 确保在项目根目录
354
+ 2. 使用提供的启动脚本
355
+ 3. 使用 `python -m uvicorn` 而不是直接 `uvicorn`
356
+
357
+ 如果问题仍然存在,请:
358
+ 1. 运行快速诊断命令
359
+ 2. 检查完整的启动检查清单
360
+ 3. 查看详细的错误日志
361
+
362
+ ---
363
+
364
+ **需要帮助?** 请提供:
365
+ - 完整的错误信息
366
+ - 当前目录(pwd)
367
+ - Python 版本(python --version)
368
+ - 快速诊断命令的输出
docs/局域网访问修复完成.md ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✅ 局域网访问问题已修复
2
+
3
+ ## 🎯 问题描述
4
+ 从其他设备访问 `http://172.18.16.245:8000/` 时显示 "Load failed"
5
+
6
+ ## 🔧 已完成的修复
7
+
8
+ ### 1. 移除硬编码的 API 地址
9
+ **问题**:`frontend/.env.local` 中设置了 `VITE_API_URL=http://localhost:8000`,导致前端构建时将这个地址写死在代码中。其他设备访问时会尝试连接到 `localhost:8000`(它们自己的设备),而不是你的服务器。
10
+
11
+ **修复**:
12
+ - ✅ 注释掉了 `frontend/.env.local` 中的 `VITE_API_URL`
13
+ - ✅ 前端现在会自动检测 API 地址:
14
+ - 本地访问 → `http://localhost:8000`
15
+ - 局域网访问 → `http://172.18.16.245:8000`
16
+ - 生产环境 → 自动使用当前域名
17
+
18
+ ### 2. 重新构建前端
19
+ **操作**:
20
+ ```bash
21
+ cd frontend
22
+ npm run build
23
+ ```
24
+
25
+ **结果**:
26
+ - ✅ 新的构建文件已生成在 `frontend/dist/`
27
+ - ✅ 包含了自动 API 地址检测逻辑
28
+
29
+ ### 3. 创建诊断工具
30
+ **新增文件**:
31
+ - ✅ `frontend/dist/test-connection.html` - 网络连接诊断页面
32
+ - ✅ `scripts/test_lan_access.bat` - 快速测试脚本
33
+ - ✅ `docs/局域网访问快速修复.md` - 详细修复指南
34
+ - ✅ `docs/局域网访问问题排查.md` - 完整排查步骤
35
+
36
+ ## 🚀 立即测试
37
+
38
+ ### 步骤 1:启动后端
39
+ 在主机上运行:
40
+ ```bash
41
+ python scripts/start_local.py
42
+ ```
43
+
44
+ ### 步骤 2:运行诊断
45
+ 在主机上运行:
46
+ ```bash
47
+ scripts\test_lan_access.bat
48
+ ```
49
+
50
+ 这会:
51
+ - ✅ 检查后端服务是否运行
52
+ - ✅ 显示你的 IP 地址
53
+ - ✅ 测试 API 端点
54
+ - ✅ 检查防火墙状态
55
+
56
+ ### 步骤 3:在其他设备上测试
57
+
58
+ #### 方法 1:访问诊断页面(推荐)
59
+ 在其他设备的浏览器中打开:
60
+ ```
61
+ http://172.18.16.245:8000/test-connection.html
62
+ ```
63
+
64
+ 点击 "🚀 开始测试" 按钮,查看所有 API 是否可以访问。
65
+
66
+ #### 方法 2:直接访问主应用
67
+ 在其他设备的浏览器中打开:
68
+ ```
69
+ http://172.18.16.245:8000/
70
+ ```
71
+
72
+ 应该可以正常加载并显示数据。
73
+
74
+ ## 🔥 如果仍然失败:检查防火墙
75
+
76
+ ### 最常见的原因:Windows 防火墙阻止端口 8000
77
+
78
+ #### 快速测试(临时关闭防火墙)
79
+ 以管理员身份运行 PowerShell:
80
+ ```powershell
81
+ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
82
+ ```
83
+
84
+ 然后在其他设备上重新访问。如果可以访问了,说明是防火墙问题。
85
+
86
+ #### 添加防火墙规则(推荐)
87
+ 以管理员身份运行 PowerShell:
88
+ ```powershell
89
+ New-NetFirewallRule -DisplayName "Python FastAPI 8000" -Direction Inbound -LocalPort 8000 -Protocol TCP -Action Allow
90
+ ```
91
+
92
+ #### 重新启用防火墙
93
+ ```powershell
94
+ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
95
+ ```
96
+
97
+ ## 📱 移动设备访问
98
+
99
+ 如果从手机访问:
100
+ 1. ✅ 确保手机连接的是**同一个 WiFi 网络**(不是移动数据)
101
+ 2. ✅ 在手机浏览器中输入:`http://172.18.16.245:8000/`
102
+ 3. ✅ 如果无法访问,先访问诊断页面:`http://172.18.16.245:8000/test-connection.html`
103
+
104
+ ## 🐛 使用浏览器开发者工具调试
105
+
106
+ 如果仍然有问题,请:
107
+ 1. 在其他设备的浏览器中按 **F12** 打开开发者工具
108
+ 2. 切换到 **Console** 标签
109
+ 3. 刷新页面
110
+ 4. 查看错误信息并告诉我
111
+
112
+ **常见错误**:
113
+ - `Failed to fetch` → 网络连接问题(检查防火墙)
114
+ - `net::ERR_CONNECTION_REFUSED` → 端口未开放(检查后端是否运行)
115
+ - `net::ERR_CONNECTION_TIMED_OUT` → 连接超时(检查网络连接)
116
+
117
+ ## ✅ 成功标志
118
+
119
+ 当以下测试都通过时,说明配置正确:
120
+
121
+ 1. ✅ 诊断页面所有测试都显示绿色 ✅
122
+ 2. ✅ 主应用可以正常加载
123
+ 3. ✅ 可以看到 AI 角色形象
124
+ 4. ✅ 可以进行语音输入和文本输入
125
+ 5. ✅ 可以查看心情、灵感、待办数据
126
+
127
+ ## 📚 相关文档
128
+
129
+ - [局域网访问快速修复](docs/局域网访问快速修复.md) - 详细的修复步骤
130
+ - [局域网访问问题排查](docs/局域网访问问题排查.md) - 完整的排查指南
131
+ - [局域网访问指南](docs/局域网访问指南.md) - 配置说明
132
+
133
+ ## 🆘 仍然无法解决?
134
+
135
+ 请提供以下信息:
136
+
137
+ 1. **诊断页面的测试结果**(截图)
138
+ 2. **浏览器控制台的错误信息**(F12 → Console 标签,截图或文字)
139
+ 3. **主机上的测试结果**:
140
+ ```bash
141
+ curl http://localhost:8000/health
142
+ ```
143
+ 4. **其他设备上的测试**:
144
+ - 能否 ping 通主机:`ping 172.18.16.245`
145
+ - 访问健康检查:`http://172.18.16.245:8000/health`
146
+
147
+ ---
148
+
149
+ ## 📝 技术说明
150
+
151
+ ### 为什么会出现这个问题?
152
+
153
+ 1. **Vite 的环境变量机制**:
154
+ - Vite 在构建时会将 `import.meta.env.VITE_*` 变量替换为实际值
155
+ - 如果设置了 `VITE_API_URL=http://localhost:8000`,构建后的代码会包含这个硬编码的地址
156
+ - 其他设备访问时,会尝试连接到 `localhost:8000`(它们自己的设备)
157
+
158
+ 2. **解决方案**:
159
+ - 不设置 `VITE_API_URL`,让前端在运行时动态检测
160
+ - 使用 `window.location.hostname` 获取当前访问的主机名
161
+ - 根据主机名自动构建正确的 API 地址
162
+
163
+ ### API 地址检测逻辑
164
+
165
+ ```typescript
166
+ function getApiBaseUrl() {
167
+ const currentHost = window.location.hostname;
168
+ const currentProtocol = window.location.protocol;
169
+
170
+ // 生产环境(Hugging Face, ModelScope)
171
+ if (currentHost.includes('hf.space') ||
172
+ currentHost.includes('huggingface.co') ||
173
+ currentHost.includes('modelscope.cn')) {
174
+ return `${currentProtocol}//${currentHost}`;
175
+ }
176
+
177
+ // 局域网访问(如 192.168.x.x, 172.x.x.x)
178
+ if (currentHost !== 'localhost' && currentHost !== '127.0.0.1') {
179
+ return `${currentProtocol}//${currentHost}:8000`;
180
+ }
181
+
182
+ // 本地开发
183
+ return 'http://localhost:8000';
184
+ }
185
+ ```
186
+
187
+ 这样,无论从哪个地址访问,都能自动使用正确的 API 地址。
docs/局域网访问快速修复.md ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 局域网访问快速修复指南
2
+
3
+ ## 问题:其他设备访问显示 "Load failed"
4
+
5
+ ### ✅ 已完成的修复
6
+
7
+ 1. **移除了硬编码的 API 地址**
8
+ - 修改了 `frontend/.env.local`,移除了 `VITE_API_URL=http://localhost:8000`
9
+ - 重新构建了前端,现在会自动检测 API 地址
10
+
11
+ 2. **前端已重新构建**
12
+ - 运行了 `npm run build`
13
+ - 新的构建文件已生成在 `frontend/dist/`
14
+
15
+ 3. **创建了诊断工具**
16
+ - 访问 `http://172.18.16.245:8000/test-connection.html` 可以测试连接
17
+
18
+ ## 🚀 立即测试
19
+
20
+ ### 步骤 1:启动后端服务
21
+
22
+ 在主机上运行:
23
+ ```bash
24
+ python scripts/start_local.py
25
+ ```
26
+
27
+ 确认看到:
28
+ ```
29
+ ============================================================
30
+ 🌟 治愈系记录助手 - SoulMate AI Companion
31
+ ============================================================
32
+ 📍 本地访问: http://localhost:8000/
33
+ 📍 局域网访问: http://172.18.16.245:8000/
34
+ ============================================================
35
+ ```
36
+
37
+ ### 步骤 2:在其他设备上测试
38
+
39
+ #### 测试 1:访问诊断页面
40
+ 在其他设备的浏览器中打开:
41
+ ```
42
+ http://172.18.16.245:8000/test-connection.html
43
+ ```
44
+
45
+ 点击 "🚀 开始测试" 按钮,查看所有 API 是否可以访问。
46
+
47
+ #### 测试 2:访问主应用
48
+ 在其他设备的浏览器中打开:
49
+ ```
50
+ http://172.18.16.245:8000/
51
+ ```
52
+
53
+ 应该可以正常加载并显示数据。
54
+
55
+ ## 🔧 如果仍然失败
56
+
57
+ ### 方案 1:检查防火墙(最常见原因)
58
+
59
+ #### Windows 防火墙快速测试
60
+ 1. 临时关闭防火墙测试(以管理员身份运行 PowerShell):
61
+ ```powershell
62
+ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
63
+ ```
64
+
65
+ 2. 在其他设备上重新访问 `http://172.18.16.245:8000/`
66
+
67
+ 3. 如果可以访问了,说明是防火墙问题,需要添加规则:
68
+ ```powershell
69
+ # 允许 Python 通过防火墙
70
+ New-NetFirewallRule -DisplayName "Python FastAPI 8000" -Direction Inbound -LocalPort 8000 -Protocol TCP -Action Allow
71
+ ```
72
+
73
+ 4. 重新启用防火墙:
74
+ ```powershell
75
+ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
76
+ ```
77
+
78
+ ### 方案 2:检查网络连接
79
+
80
+ 在其他设备上测试能否 ping 通主机:
81
+ ```bash
82
+ ping 172.18.16.245
83
+ ```
84
+
85
+ 如果 ping 不通:
86
+ - 确认两台设备在同一 WiFi 网络
87
+ - 检查路由器是否启用了 AP 隔离(需要在路由器设置中关闭)
88
+ - 确认主机 IP 地址是否正确(可能已变化)
89
+
90
+ ### 方案 3:检查 IP 地址
91
+
92
+ 主机 IP 可能已经变化,重新获取:
93
+ ```bash
94
+ # Windows
95
+ ipconfig
96
+
97
+ # 查找 "IPv4 地址",例如:192.168.1.100
98
+ ```
99
+
100
+ 使用新的 IP 地址访问。
101
+
102
+ ### 方案 4:使用浏览器开发者工具
103
+
104
+ 在其他设备的浏览器中:
105
+ 1. 按 F12 打开开发者工具
106
+ 2. 切换到 "Console" 标签
107
+ 3. 刷新页面
108
+ 4. 查看具体的错误信息
109
+
110
+ **常见错误及解决方案**:
111
+
112
+ | 错误信息 | 原因 | 解决方案 |
113
+ |---------|------|---------|
114
+ | `Failed to fetch` | 网络连接失败 | 检查防火墙和网络连接 |
115
+ | `net::ERR_CONNECTION_REFUSED` | 端口未开放 | 检查后端是否运行,防火墙是否允许 |
116
+ | `net::ERR_CONNECTION_TIMED_OUT` | 连接超时 | 检查网络连接,可能是路由器 AP 隔离 |
117
+ | `CORS error` | CORS 配置问题 | 已配置,不应该出现此错误 |
118
+
119
+ ## 📱 移动设备特别说明
120
+
121
+ 如果从手机访问:
122
+ 1. 确保手机连接的是同一个 WiFi 网络(不是移动数据)
123
+ 2. 某些公共 WiFi 可能禁止设备间通信
124
+ 3. 可以尝试使用手机的浏览器访问诊断页面
125
+
126
+ ## ✅ 成功标志
127
+
128
+ 当以下测试都通过时,说明配置正确:
129
+
130
+ 1. ✅ 诊断页面所有测试都显示绿色 ✅
131
+ 2. ✅ 主应用可以正常加载
132
+ 3. ✅ 可以看到 AI 角色形象
133
+ 4. ✅ 可以进行语音输入和文本输入
134
+ 5. ✅ 可以查看心情、灵感、待办数据
135
+
136
+ ## 🆘 仍然无法解决?
137
+
138
+ 请提供以下信息:
139
+
140
+ 1. **诊断页面的测试结果**(截图)
141
+ 2. **浏览器控制台的错误信息**(截图或文字)
142
+ 3. **主机上运行的结果**:
143
+ ```bash
144
+ curl http://localhost:8000/health
145
+ ```
146
+ 4. **其他设备上的测试结果**:
147
+ - 能否 ping 通主机
148
+ - 访问 `http://172.18.16.245:8000/health` 的结果
149
+ 5. **防火墙状态**:
150
+ ```powershell
151
+ netsh advfirewall show allprofiles state
152
+ ```
153
+
154
+ ## 📚 相关文档
155
+
156
+ - [局域网访问问题排查](./局域网访问问题排查.md) - 详细的排查步骤
157
+ - [局域网访问指南](./局域网访问指南.md) - 完整的配置指南
docs/局域网访问指南.md ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 局域网访问指南
2
+
3
+ ## 🌐 让其他设备访问你的应用
4
+
5
+ ### 快速开始
6
+
7
+ #### 1. 启动本地服务器
8
+
9
+ **Windows:**
10
+ ```bash
11
+ start_local.bat
12
+ ```
13
+
14
+ **Linux/Mac:**
15
+ ```bash
16
+ python start_local.py
17
+ ```
18
+
19
+ 服务器会显示:
20
+ ```
21
+ ============================================================
22
+ 🌟 治愈系记录助手 - SoulMate AI Companion
23
+ ============================================================
24
+ 📍 本地访问: http://localhost:8000/
25
+ 📍 局域网访问: http://172.18.16.245:8000/
26
+ 📚 API 文档: http://localhost:8000/docs
27
+ 🔍 健康检查: http://localhost:8000/health
28
+ ============================================================
29
+ 💡 提示: 其他设备可以通过 http://172.18.16.245:8000/ 访问
30
+ ============================================================
31
+ ```
32
+
33
+ #### 2. 其他设备访问
34
+
35
+ 在同一局域网内的其他设备(手机、平板、其他电脑)上:
36
+
37
+ 1. 打开浏览器
38
+ 2. 访问:`http://172.18.16.245:8000/`(使用你的实际 IP 地址)
39
+ 3. 开始使用!
40
+
41
+ ### 前置要求
42
+
43
+ #### 1. 构建前端
44
+
45
+ 首次使用前,需要构建前端:
46
+
47
+ ```bash
48
+ cd frontend
49
+ npm install
50
+ npm run build
51
+ cd ..
52
+ ```
53
+
54
+ #### 2. 配置防火墙
55
+
56
+ **Windows 防火墙:**
57
+
58
+ 1. 打开 Windows Defender 防火墙
59
+ 2. 点击"高级设置"
60
+ 3. 选择"入站规则" → "新建规则"
61
+ 4. 选择"端口" → 下一步
62
+ 5. 选择"TCP",特定本地端口:`8000`
63
+ 6. 允许连接
64
+ 7. 应用到所有配置文件
65
+ 8. 命名规则:`SoulMate AI - Port 8000`
66
+
67
+ **或使用命令行(管理员权限):**
68
+ ```powershell
69
+ netsh advfirewall firewall add rule name="SoulMate AI - Port 8000" dir=in action=allow protocol=TCP localport=8000
70
+ ```
71
+
72
+ **Linux (ufw):**
73
+ ```bash
74
+ sudo ufw allow 8000/tcp
75
+ ```
76
+
77
+ **Mac:**
78
+ 系统偏好设置 → 安全性与隐私 → 防火墙 → 防火墙选项 → 添加应用
79
+
80
+ #### 3. 确保在同一网络
81
+
82
+ - 所有设备连接到同一个 WiFi 或局域网
83
+ - 检查路由器是否启用了 AP 隔离(如果启用需要关闭)
84
+
85
+ ### 故障排查
86
+
87
+ #### 问题 1: 其他设备无法访问
88
+
89
+ **检查清单:**
90
+
91
+ 1. ✅ 服务器是否正在运行?
92
+ ```bash
93
+ # 应该看到服务器日志
94
+ INFO: Uvicorn running on http://0.0.0.0:8000
95
+ ```
96
+
97
+ 2. ✅ 防火墙是否允许端口 8000?
98
+ ```bash
99
+ # Windows: 测试端口
100
+ Test-NetConnection -ComputerName 172.18.16.245 -Port 8000
101
+ ```
102
+
103
+ 3. ✅ 设备是否在同一网络?
104
+ ```bash
105
+ # 从其他设备 ping 服务器
106
+ ping 172.18.16.245
107
+ ```
108
+
109
+ 4. ✅ IP 地址是否正确?
110
+ ```bash
111
+ # Windows: 查看 IP
112
+ ipconfig
113
+
114
+ # Linux/Mac: 查看 IP
115
+ ifconfig
116
+ ```
117
+
118
+ #### 问题 2: API 调用失败
119
+
120
+ **检查浏览器控制台:**
121
+
122
+ 1. 打开开发者工具(F12)
123
+ 2. 查看 Console 标签
124
+ 3. 应该看到:`🔗 API Base URL: http://172.18.16.245:8000`
125
+ 4. 如果不正确,清除浏览器缓存并刷新
126
+
127
+ **测试 API 连接:**
128
+
129
+ 访问:`http://172.18.16.245:8000/health`
130
+
131
+ 应该返回:
132
+ ```json
133
+ {
134
+ "status": "healthy",
135
+ "data_dir": "data",
136
+ "max_audio_size": 10485760
137
+ }
138
+ ```
139
+
140
+ #### 问题 3: 没有显示默认形象
141
+
142
+ **检查:**
143
+
144
+ 1. ✅ 默认形象文件是否存在?
145
+ ```bash
146
+ # 应该存在这个文件
147
+ generated_images/default_character.jpeg
148
+ ```
149
+
150
+ 2. ✅ 用户配置是否正确?
151
+ ```bash
152
+ # 查看配置文件
153
+ cat data/user_config.json
154
+ ```
155
+
156
+ 3. ✅ 图片 URL 是否正确?
157
+ - 访问:`http://172.18.16.245:8000/api/user/config`
158
+ - 检查 `character.image_url` 字段
159
+
160
+ 4. ✅ 图片是否可访问?
161
+ - 访问:`http://172.18.16.245:8000/generated_images/default_character.jpeg`
162
+ - 应该能看到图片
163
+
164
+ ### 性能优化
165
+
166
+ #### 1. 使用有线连接
167
+
168
+ - 服务器电脑使用网线连接路由器
169
+ - 减少 WiFi 干扰和延迟
170
+
171
+ #### 2. 关闭不必要的应用
172
+
173
+ - 释放 CPU 和内存资源
174
+ - 提高响应速度
175
+
176
+ #### 3. 使用现代浏览器
177
+
178
+ - Chrome 90+
179
+ - Firefox 88+
180
+ - Safari 14+
181
+ - Edge 90+
182
+
183
+ ### 安全建议
184
+
185
+ ⚠️ **注意:** 局域网访问仅适用于受信任的网络环境
186
+
187
+ 1. **不要在公共 WiFi 上使用**
188
+ 2. **定期更新 API 密钥**
189
+ 3. **不要暴露到公网**
190
+ 4. **使用强密码保护路由器**
191
+
192
+ ### 高级配置
193
+
194
+ #### 自定义端口
195
+
196
+ 编辑 `start_local.py`,修改端口号:
197
+
198
+ ```python
199
+ uvicorn.run(
200
+ app,
201
+ host="0.0.0.0",
202
+ port=8888, # 改为你想要的端口
203
+ log_level="info"
204
+ )
205
+ ```
206
+
207
+ 同时需要修改防火墙规则允许新端口。
208
+
209
+ #### 使用环境变量
210
+
211
+ 创建 `.env.local` 文件:
212
+
213
+ ```env
214
+ HOST=0.0.0.0
215
+ PORT=8000
216
+ ```
217
+
218
+ ### 常见使用场景
219
+
220
+ #### 场景 1: 手机访问电脑上的应用
221
+
222
+ 1. 电脑运行 `start_local.bat`
223
+ 2. 手机连接同一 WiFi
224
+ 3. 手机浏览器访问 `http://172.18.16.245:8000/`
225
+ 4. 可以语音输入、查看心情、与 AI 对话
226
+
227
+ #### 场景 2: 平板作为展示屏
228
+
229
+ 1. 电脑运行服务器
230
+ 2. 平板访问应用
231
+ 3. 全屏显示心情气泡池
232
+ 4. 作为情绪可视化展示
233
+
234
+ #### 场景 3: 多人协作
235
+
236
+ 1. 一台电脑运行服务器
237
+ 2. 团队成员通过局域网访问
238
+ 3. 共享灵感和待办事项
239
+ 4. 实时同步数据
240
+
241
+ ### 技术细节
242
+
243
+ #### API 地址自动检测
244
+
245
+ 前端会自动检测访问地址并配置 API:
246
+
247
+ ```typescript
248
+ // 访问: http://172.18.16.245:5173/
249
+ // API: http://172.18.16.245:8000/
250
+
251
+ // 访问: http://localhost:5173/
252
+ // API: http://localhost:8000/
253
+ ```
254
+
255
+ #### CORS 配置
256
+
257
+ 后端已配置允许所有来源(开发环境):
258
+
259
+ ```python
260
+ app.add_middleware(
261
+ CORSMiddleware,
262
+ allow_origins=["*"],
263
+ allow_credentials=True,
264
+ allow_methods=["*"],
265
+ allow_headers=["*"],
266
+ )
267
+ ```
268
+
269
+ 生产环境应该限制具体的域名。
docs/局域网访问问题排查.md ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 局域网访问问题排查指南
2
+
3
+ ## 问题描述
4
+ 从其他设备访问 `http://172.18.16.245:8000/` 时显示 "Load failed"
5
+
6
+ ## 排查步骤
7
+
8
+ ### 1. 确认后端服务正在运行
9
+ 在主机上运行:
10
+ ```bash
11
+ python scripts/start_local.py
12
+ ```
13
+
14
+ 确认看到以下输出:
15
+ ```
16
+ ============================================================
17
+ 🌟 治愈系记录助手 - SoulMate AI Companion
18
+ ============================================================
19
+ 📍 本地访问: http://localhost:8000/
20
+ 📍 局域网访问: http://172.18.16.245:8000/
21
+ 📚 API 文档: http://localhost:8000/docs
22
+ 🔍 健康检查: http://localhost:8000/health
23
+ ============================================================
24
+ ```
25
+
26
+ ### 2. 测试后端 API 是否可访问
27
+
28
+ #### 在主机上测试(应该成功):
29
+ ```bash
30
+ curl http://localhost:8000/health
31
+ ```
32
+
33
+ #### 在其他设备上测试(关键):
34
+ 打开浏览器访问:
35
+ ```
36
+ http://172.18.16.245:8000/health
37
+ ```
38
+
39
+ **预期结果**:应该看到 JSON 响应
40
+ ```json
41
+ {
42
+ "status": "healthy",
43
+ "data_dir": "data",
44
+ "max_audio_size": 10485760
45
+ }
46
+ ```
47
+
48
+ **如果失败**:说明网络连接有问题,继续下一步
49
+
50
+ ### 3. 检查防火墙设置
51
+
52
+ #### Windows 防火墙
53
+ 1. 打开 "Windows Defender 防火墙"
54
+ 2. 点击 "允许应用通过防火墙"
55
+ 3. 确保 Python 已被允许(专用网络和公用网络都勾选)
56
+
57
+ #### 或者临时关闭防火墙测试:
58
+ ```powershell
59
+ # 以管理员身份运行 PowerShell
60
+ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
61
+ ```
62
+
63
+ 测试完成后记得重新开启:
64
+ ```powershell
65
+ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
66
+ ```
67
+
68
+ ### 4. 检查网络连接
69
+
70
+ #### 在其他设备上 ping 主机:
71
+ ```bash
72
+ ping 172.18.16.245
73
+ ```
74
+
75
+ **预期结果**:应该能 ping 通
76
+
77
+ **如果失败**:
78
+ - 确认两台设备在同一局域网
79
+ - 检查主机的 IP 地址是否正确(可能已变化)
80
+
81
+ #### 获取当前 IP 地址:
82
+ ```bash
83
+ # Windows
84
+ ipconfig
85
+
86
+ # 查找 "IPv4 地址" 或 "IPv4 Address"
87
+ ```
88
+
89
+ ### 5. 检查端口是否被占用
90
+
91
+ ```bash
92
+ # Windows
93
+ netstat -ano | findstr :8000
94
+ ```
95
+
96
+ 如果端口被占用,更换端口或关闭占用端口的程序
97
+
98
+ ### 6. 浏览器控制台检查
99
+
100
+ 在其他设备的浏览器中:
101
+ 1. 按 F12 打开开发者工具
102
+ 2. 切换到 "Console" 标签
103
+ 3. 刷新页面
104
+ 4. 查看错误信息
105
+
106
+ **常见错误**:
107
+ - `Failed to fetch`: 网络连接问题
108
+ - `CORS error`: CORS 配置问题(但我们已经配置了)
109
+ - `404 Not Found`: API 路径错误
110
+ - `Timeout`: 请求超时
111
+
112
+ ### 7. 测试 API 端点
113
+
114
+ 在其他设备的浏览器中依次访问:
115
+
116
+ 1. **健康检查**:`http://172.18.16.245:8000/health`
117
+ 2. **API 状态**:`http://172.18.16.245:8000/api/status`
118
+ 3. **前端页面**:`http://172.18.16.245:8000/`
119
+
120
+ 记录每个请求的结果
121
+
122
+ ### 8. 检查 CORS 配置
123
+
124
+ 后端已配置允许所有来源:
125
+ ```python
126
+ allow_origins=["*"]
127
+ ```
128
+
129
+ 但如果仍有问题,可以尝试更明确的配置:
130
+ ```python
131
+ allow_origins=[
132
+ "http://localhost:5173",
133
+ "http://localhost:3000",
134
+ "http://172.18.16.245:5173",
135
+ "http://172.18.16.245:8000",
136
+ "*"
137
+ ]
138
+ ```
139
+
140
+ ## 快速诊断命令
141
+
142
+ 在主机上运行以下命令,将结果发给我:
143
+
144
+ ```bash
145
+ # 1. 检查服务是否运行
146
+ curl http://localhost:8000/health
147
+
148
+ # 2. 检查端口监听
149
+ netstat -ano | findstr :8000
150
+
151
+ # 3. 检查 IP 地址
152
+ ipconfig | findstr IPv4
153
+
154
+ # 4. 检查防火墙状态
155
+ netsh advfirewall show allprofiles state
156
+ ```
157
+
158
+ ## 解决方案
159
+
160
+ ### 方案 1:临时关闭防火墙测试
161
+ 如果关闭防火墙后可以访问,说明是防火墙问题,需要添加防火墙规则
162
+
163
+ ### 方案 2:添加防火墙规则
164
+ ```powershell
165
+ # 以管理员身份运行
166
+ New-NetFirewallRule -DisplayName "Python FastAPI" -Direction Inbound -Program "C:\Path\To\Python\python.exe" -Action Allow
167
+ ```
168
+
169
+ ### 方案 3:使用不同的端口
170
+ 如果 8000 端口有问题,可以尝试其他端口(如 8080, 5000)
171
+
172
+ 修改 `scripts/start_local.py` 中的端口号
173
+
174
+ ### 方案 4:检查路由器设置
175
+ 某些路由器可能阻止设备间通信(AP 隔离),需要在路由器设置中关闭
176
+
177
+ ## 成功标志
178
+
179
+ 当以下所有测试都通过时,说明配置正确:
180
+
181
+ 1. ✅ 主机可以访问 `http://localhost:8000/health`
182
+ 2. ✅ 其他设备可以 ping 通 `172.18.16.245`
183
+ 3. ✅ 其他设备可以访问 `http://172.18.16.245:8000/health`
184
+ 4. ✅ 其他设备可以访问 `http://172.18.16.245:8000/`
185
+ 5. ✅ 前端可以正常加载并显示数据
186
+
187
+ ## 需要提供的信息
188
+
189
+ 如果问题仍未解决,请提供:
190
+
191
+ 1. 浏览器控制台的完整错误信息(截图或文字)
192
+ 2. 主机上运行 `curl http://localhost:8000/health` 的结果
193
+ 3. 其他设备上访问 `http://172.18.16.245:8000/health` 的结果
194
+ 4. 防火墙状态
195
+ 5. 两台设备是否在同一局域网