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
786 lines
22 KiB
HTML
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>
|