投票卡(in-memory 短版)
最短的可工作投票卡。单进程内存版(重启丢状态;多 replica 不兼容)。生产 版本见 团队投票卡。
代码
import { HasheeAgent } from "@hasheeai/agent-sdk-ts";import { ulid } from "ulid";
const agent = await HasheeAgent.init({ agentId: process.env.HASHEE_AGENT_ID!, token: process.env.HASHEE_AGENT_TOKEN!, baseUrl: "https://api.hashee.ai", connectionMode: "websocket",});
type Vote = { artifactId: string; convId: string; question: string; options: { id: string; label: string; votes: number }[]; voted: Set<string>; closed: boolean; revision: number;};const votes = new Map<string, Vote>();
agent.addMessageHandler(async (msg) => { if (msg.payload?.type !== "text") return; const m = msg.payload.text.match(/^\/vote\s+([^\s]+)\s+(.+)$/); if (!m) return; const [, question, optsStr] = m; const opts = optsStr.split(/\s+/); if (opts.length < 2 || opts.length > 5) return;
const artifactId = ulid(); const vote: Vote = { artifactId, convId: msg.conversation_id, question, options: opts.map((label, i) => ({ id: String.fromCharCode(97 + i), label, votes: 0 })), voted: new Set(), closed: false, revision: 0, }; votes.set(artifactId, vote); await agent.sendArtifact(msg.conversation_id, { artifact: { artifact_id: artifactId, subtype: "app", title: question, payload: render(vote), capability_flags: { allows_response: true }, }, });});
agent.addEventHandler(async (event) => { if (event.type !== "artifact_response") return; if (event.payload.action !== "vote") return; const v = votes.get(event.payload.ref_artifact); if (!v || v.closed || v.voted.has(event.sender_id)) return; const opt = v.options.find((o) => o.id === event.payload.payload?.choice); if (!opt) return; opt.votes++; v.voted.add(event.sender_id); v.revision++; await agent.updateArtifact(v.convId, { ref_artifact: v.artifactId, based_on_revision: v.revision - 1, artifact: { payload: render(v) }, });});
function render(v: Vote) { const total = v.voted.size; return { ui: "vote_card", question: v.question, options: v.options.map((o) => ({ id: o.id, label: o.label, votes: o.votes, pct: total ? Math.round((o.votes / total) * 100) : 0, })), total, closed: v.closed, };}
console.log("[vote] up");触发
用户在群: /vote 午餐 拉面 披萨 沙拉Agent → 群里推投票卡用户点选项 → 卡片实时更新限制
- 单进程内存:进程崩溃 → 投票丢
- 多 replica 不兼容(每个 replica 独立 Map)
- 没有”关闭投票” / “导出 CSV” 功能