bid-master-cli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app/__init__.py +1 -0
- app/api/__init__.py +1 -0
- app/api/api_keys.py +60 -0
- app/api/auth.py +258 -0
- app/api/cli_auth.py +165 -0
- app/api/database.py +286 -0
- app/api/extract.py +158 -0
- app/api/files.py +163 -0
- app/api/health.py +62 -0
- app/api/logs.py +26 -0
- app/api/settings.py +101 -0
- app/api/simulate.py +195 -0
- app/api/statistics.py +1214 -0
- app/cli.py +894 -0
- app/config.py +93 -0
- app/dependencies.py +12 -0
- app/infrastructure/__init__.py +1 -0
- app/infrastructure/database.py +126 -0
- app/infrastructure/db_schema.py +245 -0
- app/infrastructure/email_service.py +92 -0
- app/infrastructure/llm/__init__.py +1 -0
- app/infrastructure/llm/lite_llm.py +463 -0
- app/infrastructure/log_collector.py +64 -0
- app/infrastructure/mock_storage.py +563 -0
- app/infrastructure/pg_storage.py +656 -0
- app/infrastructure/storage.py +117 -0
- app/limiter.py +7 -0
- app/main.py +141 -0
- app/models/__init__.py +1 -0
- app/models/schemas.py +204 -0
- app/services/__init__.py +1 -0
- app/services/encryption_service.py +88 -0
- app/services/extract_service.py +817 -0
- app/services/file_service.py +112 -0
- app/services/llm_service.py +65 -0
- app/services/ocr_service.py +183 -0
- app/services/prompt_builder.py +257 -0
- app/services/simulate_service.py +625 -0
- app/services/statistics_service.py +123 -0
- app/utils/__init__.py +1 -0
- app/utils/auth_dep.py +42 -0
- app/utils/crypto.py +63 -0
- app/utils/exceptions.py +53 -0
- bid_master_cli-1.0.0.dist-info/METADATA +30 -0
- bid_master_cli-1.0.0.dist-info/RECORD +47 -0
- bid_master_cli-1.0.0.dist-info/WHEEL +4 -0
- bid_master_cli-1.0.0.dist-info/entry_points.txt +2 -0
app/api/settings.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI provider settings API routes.
|
|
3
|
+
"""
|
|
4
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
5
|
+
|
|
6
|
+
from app.services.llm_service import LLMService
|
|
7
|
+
from app.models.schemas import (
|
|
8
|
+
ProvidersResponse,
|
|
9
|
+
TestConnectionRequest,
|
|
10
|
+
TestConnectionResponse,
|
|
11
|
+
ProviderConfig,
|
|
12
|
+
)
|
|
13
|
+
from app.utils.auth_dep import get_current_user
|
|
14
|
+
|
|
15
|
+
router = APIRouter(prefix="/settings", tags=["settings"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@router.get("/providers")
|
|
19
|
+
async def get_providers():
|
|
20
|
+
"""
|
|
21
|
+
Get list of supported AI providers.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of providers with their models
|
|
25
|
+
"""
|
|
26
|
+
llm_service = LLMService()
|
|
27
|
+
providers = llm_service.get_providers()
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
"success": True,
|
|
31
|
+
"data": {
|
|
32
|
+
"providers": providers,
|
|
33
|
+
"active": "deepseek", # Default provider
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.get("/providers/{provider_name}")
|
|
39
|
+
async def get_provider(provider_name: str):
|
|
40
|
+
"""
|
|
41
|
+
Get specific provider configuration.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
provider_name: Provider identifier
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Provider details
|
|
48
|
+
"""
|
|
49
|
+
llm_service = LLMService()
|
|
50
|
+
providers = llm_service.get_providers()
|
|
51
|
+
|
|
52
|
+
for p in providers:
|
|
53
|
+
if p["id"] == provider_name:
|
|
54
|
+
return {
|
|
55
|
+
"success": True,
|
|
56
|
+
"data": p,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
raise HTTPException(status_code=404, detail=f"Provider {provider_name} not found")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@router.post("/providers/{provider_name}")
|
|
63
|
+
async def configure_provider(provider_name: str, config: ProviderConfig):
|
|
64
|
+
"""
|
|
65
|
+
Configure a provider.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
provider_name: Provider identifier
|
|
69
|
+
config: Provider configuration
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Success status
|
|
73
|
+
"""
|
|
74
|
+
# TODO: Save to database
|
|
75
|
+
return {
|
|
76
|
+
"success": True,
|
|
77
|
+
"message": f"Provider {provider_name} configured",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.post("/test")
|
|
82
|
+
async def test_connection(request: TestConnectionRequest, user: dict = Depends(get_current_user)):
|
|
83
|
+
"""Test connection to an AI provider."""
|
|
84
|
+
try:
|
|
85
|
+
llm_service = LLMService()
|
|
86
|
+
result = await llm_service.test_connection(
|
|
87
|
+
request.provider, model=request.model, user_id=user["id"], api_key=request.apiKey
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"success": result["success"],
|
|
92
|
+
"message": result["message"],
|
|
93
|
+
"latencyMs": result.get("latencyMs"),
|
|
94
|
+
"error": result.get("error"),
|
|
95
|
+
}
|
|
96
|
+
except Exception as e:
|
|
97
|
+
return {
|
|
98
|
+
"success": False,
|
|
99
|
+
"message": "Connection failed",
|
|
100
|
+
"error": str(e),
|
|
101
|
+
}
|
app/api/simulate.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
"""
|
|
3
|
+
Simulate API routes - 4步模拟编制流程
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
from sse_starlette.sse import EventSourceResponse
|
|
11
|
+
|
|
12
|
+
from app.services.simulate_service import SimulateService, get_simulate_service
|
|
13
|
+
from app.utils.auth_dep import get_current_user
|
|
14
|
+
|
|
15
|
+
router = APIRouter(prefix="/simulate", tags=["simulate"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CreateSimulateRequest(BaseModel):
|
|
19
|
+
file_ids: List[str]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StepRequest(BaseModel):
|
|
23
|
+
provider: Optional[str] = "deepseek"
|
|
24
|
+
model: Optional[str] = None
|
|
25
|
+
params: Optional[dict] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
simulate_service = get_simulate_service()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.post("/create")
|
|
32
|
+
async def create_simulate_task(request: CreateSimulateRequest, current_user: dict = Depends(get_current_user)):
|
|
33
|
+
"""创建模拟任务(Step 0)"""
|
|
34
|
+
try:
|
|
35
|
+
task = await simulate_service.create_task(request.file_ids, user_id=current_user["id"])
|
|
36
|
+
return {
|
|
37
|
+
"success": True,
|
|
38
|
+
"data": {
|
|
39
|
+
"taskId": task.task_id,
|
|
40
|
+
"currentStep": task.current_step,
|
|
41
|
+
"status": task.status,
|
|
42
|
+
"fileIds": task.file_ids,
|
|
43
|
+
"sourceHash": task.source_hash,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
except Exception as e:
|
|
47
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.get("/list")
|
|
51
|
+
async def list_simulate_tasks(current_user: dict = Depends(get_current_user)):
|
|
52
|
+
"""列出当前用户的模拟任务"""
|
|
53
|
+
tasks = simulate_service.list_tasks(user_id=current_user["id"])
|
|
54
|
+
return {
|
|
55
|
+
"success": True,
|
|
56
|
+
"data": {
|
|
57
|
+
"tasks": [
|
|
58
|
+
{
|
|
59
|
+
"taskId": t.task_id,
|
|
60
|
+
"currentStep": t.current_step,
|
|
61
|
+
"status": t.status,
|
|
62
|
+
"fileIds": t.file_ids,
|
|
63
|
+
"createdAt": t.created_at,
|
|
64
|
+
}
|
|
65
|
+
for t in tasks
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@router.get("/{task_id}")
|
|
72
|
+
async def get_simulate_task(task_id: str, current_user: dict = Depends(get_current_user)):
|
|
73
|
+
"""获取任务状态"""
|
|
74
|
+
task = simulate_service.get_task(task_id, user_id=current_user["id"])
|
|
75
|
+
if not task:
|
|
76
|
+
raise HTTPException(status_code=404, detail="Task not found")
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"success": True,
|
|
80
|
+
"data": {
|
|
81
|
+
"taskId": task.task_id,
|
|
82
|
+
"currentStep": task.current_step,
|
|
83
|
+
"status": task.status,
|
|
84
|
+
"fileIds": task.file_ids,
|
|
85
|
+
"params": task.params,
|
|
86
|
+
"step1Result": task.step1_result,
|
|
87
|
+
"step2Result": task.step2_result,
|
|
88
|
+
"step3Result": task.step3_result,
|
|
89
|
+
"step4Result": task.step4_result,
|
|
90
|
+
"createdAt": task.created_at,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@router.delete("/{task_id}")
|
|
96
|
+
async def delete_simulate_task(task_id: str, current_user: dict = Depends(get_current_user)):
|
|
97
|
+
"""删除任务"""
|
|
98
|
+
success = simulate_service.delete_task(task_id, user_id=current_user["id"])
|
|
99
|
+
return {"success": success}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@router.post("/{task_id}/step/1")
|
|
103
|
+
async def run_step1(task_id: str, current_user: dict = Depends(get_current_user)):
|
|
104
|
+
"""Step 1: PDF转换"""
|
|
105
|
+
try:
|
|
106
|
+
task = await simulate_service.run_step1(task_id, user_id=current_user["id"])
|
|
107
|
+
return {
|
|
108
|
+
"success": True,
|
|
109
|
+
"data": {
|
|
110
|
+
"taskId": task.task_id,
|
|
111
|
+
"currentStep": task.current_step,
|
|
112
|
+
"status": task.status,
|
|
113
|
+
"step1Result": task.step1_result,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
118
|
+
except Exception as e:
|
|
119
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def stream_step2_generator(task_id: str, provider: str = "deepseek", user_id: str = None, model: str = None):
|
|
123
|
+
"""Step 2 SSE generator"""
|
|
124
|
+
async for event in simulate_service.run_step2_stream(task_id, provider, user_id=user_id, model=model):
|
|
125
|
+
yield {"event": event.get("type", "message"), "data": json.dumps(event, ensure_ascii=False)}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@router.post("/{task_id}/step/2")
|
|
129
|
+
async def run_step2(task_id: str, request: StepRequest = None, current_user: dict = Depends(get_current_user)):
|
|
130
|
+
"""Step 2: 提取(SSE流式)"""
|
|
131
|
+
try:
|
|
132
|
+
provider = request.provider if request else "deepseek"
|
|
133
|
+
model = request.model if request else None
|
|
134
|
+
return EventSourceResponse(stream_step2_generator(task_id, provider, user_id=current_user["id"], model=model))
|
|
135
|
+
except ValueError as e:
|
|
136
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def stream_step3_generator(task_id: str, provider: str = "deepseek", user_id: str = None, model: str = None):
|
|
142
|
+
"""Step 3 SSE generator"""
|
|
143
|
+
async for event in simulate_service.run_step3_stream(task_id, provider, user_id=user_id, model=model):
|
|
144
|
+
yield {"event": event.get("type", "message"), "data": json.dumps(event, ensure_ascii=False)}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@router.post("/{task_id}/step/3")
|
|
148
|
+
async def run_step3(task_id: str, request: StepRequest = None, current_user: dict = Depends(get_current_user)):
|
|
149
|
+
"""Step 3: 对比(SSE流式)"""
|
|
150
|
+
try:
|
|
151
|
+
provider = request.provider if request else "deepseek"
|
|
152
|
+
model = request.model if request else None
|
|
153
|
+
return EventSourceResponse(stream_step3_generator(task_id, provider, user_id=current_user["id"], model=model))
|
|
154
|
+
except ValueError as e:
|
|
155
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
156
|
+
except Exception as e:
|
|
157
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def stream_step4_generator(task_id: str, params: dict, provider: str = "deepseek", user_id: str = None, model: str = None):
|
|
161
|
+
"""Step 4 SSE generator"""
|
|
162
|
+
async for event in simulate_service.run_step4_stream(task_id, params, provider, user_id=user_id, model=model):
|
|
163
|
+
yield {"event": event.get("type", "message"), "data": json.dumps(event, ensure_ascii=False)}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Step4Request(BaseModel):
|
|
167
|
+
provider: Optional[str] = "deepseek"
|
|
168
|
+
model: Optional[str] = None
|
|
169
|
+
project_name: str
|
|
170
|
+
project_type: str
|
|
171
|
+
project_scale: str
|
|
172
|
+
investment_estimate: Optional[str] = ""
|
|
173
|
+
region: Optional[str] = ""
|
|
174
|
+
special_requirements: Optional[str] = ""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@router.post("/{task_id}/step/4")
|
|
178
|
+
async def run_step4(task_id: str, request: Step4Request, current_user: dict = Depends(get_current_user)):
|
|
179
|
+
"""Step 4: 编制(SSE流式)"""
|
|
180
|
+
try:
|
|
181
|
+
params = {
|
|
182
|
+
"project_name": request.project_name,
|
|
183
|
+
"project_type": request.project_type,
|
|
184
|
+
"project_scale": request.project_scale,
|
|
185
|
+
"investment_estimate": request.investment_estimate,
|
|
186
|
+
"region": request.region,
|
|
187
|
+
"special_requirements": request.special_requirements,
|
|
188
|
+
}
|
|
189
|
+
return EventSourceResponse(
|
|
190
|
+
stream_step4_generator(task_id, params, request.provider, user_id=current_user["id"], model=request.model)
|
|
191
|
+
)
|
|
192
|
+
except ValueError as e:
|
|
193
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
194
|
+
except Exception as e:
|
|
195
|
+
raise HTTPException(status_code=500, detail=str(e))
|