projects/repobot/v4/static/index.html

71 lignes · 2.6 Ko

Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>RepoBot · httpx 答疑助手</title>
<style>
  body { font-family: system-ui, sans-serif; max-width: 760px; margin: 0 auto; padding: 16px; line-height: 1.6; }
  #log { white-space: pre-wrap; }
  .user { font-weight: 600; margin-top: 16px; }
  .step { color: #888; font-size: 13px; }
  .meta { color: #888; font-size: 12px; margin-bottom: 8px; }
  form { display: flex; gap: 8px; margin-top: 16px; }
  input { flex: 1; padding: 8px; font-size: 15px; }
</style>
</head>
<body>
<h2>RepoBot</h2>
<p class="meta">httpx 答疑助手。会查官方文档和源码,并注明出处。</p>
<div id="log"></div>
<form id="form"><input id="q" placeholder="比如:httpx 默认会跟随重定向吗?" autofocus><button>发送</button></form>
<script>
const log = document.getElementById("log");
const history = [];

function add(cls, text) {
  const div = document.createElement("div");
  div.className = cls;
  div.textContent = text;
  log.appendChild(div);
  return div;
}

document.getElementById("form").onsubmit = async (e) => {
  e.preventDefault();
  const input = document.getElementById("q");
  const message = input.value.trim();
  if (!message) return;
  input.value = "";
  add("user", "你:" + message);
  const answer = add("answer", "");

  // EventSource 只能发 GET,这里用 fetch 发 POST,再自己按行解析 SSE
  const resp = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ message, history }),
  });
  const reader = resp.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "", text = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const events = buffer.split("\n\n");
    buffer = events.pop();  // 最后一段可能还没收完整,留到下次
    for (const e of events) {
      if (!e.startsWith("data: ")) continue;
      const ev = JSON.parse(e.slice(6));
      if (ev.type === "tool") log.insertBefore(Object.assign(document.createElement("div"), { className: "step", textContent: "正在调用 " + ev.name + " " + ev.args }), answer);
      if (ev.type === "token") { text += ev.text; answer.textContent = text; }
      if (ev.type === "done") add("meta", `${ev.steps} 次模型调用,${ev.tool_calls} 次工具调用,${ev.cost} 美元`);
    }
  }
  history.push({ role: "user", content: message }, { role: "assistant", content: text });
};
</script>
</body>
</html>