@stackline/ai-ui
v0.0.4
Published
Framework-neutral Stackline AI Studio web component.
Maintainers
Keywords
Readme
@stackline/ai-ui
Framework-neutral Stackline AI Studio web component for secure AI chat apps, with model picker, language picker, safe Markdown, local history, RAG evidence display, custom styling hooks, and backend-first Ollama integration.
Documentation & Live Demos | npm | Issues | Repository | Community Discussions
Latest tested package release: 0.0.4
Credits: Stackline AI Studio component architecture, publishing, and documentation by Alexandro Paixao Marques.
Why this package?
@stackline/ai-ui is the browser-facing Studio component for Stackline AI apps. It gives simple users a drop-in interface, while advanced teams can control endpoints, model selection, translations, persistence, CSS variables, CSS parts, and custom headers.
The component is intentionally backend-first: it never stores provider keys, database URLs, SQL, RAG filters, or memory paths. It calls your backend through /models and /chat.
Features
| Feature | Supported |
| :--- | :---: |
| Drop-in <stackline-ai-studio> custom element | ✅ |
| Framework-neutral usage | ✅ |
| Model picker powered by @stackline/multiselect | ✅ |
| Language picker with built-in en, pt, fr, es plus custom languages | ✅ |
| Safe Markdown and limited safe HTML rendering | ✅ |
| LocalStorage history with quota protection | ✅ |
| RAG evidence display without persisting evidence metadata | ✅ |
| Clear conversation button | ✅ |
| Custom endpoint attributes | ✅ |
| CSS custom properties and CSS parts | ✅ |
| Public methods and DOM events | ✅ |
Table of Contents
- Why this package?
- Features
- Status
- The Important Part
- Install By Situation
- Complete Browser-To-Ollama Tutorial
- Minimal UI Markup After The Backend Exists
- Full UI Markup
- Public API
- Attributes
- Methods
- Events
- Endpoint Schemas
- Styling
- Security
Status
Initial public API, ESM-only, TypeScript declarations included. The package
auto-registers <stackline-ai-studio> when imported in a browser.
The Important Part
<stackline-ai-studio></stackline-ai-studio> is not a complete AI application.
It is the frontend component. It needs backend endpoints that return models and
chat responses.
Required runtime path:
Browser
-> <stackline-ai-studio>
-> GET /api/ai/models
-> POST /api/ai/chat
-> @stackline/ai-server
-> @stackline/ai
-> provider adapter, for example @stackline/ai-ollamaDo not put Ollama Cloud keys, provider keys, database URLs, SQL, RAG filters, or memory paths in browser code.
Install By Situation
Existing Compatible Backend
Use this only when your backend already provides GET /api/ai/models and
POST /api/ai/chat.
npm install @stackline/ai-uiFull UI With Ollama
This is the normal install when you want the Studio tag to work against local Ollama:
npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui
npm install -D vite
mkdir -p srcFull UI With Ollama And SQLite Memory
npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui @stackline/ai-memory-sqlite
npm install -D vite
mkdir -p data srcFull UI With Ollama And PostgreSQL RAG
npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui @stackline/ai-rag-postgres
npm install -D vite
mkdir -p srcComplete Stack
npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui @stackline/ai-memory-sqlite @stackline/ai-rag-postgres
npm install -D vite
mkdir -p data sql srcRequirements
- Browser with Custom Elements and Shadow DOM.
- Backend endpoints compatible with
@stackline/ai-server. - A model returned by
GET /api/ai/models, or an explicitmodelattribute.
When To Use
Use this package when you want a drop-in AI chat UI for Vanilla, Angular, React, Vue, Svelte, Astro, or any frontend that can render a custom element.
When Not To Use
Do not use this package as a backend or security layer. It cannot protect provider credentials, database credentials, or private RAG data.
Complete Browser-To-Ollama Tutorial
This section starts from an empty folder and reaches a working
<stackline-ai-studio> connected to local Ollama.
1. Create The Project
mkdir stackline-ai-ui-starter
cd stackline-ai-ui-starter
npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui
npm install -D vite2. Verify Ollama
ollama --version
ollama list
ollama pull llama3.1
curl http://127.0.0.1:11434/api/tagsUse the exact model name from the NAME column of ollama list. Examples:
llama3.1
llama3.1:latest
qwen2.5:latest3. Configure The Backend
Create .env:
PORT=8787
WEB_ORIGIN=http://localhost:4623
OLLAMA_TARGET=http://127.0.0.1:11434
OLLAMA_MODEL=llama3.1OLLAMA_MODEL=auto is supported, but an explicit model is easier to debug for
the first run.
4. Create The Backend Server
Create index.js:
import { createServer } from "node:http";
import { existsSync, readFileSync } from "node:fs";
import { createStacklineAIServer } from "@stackline/ai/server";
import { createStacklineAIHttpHandler } from "@stackline/ai-server";
import { ollamaProvider } from "@stackline/ai-ollama";
function loadEnv(path = new URL(".env", import.meta.url)) {
if (!existsSync(path)) return;
for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
if (!match || process.env[match[1]]) continue;
process.env[match[1]] = match[2].replace(/^['"]|['"]$/g, "");
}
}
async function requestFromNode(req) {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
return new Request(`http://${req.headers.host || "localhost"}${req.url}`, {
method: req.method,
headers: req.headers,
body: chunks.length ? Buffer.concat(chunks) : undefined,
});
}
async function writeNodeResponse(res, response) {
res.statusCode = response.status;
response.headers.forEach((value, key) => res.setHeader(key, value));
res.end(Buffer.from(await response.arrayBuffer()));
}
loadEnv();
const model = process.env.OLLAMA_MODEL || "auto";
if (!model.trim()) throw new Error("OLLAMA_MODEL is empty.");
const ai = createStacklineAIServer({
provider: ollamaProvider({
target: process.env.OLLAMA_TARGET || "http://127.0.0.1:11434",
model,
}),
rag: false,
memory: false,
});
const handleAI = createStacklineAIHttpHandler({
server: ai,
basePath: "/api/ai",
cors: { origins: [process.env.WEB_ORIGIN || "http://localhost:4623"] },
});
const server = createServer(async (req, res) => {
try {
const response = await handleAI(await requestFromNode(req));
await writeNodeResponse(res, response);
} catch (cause) {
const message = cause instanceof Error ? cause.message : "Unexpected server error.";
res.writeHead(500, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify({ error: { message, status: 500 } }));
}
});
server.listen(Number(process.env.PORT || 8787), () => {
console.log("Stackline AI API: http://127.0.0.1:8787/api/ai");
});5. Test The Backend Before The UI
node index.jsIn another terminal:
curl http://127.0.0.1:8787/api/ai/health
curl http://127.0.0.1:8787/api/ai/models
curl http://127.0.0.1:8787/api/ai/chat \
-H 'content-type: application/json' \
-d '{"model":"llama3.1","messages":[{"role":"user","content":"Reply with one short sentence."}]}'If you see:
Ollama chat requires a model. Use a model name or model: "auto".then model reached the Ollama adapter empty or auto could not resolve an
installed model. Fix it by running ollama list, copying the exact model name,
and setting both:
OLLAMA_MODEL=llama3.1model="llama3.1"6. Create The UI
Create index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Stackline AI Studio</title>
</head>
<body>
<stackline-ai-studio
endpoint="/api/ai/chat"
models-endpoint="/api/ai/models"
model="llama3.1"
theme="material"
language="en"
storage-key="stackline-ai-ui-starter"
history-limit="50"
></stackline-ai-studio>
<script type="module" src="/src/index.js"></script>
</body>
</html>Create src/index.js:
import "@stackline/ai-ui";Create src/style.css if you want a full-screen shell:
html,
body {
margin: 0;
min-height: 100%;
}
stackline-ai-studio {
min-height: 100vh;
}Create vite.config.js:
import { defineConfig } from "vite";
export default defineConfig({
server: {
host: "0.0.0.0",
port: 4623,
proxy: {
"/api/ai": "http://127.0.0.1:8787",
},
},
});Run:
npx vite --host 0.0.0.0 --port 4623Open:
http://localhost:4623/Minimal UI Markup After The Backend Exists
After /api/ai/models and /api/ai/chat are working, the UI can be as small
as:
import "@stackline/ai-ui";<stackline-ai-studio></stackline-ai-studio>Default endpoints:
GET /api/ai/modelsPOST /api/ai/chat
Full UI Markup
<stackline-ai-studio
endpoint="/api/ai/chat"
models-endpoint="/api/ai/models"
theme="material"
model="llama3.1"
language="en"
storage-key="company-ai"
history-limit="50"
storage-max-bytes="524288"
></stackline-ai-studio>Public API
defineStacklineAIStudio(win?)stacklineAIStudioTagNameStacklineAIStudioElementStacklineAIStudioMessageStacklineAIStudioModelStacklineAIStudioBuiltInLanguageStacklineAIStudioLanguageStacklineAIStudioLanguageOptionStacklineAIStudioTranslationPackStacklineAIStudioTranslationPacksStacklineAIStudioTranslationInputStacklineAIStudioTranslationLoaderStacklineAIStudioTranslationsStacklineAIStudioStoredState
Attributes
endpointmodels-endpointmodelthemetitlesubtitleplaceholderlanguagelanglanguageslabelstranslationstranslation-packsshow-language-pickerpersiststorage-keyhistory-limitstorage-max-bytes
Methods
const studio = document.querySelector("stackline-ai-studio");
await studio.send("Summarize this ticket.");
studio.setModel("llama3.1");
studio.setLanguage("pt");
studio.setTranslations({ send: "Ask" }); // current language override
studio.setLanguages([
{ id: "en", label: "EN", nativeName: "English" },
{ id: "pt", label: "PT", nativeName: "Português" },
{ id: "de", label: "DE", nativeName: "Deutsch" }
]);
studio.setTranslationPacks({
de: {
placeholder: "Schreiben Sie Ihre Nachricht...",
send: "Senden",
clear: "Leeren"
}
});
studio.registerLanguage("it", { send: "Invia" });
studio.clear();
studio.focusComposer();Events
studio.addEventListener("stackline-response", (event) => {
console.log(event.detail.content, event.detail.metadata);
});
studio.addEventListener("stackline-error", (event) => {
console.error(event.detail.error);
});
studio.addEventListener("stackline-model-change", (event) => {
console.log(event.detail.model);
});
studio.addEventListener("stackline-language-change", (event) => {
console.log(event.detail.language);
});Endpoint Schemas
GET /api/ai/models must return:
{
"models": [
{ "id": "llama3.1", "name": "llama3.1", "provider": "ollama" }
]
}POST /api/ai/chat must accept:
{
"model": "llama3.1",
"messages": [
{ "role": "user", "content": "Hello" }
]
}and return:
{
"message": {
"role": "assistant",
"content": "Hello.",
"model": "llama3.1"
},
"content": "Hello.",
"model": "llama3.1"
}The UI accepts either top-level content or message.content.
Local Persistence
The component stores messages, selected model, and selected language in
localStorage unless persist="false".
Defaults:
history-limit:50storage-max-bytes:524288- generated storage key:
stackline-ai-studio:<path>:<endpoint>
RAG evidence metadata is removed before browser persistence.
Languages
Built-in language codes:
enptfres
The built-ins are only the default. Applications can add their own languages without changing the package.
HTML configuration
Use languages for the picker options and translation-packs for per-language
text:
<stackline-ai-studio
language="de"
languages='[
{ "id": "en", "label": "EN", "nativeName": "English" },
{ "id": "pt", "label": "PT", "nativeName": "Português" },
{ "id": "de", "label": "DE", "nativeName": "Deutsch" }
]'
translation-packs='{
"de": {
"placeholder": "Schreiben Sie Ihre Nachricht...",
"send": "Senden",
"clear": "Leeren"
}
}'
></stackline-ai-studio>labels and root-level translations still override the active language:
<stackline-ai-studio labels='{ "send": "Ask" }'></stackline-ai-studio>JavaScript configuration
const studio = document.querySelector("stackline-ai-studio");
studio.setLanguages([
{ id: "en", label: "EN", nativeName: "English" },
{ id: "pt", label: "PT", nativeName: "Português" },
{ id: "fr", label: "FR", nativeName: "Français" },
{ id: "es", label: "ES", nativeName: "Español" },
{ id: "de", label: "DE", nativeName: "Deutsch" }
]);
studio.setTranslationPacks({
de: {
title: "Stackline AI Studio",
subtitle: "Sichere KI-Unterhaltung.",
placeholder: "Schreiben Sie Ihre Nachricht...",
send: "Senden",
sending: "Wird gesendet",
clear: "Leeren"
}
});
studio.setLanguage("de");If you only need to add one language, use registerLanguage:
studio.registerLanguage(
{ id: "it", label: "IT", nativeName: "Italiano" },
{ placeholder: "Scrivi un messaggio...", send: "Invia" }
);For many languages, lazy-load translation files:
studio.setLanguages([
{ id: "en", label: "EN", nativeName: "English" },
{ id: "ja", label: "JA", nativeName: "日本語" }
]);
studio.loadTranslations = async (language) => {
const response = await fetch(`/i18n/${language}.json`);
return response.ok ? response.json() : null;
};
studio.setLanguage("ja");Fallback order:
- active-language custom pack;
- built-in language or short-code match, such as
pt-BR->pt; - English built-in text;
- active-language one-off overrides from
labels, roottranslations, orsetTranslations({ send: "Ask" }).
Styling
The component uses Shadow DOM and exposes CSS parts such as:
studioheaderheader-actionsmodel-selectlanguage-selectmessagesmessage usermessage assistantclear-buttoncomposercomposer-inputsend-buttonerrorempty
Common CSS custom properties:
stackline-ai-studio {
--sai-accent: #0f8f7e;
--sai-accent-strong: #0a6d60;
}Markdown And HTML Safety
Assistant responses are rendered as safe Markdown with a limited safe HTML subset. Code fences remain escaped, so HTML examples render as code. Unsafe tags and unsafe link schemes are removed.
Test The Example
pnpm --filter stackline-ai-local-demo smokeSecurity
The UI is not a security boundary. Enforce authentication, authorization, model policy, rate limits, RAG filters, and provider credentials on the backend.
Limitations
This is a styled Studio web component, not a fully headless UI package.
Versioning
Use the same release line as the backend Stackline AI packages.
License
MIT
Documentation
- Full tutorial:
docs/getting-started/full-stack-tutorial.md - Package reference:
docs/reference/packages.md
