willian166 commited on
Commit
e268813
·
verified ·
1 Parent(s): 8eaece6

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .env.example +6 -0
  2. Dockerfile +17 -0
  3. README.md +13 -6
  4. app.py +242 -0
  5. index.html +397 -0
  6. requirements.txt +1 -0
.env.example ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ LLM_PROVIDER=
2
+ LLM_BASE_URL=
3
+ LLM_API_KEY=
4
+ LLM_MODEL=
5
+ APP_ENV=production
6
+ LOG_LEVEL=INFO
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ ENV PYTHONDONTWRITEBYTECODE=1 \
6
+ PYTHONUNBUFFERED=1 \
7
+ GRADIO_SERVER_NAME=0.0.0.0 \
8
+ GRADIO_SERVER_PORT=7860
9
+
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ EXPOSE 7860
16
+
17
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,13 +1,20 @@
1
  ---
2
  title: Agent Architect
3
- emoji: 🐨
4
- colorFrom: green
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.24.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Agent Architect
3
+ emoji: 🧭
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
 
 
7
  app_file: app.py
8
  pinned: false
9
+ license: mit
10
  ---
11
 
12
+ # Agent Architect
13
+
14
+ 一个部署在 Hugging Face Spaces 的 Agent 架构师 MVP。
15
+
16
+ 它会通过 6 轮对话收集用户需求,生成完整的 Agent 岗位卡,并提供 `.md` 文件下载。
17
+
18
+ ## Hugging Face Spaces
19
+
20
+ 该项目使用 Gradio SDK,Space 硬件可选择 ZeroGPU。
app.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import gradio as gr
9
+
10
+
11
+ QUESTIONS = [
12
+ {
13
+ "key": "input",
14
+ "title": "第1轮:输入",
15
+ "question": "这个工作的输入是什么?从哪里来?什么格式?",
16
+ },
17
+ {
18
+ "key": "workflow",
19
+ "title": "第2轮:处理动作",
20
+ "question": "拿到输入后,具体要做哪几步?请描述主要动作,我会帮你拆成 3-8 个步骤。",
21
+ },
22
+ {
23
+ "key": "output",
24
+ "title": "第3轮:输出",
25
+ "question": "做完之后输出什么?放哪里?什么格式?",
26
+ },
27
+ {
28
+ "key": "success",
29
+ "title": "第4轮:成功标准",
30
+ "question": "怎么判断做对了?怎么判断做错了?",
31
+ },
32
+ {
33
+ "key": "fallback",
34
+ "title": "第5轮:人工兜底",
35
+ "question": "什么情况需要人工介入?你希望在哪个环节检查?",
36
+ },
37
+ {
38
+ "key": "out_of_scope",
39
+ "title": "第6轮:本期不做",
40
+ "question": "有什么是这个 Agent 现在明确不应该做的?请列 3-5 条。",
41
+ },
42
+ ]
43
+
44
+ EXPORT_DIR = Path("exports")
45
+ EXPORT_DIR.mkdir(exist_ok=True)
46
+
47
+
48
+ def _initial_state() -> dict[str, Any]:
49
+ return {
50
+ "step": 0,
51
+ "answers": {},
52
+ "summaries": {},
53
+ "final_card": "",
54
+ "file_path": None,
55
+ "done": False,
56
+ }
57
+
58
+
59
+ def _chat_line(role: str, content: str) -> dict[str, str]:
60
+ return {"role": role, "content": content}
61
+
62
+
63
+ def _first_message() -> list[dict[str, str]]:
64
+ q = QUESTIONS[0]
65
+ return [
66
+ _chat_line(
67
+ "assistant",
68
+ f"{q['title']}\n\n{q['question']}",
69
+ )
70
+ ]
71
+
72
+
73
+ def _summarize(key: str, answer: str) -> str:
74
+ clean = " ".join(answer.strip().split())
75
+ labels = {
76
+ "input": "输入",
77
+ "workflow": "处理动作",
78
+ "output": "输出",
79
+ "success": "成功标准",
80
+ "fallback": "人工兜底",
81
+ "out_of_scope": "本期不做",
82
+ }
83
+ return f"{labels[key]}:{clean}"
84
+
85
+
86
+ def _split_items(text: str, fallback_prefix: str) -> list[str]:
87
+ raw = text.replace(";", "\n").replace(";", "\n").replace("。", "\n")
88
+ parts = []
89
+ for line in raw.splitlines():
90
+ item = line.strip(" -0123456789.、\t")
91
+ if item:
92
+ parts.append(item)
93
+ if len(parts) <= 1:
94
+ return [text.strip() or fallback_prefix]
95
+ return parts[:8]
96
+
97
+
98
+ def _agent_name(answers: dict[str, str]) -> str:
99
+ seed = answers.get("input", "") + " " + answers.get("output", "")
100
+ if "文案" in seed:
101
+ return "文案架构师"
102
+ if "图片" in seed or "提示词" in seed:
103
+ return "提示词助手"
104
+ if "客服" in seed:
105
+ return "客服助手"
106
+ if "抖音" in seed:
107
+ return "抖音助手"
108
+ return "岗位卡助手"
109
+
110
+
111
+ def _build_card(answers: dict[str, str]) -> str:
112
+ name = _agent_name(answers)
113
+ workflow = _split_items(answers.get("workflow", ""), "根据用户回答执行任务")
114
+ out_of_scope = _split_items(answers.get("out_of_scope", ""), "不做超出本期范围的事项")[:5]
115
+ while len(out_of_scope) < 3:
116
+ out_of_scope.append("不在信息不清楚时编造细节")
117
+
118
+ workflow_md = "\n".join(f"{idx}. {item}" for idx, item in enumerate(workflow, 1))
119
+ scope_md = "\n".join(f"- 不做 {idx}:{item}" for idx, item in enumerate(out_of_scope, 1))
120
+
121
+ return f"""## Agent 岗位卡
122
+
123
+ ### 岗位名称
124
+ {name}
125
+
126
+ ### 一句话岗位定义
127
+ 当收到用户需求时,自动分析并追问关键信息,生成可直接交给 Agent 使用的岗位卡,并提供 Markdown 文件下载。
128
+
129
+ ### 输入
130
+ - 输入 1:{answers.get("input", "用户在对话中提交的自然语言需求。")}
131
+
132
+ ### 处理动作
133
+ {workflow_md}
134
+
135
+ ### 输出
136
+ - 输出 1:{answers.get("output", "完整的 Agent 岗位卡;在 Hugging Face Spaces 页面中提供下载按钮;格式为 .md Markdown 文件。")}
137
+
138
+ ### 成功标准
139
+ - 做对了:{answers.get("success", "岗位卡完整、清晰、可执行,格式符合 Markdown,用户下载后可以直接使用。")}
140
+ - 做错了:信息缺失、没有追问清楚就生成、内容空泛、格式不符合 Markdown,或下载后不能直接用于指导 Agent 工作。
141
+
142
+ ### 人工兜底
143
+ - 介入条件:{answers.get("fallback", "用户需求模糊、前后矛盾、涉及高风险内容,或多轮追问后仍无法确认关键信息。")}
144
+ - 检查环节:每轮用户回答后、生成岗位卡前、用户提出修改意见后。
145
+
146
+ ### 本期不做
147
+ {scope_md}
148
+ """
149
+
150
+
151
+ def _save_card(card: str) -> str:
152
+ safe_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
153
+ file_path = EXPORT_DIR / f"{safe_name}.md"
154
+ file_path.write_text(card, encoding="utf-8")
155
+ return str(file_path)
156
+
157
+
158
+ def start() -> tuple[list[dict[str, str]], dict[str, Any], str, str | None]:
159
+ return _first_message(), _initial_state(), "", None
160
+
161
+
162
+ def respond(message: str, history: list[dict[str, str]], state: dict[str, Any]):
163
+ if not state:
164
+ state = _initial_state()
165
+ history = history or []
166
+ message = (message or "").strip()
167
+ if not message:
168
+ return history, state, "", state.get("file_path")
169
+
170
+ history.append(_chat_line("user", message))
171
+
172
+ if state.get("done"):
173
+ if message.upper() in {"OK"} or message in {"可以了", "没了", "没有", "不用", "定稿"}:
174
+ history.append(_chat_line("assistant", "好的,这份 Agent 岗位卡就定稿。"))
175
+ return history, state, "", state.get("file_path")
176
+ state["final_card"] = message
177
+ file_path = _save_card(message)
178
+ state["file_path"] = file_path
179
+ history.append(
180
+ _chat_line(
181
+ "assistant",
182
+ "我已按你的反馈更新岗位卡,并重新生成了可下载文件。这份岗位卡有哪里需要调整吗?",
183
+ )
184
+ )
185
+ return history, state, "", file_path
186
+
187
+ step = state["step"]
188
+ question = QUESTIONS[step]
189
+ key = question["key"]
190
+ state["answers"][key] = message
191
+ summary = _summarize(key, message)
192
+ state["summaries"][key] = summary
193
+
194
+ if len(message) < 4:
195
+ history.append(_chat_line("assistant", f"我先归纳为:{summary}\n\n这个回答还比较短,可以再具体一点吗?"))
196
+ return history, state, "", state.get("file_path")
197
+
198
+ state["step"] += 1
199
+ if state["step"] < len(QUESTIONS):
200
+ next_q = QUESTIONS[state["step"]]
201
+ history.append(
202
+ _chat_line(
203
+ "assistant",
204
+ f"归纳确认:{summary}\n\n{next_q['title']}\n\n{next_q['question']}",
205
+ )
206
+ )
207
+ return history, state, "", state.get("file_path")
208
+
209
+ card = _build_card(state["answers"])
210
+ file_path = _save_card(card)
211
+ state["final_card"] = card
212
+ state["file_path"] = file_path
213
+ state["done"] = True
214
+ history.append(
215
+ _chat_line(
216
+ "assistant",
217
+ f"归纳确认:{summary}\n\n{card}\n\n这份岗位卡有哪里需要调整吗?",
218
+ )
219
+ )
220
+ return history, state, "", file_path
221
+
222
+
223
+ with gr.Blocks(title="Agent 架构师") as demo:
224
+ gr.Markdown("# Agent 架构师\n把一句话需求,通过多轮访谈整理成可下载的 Agent 岗位卡。")
225
+ state = gr.State(_initial_state())
226
+ chatbot = gr.Chatbot(height=560)
227
+ user_input = gr.Textbox(placeholder="例如:我想做一个抖音文案 Agent", label="你的回答")
228
+ with gr.Row():
229
+ send = gr.Button("发送", variant="primary")
230
+ reset = gr.Button("重新开始")
231
+ download = gr.File(label="下载 Markdown 岗位卡")
232
+
233
+ demo.load(start, outputs=[chatbot, state, user_input, download])
234
+ send.click(respond, inputs=[user_input, chatbot, state], outputs=[chatbot, state, user_input, download])
235
+ user_input.submit(respond, inputs=[user_input, chatbot, state], outputs=[chatbot, state, user_input, download])
236
+ reset.click(start, outputs=[chatbot, state, user_input, download])
237
+
238
+
239
+ if __name__ == "__main__":
240
+ server_name = os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
241
+ server_port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
242
+ demo.launch(server_name=server_name, server_port=server_port)
index.html ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Agent 架构师</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light;
10
+ --bg: #f7f8fb;
11
+ --panel: #ffffff;
12
+ --text: #20242c;
13
+ --muted: #667085;
14
+ --line: #d9dee8;
15
+ --accent: #176b87;
16
+ --accent-2: #2f8f6f;
17
+ --user: #e8f4f8;
18
+ --assistant: #ffffff;
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ min-height: 100vh;
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ font-family: Arial, "Microsoft YaHei", sans-serif;
31
+ }
32
+
33
+ .app {
34
+ display: grid;
35
+ grid-template-columns: minmax(0, 1fr) 360px;
36
+ min-height: 100vh;
37
+ }
38
+
39
+ main {
40
+ display: flex;
41
+ flex-direction: column;
42
+ min-width: 0;
43
+ }
44
+
45
+ header {
46
+ padding: 22px 28px 14px;
47
+ border-bottom: 1px solid var(--line);
48
+ background: var(--panel);
49
+ }
50
+
51
+ h1 {
52
+ margin: 0 0 6px;
53
+ font-size: 24px;
54
+ font-weight: 700;
55
+ letter-spacing: 0;
56
+ }
57
+
58
+ .subtitle {
59
+ margin: 0;
60
+ color: var(--muted);
61
+ font-size: 14px;
62
+ line-height: 1.5;
63
+ }
64
+
65
+ #chat {
66
+ flex: 1;
67
+ overflow-y: auto;
68
+ padding: 24px 28px;
69
+ }
70
+
71
+ .message {
72
+ max-width: 860px;
73
+ margin-bottom: 14px;
74
+ padding: 14px 16px;
75
+ border: 1px solid var(--line);
76
+ border-radius: 8px;
77
+ line-height: 1.65;
78
+ white-space: pre-wrap;
79
+ }
80
+
81
+ .assistant {
82
+ background: var(--assistant);
83
+ }
84
+
85
+ .user {
86
+ margin-left: auto;
87
+ background: var(--user);
88
+ border-color: #b9dce8;
89
+ }
90
+
91
+ .composer {
92
+ display: grid;
93
+ grid-template-columns: minmax(0, 1fr) auto auto;
94
+ gap: 10px;
95
+ padding: 16px 28px 22px;
96
+ border-top: 1px solid var(--line);
97
+ background: var(--panel);
98
+ }
99
+
100
+ textarea {
101
+ width: 100%;
102
+ min-height: 48px;
103
+ max-height: 160px;
104
+ resize: vertical;
105
+ padding: 12px 14px;
106
+ border: 1px solid var(--line);
107
+ border-radius: 8px;
108
+ color: var(--text);
109
+ font: inherit;
110
+ }
111
+
112
+ button,
113
+ a.button {
114
+ display: inline-flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ min-height: 48px;
118
+ padding: 0 16px;
119
+ border: 1px solid var(--accent);
120
+ border-radius: 8px;
121
+ background: var(--accent);
122
+ color: #fff;
123
+ font: inherit;
124
+ text-decoration: none;
125
+ cursor: pointer;
126
+ }
127
+
128
+ button.secondary {
129
+ background: #fff;
130
+ color: var(--accent);
131
+ }
132
+
133
+ aside {
134
+ border-left: 1px solid var(--line);
135
+ background: var(--panel);
136
+ padding: 22px 18px;
137
+ }
138
+
139
+ h2 {
140
+ margin: 0 0 14px;
141
+ font-size: 16px;
142
+ }
143
+
144
+ .status {
145
+ display: grid;
146
+ gap: 10px;
147
+ }
148
+
149
+ .step {
150
+ padding: 10px 12px;
151
+ border: 1px solid var(--line);
152
+ border-radius: 8px;
153
+ color: var(--muted);
154
+ font-size: 14px;
155
+ }
156
+
157
+ .step.active {
158
+ border-color: var(--accent);
159
+ color: var(--accent);
160
+ background: #eef8fb;
161
+ }
162
+
163
+ .step.done {
164
+ border-color: #b8dfcf;
165
+ color: var(--accent-2);
166
+ background: #f0faf5;
167
+ }
168
+
169
+ #download {
170
+ width: 100%;
171
+ margin-top: 18px;
172
+ display: none;
173
+ }
174
+
175
+ @media (max-width: 860px) {
176
+ .app {
177
+ grid-template-columns: 1fr;
178
+ }
179
+
180
+ aside {
181
+ border-left: 0;
182
+ border-top: 1px solid var(--line);
183
+ }
184
+
185
+ .composer {
186
+ grid-template-columns: 1fr;
187
+ }
188
+ }
189
+ </style>
190
+ </head>
191
+ <body>
192
+ <div class="app">
193
+ <main>
194
+ <header>
195
+ <h1>Agent 架构师</h1>
196
+ <p class="subtitle">通过 6 轮访谈,把一句话需求整理成可下载的 Agent 岗位卡。</p>
197
+ </header>
198
+ <section id="chat" aria-live="polite"></section>
199
+ <section class="composer">
200
+ <textarea id="input" placeholder="请输入你的回答,例如:我想做一个抖音文案 Agent"></textarea>
201
+ <button id="send">发送</button>
202
+ <button id="reset" class="secondary">重新开始</button>
203
+ </section>
204
+ </main>
205
+ <aside>
206
+ <h2>访谈进度</h2>
207
+ <div id="steps" class="status"></div>
208
+ <a id="download" class="button" download="agent-card.md">下载 Markdown</a>
209
+ </aside>
210
+ </div>
211
+
212
+ <script>
213
+ const questions = [
214
+ ["input", "第1轮:输入", "这个工作的输入是什么?从哪里来?什么格式?"],
215
+ ["workflow", "第2轮:处理动作", "���到输入后,具体要做哪几步?请描述主要动作,我会帮你拆成 3-8 个步骤。"],
216
+ ["output", "第3轮:输出", "做完之后输出什么?放哪里?什么格式?"],
217
+ ["success", "第4轮:成功标准", "怎么判断做对了?怎么判断做错了?"],
218
+ ["fallback", "第5轮:人工兜底", "什么情况需要人工介入?你希望在哪个环节检查?"],
219
+ ["outOfScope", "第6轮:本期不做", "有什么是这个 Agent 现在明确不应该做的?请列 3-5 条。"]
220
+ ];
221
+
222
+ const state = {
223
+ step: 0,
224
+ answers: {},
225
+ done: false,
226
+ card: ""
227
+ };
228
+
229
+ const chat = document.querySelector("#chat");
230
+ const input = document.querySelector("#input");
231
+ const send = document.querySelector("#send");
232
+ const reset = document.querySelector("#reset");
233
+ const steps = document.querySelector("#steps");
234
+ const download = document.querySelector("#download");
235
+
236
+ function addMessage(role, text) {
237
+ const div = document.createElement("div");
238
+ div.className = `message ${role}`;
239
+ div.textContent = text;
240
+ chat.appendChild(div);
241
+ chat.scrollTop = chat.scrollHeight;
242
+ }
243
+
244
+ function renderSteps() {
245
+ steps.innerHTML = "";
246
+ questions.forEach((question, index) => {
247
+ const div = document.createElement("div");
248
+ div.className = "step";
249
+ if (index < state.step || state.done) div.classList.add("done");
250
+ if (index === state.step && !state.done) div.classList.add("active");
251
+ div.textContent = question[1];
252
+ steps.appendChild(div);
253
+ });
254
+ }
255
+
256
+ function summarize(key, value) {
257
+ const labels = {
258
+ input: "输入",
259
+ workflow: "处理动作",
260
+ output: "输出",
261
+ success: "成功标准",
262
+ fallback: "人工兜底",
263
+ outOfScope: "本期不做"
264
+ };
265
+ return `${labels[key]}:${value.trim().replace(/\s+/g, " ")}`;
266
+ }
267
+
268
+ function splitItems(text, fallback) {
269
+ const items = text
270
+ .replace(/[;;。]/g, "\n")
271
+ .split("\n")
272
+ .map((item) => item.replace(/^[\s\-\d.、]+/, "").trim())
273
+ .filter(Boolean);
274
+ return items.length > 1 ? items.slice(0, 8) : [text.trim() || fallback];
275
+ }
276
+
277
+ function agentName() {
278
+ const seed = `${state.answers.input || ""} ${state.answers.output || ""}`;
279
+ if (seed.includes("文案")) return "文案架构师";
280
+ if (seed.includes("图片") || seed.includes("提示词")) return "提示词助手";
281
+ if (seed.includes("客服")) return "客服助手";
282
+ if (seed.includes("抖音")) return "抖音助手";
283
+ return "岗位卡助手";
284
+ }
285
+
286
+ function buildCard() {
287
+ const workflow = splitItems(state.answers.workflow || "", "根据用户回答执行任务");
288
+ const outOfScope = splitItems(state.answers.outOfScope || "", "不做超出本期范围的事项").slice(0, 5);
289
+ while (outOfScope.length < 3) outOfScope.push("不在信息不清楚时编造细节");
290
+ const workflowMd = workflow.map((item, index) => `${index + 1}. ${item}`).join("\n");
291
+ const scopeMd = outOfScope.map((item, index) => `- 不做 ${index + 1}:${item}`).join("\n");
292
+
293
+ return `## Agent 岗位卡
294
+
295
+ ### 岗位名称
296
+ ${agentName()}
297
+
298
+ ### 一句话岗位定义
299
+ 当收到用户需求时,自动分析并追问关键信息,生成可直接交给 Agent 使用的岗位卡,并提供 Markdown 文件下载。
300
+
301
+ ### 输入
302
+ - 输入 1:${state.answers.input || "用户在对话中提交的自然语言需求。"}
303
+
304
+ ### 处理动作
305
+ ${workflowMd}
306
+
307
+ ### 输出
308
+ - 输出 1:${state.answers.output || "完整的 Agent 岗位卡;在 Hugging Face Spaces 页面中提供下载按钮;格式为 .md Markdown 文件。"}
309
+
310
+ ### 成功标准
311
+ - 做对了:${state.answers.success || "岗位卡完整、清晰、可执行,格式符合 Markdown,用户下载后可以直接使用。"}
312
+ - 做错了:信息缺失、没有追问清楚就生成、内容空泛、格式不符合 Markdown,或下载后不能直接用于指导 Agent 工作。
313
+
314
+ ### 人工兜底
315
+ - 介入条件:${state.answers.fallback || "用户需求模糊、前后矛盾、涉及高风险内容,或多轮追问后仍无法确认关键信息。"}
316
+ - 检查环节:每轮用户回答后、生成岗位卡前、用户提出修改意见后。
317
+
318
+ ### 本期不做
319
+ ${scopeMd}
320
+ `;
321
+ }
322
+
323
+ function updateDownload() {
324
+ if (!state.card) return;
325
+ const blob = new Blob([state.card], { type: "text/markdown;charset=utf-8" });
326
+ const url = URL.createObjectURL(blob);
327
+ const now = new Date();
328
+ const stamp = now.toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
329
+ download.href = url;
330
+ download.download = `${stamp}.md`;
331
+ download.style.display = "inline-flex";
332
+ }
333
+
334
+ function handleSend() {
335
+ const value = input.value.trim();
336
+ if (!value) return;
337
+ addMessage("user", value);
338
+ input.value = "";
339
+
340
+ if (state.done) {
341
+ if (["OK", "可以了", "没有", "没了", "定稿"].includes(value.toUpperCase()) || ["可以了", "没有", "没了", "定稿"].includes(value)) {
342
+ addMessage("assistant", "好的,这份 Agent 岗位卡就定稿。");
343
+ return;
344
+ }
345
+ state.card = value;
346
+ updateDownload();
347
+ addMessage("assistant", "我已按你的反馈更新岗位卡,并重新生成了可下载文件。这份岗位卡有哪里需要调整吗?");
348
+ return;
349
+ }
350
+
351
+ const question = questions[state.step];
352
+ const key = question[0];
353
+ state.answers[key] = value;
354
+ const summary = summarize(key, value);
355
+
356
+ if (value.length < 4) {
357
+ addMessage("assistant", `我先归纳为:${summary}\n\n这个回答还比较短,可以再具体一点吗?`);
358
+ return;
359
+ }
360
+
361
+ state.step += 1;
362
+ renderSteps();
363
+
364
+ if (state.step < questions.length) {
365
+ const next = questions[state.step];
366
+ addMessage("assistant", `归纳确认:${summary}\n\n${next[1]}\n\n${next[2]}`);
367
+ return;
368
+ }
369
+
370
+ state.done = true;
371
+ state.card = buildCard();
372
+ renderSteps();
373
+ updateDownload();
374
+ addMessage("assistant", `归纳确认:${summary}\n\n${state.card}\n\n这份岗位卡有哪里需要调整吗?`);
375
+ }
376
+
377
+ function boot() {
378
+ chat.innerHTML = "";
379
+ state.step = 0;
380
+ state.answers = {};
381
+ state.done = false;
382
+ state.card = "";
383
+ download.style.display = "none";
384
+ download.removeAttribute("href");
385
+ renderSteps();
386
+ addMessage("assistant", `${questions[0][1]}\n\n${questions[0][2]}`);
387
+ }
388
+
389
+ send.addEventListener("click", handleSend);
390
+ input.addEventListener("keydown", (event) => {
391
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) handleSend();
392
+ });
393
+ reset.addEventListener("click", boot);
394
+ boot();
395
+ </script>
396
+ </body>
397
+ </html>
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio==6.24.0