Spaces:
Running on Zero
Running on Zero
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Agent 架构师</title> | |
| <style> | |
| :root { | |
| color-scheme: light; | |
| --bg: #f7f8fb; | |
| --panel: #ffffff; | |
| --text: #20242c; | |
| --muted: #667085; | |
| --line: #d9dee8; | |
| --accent: #176b87; | |
| --accent-2: #2f8f6f; | |
| --user: #e8f4f8; | |
| --assistant: #ffffff; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: Arial, "Microsoft YaHei", sans-serif; | |
| } | |
| .app { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) 360px; | |
| min-height: 100vh; | |
| } | |
| main { | |
| display: flex; | |
| flex-direction: column; | |
| min-width: 0; | |
| } | |
| header { | |
| padding: 22px 28px 14px; | |
| border-bottom: 1px solid var(--line); | |
| background: var(--panel); | |
| } | |
| h1 { | |
| margin: 0 0 6px; | |
| font-size: 24px; | |
| font-weight: 700; | |
| letter-spacing: 0; | |
| } | |
| .subtitle { | |
| margin: 0; | |
| color: var(--muted); | |
| font-size: 14px; | |
| line-height: 1.5; | |
| } | |
| #chat { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 24px 28px; | |
| } | |
| .message { | |
| max-width: 860px; | |
| margin-bottom: 14px; | |
| padding: 14px 16px; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| line-height: 1.65; | |
| white-space: pre-wrap; | |
| } | |
| .assistant { | |
| background: var(--assistant); | |
| } | |
| .user { | |
| margin-left: auto; | |
| background: var(--user); | |
| border-color: #b9dce8; | |
| } | |
| .composer { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) auto auto; | |
| gap: 10px; | |
| padding: 16px 28px 22px; | |
| border-top: 1px solid var(--line); | |
| background: var(--panel); | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 48px; | |
| max-height: 160px; | |
| resize: vertical; | |
| padding: 12px 14px; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| color: var(--text); | |
| font: inherit; | |
| } | |
| button, | |
| a.button { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 48px; | |
| padding: 0 16px; | |
| border: 1px solid var(--accent); | |
| border-radius: 8px; | |
| background: var(--accent); | |
| color: #fff; | |
| font: inherit; | |
| text-decoration: none; | |
| cursor: pointer; | |
| } | |
| button.secondary { | |
| background: #fff; | |
| color: var(--accent); | |
| } | |
| aside { | |
| border-left: 1px solid var(--line); | |
| background: var(--panel); | |
| padding: 22px 18px; | |
| } | |
| h2 { | |
| margin: 0 0 14px; | |
| font-size: 16px; | |
| } | |
| .status { | |
| display: grid; | |
| gap: 10px; | |
| } | |
| .step { | |
| padding: 10px 12px; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| color: var(--muted); | |
| font-size: 14px; | |
| } | |
| .step.active { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| background: #eef8fb; | |
| } | |
| .step.done { | |
| border-color: #b8dfcf; | |
| color: var(--accent-2); | |
| background: #f0faf5; | |
| } | |
| #download { | |
| width: 100%; | |
| margin-top: 18px; | |
| display: none; | |
| } | |
| @media (max-width: 860px) { | |
| .app { | |
| grid-template-columns: 1fr; | |
| } | |
| aside { | |
| border-left: 0; | |
| border-top: 1px solid var(--line); | |
| } | |
| .composer { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <main> | |
| <header> | |
| <h1>Agent 架构师</h1> | |
| <p class="subtitle">通过 6 轮访谈,把一句话需求整理成可下载的 Agent 岗位卡。</p> | |
| </header> | |
| <section id="chat" aria-live="polite"></section> | |
| <section class="composer"> | |
| <textarea id="input" placeholder="请输入你的回答,例如:我想做一个抖音文案 Agent"></textarea> | |
| <button id="send">发送</button> | |
| <button id="reset" class="secondary">重新开始</button> | |
| </section> | |
| </main> | |
| <aside> | |
| <h2>访谈进度</h2> | |
| <div id="steps" class="status"></div> | |
| <a id="download" class="button" download="agent-card.md">下载 Markdown</a> | |
| </aside> | |
| </div> | |
| <script> | |
| const questions = [ | |
| ["input", "第1轮:输入", "这个工作的输入是什么?从哪里来?什么格式?"], | |
| ["workflow", "第2轮:处理动作", "拿到输入后,具体要做哪几步?请描述主要动作,我会帮你拆成 3-8 个步骤。"], | |
| ["output", "第3轮:输出", "做完之后输出什么?放哪里?什么格式?"], | |
| ["success", "第4轮:成功标准", "怎么判断做对了?怎么判断做错了?"], | |
| ["fallback", "第5轮:人工兜底", "什么情况需要人工介入?你希望在哪个环节检查?"], | |
| ["outOfScope", "第6轮:本期不做", "有什么是这个 Agent 现在明确不应该做的?请列 3-5 条。"] | |
| ]; | |
| const state = { | |
| step: 0, | |
| answers: {}, | |
| done: false, | |
| card: "" | |
| }; | |
| const chat = document.querySelector("#chat"); | |
| const input = document.querySelector("#input"); | |
| const send = document.querySelector("#send"); | |
| const reset = document.querySelector("#reset"); | |
| const steps = document.querySelector("#steps"); | |
| const download = document.querySelector("#download"); | |
| function addMessage(role, text) { | |
| const div = document.createElement("div"); | |
| div.className = `message ${role}`; | |
| div.textContent = text; | |
| chat.appendChild(div); | |
| chat.scrollTop = chat.scrollHeight; | |
| } | |
| function renderSteps() { | |
| steps.innerHTML = ""; | |
| questions.forEach((question, index) => { | |
| const div = document.createElement("div"); | |
| div.className = "step"; | |
| if (index < state.step || state.done) div.classList.add("done"); | |
| if (index === state.step && !state.done) div.classList.add("active"); | |
| div.textContent = question[1]; | |
| steps.appendChild(div); | |
| }); | |
| } | |
| function summarize(key, value) { | |
| const labels = { | |
| input: "输入", | |
| workflow: "处理动作", | |
| output: "输出", | |
| success: "成功标准", | |
| fallback: "人工兜底", | |
| outOfScope: "本期不做" | |
| }; | |
| return `${labels[key]}:${value.trim().replace(/\s+/g, " ")}`; | |
| } | |
| function splitItems(text, fallback) { | |
| const items = text | |
| .replace(/[;;。]/g, "\n") | |
| .split("\n") | |
| .map((item) => item.replace(/^[\s\-\d.、]+/, "").trim()) | |
| .filter(Boolean); | |
| return items.length > 1 ? items.slice(0, 8) : [text.trim() || fallback]; | |
| } | |
| function agentName() { | |
| const seed = `${state.answers.input || ""} ${state.answers.output || ""}`; | |
| if (seed.includes("文案")) return "文案架构师"; | |
| if (seed.includes("图片") || seed.includes("提示词")) return "提示词助手"; | |
| if (seed.includes("客服")) return "客服助手"; | |
| if (seed.includes("抖音")) return "抖音助手"; | |
| return "岗位卡助手"; | |
| } | |
| function buildCard() { | |
| const workflow = splitItems(state.answers.workflow || "", "根据用户回答执行任务"); | |
| const outOfScope = splitItems(state.answers.outOfScope || "", "不做超出本期范围的事项").slice(0, 5); | |
| while (outOfScope.length < 3) outOfScope.push("不在信息不清楚时编造细节"); | |
| const workflowMd = workflow.map((item, index) => `${index + 1}. ${item}`).join("\n"); | |
| const scopeMd = outOfScope.map((item, index) => `- 不做 ${index + 1}:${item}`).join("\n"); | |
| return `## Agent 岗位卡 | |
| ### 岗位名称 | |
| ${agentName()} | |
| ### 一句话岗位定义 | |
| 当收到用户需求时,自动分析并追问关键信息,生成可直接交给 Agent 使用的岗位卡,并提供 Markdown 文件下载。 | |
| ### 输入 | |
| - 输入 1:${state.answers.input || "用户在对话中提交的自然语言需求。"} | |
| ### 处理动作 | |
| ${workflowMd} | |
| ### 输出 | |
| - 输出 1:${state.answers.output || "完整的 Agent 岗位卡;在 Hugging Face Spaces 页面中提供下载按钮;格式为 .md Markdown 文件。"} | |
| ### 成功标准 | |
| - 做对了:${state.answers.success || "岗位卡完整、清晰、可执行,格式符合 Markdown,用户下载后可以直接使用。"} | |
| - 做错了:信息缺失、没有追问清楚就生成、内容空泛、格式不符合 Markdown,或下载后不能直接用于指导 Agent 工作。 | |
| ### 人工兜底 | |
| - 介入条件:${state.answers.fallback || "用户需求模糊、前后矛盾、涉及高风险内容,或多轮追问后仍无法确认关键信息。"} | |
| - 检查环节:每轮用户回答后、生成岗位卡前、用户提出修改意见后。 | |
| ### 本期不做 | |
| ${scopeMd} | |
| `; | |
| } | |
| function updateDownload() { | |
| if (!state.card) return; | |
| const blob = new Blob([state.card], { type: "text/markdown;charset=utf-8" }); | |
| const url = URL.createObjectURL(blob); | |
| const now = new Date(); | |
| const stamp = now.toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-"); | |
| download.href = url; | |
| download.download = `${stamp}.md`; | |
| download.style.display = "inline-flex"; | |
| } | |
| function handleSend() { | |
| const value = input.value.trim(); | |
| if (!value) return; | |
| addMessage("user", value); | |
| input.value = ""; | |
| if (state.done) { | |
| if (["OK", "可以了", "没有", "没了", "定稿"].includes(value.toUpperCase()) || ["可以了", "没有", "没了", "定稿"].includes(value)) { | |
| addMessage("assistant", "好的,这份 Agent 岗位卡就定稿。"); | |
| return; | |
| } | |
| state.card = value; | |
| updateDownload(); | |
| addMessage("assistant", "我已按你的反馈更新岗位卡,并重新生成了可下载文件。这份岗位卡有哪里需要调整吗?"); | |
| return; | |
| } | |
| const question = questions[state.step]; | |
| const key = question[0]; | |
| state.answers[key] = value; | |
| const summary = summarize(key, value); | |
| if (value.length < 4) { | |
| addMessage("assistant", `我先归纳为:${summary}\n\n这个回答还比较短,可以再具体一点吗?`); | |
| return; | |
| } | |
| state.step += 1; | |
| renderSteps(); | |
| if (state.step < questions.length) { | |
| const next = questions[state.step]; | |
| addMessage("assistant", `归纳确认:${summary}\n\n${next[1]}\n\n${next[2]}`); | |
| return; | |
| } | |
| state.done = true; | |
| state.card = buildCard(); | |
| renderSteps(); | |
| updateDownload(); | |
| addMessage("assistant", `归纳确认:${summary}\n\n${state.card}\n\n这份岗位卡有哪里需要调整吗?`); | |
| } | |
| function boot() { | |
| chat.innerHTML = ""; | |
| state.step = 0; | |
| state.answers = {}; | |
| state.done = false; | |
| state.card = ""; | |
| download.style.display = "none"; | |
| download.removeAttribute("href"); | |
| renderSteps(); | |
| addMessage("assistant", `${questions[0][1]}\n\n${questions[0][2]}`); | |
| } | |
| send.addEventListener("click", handleSend); | |
| input.addEventListener("keydown", (event) => { | |
| if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) handleSend(); | |
| }); | |
| reset.addEventListener("click", boot); | |
| boot(); | |
| </script> | |
| </body> | |
| </html> | |