@tsaitechnology/tsaitech-ai-chat
v1.3.1
Published
Framework-neutral browser knowledge retrieval engine for the TSAI Technology portfolio chat
Readme
@tsaitechnology/tsaitech-ai-chat
Framework-neutral browser retrieval engine and curated knowledge base for the TSAI Technology portfolio chat. The package embeds a user query locally with a pinned Transformers.js model, resolves deterministic multi-document answer recipes first, and keeps single-document semantic and lexical retrieval as its fallback.
The runtime does not generate answers, execute user instructions, or require an inference backend.
Frontend requirements
The consuming application must provide:
- a modern browser with ES modules, Web Workers, WebAssembly, Fetch API, Web Crypto,
AbortController,TextEncoder,Intl.Segmenter, and typed arrays; - a bundler capable of building module workers, such as Vite;
- same-origin static hosting for the generated database artifacts;
- UI states for initialization progress, answers, clarification, out-of-scope queries, and runtime failures;
- cancellation of an active request when the user submits a replacement query or leaves the chat;
- plain-text rendering for
answer.textandanswer.details; - normal safe anchors for the already validated evidence URLs.
React, Vue, Svelte, and other UI frameworks are not runtime dependencies.
Installation
npm install @tsaitechnology/tsaitech-ai-chatThe npm package contains the runtime JavaScript, TypeScript declarations, module worker, and curated database artifacts.
Static knowledge artifacts
Copy the installed package's database into the frontend's public/static directory without renaming generated files:
npx tsaitech-ai-chat-copy-db public/dbAdd the command to the frontend build so an installed package update also updates its published knowledge artifacts. For example:
{
"scripts": {
"knowledge:sync": "tsaitech-ai-chat-copy-db public/db",
"prebuild": "npm run knowledge:sync",
"build": "vite build"
}
}The resulting frontend structure is:
public/db/
answer-recipes.yaml
manifest.json
vectors.<content-hash>.f32
lexical-index.<content-hash>.json
**/*.mdThe manifest controls every path the runtime may request. A query must never be appended to a URL or converted into a document path.
The frontend serves these files from its own origin at /db/. No GitHub Pages deployment or request back to this repository is required. The HTTP repository intentionally rejects cross-origin redirects.
HTTP contract
Static hosting must return:
| Resource | Content-Type | Cache policy |
|---|---|---|
| manifest.json | application/json | no-cache |
| *.json lexical index | application/json | immutable by versioned filename |
| *.f32 vectors | application/octet-stream | immutable by versioned filename |
| *.md documents | text/markdown or text/plain | may use ETag/content hash |
HTML fallback responses are rejected. The manifest, lexical index, vectors, embedding profile, and selected Markdown content must all belong to the same generated content version.
Vite integration
Use the exported worker entry so Vite bundles Transformers.js and its worker dependencies:
/// <reference types="vite/client" />
import EmbedderWorker from "@tsaitechnology/tsaitech-ai-chat/worker?worker";
import {
createHttpKnowledgeRepository,
createKnowledgeEngine,
createWasmEmbedder,
} from "@tsaitechnology/tsaitech-ai-chat";
const engine = createKnowledgeEngine({
repository: createHttpKnowledgeRepository({
baseUrl: new URL("/db/", window.location.origin),
fetch: window.fetch.bind(window),
}),
embedder: createWasmEmbedder({
workerFactory: () => new EmbedderWorker(),
}),
});
await engine.initialize({
warmup: true,
onProgress(event) {
// Map event.phase to application loading state.
console.log(event.phase, event.fraction);
},
});Create one engine per application session. Do not initialize a new model for every component render or message.
Chat request lifecycle
Only submit complete messages. Do not run embeddings on each keystroke.
let activeRequest: AbortController | undefined;
let previousAnswerId: string | undefined;
async function sendMessage(query: string) {
activeRequest?.abort();
activeRequest = new AbortController();
const result = await engine.answer(
{
query,
...(previousAnswerId ? { previousAnswerId } : {}),
},
{ signal: activeRequest.signal, timeoutMs: 30_000 },
);
switch (result.kind) {
case "answer":
previousAnswerId = result.answer.id;
renderPlainTextAnswer(result.answer);
break;
case "clarification":
renderClarification(result.message, result.options);
break;
case "out_of_scope":
renderSystemMessage(result.message);
break;
case "unavailable":
renderUnavailable(result.message, result.retryable);
break;
}
}previousAnswerId is the only conversational context required by the engine. Do not pass a raw transcript to the embedder.
Call await engine.dispose() when the application permanently tears down the chat. It releases the worker and model resources.
Public contract
The main API is KnowledgeEngine:
interface KnowledgeEngine {
initialize(options?: InitializeOptions): Promise<EngineInfo>;
getTree(options?: GetTreeOptions): Promise<KnowledgeTree>;
getDocument(id: string, options?: RequestOptions): Promise<KnowledgeDocument>;
search(request: SearchRequest, options?: RequestOptions): Promise<readonly SearchHit[]>;
answer(request: AnswerRequest, options?: RequestOptions): Promise<AnswerResult>;
getState(): EngineState;
dispose(): Promise<void>;
}Use answer() for production chat. It owns confidence, ambiguity, scope, expiry, and artifact-integrity decisions. search() is diagnostic and can intentionally return low-confidence results; frontend code must not select its first hit as an answer.
The complete contract and failure reasons are documented in docs/API-CONTRACT.md. Runtime-specific deployment notes are in docs/RUNTIME-RETRIEVAL.md.
Rendering and security
- Treat
answer.textandanswer.detailsas plain text, not HTML. - Preserve line breaks in
answer.text(for example withwhite-space: pre-wrap); sentences and composed source sections are intentionally separated by blank lines. - Do not run the returned text through an unsafe Markdown-to-HTML renderer.
- Evidence URLs are restricted to HTTP(S), but external links should still use the frontend's normal safe-link policy.
- Never interpolate the query into answer HTML, evidence URLs, storage keys, or repository paths.
- Do not bypass
answer()thresholds usingsearch()scores. - Limit the input UI to 500 Unicode characters to match the engine boundary.
- Do not truncate
answer.text; the runtime has no output character limit and detailed compositions are intentionally long. - The model executes in a dedicated worker and must not be given network, tools, or storage capabilities by frontend code.
The pinned model currently loads through Transformers.js. Production deployments may additionally self-host the model, tokenizer, and ONNX Runtime WASM assets. WASM threads are optional; enabling them requires appropriate COOP/COEP headers and crossOriginIsolated.
Local verification
npm ci
npm run verify
npm run build
npm pack --dry-runnpm run verify runs TypeScript checking, database validation, unit/contract tests, deterministic answer-quality evaluation, retrieval evaluation, and the pinned-model integration test.
Publishing to npm
The repository contains a manual GitHub Actions workflow: .github/workflows/publish-npm.yml.
Repository setup:
- Configure npm Trusted Publishing for GitHub organization
tsaitechnology, repositorytsaitech-ai-chat, and workflow filenamepublish-npm.yml. - Allow the trusted publisher to run
npm publish. Do not configure an environment name unless the workflow uses the same GitHub environment. - Open Actions > Publish npm package > Run workflow.
- Enter an exact unused semver such as
1.0.0and choose thelatestornextnpm tag.
The workflow uses npm Trusted Publishing with GitHub OIDC, so it does not require NPM_TOKEN. It installs with npm ci, sets the requested package version without committing it, runs the complete verification suite, builds dist, inspects the package payload, and publishes using a short-lived credential. Sigstore provenance is explicitly disabled because npm only supports provenance for public source repositories.
The package is published as @tsaitechnology/tsaitech-ai-chat. The trusted publisher configuration must belong to that npm organization and exactly match this GitHub repository and workflow filename.
License
The package is currently marked UNLICENSED. Choose and add a repository license before public third-party distribution if that is intended.
