Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

786 lines
22 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ELIZA - elizaOS Browser Demo</title>
<!-- Import Map for elizaOS packages -->
<script type="importmap">
{
"imports": {
"@elizaos/core": "../../../packages/core/dist/browser/index.browser.js",
"@elizaos/plugin-anthropic": "../../../plugins/plugin-anthropic/dist/browser/index.browser.js",
"@elizaos/plugin-elizacloud": "../../../plugins/plugin-elizacloud/dist/browser/index.browser.js",
"@elizaos/plugin-openai": "../../../plugins/plugin-openai/dist/browser/index.browser.js",
"@elizaos/plugin-openrouter": "../../../plugins/plugin-openrouter/dist/browser/index.browser.js",
"@elizaos/plugin-localdb": "../../../plugins/plugin-localdb/dist/index.browser.js",
"@elizaos/plugin-inmemorydb": "../../../plugins/plugin-inmemorydb/dist/browser/index.browser.js",
"uuid": "https://esm.sh/uuid@11"
}
}
</script>
<style>
:root {
--terminal-green: #33ff33;
--terminal-green-dim: #1a991a;
--terminal-green-glow: #00ff00;
--terminal-bg: #0a0a0a;
--terminal-bg-light: #1a1a1a;
--scanline-opacity: 0.05;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
background: #000;
overflow: hidden;
}
body {
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
background: var(--terminal-bg);
}
/* Full screen terminal */
.crt-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: var(--terminal-bg);
}
.crt-bezel {
position: relative;
width: 100%;
height: 100%;
background: var(--terminal-bg);
overflow: hidden;
}
/* Scanlines */
.crt-bezel::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, var(--scanline-opacity)) 2px,
rgba(0, 0, 0, var(--scanline-opacity)) 4px
);
pointer-events: none;
z-index: 100;
}
/* Screen glow effect */
.crt-bezel::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(
ellipse at center,
rgba(51, 255, 51, 0.03) 0%,
transparent 70%
);
pointer-events: none;
z-index: 99;
}
.terminal {
display: flex;
flex-direction: column;
height: 100%;
padding: 20px 40px;
position: relative;
}
/* Header */
.header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid var(--terminal-green-dim);
}
.header h1 {
font-size: 2.5rem;
color: var(--terminal-green);
text-shadow:
0 0 10px var(--terminal-green-glow),
0 0 20px var(--terminal-green-glow),
0 0 30px var(--terminal-green-glow);
letter-spacing: 0.3em;
margin-bottom: 5px;
animation: flicker 8s infinite;
}
/* Status bar */
.status-bar {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: var(--terminal-green-dim);
padding: 8px 0;
border-bottom: 1px dashed var(--terminal-green-dim);
margin-bottom: 15px;
}
.status-bar .status-item {
display: flex;
align-items: center;
gap: 6px;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--terminal-green);
box-shadow: 0 0 6px var(--terminal-green-glow);
animation: pulse 2s infinite;
}
.status-indicator.loading {
animation: blink 0.5s infinite;
}
/* Chat area */
.chat-container {
flex: 1;
overflow-y: auto;
padding-right: 10px;
scroll-behavior: smooth;
}
.chat-container::-webkit-scrollbar {
width: 6px;
}
.chat-container::-webkit-scrollbar-track {
background: var(--terminal-bg-light);
}
.chat-container::-webkit-scrollbar-thumb {
background: var(--terminal-green-dim);
border-radius: 3px;
}
.message {
margin-bottom: 15px;
line-height: 1.6;
}
.message .label {
color: var(--terminal-green);
font-weight: 500;
}
.message .text {
color: var(--terminal-green);
opacity: 0.9;
}
.message.user .label {
color: #88ff88;
}
.message.eliza .text {
text-shadow: 0 0 5px rgba(51, 255, 51, 0.3);
}
.message.system {
color: var(--terminal-green-dim);
font-style: italic;
text-align: center;
padding: 10px;
border: 1px dashed var(--terminal-green-dim);
margin: 20px 0;
}
/* Typing indicator */
.typing-indicator {
display: none;
color: var(--terminal-green-dim);
margin-bottom: 15px;
}
.typing-indicator.visible {
display: block;
}
.typing-indicator .dots::after {
content: "";
animation: dots 1.5s steps(4, end) infinite;
}
/* Input area */
.input-area {
display: flex;
gap: 10px;
padding-top: 15px;
border-top: 1px solid var(--terminal-green-dim);
}
.prompt-symbol {
color: var(--terminal-green);
font-size: 1.2rem;
line-height: 38px;
text-shadow: 0 0 5px var(--terminal-green-glow);
}
.input-wrapper {
flex: 1;
position: relative;
}
#user-input {
width: 100%;
background: transparent;
border: 1px solid var(--terminal-green-dim);
color: var(--terminal-green);
font-family: inherit;
font-size: 1.1rem;
padding: 8px 12px;
outline: none;
caret-color: var(--terminal-green);
}
#user-input:focus {
border-color: var(--terminal-green);
box-shadow: 0 0 10px rgba(51, 255, 51, 0.2);
}
#user-input::placeholder {
color: var(--terminal-green-dim);
opacity: 0.5;
}
#user-input:disabled {
opacity: 0.5;
}
#send-btn {
background: transparent;
border: 1px solid var(--terminal-green);
color: var(--terminal-green);
font-family: inherit;
font-size: 1rem;
padding: 8px 20px;
cursor: pointer;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.1em;
}
#send-btn:hover:not(:disabled) {
background: var(--terminal-green);
color: var(--terminal-bg);
box-shadow: 0 0 15px var(--terminal-green-glow);
}
#send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Animations */
@keyframes flicker {
0%,
100% {
opacity: 1;
}
92% {
opacity: 1;
}
93% {
opacity: 0.8;
}
94% {
opacity: 1;
}
97% {
opacity: 0.9;
}
98% {
opacity: 1;
}
}
@keyframes pulse {
0%,
100% {
opacity: 1;
box-shadow: 0 0 6px var(--terminal-green-glow);
}
50% {
opacity: 0.6;
box-shadow: 0 0 3px var(--terminal-green-glow);
}
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
@keyframes dots {
0% {
content: "";
}
25% {
content: ".";
}
50% {
content: "..";
}
75% {
content: "...";
}
100% {
content: "";
}
}
/* Mobile adjustments */
@media (max-width: 600px) {
.terminal {
padding: 15px;
}
.header h1 {
font-size: 1.8rem;
letter-spacing: 0.15em;
}
.status-bar {
font-size: 0.75rem;
flex-direction: column;
gap: 5px;
}
.input-area {
flex-direction: column;
}
.prompt-symbol {
display: none;
}
}
</style>
</head>
<body>
<div class="crt-container">
<div class="crt-bezel">
<div class="terminal">
<header class="header">
<h1>ELIZA</h1>
</header>
<div class="status-bar">
<div class="status-item">
<span class="status-indicator" id="db-status"></span>
<span id="db-status-text">Ready</span>
</div>
</div>
<div class="chat-container" id="chat">
<div class="message system" id="init-message">
<span class="text">Initializing elizaOS AgentRuntime...</span>
</div>
</div>
<div class="typing-indicator" id="typing">
<span class="label">ELIZA:</span> <span class="dots"></span>
</div>
<div class="input-area">
<span class="prompt-symbol">></span>
<div class="input-wrapper">
<input
type="text"
id="user-input"
placeholder="Tell me what's troubling you..."
autocomplete="off"
disabled
/>
</div>
<button id="send-btn" disabled type="button">Send</button>
</div>
</div>
</div>
</div>
<!-- elizaOS Browser Runtime - mirrors examples/chat/chat.ts -->
<script type="module">
/**
* elizaOS Browser Demo
*
* This is the browser equivalent of examples/chat/chat.ts
* Uses:
* - @elizaos/core (AgentRuntime)
* - @elizaos/plugin-localdb (localStorage persistence)
* - a real inference provider chosen by which API key is configured
* (OpenAI → OpenRouter → Anthropic → Eliza Cloud, in priority order)
*
* Set a key before loading, e.g. in the page console / boot script:
* globalThis.ELIZA_ENV = { OPENAI_API_KEY: "sk-..." };
*/
import {
AgentRuntime,
ChannelType,
createMessageMemory,
stringToUuid,
} from "@elizaos/core";
import { plugin as localdbPlugin } from "@elizaos/plugin-localdb";
import { v4 as uuidv4 } from "uuid";
// ============================================================================
// Inference provider selection (priority: openai → openrouter → anthropic → eliza cloud)
// ============================================================================
const PROVIDER_PRIORITY = [
{ id: "openai", envKey: "OPENAI_API_KEY", secretName: "OPENAI_API_KEY" },
{
id: "openrouter",
envKey: "OPENROUTER_API_KEY",
secretName: "OPENROUTER_API_KEY",
},
{
id: "anthropic",
envKey: "ANTHROPIC_API_KEY",
secretName: "ANTHROPIC_API_KEY",
},
{
id: "elizacloud",
envKey: "ELIZA_API_KEY",
secretName: "ELIZAOS_CLOUD_API_KEY",
},
];
function readEnv() {
const fromProcess =
typeof process !== "undefined" && process.env ? process.env : {};
const fromWindow = globalThis.ELIZA_ENV ?? {};
return { ...fromProcess, ...fromWindow };
}
function selectProvider() {
const env = readEnv();
for (const provider of PROVIDER_PRIORITY) {
const apiKey = env[provider.envKey];
if (apiKey) {
return {
id: provider.id,
secretName: provider.secretName,
apiKey,
};
}
}
throw new Error(
"No inference provider configured. Set one of OPENAI_API_KEY, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or ELIZA_API_KEY.",
);
}
// Static specifiers so the import map can resolve each provider's browser build.
async function loadProviderPlugin(id) {
switch (id) {
case "openai":
return (await import("@elizaos/plugin-openai")).openaiPlugin;
case "openrouter":
return (await import("@elizaos/plugin-openrouter")).openrouterPlugin;
case "anthropic":
return (await import("@elizaos/plugin-anthropic")).anthropicPlugin;
case "elizacloud":
return (await import("@elizaos/plugin-elizacloud"))
.elizaOSCloudPlugin;
default:
throw new Error(`Unknown inference provider: ${id}`);
}
}
// ============================================================================
// Character (same as chat.ts)
// ============================================================================
const character = {
name: "Eliza",
bio: "I am ELIZA, a Rogerian psychotherapist simulation created at MIT in 1966.",
};
// ============================================================================
// DOM Helpers
// ============================================================================
function getElements() {
return {
chat: document.getElementById("chat"),
userInput: document.getElementById("user-input"),
sendBtn: document.getElementById("send-btn"),
typing: document.getElementById("typing"),
dbStatus: document.getElementById("db-status"),
dbStatusText: document.getElementById("db-status-text"),
initMessage: document.getElementById("init-message"),
};
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
function addMessage(text, isUser = false) {
const { chat } = getElements();
const messageDiv = document.createElement("div");
messageDiv.className = `message ${isUser ? "user" : "eliza"}`;
messageDiv.innerHTML = `
<span class="label">${isUser ? "YOU" : "ELIZA"}:</span>
<span class="text">${escapeHtml(text)}</span>
`;
chat.appendChild(messageDiv);
chat.scrollTop = chat.scrollHeight;
}
function showTyping() {
const { typing } = getElements();
typing.classList.add("visible");
}
function hideTyping() {
const { typing } = getElements();
typing.classList.remove("visible");
}
function updateStatus(text, isLoading = false) {
const { dbStatus, dbStatusText } = getElements();
dbStatusText.textContent = text;
if (isLoading) {
dbStatus.classList.add("loading");
} else {
dbStatus.classList.remove("loading");
}
}
function setInputEnabled(enabled) {
const { userInput, sendBtn } = getElements();
userInput.disabled = !enabled;
sendBtn.disabled = !enabled;
if (enabled) {
userInput.focus();
}
}
// ============================================================================
// Runtime State
// ============================================================================
let runtime = null;
let isProcessing = false;
// Connection IDs (same pattern as chat.ts)
const userId = uuidv4();
const roomId = stringToUuid("chat-room");
const worldId = stringToUuid("chat-world");
// ============================================================================
// Initialize Runtime (mirrors chat.ts)
// ============================================================================
async function initializeRuntime() {
console.log("🚀 Starting Eliza (Browser)...\n");
updateStatus("Selecting inference provider...", true);
// Pick the provider from whichever API key is configured, then load its
// browser build lazily and pass the key through as a character secret.
const provider = selectProvider();
const providerPlugin = await loadProviderPlugin(provider.id);
const runtimeCharacter = {
...character,
settings: {
...character.settings,
secrets: {
...character.settings?.secrets,
[provider.secretName]: provider.apiKey,
},
},
};
console.log(`🧠 Inference provider: ${provider.id}\n`);
updateStatus(`Initializing runtime (${provider.id})...`, true);
// Create runtime with plugins (browser version)
// - localdbPlugin instead of sqlPlugin (localStorage for browser)
// - the selected inference provider plugin (openai / openrouter / anthropic / elizacloud)
runtime = new AgentRuntime({
character: runtimeCharacter,
plugins: [localdbPlugin, providerPlugin],
});
updateStatus("Initializing AgentRuntime...", true);
await runtime.initialize();
updateStatus("Setting up connection...", true);
// Setup connection (same as chat.ts)
await runtime.ensureConnection({
entityId: userId,
roomId,
worldId,
userName: "User",
source: "browser",
channelId: "chat",
serverId: "browser-server",
type: ChannelType.DM,
});
updateStatus(`Ready · ${provider.id}`);
// Remove init message
const { initMessage } = getElements();
initMessage.remove();
// Enable input
setInputEnabled(true);
console.log("💬 Chat with Eliza (browser runtime)\n");
// Welcome message with typing animation
await new Promise((resolve) => setTimeout(resolve, 500));
showTyping();
await new Promise((resolve) => setTimeout(resolve, 800));
hideTyping();
addMessage("Hello! How can I help you today?");
}
// ============================================================================
// Send Message (mirrors chat.ts)
// ============================================================================
async function sendMessage() {
const { userInput } = getElements();
const text = userInput.value.trim();
if (!text || isProcessing || !runtime) return;
if (!runtime.messageService) {
throw new Error("Runtime messageService not available");
}
isProcessing = true;
setInputEnabled(false);
// Display user message
addMessage(text, true);
userInput.value = "";
showTyping();
// Create an output bubble now so we can stream into it.
const { chat } = getElements();
const responseDiv = document.createElement("div");
responseDiv.className = "message eliza";
responseDiv.innerHTML = `
<span class="label">ELIZA:</span>
<span class="text"></span>
`;
chat.appendChild(responseDiv);
chat.scrollTop = chat.scrollHeight;
const responseTextEl = responseDiv.querySelector(".text");
let responseText = "";
const messageMemory = createMessageMemory({
id: uuidv4(),
entityId: userId,
roomId,
content: {
text,
source: "client_chat",
channelType: ChannelType.DM,
},
});
await runtime.messageService.handleMessage(
runtime,
messageMemory,
async (content) => {
// Non-stream fallback: if we didn't get chunks, use the final text.
if (!responseText && content && typeof content.text === "string") {
responseText = content.text;
if (responseTextEl) responseTextEl.textContent = responseText;
}
return [];
},
{
onStreamChunk: async (chunk) => {
// Hide typing as soon as we start receiving output.
if (responseText.length === 0) hideTyping();
responseText += chunk;
if (responseTextEl) responseTextEl.textContent = responseText;
chat.scrollTop = chat.scrollHeight;
},
},
);
hideTyping();
isProcessing = false;
setInputEnabled(true);
}
// ============================================================================
// Event Listeners
// ============================================================================
document.addEventListener("DOMContentLoaded", () => {
const { sendBtn, userInput } = getElements();
sendBtn.addEventListener("click", sendMessage);
userInput.addEventListener("keypress", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Start initialization; surface configuration errors (e.g. no API key) in the UI.
initializeRuntime().catch((error) => {
updateStatus("Configuration error");
const { initMessage } = getElements();
if (initMessage) {
initMessage.querySelector(".text").textContent = error.message;
}
throw error;
});
});
// Export runtime for debugging
window.elizaRuntime = runtime;
</script>
</body>
</html>