The user watches the widget live — stream content fast, one section per tool call.
npm install -g @openduo/duoduo-widgets # if not installed
duoduo-widget open --title "Dashboard" --ttl-seconds 300
Send links.feishu_sidebar or links.browser to user. NEVER send control_url/control_token.
cat > /tmp/w-{wid}.html << 'SKELETON'
<div style="background:#1a1a1a;color:#e0e0e0;padding:20px;font-family:system-ui;min-height:100vh;">
<h1 style="color:#fff;font-size:28px;font-weight:500;margin:0 0 4px;">Title</h1>
<p style="color:#999;font-size:14px;margin:0 0 20px;">Subtitle</p>
<!-- NEXT -->
</div>
SKELETON
cat /tmp/w-{wid}.html | duoduo-widget update --wid "wid_..."
python3 - /tmp/w-{wid}.html << 'PYEOF'
import sys
f = sys.argv[1]
html = open(f).read()
section = """<div style="background:#2a2a2a;padding:16px;border-radius:8px;margin-bottom:12px;">
<h3 style="margin:0 0 8px;color:#fff;font-size:16px;font-weight:500;">Section title</h3>
<p style="margin:0;color:#999;font-size:14px;">Content — $100 safe, no escaping needed</p>
</div>
<!-- NEXT -->"""
html = html.replace('<!-- NEXT -->', section)
open(f, 'w').write(html)
PYEOF
cat /tmp/w-{wid}.html | duoduo-widget update --wid "wid_..."
Quoted heredoc 'PYEOF' — write raw HTML, no shell escaping. Only change content inside """...""".
--patch (preferred for data-heavy widgets)After the skeleton is pushed, use --patch to update specific parts of the page without re-sending the entire HTML. This is faster, uses less bandwidth, and avoids morphdom re-rendering.
duoduo-widget update --wid "wid_..." --patch '[
{"op":"append","selector":"#rows","html":"<tr><td>New item</td><td>$100</td></tr>"},
{"op":"text","selector":"#count","text":"42"},
{"op":"innerHTML","selector":"#status","html":"<strong style=\"color:#4ade80\">Done</strong>"}
]'
Patch operations:
| Op | What it does | Requires |
|---|---|---|
| ----------- | ------------------------------------------ | -------- |
append | Insert html as last child of selector | html |
prepend | Insert html as first child of selector | html |
replace | Replace element matching selector | html |
innerHTML | Set innerHTML of selector | html |
text | Set textContent of selector | text |
remove | Remove element matching selector | — |
When to use patch vs full HTML:
Important: Patches update the live viewer instantly but do NOT update the stored HTML on the server. To ensure the finalized artifact includes all changes, use this pattern:
update --html (keep the temp file)--patch for live viewer speed/tmp/w-{wid}.html locallyfinalize, do one last cat /tmp/w-{wid}.html | duoduo-widget update --wid ...finalizeOr simply: pass the final complete HTML via duoduo-widget finalize --wid ... --html "..." if available.
Skeleton design for patch: Give target elements id attributes so patches can address them:
<tbody id="rows"></tbody>
<!-- append rows here -->
<span id="count">0</span>
<!-- update text here -->
<div id="status">Loading...</div>
<!-- update status here -->
duoduo-widget finalize --wid "wid_..."
references/html_patterns.md — read it first, pick a section template, change only the data values. Never design HTML from scratch_hints in update output: no_viewers → send link; ttl_low/ttl_expiring → finalize now; many_updates → wrap upduoduo-widget open --title "Confirm" --ttl-seconds 300 \
--interaction-mode submit --interaction-prompt "Review and confirm"
Button:
Read result: duoduo-widget wait --wid "wid_..." --timeout-seconds 120
cdnjs.cloudflare.com, esm.sh, cdn.jsdelivr.net, unpkg.comfetch(), XMLHttpRequest, WebSocket, eval(), new Function()Templates — read references/html_patterns.md first. Copy a template, change data values only.
| Command | Purpose | Key flags |
|---|---|---|
| ---------- | ------------------ | ------------------------------------------------------------------------ |
open | Create draft | --title, --ttl-seconds, --interaction-mode, --interaction-prompt |
update | Push HTML or patch | --wid, stdin or --html, --patch , --text-fallback |
finalize | Freeze | --wid |
wait | Block for submit | --wid, --timeout-seconds |
get | Poll status | --wid |
draft → finalized → awaiting_input → submitted (terminal)
Finalized artifacts are permanent. Fork: open --fork .
WIDGET_SERVICE_URL env var (default: https://aidgets.dev).
共 1 个版本