initial: blog-app snapshot

This commit is contained in:
omo
2026-08-17 17:11:27 +08:00
commit 65cb9d5ead
125 changed files with 14720 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
def generate():
start_time = time.time()
yield f"event: step\ndata: {json.dumps({'step': 'context', 'content': f'已收集上下文: {data_summary}'}, ensure_ascii=False)}\n\n"
yield f"event: step\ndata: {json.dumps({'step': 'prompt', 'content': full_prompt_display}, ensure_ascii=False)}\n\n"
try:
resp = fake_post(
"http://127.0.0.1:4000/v1/chat/completions",
json={"model": "default", "messages": messages, "stream": True, "max_tokens": 1024},
stream=True,
timeout=120
)
resp.raise_for_status()
last_keepalive = time.time()
full_text = ""
# 滚动 buffer: 分隔符可能跨 chunk (如 "===REAS" + "ONING==="),
# 保留尾部最长分隔符前缀长度的字符, 不立即下发
DELIM_R = "===REASONING==="
DELIM_C = "===CONCLUSION==="
holdback = max(len(DELIM_R), len(DELIM_C)) - 1
pending = ""
reasoning_started = False
conclusion_started = False
def emit_token(t):
return f"event: token\ndata: {json.dumps({'text': t}, ensure_ascii=False)}\n\n"
for line in resp.iter_lines():
if not line:
continue
line = line.decode("utf-8") if isinstance(line, bytes) else line
now = time.time()
if now - last_keepalive > 15:
yield ": keepalive\n\n"
last_keepalive = now
if line.startswith("data: "):
chunk_data = line[6:]
if chunk_data.strip() == "[DONE]":
break
try:
chunk = json.loads(chunk_data)
delta = chunk.get("choices", [{}])[0].get("delta", {})
token_text = delta.get("content", "")
except json.JSONDecodeError:
continue
if not token_text:
continue
full_text += token_text
pending += token_text
# 循环剥离 pending 里确定安全的部分:
# 遇到完整分隔符 -> 发 section_start 事件, 丢弃分隔符;
# 遇到分隔符前缀后缀 -> 留在 pending 等待后续 chunk
while pending:
idx_r = pending.find(DELIM_R)
idx_c = pending.find(DELIM_C)
# 找最早出现的完整分隔符
idx = -1
delim = None
if idx_r != -1 and (idx_c == -1 or idx_r < idx_c):
idx, delim = idx_r, DELIM_R
elif idx_c != -1:
idx, delim = idx_c, DELIM_C
if idx != -1:
# 分隔符前的正文是安全的, 直接下发
if idx > 0:
if not reasoning_started:
reasoning_started = True
yield f"event: reasoning_start\ndata: {{}}\n\n"
yield emit_token(pending[:idx])
pending = pending[idx + len(delim):]
if delim == DELIM_R:
if not reasoning_started:
reasoning_started = True
yield f"event: reasoning_start\ndata: {{}}\n\n"
else:
if not reasoning_started:
# 防御: 模型跳过 REASONING 直接给 CONCLUSION
reasoning_started = True
yield f"event: reasoning_start\ndata: {{}}\n\n"
if not conclusion_started:
conclusion_started = True
yield f"event: conclusion_start\ndata: {{}}\n\n"
continue
# 无完整分隔符: 检查尾部是否为某分隔符的前缀
keep = 0
tail = pending[-holdback:] if len(pending) > holdback else pending
for d in (DELIM_R, DELIM_C):
for k in range(min(len(tail), len(d) - 1), 0, -1):
if tail.endswith(d[:k]):
keep = max(keep, k)
break
safe = pending[:len(pending) - keep] if keep else pending
pending = pending[len(safe):]
if not safe:
break
if not reasoning_started:
reasoning_started = True
yield f"event: reasoning_start\ndata: {{}}\n\n"
yield emit_token(safe)
# 流结束: 残余 pending 全部下发
if pending:
if not reasoning_started:
reasoning_started = True
yield f"event: reasoning_start\ndata: {{}}\n\n"
yield emit_token(pending)
pending = ""
except Exception as e:
yield f"event: error\ndata: {json.dumps({'error': f'LLM call failed: {str(e)}'}, ensure_ascii=False)}\n\n"
return
# 防御: 模型没给分隔符时补发 section_start, 保证前端 tab 一定有入口
if not reasoning_started:
yield f"event: reasoning_start\ndata: {{}}\n\n"
if not conclusion_started:
yield f"event: conclusion_start\ndata: {{}}\n\n"
elapsed = round(time.time() - start_time, 1)
yield f"event: step\ndata: {json.dumps({'step': 'complete', 'content': f'分析完成, 总耗时 {elapsed}'}, ensure_ascii=False)}\n\n"