@parallelworks/ai-chat
v0.4.1
Published
Headless AI chat interface with adapters for OpenAI-compatible backends
Readme
@parallelworks/ai-chat
Headless AI chat interface: a React chat UI (thread, composer, streaming,
sidebar, model selector, attachments, sharing, branching) decoupled from any
specific backend through a ChatAdapter interface.
It is both a component library and a runnable app.
Run it
No install, no backend, no credentials — canned streamed replies on http://localhost:4022:
pnpm dlx @parallelworks/ai-chatAgainst any OpenAI-compatible endpoint, including the ACTIVATE gateway's
OpenAI-compatible surface. The key is read from the environment and stays in the
server process; the browser is handed a relative URL and this process attaches
the Authorization header when it forwards, so the key never appears in page
source:
OPENAI_API_KEY=<key> pnpm dlx @parallelworks/ai-chat \
--openai-base-url=https://activate.parallel.works/api/openai/v1 \
--openai-model=gpt-5.6-lunaDrop --openai-model to offer every model the endpoint exposes.
Against ACTIVATE. PW credentials encode the host they belong to, so the target is read out of the key itself rather than from an ambient context that might point somewhere else:
PW_API_KEY=<key> pnpm dlx @parallelworks/ai-chat --activatePW_API_KEY is required here. pw auth stores a credential for the CLI, but
that store is Go-side and deliberately not printable, so the key has to be
passed in.
| Flag | Default | Notes |
| --- | --- | --- |
| --port=<n> | 4022 | Also reads PORT |
| --openai-base-url=<url> | — | Selects OpenAI-compatible mode |
| --openai-model=<id> | all | Restrict the model list to one id |
| --activate | — | Selects ACTIVATE mode |
| --platform-host=<host> | host encoded in the credential | Override the target |
| --help | — | Print usage |
OPENAI_API_KEY and PW_API_KEY come from the environment. Mock and
OpenAI modes persist conversations in localStorage; ACTIVATE mode persists
server-side. The ACTIVATE adapter used here omits the sharing capability, so
that UI is hidden — the in-repo web app carries the full typed adapter.
Use it as components
Everything is exported from the root, so mount the whole layout or compose the
pieces into your own shell. State lives in ChatProvider and is read with
useChat:
import {
ChatProvider, ChatLayout, ChatThread, ChatSidebar, ChatInput,
ChatToolbar, ModelSelector, ShareDialog, useChat,
} from '@parallelworks/ai-chat'
import { createMockChatAdapter } from '@parallelworks/ai-chat/adapters/mock'
import '@parallelworks/ai-chat/styles.css'
export function Chat() {
return (
<ChatProvider
adapter={createMockChatAdapter()}
currentUser={{ id: 'u1', username: 'you', name: 'You' }}
navigation={{ toConversation: id => {}, toNewChat: () => {}, toAttachments: () => {} }}
notify={{ success: console.log, error: console.error, info: console.log }}
>
<ChatLayout>
<ChatThread />
</ChatLayout>
</ChatProvider>
)
}Swap createMockChatAdapter() for createOpenAIChatAdapter({ baseUrl, apiKey })
from @parallelworks/ai-chat/adapters/openai, or implement ChatAdapter
against your own backend. Optional capability groups that you leave undefined
hide their UI rather than erroring.
Architecture
ChatAdapter(src/adapter/types.ts) is the backend boundary. Required capabilities:conversations,models, andstreamCompletion. Optional capability groups (providers,attachments,sharing,allocations) hide their UI affordances when absent.- The OpenAI-compatible streaming core lives in
./adapters/openai: SSE parsing for chat-completions and Responses API transports, plus error mapping. Adapter methods throwChatAdapterErrorwith a user-facingmessage. - All types crossing the boundary are package-owned (
src/types.ts). Model entries and wire messages keep OpenAI-compatible snake_case field names so OpenAI-compatible backends map in without translation.
Styling
The UI is styled with Tailwind utility classes over a small CSS-variable theme
contract (--theme-*), plus chat-specific rules:
@parallelworks/ui/theme.css— the sharedtheme-*utilities and default--theme-*values. Hosts override the variables at runtime.@parallelworks/ai-chat/chat.css— markdown rendering and streaming animations.
Tailwind v4 consumers import both from their own Tailwind entry and add an
@source for this package's files so the utility classes are generated:
@import 'tailwindcss';
@import '@parallelworks/ui/theme.css';
@import '@parallelworks/ai-chat/chat.css';
@source '../node_modules/@parallelworks/ui/src/**/*.{ts,tsx}';
@source '../node_modules/@parallelworks/ai-chat/src/**/*.{ts,tsx}';(Against the published package, point @source at dist/**/*.js instead.)
Consumers without a Tailwind build can import the prebuilt self-contained
@parallelworks/ai-chat/styles.css instead. Note that the prebuilt
stylesheet includes Tailwind's preflight reset; if the host page also ships
its own Tailwind build, prefer the @source route to avoid a double
preflight.
The prebuilt stylesheet nests all of its rules in the pw-ai-chat cascade
layer (declaring pw-ui before it), and @parallelworks/ui/styles.css nests
its rules in pw-ui — so when both are loaded, precedence comes from the
layer order and the import order of the two files does not matter. The chat
sheet also scans the ui package's sources, so on its own it already carries
every utility the ui components use.
