import '../shared/base.css'; import { App } from '@modelcontextprotocol/ext-apps'; interface VoteData { question: string; options: string[]; winner: string | null; isTie: boolean; tally: Record; confidenceByOption: Record; votes: { voter: string; nickname: string; choice: string; confidence: number; reasoning: string; }[]; totalVoters: number; validVotes: number; consensusLevel: 'unanimous' | 'majority' | 'plurality' | 'split' | 'none'; } const consensusConfig: Record = { unanimous: { color: '#4caf50', label: 'Unanimous' }, majority: { color: '#2196f3', label: 'Majority' }, plurality: { color: '#ff9800', label: 'Plurality' }, split: { color: '#ff5722', label: 'Split' }, none: { color: '#9e9e9e', label: 'No Consensus' }, }; const app = new App({ name: 'DuckVote', version: '1.0.0' }, {}); app.ontoolresult = (params) => { const container = document.getElementById('app')!; if (params.isError) { container.innerHTML = `
Tool execution failed
`; return; } const content = params.content; if (!content || !Array.isArray(content) || content.length < 2) { container.innerHTML = `
No structured data received
`; return; } try { const data: VoteData = JSON.parse((content[1] as { type: string; text: string }).text); render(data); } catch { container.innerHTML = `
Failed to parse vote data
`; } }; function render(data: VoteData) { const container = document.getElementById('app')!; const cfg = consensusConfig[data.consensusLevel] || consensusConfig.none; const maxVotes = Math.max(...Object.values(data.tally), 1); let html = `

${esc(data.question)}

`; // Winner card if (data.winner) { html += `
`; html += `Winner`; html += `${esc(data.winner)}`; if (data.isTie) { html += `Tie-breaker by confidence`; } html += `
`; } else { html += `
No valid votes
`; } // Consensus badge html += `
${cfg.label}
`; // Bar chart html += `

Vote Tally

`; const sortedOptions = [...data.options].sort( (a, b) => (data.tally[b] || 0) - (data.tally[a] || 0) ); for (const opt of sortedOptions) { const votes = data.tally[opt] || 0; const conf = data.confidenceByOption[opt] || 0; const pct = (votes / maxVotes) * 100; const isWinner = opt === data.winner; html += `
`; html += `
${esc(opt)}
`; html += `
`; html += `
${votes} vote${votes !== 1 ? 's' : ''} (${conf}%)
`; html += `
`; } html += `
`; // Individual voters html += `

Individual Votes

`; for (const v of data.votes) { const hasChoice = !!v.choice; html += `
`; html += `
${esc(v.nickname)}
`; if (hasChoice) { html += `
${esc(v.choice)}
`; html += `
`; html += `
${v.confidence}% confidence
`; if (v.reasoning) { html += `
Reasoning

${esc(v.reasoning)}

`; } } else { html += `
Invalid vote
`; } html += `
`; } html += `
`; // Footer html += ``; container.innerHTML = html; } function esc(s: string): string { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } app.connect().catch(console.error);