exable324 commited on
Commit
13fbcb2
·
verified ·
1 Parent(s): 702c4ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -79
app.py CHANGED
@@ -9,7 +9,7 @@ from fastapi import FastAPI, Request, HTTPException, Depends, status
9
  from fastapi.responses import StreamingResponse
10
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
11
 
12
- # --- 1. 全局配置与状态缓存 ---
13
  app = FastAPI()
14
  security = HTTPBearer()
15
 
@@ -24,17 +24,17 @@ class TokenCache:
24
 
25
  cache = TokenCache()
26
 
27
- # --- 2. 核心后端逻辑 (带自动刷新) ---
28
 
29
  def fetch_account():
30
- """注册新账号并初始化缓存"""
31
  try:
32
  reg_url = f"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={API_KEY}"
33
  reg_resp = requests.post(reg_url, json={"returnSecureToken": True}, headers={"origin": "https://www.aidocmaker.com"}, timeout=10).json()
34
  token = reg_resp.get("idToken")
35
  if not token: return None
36
 
37
- # 初始同步余额
38
  bal_url = "https://level2labs-prod--adm-agent-helper-user-get-credits.modal.run/"
39
  bal_resp = requests.post(bal_url, headers={"Authorization": f"Bearer {token}"}, timeout=10).json()
40
 
@@ -45,13 +45,11 @@ def fetch_account():
45
  return None
46
 
47
  def get_valid_token():
48
- """复用缓存或静默刷新"""
49
  if cache.token and cache.balance > 0:
50
  return cache.token
51
  return fetch_account()
52
 
53
  def sync_balance():
54
- """查询并更新当前 Token 的余额"""
55
  if not cache.token: return 0
56
  url = "https://level2labs-prod--adm-agent-helper-user-get-credits.modal.run/"
57
  try:
@@ -62,19 +60,25 @@ def sync_balance():
62
  cache.balance = 0
63
  return 0
64
 
65
- # --- 3. API 解析辅助函数 ---
66
 
67
- def clean_sse_line(line):
68
- """解析 upstream 返回的每一行,处理可能存在的 data: 前缀"""
69
- line = line.decode("utf-8").strip()
70
  if not line: return None
71
- # 移除 data: 前缀(如果存在)
72
- if line.startswith("data: "):
73
- line = line[6:]
74
- if line == "[DONE]":
75
- return None
 
 
 
 
 
76
  try:
77
- return json.loads(line)
 
 
78
  except:
79
  return None
80
 
@@ -82,7 +86,7 @@ def clean_sse_line(line):
82
 
83
  async def verify_api(auth: HTTPAuthorizationCredentials = Depends(security)):
84
  if auth.credentials != ACCESS_TOKEN:
85
- raise HTTPException(status_code=401, detail="Unauthorized")
86
  return auth.credentials
87
 
88
  @app.get("/v1/models")
@@ -97,26 +101,25 @@ async def chat_completions(request: Request, t: str = Depends(verify_api)):
97
  content = body.get("messages", [])[-1]["content"] if body.get("messages") else ""
98
 
99
  jwt = get_valid_token()
100
- if not jwt: raise HTTPException(status_code=500, detail="Provider Error")
101
 
102
  url = "https://level2labs-prod--adm-agent-send-chat-message.modal.run/"
103
- headers = {"Authorization": f"Bearer {jwt}"}
104
  data = {
105
  "content": (None, content), "model": (None, model),
106
  "conversation_id": (None, f"c-{uuid.uuid4()}"),
107
  "message_id": (None, f"m-{uuid.uuid4()}"), "max_mode": (None, "true")
108
  }
109
 
110
- target_resp = requests.post(url, files=data, headers=headers, stream=True, timeout=60)
111
 
112
  def generate():
113
  cid = f"chatcmpl-{uuid.uuid4()}"
114
  for line in target_resp.iter_lines():
115
- item = clean_sse_line(line)
116
- if item and "data" in item:
117
  chunk = {
118
  "id": cid, "object": "chat.completion.chunk", "created": int(time.time()),
119
- "model": model, "choices": [{"index": 0, "delta": {"content": item["data"]}, "finish_reason": None}]
120
  }
121
  yield f"data: {json.dumps(chunk)}\n\n"
122
  yield "data: [DONE]\n\n"
@@ -127,84 +130,85 @@ async def chat_completions(request: Request, t: str = Depends(verify_api)):
127
  else:
128
  full_text = ""
129
  for line in target_resp.iter_lines():
130
- item = clean_sse_line(line)
131
- if item and "data" in item:
132
- full_text += item["data"]
133
  sync_balance()
134
  return {
135
  "id": f"chatcmpl-{uuid.uuid4()}", "object": "chat.completion", "created": int(time.time()),
136
  "model": model, "choices": [{"index": 0, "message": {"role": "assistant", "content": full_text}, "finish_reason": "stop"}]
137
  }
138
 
139
- # --- 5. Gradio UI 管理面板 ---
140
-
141
- with gr.Blocks(title="AI Doc Maker 管理面板") as demo:
142
- # 状态存储
143
- current_jwt_state = gr.State("")
144
 
145
- # 登录门禁
 
146
  with gr.Column(visible=True) as login_view:
147
- gr.Markdown("## 🔐 访问受限\n请输入 `ACCESS_TOKEN` 以解锁管理面板。")
148
- pwd = gr.Textbox(label="Token", type="password")
149
- login_btn = gr.Button("验证并进入", variant="primary")
150
- err_msg = gr.Markdown()
151
 
152
- # 主面板
153
  with gr.Column(visible=False) as main_view:
154
- gr.Markdown("# 🚀 AI Doc Maker 管理面板")
155
 
156
- with gr.Tab("余额与账号"):
157
- with gr.Row():
158
- ui_token = gr.Textbox(label="当前活跃 Token", interactive=False)
159
- ui_balance = gr.Textbox(label="剩余可用额度", interactive=False)
160
- ui_refresh = gr.Button("手动刷新/注册新账号")
161
-
162
- with gr.Tab("对话与语音"):
163
- ui_input = gr.Textbox(label="输入内容", lines=3, placeholder="输入文字...")
164
- with gr.Row():
165
- ui_chat_btn = gr.Button("发送消息", variant="primary")
166
- ui_tts_btn = gr.Button(" 生成语音", variant="secondary")
167
 
168
- ui_reply = gr.Textbox(label="AI 回复", interactive=False)
169
- with gr.Row():
170
- ui_raw_json = gr.Textbox(label="原始 JSON (聊天)", interactive=False)
171
- ui_audio_json = gr.Textbox(label="音频 JSON", interactive=False)
172
- ui_audio_play = gr.Audio(label="播放器", interactive=False)
173
-
174
- # --- 逻辑处理 ---
175
- def on_login(token):
176
  if token == ACCESS_TOKEN:
177
  jwt = get_valid_token()
178
  bal = sync_balance()
179
- return gr.update(visible=False), gr.update(visible=True), jwt, bal, jwt, ""
180
- return gr.update(visible=True), gr.update(visible=False), "", "", "", "验证失败"
181
 
182
- login_btn.click(on_login, inputs=pwd, outputs=[login_view, main_view, ui_token, ui_balance, current_jwt_state, err_msg])
183
 
184
- def on_refresh():
185
  jwt = fetch_account()
186
  bal = sync_balance()
187
- return jwt, bal, jwt
188
 
189
- ui_refresh.click(on_refresh, outputs=[ui_token, ui_balance, current_jwt_state])
190
 
191
- def on_ui_chat(jwt, text):
192
- if not jwt: yield "请先登录", ""; return
 
 
193
  url = "https://level2labs-prod--adm-agent-send-chat-message.modal.run/"
194
  data = {"content": (None, text), "model": (None, "auto"), "conversation_id": (None, "ui"), "message_id": (None, "ui"), "max_mode": (None, "true")}
195
  full_text = ""
196
- resp = requests.post(url, files=data, headers={"Authorization": f"Bearer {jwt}"}, stream=True)
197
- for line in resp.iter_lines():
198
- item = clean_sse_line(line)
199
- if item and "data" in item:
200
- full_text += item["data"]
201
- yield full_text, json.dumps(item, ensure_ascii=False)
202
- sync_balance()
 
 
 
203
 
204
- ui_chat_btn.click(on_ui_chat, inputs=[current_jwt_state, ui_input], outputs=[ui_reply, ui_raw_json])
205
 
206
- def on_ui_tts(jwt, text):
207
- if not jwt: return "请先登录", None
 
208
  try:
209
  url_create = "https://level2labs-prod--adm-agent-audio-create-audio-with-assistant.modal.run/"
210
  headers = {"Authorization": f"Bearer {jwt}"}
@@ -212,12 +216,13 @@ with gr.Blocks(title="AI Doc Maker 管理面板") as demo:
212
  name = res.get("name")
213
  url_get = f"https://level2labs-prod--adm-agent-audio-get-audio-playback-url.modal.run/?name={name}"
214
  audio_url = requests.get(url_get, headers=headers).json().get("url")
215
- return json.dumps(res, indent=2), audio_url
216
- except Exception as e:
217
- return f"生成失败: {e}", None
218
 
219
- ui_tts_btn.click(on_ui_tts, inputs=[current_jwt_state, ui_input], outputs=[ui_audio_json, ui_audio_play])
220
 
 
221
  app = gr.mount_gradio_app(app, demo, path="/")
222
 
223
  if __name__ == "__main__":
 
9
  from fastapi.responses import StreamingResponse
10
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
11
 
12
+ # --- 1. 配置与状态缓存 ---
13
  app = FastAPI()
14
  security = HTTPBearer()
15
 
 
24
 
25
  cache = TokenCache()
26
 
27
+ # --- 2. 核心后端逻辑 ---
28
 
29
  def fetch_account():
30
+ """注册新账号"""
31
  try:
32
  reg_url = f"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={API_KEY}"
33
  reg_resp = requests.post(reg_url, json={"returnSecureToken": True}, headers={"origin": "https://www.aidocmaker.com"}, timeout=10).json()
34
  token = reg_resp.get("idToken")
35
  if not token: return None
36
 
37
+ # 同步余额
38
  bal_url = "https://level2labs-prod--adm-agent-helper-user-get-credits.modal.run/"
39
  bal_resp = requests.post(bal_url, headers={"Authorization": f"Bearer {token}"}, timeout=10).json()
40
 
 
45
  return None
46
 
47
  def get_valid_token():
 
48
  if cache.token and cache.balance > 0:
49
  return cache.token
50
  return fetch_account()
51
 
52
  def sync_balance():
 
53
  if not cache.token: return 0
54
  url = "https://level2labs-prod--adm-agent-helper-user-get-credits.modal.run/"
55
  try:
 
60
  cache.balance = 0
61
  return 0
62
 
63
+ # --- 3. 稳健的 SSE 解析器 ---
64
 
65
+ def parse_upstream_line(line):
66
+ """解析 upstream 返回的每一行,兼容 data: 前缀和纯 JSON"""
 
67
  if not line: return None
68
+ decoded_line = line.decode("utf-8").strip()
69
+ if not decoded_line: return None
70
+
71
+ # 处理 SSE 常见的 data: 前缀
72
+ json_str = decoded_line
73
+ if decoded_line.startswith("data: "):
74
+ json_str = decoded_line[6:].strip()
75
+
76
+ if json_str == "[DONE]": return None
77
+
78
  try:
79
+ data_obj = json.loads(json_str)
80
+ # 根据之前的日志,内容通常在 "data" 字段
81
+ return data_obj.get("data", "")
82
  except:
83
  return None
84
 
 
86
 
87
  async def verify_api(auth: HTTPAuthorizationCredentials = Depends(security)):
88
  if auth.credentials != ACCESS_TOKEN:
89
+ raise HTTPException(status_code=401, detail="Invalid Access Token")
90
  return auth.credentials
91
 
92
  @app.get("/v1/models")
 
101
  content = body.get("messages", [])[-1]["content"] if body.get("messages") else ""
102
 
103
  jwt = get_valid_token()
104
+ if not jwt: raise HTTPException(status_code=500, detail="Failed to get provider token")
105
 
106
  url = "https://level2labs-prod--adm-agent-send-chat-message.modal.run/"
 
107
  data = {
108
  "content": (None, content), "model": (None, model),
109
  "conversation_id": (None, f"c-{uuid.uuid4()}"),
110
  "message_id": (None, f"m-{uuid.uuid4()}"), "max_mode": (None, "true")
111
  }
112
 
113
+ target_resp = requests.post(url, files=data, headers={"Authorization": f"Bearer {jwt}"}, stream=True, timeout=60)
114
 
115
  def generate():
116
  cid = f"chatcmpl-{uuid.uuid4()}"
117
  for line in target_resp.iter_lines():
118
+ text_chunk = parse_upstream_line(line)
119
+ if text_chunk:
120
  chunk = {
121
  "id": cid, "object": "chat.completion.chunk", "created": int(time.time()),
122
+ "model": model, "choices": [{"index": 0, "delta": {"content": text_chunk}, "finish_reason": None}]
123
  }
124
  yield f"data: {json.dumps(chunk)}\n\n"
125
  yield "data: [DONE]\n\n"
 
130
  else:
131
  full_text = ""
132
  for line in target_resp.iter_lines():
133
+ text_chunk = parse_upstream_line(line)
134
+ if text_chunk:
135
+ full_text += text_chunk
136
  sync_balance()
137
  return {
138
  "id": f"chatcmpl-{uuid.uuid4()}", "object": "chat.completion", "created": int(time.time()),
139
  "model": model, "choices": [{"index": 0, "message": {"role": "assistant", "content": full_text}, "finish_reason": "stop"}]
140
  }
141
 
142
+ # --- 5. Gradio 管理面板 ---
 
 
 
 
143
 
144
+ with gr.Blocks(title="AI Doc Maker 管理面板", theme=gr.themes.Soft()) as demo:
145
+ # 鉴权门禁
146
  with gr.Column(visible=True) as login_view:
147
+ gr.Markdown("## 🔐 访问受限\n请输入系统配置的 `ACCESS_TOKEN` 以解锁界面。")
148
+ pwd = gr.Textbox(label="ACCESS_TOKEN", type="password")
149
+ login_btn = gr.Button("解锁系统", variant="primary")
150
+ login_err = gr.Markdown()
151
 
152
+ # 主操作区
153
  with gr.Column(visible=False) as main_view:
154
+ gr.Markdown("# 🚀 AI Doc Maker 后台管理")
155
 
156
+ with gr.Row():
157
+ ui_token = gr.Textbox(label="当前 Token (缓存)", interactive=False)
158
+ ui_balance = gr.Textbox(label="剩余额度", interactive=False)
159
+ ui_refresh = gr.Button("🔄 刷新账号")
160
+
161
+ with gr.Row():
162
+ with gr.Column():
163
+ ui_input = gr.Textbox(label="输入内容", lines=5, placeholder="写个笑话...")
164
+ with gr.Row():
165
+ ui_chat_btn = gr.Button("💬 发送对话", variant="primary")
166
+ ui_tts_btn = gr.Button("🎙️ 生成语音", variant="secondary")
167
 
168
+ with gr.Column():
169
+ ui_output = gr.Textbox(label="AI 回复", lines=8, interactive=False)
170
+ ui_audio = gr.Audio(label="语音预览", interactive=False)
171
+
172
+ # --- UI 交互逻辑 ---
173
+ def handle_login(token):
 
 
174
  if token == ACCESS_TOKEN:
175
  jwt = get_valid_token()
176
  bal = sync_balance()
177
+ return gr.update(visible=False), gr.update(visible=True), jwt, str(bal), ""
178
+ return gr.update(visible=True), gr.update(visible=False), "", "", "❌ 密码错误"
179
 
180
+ login_btn.click(handle_login, inputs=pwd, outputs=[login_view, main_view, ui_token, ui_balance, login_err])
181
 
182
+ def handle_refresh():
183
  jwt = fetch_account()
184
  bal = sync_balance()
185
+ return jwt, str(bal)
186
 
187
+ ui_refresh.click(handle_refresh, outputs=[ui_token, ui_balance])
188
 
189
+ def handle_ui_chat(text):
190
+ jwt = get_valid_token()
191
+ if not jwt: yield "Token 获取失败"; return
192
+
193
  url = "https://level2labs-prod--adm-agent-send-chat-message.modal.run/"
194
  data = {"content": (None, text), "model": (None, "auto"), "conversation_id": (None, "ui"), "message_id": (None, "ui"), "max_mode": (None, "true")}
195
  full_text = ""
196
+ try:
197
+ resp = requests.post(url, files=data, headers={"Authorization": f"Bearer {jwt}"}, stream=True, timeout=60)
198
+ for line in resp.iter_lines():
199
+ chunk = parse_upstream_line(line)
200
+ if chunk:
201
+ full_text += chunk
202
+ yield full_text
203
+ sync_balance()
204
+ except Exception as e:
205
+ yield f"请求出错: {e}"
206
 
207
+ ui_chat_btn.click(handle_ui_chat, inputs=ui_input, outputs=ui_output)
208
 
209
+ def handle_ui_tts(text):
210
+ jwt = get_valid_token()
211
+ if not jwt: return None
212
  try:
213
  url_create = "https://level2labs-prod--adm-agent-audio-create-audio-with-assistant.modal.run/"
214
  headers = {"Authorization": f"Bearer {jwt}"}
 
216
  name = res.get("name")
217
  url_get = f"https://level2labs-prod--adm-agent-audio-get-audio-playback-url.modal.run/?name={name}"
218
  audio_url = requests.get(url_get, headers=headers).json().get("url")
219
+ return audio_url
220
+ except:
221
+ return None
222
 
223
+ ui_tts_btn.click(handle_ui_tts, inputs=ui_input, outputs=ui_audio)
224
 
225
+ # 挂载并启动
226
  app = gr.mount_gradio_app(app, demo, path="/")
227
 
228
  if __name__ == "__main__":