-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
208 lines (167 loc) · 6.4 KB
/
Copy pathmain.py
File metadata and controls
208 lines (167 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""
LearnWithAI – AI 辅助深度学习平台
启动方式:
# 1. 安装依赖
pip install -r requirements.txt
# 2. 配置环境变量(可选,有默认值)
export LLM_PROVIDER=openai # openai / anthropic / ollama
export LLM_MODEL=gpt-4o-mini # 模型名
export LLM_API_KEY=sk-xxx # API Key
export LLM_API_BASE=https://xxx # 自定义 base_url(可选)
# 3. 启动
python main.py
打开浏览器访问 http://127.0.0.1:7860
"""
import logging
import os
import time
from contextlib import asynccontextmanager
import uvicorn
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from app.config import settings
from app.database import init_db, get_db_type
from app.routes import areas, chat, auth, notes, rag, admin, plan, skills, study_plans
# ── 日志配置 ──────────────────────────────
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
# 关掉 uvicorn 访问日志的重复(我们用自己 middleware 记录)
logging.getLogger("uvicorn.access").disabled = True
log = logging.getLogger("learnwithai")
# ── 生命周期事件(必须在 FastAPI() 之前定义) ──
@asynccontextmanager
async def lifespan(app: FastAPI):
"""启动时初始化数据库,关闭时可做资源清理"""
init_db()
db_type = get_db_type()
if db_type == "SQLite":
log.info("数据库初始化完成 [%s]: %s", db_type, settings.DB_PATH)
else:
log.info("数据库初始化完成 [%s]", db_type)
yield
# 关闭时在此处添加清理逻辑(如关闭连接池)
# 创建应用
app = FastAPI(title=settings.PROJECT_NAME, description=settings.PROJECT_DESC, lifespan=lifespan)
# ── 请求日志中间件 ─────────────────────────
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""记录每个请求的方法、路径、状态码和耗时"""
start = time.time()
try:
response = await call_next(request)
elapsed = time.time() - start
log.info(
"%s %s → %s (%.0fms)",
request.method,
request.url.path,
response.status_code,
elapsed * 1000,
)
return response
except Exception as exc:
elapsed = time.time() - start
log.exception(
"%s %s → ❌ %s (%.0fms)",
request.method,
request.url.path,
repr(exc),
elapsed * 1000,
)
# 重新抛出,让 FastAPI 的默认异常处理器处理
raise
# ── 静态文件 ──────────────────────────────
app.mount("/static", StaticFiles(directory="app/static"), name="static")
# ── 注册路由 ──────────────────────────────
app.include_router(auth.router)
app.include_router(areas.router)
app.include_router(chat.router)
app.include_router(notes.router)
app.include_router(rag.router)
app.include_router(admin.router)
app.include_router(plan.router)
app.include_router(skills.router)
app.include_router(study_plans.router)
@app.get("/")
async def root():
"""重定向到新主页"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/index.html")
@app.get("/domain")
async def domain_page():
"""领域页面(原三栏布局)"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/domain.html")
@app.get("/notes")
async def notes_page():
"""笔记页面(两栏:领域树 + 笔记编辑器)"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/notes.html")
@app.get("/plan")
async def plan_page():
"""Plan Mode 深度学习规划页面"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/plan.html")
@app.get("/plan-checkpoints")
async def plan_checkpoints_page():
"""Plan 检查点分析页面(管理员)"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/plan-checkpoints.html")
@app.get("/area-manage")
async def area_manage_page():
"""领域管理页面(管理员)"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/area-manage.html")
@app.get("/plans")
async def plans_page():
"""学习计划页面(代办事项)"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/plans.html")
@app.get("/mobile")
async def mobile_root():
"""移动端首页"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/mobile/home.html")
@app.get("/mobile/domain")
async def mobile_domain_page():
"""移动端领域页面"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/mobile/index.html")
@app.get("/mobile/notes")
async def mobile_notes_page():
"""移动端笔记页面"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/mobile/notes.html")
@app.get("/mobile/plan")
async def mobile_plan_page():
"""移动端 Plan Mode 页面"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/static/mobile/plan.html")
if __name__ == "__main__":
is_dev = os.getenv("ENV", "development").lower() in ("dev", "development", "local")
if is_dev:
print(f" 🌲 探知 开发模式")
print(f" 📡 {settings.HOST}:{settings.PORT}")
print(f" 🤖 {settings.LLM_PROVIDER} / {settings.LLM_MODEL}")
print(f" 💾 {get_db_type()}: {settings.DB_PATH if get_db_type() == 'SQLite' else settings.DATABASE_URL or settings.DB_PATH}")
print()
uvicorn.run(
"main:app",
host=settings.HOST,
port=settings.PORT,
reload=True,
log_level="debug",
)
else:
# 生产模式:由 gunicorn 或 systemd 启动,不使用 reload
import uvicorn
uvicorn.run(
"main:app",
host=settings.HOST,
port=settings.PORT,
reload=False,
log_level="info",
workers=4,
)