mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-10 08:27:40 +00:00
fix: hello world plugin
This commit is contained in:
133
examples/plugins/hello-world/index.js
Normal file
133
examples/plugins/hello-world/index.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Hello World plugin — module entry point.
|
||||
*
|
||||
* The host calls mount(container, api) when the plugin tab is activated and
|
||||
* unmount(container) when it is torn down.
|
||||
*
|
||||
* api shape:
|
||||
* api.context — current PluginContext snapshot
|
||||
* api.onContextChange(cb) → unsubscribe — called whenever theme/project/session changes
|
||||
* api.rpc(method, path, body?) → Promise — proxied to this plugin's server subprocess
|
||||
*/
|
||||
|
||||
export function mount(container, api) {
|
||||
// ── Build DOM ──────────────────────────────────────────────────────────────
|
||||
container.innerHTML = `
|
||||
<div id="hw-root" style="
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
">
|
||||
<h1 style="font-size: 1.4rem; margin: 0 0 8px;">Hello from a plugin!</h1>
|
||||
<p style="font-size: 0.9rem; opacity: 0.7; margin: 0 0 20px;">
|
||||
This tab is rendered by a plain ES module — no iframe.
|
||||
</p>
|
||||
|
||||
<h2 style="font-size: 1rem; margin: 0 0 6px; opacity: 0.8;">Context</h2>
|
||||
<dl style="font-size: 0.85rem; opacity: 0.7; margin: 0 0 24px;">
|
||||
<dt style="font-weight: 600; margin-top: 6px;">Theme</dt>
|
||||
<dd id="hw-theme" style="margin-left: 12px;">—</dd>
|
||||
<dt style="font-weight: 600; margin-top: 6px;">Project</dt>
|
||||
<dd id="hw-project" style="margin-left: 12px;">—</dd>
|
||||
<dt style="font-weight: 600; margin-top: 6px;">Session</dt>
|
||||
<dd id="hw-session" style="margin-left: 12px;">—</dd>
|
||||
</dl>
|
||||
|
||||
<h2 style="font-size: 1rem; margin: 0 0 6px; opacity: 0.8;">Server RPC</h2>
|
||||
<p style="font-size: 0.85rem; opacity: 0.7; margin: 0 0 10px;">
|
||||
Calls this plugin's Node.js server subprocess via the RPC bridge.
|
||||
</p>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<button id="hw-btn-hello">GET /hello</button>
|
||||
<button id="hw-btn-echo">POST /echo</button>
|
||||
</div>
|
||||
<pre id="hw-result" style="
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
">Click a button to make an RPC call…</pre>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// ── Theme helper ──────────────────────────────────────────────────────────
|
||||
function applyTheme(theme) {
|
||||
const root = container.querySelector('#hw-root');
|
||||
if (!root) return;
|
||||
const isDark = theme === 'dark';
|
||||
root.style.background = isDark ? '#1a1a2e' : '#ffffff';
|
||||
root.style.color = isDark ? '#e0e0e0' : '#1a1a2e';
|
||||
const pre = container.querySelector('#hw-result');
|
||||
if (pre) pre.style.background = isDark ? '#2a2a3e' : '#f0f0f4';
|
||||
}
|
||||
|
||||
// ── Render context values ─────────────────────────────────────────────────
|
||||
function renderContext(ctx) {
|
||||
const t = container.querySelector('#hw-theme');
|
||||
const p = container.querySelector('#hw-project');
|
||||
const s = container.querySelector('#hw-session');
|
||||
if (t) t.textContent = ctx.theme || '—';
|
||||
if (p) p.textContent = ctx.project ? ctx.project.name : '(none)';
|
||||
if (s) s.textContent = ctx.session ? (ctx.session.title || ctx.session.id) : '(none)';
|
||||
applyTheme(ctx.theme);
|
||||
}
|
||||
|
||||
// Apply initial context
|
||||
renderContext(api.context);
|
||||
|
||||
// Subscribe to future changes
|
||||
const unsubscribe = api.onContextChange(renderContext);
|
||||
|
||||
// ── Button styles ─────────────────────────────────────────────────────────
|
||||
container.querySelectorAll('button').forEach((btn) => {
|
||||
Object.assign(btn.style, {
|
||||
padding: '8px 16px',
|
||||
border: '1px solid currentColor',
|
||||
borderRadius: '6px',
|
||||
background: 'transparent',
|
||||
color: 'inherit',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.85rem',
|
||||
opacity: '0.8',
|
||||
});
|
||||
btn.addEventListener('mouseenter', () => { btn.style.opacity = '1'; });
|
||||
btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.8'; });
|
||||
});
|
||||
|
||||
// ── RPC buttons ───────────────────────────────────────────────────────────
|
||||
const resultEl = container.querySelector('#hw-result');
|
||||
|
||||
container.querySelector('#hw-btn-hello').addEventListener('click', async () => {
|
||||
resultEl.textContent = 'Loading…';
|
||||
try {
|
||||
const data = await api.rpc('GET', '/hello');
|
||||
resultEl.textContent = JSON.stringify(data, null, 2);
|
||||
} catch (err) {
|
||||
resultEl.textContent = `Error: ${err.message}`;
|
||||
}
|
||||
});
|
||||
|
||||
container.querySelector('#hw-btn-echo').addEventListener('click', async () => {
|
||||
resultEl.textContent = 'Loading…';
|
||||
try {
|
||||
const data = await api.rpc('POST', '/echo', { greeting: 'Hello from the plugin module!' });
|
||||
resultEl.textContent = JSON.stringify(data, null, 2);
|
||||
} catch (err) {
|
||||
resultEl.textContent = `Error: ${err.message}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Store unsubscribe so unmount can clean up
|
||||
container._hwUnsubscribe = unsubscribe;
|
||||
}
|
||||
|
||||
export function unmount(container) {
|
||||
if (typeof container._hwUnsubscribe === 'function') {
|
||||
container._hwUnsubscribe();
|
||||
delete container._hwUnsubscribe;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
}
|
||||
@@ -5,9 +5,9 @@
|
||||
"description": "A minimal example plugin that demonstrates the plugin API.",
|
||||
"author": "Claude Code UI",
|
||||
"icon": "Puzzle",
|
||||
"type": "iframe",
|
||||
"type": "module",
|
||||
"slot": "tab",
|
||||
"entry": "index.html",
|
||||
"entry": "index.js",
|
||||
"server": "server.js",
|
||||
"permissions": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user