跳转到内容

投票卡(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” 功能

进阶(生产版)