File size: 12,046 Bytes
e268813
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
<!doctype html>
<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>