@draht/web-ui

中文文档
2026.11.5发布于 2026/11/06MITTypeScriptES Module近 30 天下载 2189

由 @draht/ai提供动力的 AI 聊天界面可重复使用的 Web UI 组件

中文使用文档
文档目录36 节
  1. 特征
  2. 安装
  3. 快速启动
  4. 建筑
  5. 构成部分
  6. 聊天小组
  7. 代理界面
  8. (来自@draht/代理核心)
  9. 信件类型
  10. 用户信息
  11. 人工行为
  12. 自定义信件类型
  13. 信件变形器
  14. 工具
  15. Java脚本 REPL
  16. 提取文档
  17. 艺术工具
  18. 自定义工具渲染器
  19. 存储
  20. 设置
  21. 设置
  22. 提供商KeysStore
  23. 会话Store
  24. 自定义提供程序
  25. 附录
  26. CORS 代理服务器
  27. 对话框
  28. 设置对话框
  29. 会话列表
  30. ApiKeyPromptDialog 数据
  31. 模型选择器
  32. 样式
  33. 国际化
  34. 实例
  35. 已知问题
  36. 许可证

校对:soup

用于构建 AI 聊天界面的可重复使用的 Web UI 组件 @draht/ai (英语) 财务报告和审定财务报表 @ draht/ 代理核心。 。 。 .

与 小号 网页组件和 Tailwind CSS v4.

特征

  • 聊天用户界面: 与消息历史、流出和工具执行完成接口
  • 工具:JavaScript RePL,文件提取,和文物(HTML,SVG,Markdown等)
  • 附录: PDF, DOCX, XLSX, PPTX, 有预览和文本提取的图像
  • 艺术品: 交互式 HTML, SVG, 有沙盒执行的马克下
  • 存储: 用于会话、 API 密钥和设置的索引化 DB 备份存储
  • CORS 代理服务器: 浏览器环境的自动代理处理
  • 自定义提供者: 支持 Ollama, LM Studio, vLLM, 和 OpenAI 兼容的 API

安装

npm install @draht/web-ui @draht/agent-core @draht/ai

快速启动

见 实例 完整的工作应用程序目录.

import { Agent } from '@draht/agent-core';
import { getModel } from '@draht/ai';
import {
  ChatPanel,
  AppStorage,
  IndexedDBStorageBackend,
  ProviderKeysStore,
  SessionsStore,
  SettingsStore,
  setAppStorage,
  defaultConvertToLlm,
  ApiKeyPromptDialog,
} from '@draht/web-ui';
import '@draht/web-ui/app.css';

// Set up storage
const settings = new SettingsStore();
const providerKeys = new ProviderKeysStore();
const sessions = new SessionsStore();

const backend = new IndexedDBStorageBackend({
  dbName: 'my-app',
  version: 1,
  stores: [
    settings.getConfig(),
    providerKeys.getConfig(),
    sessions.getConfig(),
    SessionsStore.getMetadataConfig(),
  ],
});

settings.setBackend(backend);
providerKeys.setBackend(backend);
sessions.setBackend(backend);

const storage = new AppStorage(settings, providerKeys, sessions, undefined, backend);
setAppStorage(storage);

// Create agent
const agent = new Agent({
  initialState: {
    systemPrompt: 'You are a helpful assistant.',
    model: getModel('anthropic', 'claude-sonnet-5'),
    thinkingLevel: 'off',
    messages: [],
    tools: [],
  },
  convertToLlm: defaultConvertToLlm,
});

// Create chat panel
const chatPanel = new ChatPanel();
await chatPanel.setAgent(agent, {
  onApiKeyRequired: (provider) => ApiKeyPromptDialog.prompt(provider),
});

document.body.appendChild(chatPanel);

建筑

┌─────────────────────────────────────────────────────┐
│                    ChatPanel                        │
│  ┌─────────────────────┐  ┌─────────────────────┐   │
│  │   AgentInterface    │  │   ArtifactsPanel    │   │
│  │  (messages, input)  │  │  (HTML, SVG, MD)    │   │
│  └─────────────────────┘  └─────────────────────┘   │
└─────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────┐
│              Agent (from @draht/agent-core)             │
│  - State management (messages, model, tools)        │
│  - Event emission (agent_start, message_update, ...)│
│  - Tool execution                                   │
└─────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────┐
│                   AppStorage                        │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐             │
│  │ Settings │ │ Provider │ │ Sessions │             │
│  │  Store   │ │Keys Store│ │  Store   │             │
│  └──────────┘ └──────────┘ └──────────┘             │
│                     │                               │
│              IndexedDBStorageBackend                │
└─────────────────────────────────────────────────────┘

构成部分

聊天小组

与内置文物面板的高层次聊天界面.

const chatPanel = new ChatPanel();
await chatPanel.setAgent(agent, {
  // Prompt for API key when needed
  onApiKeyRequired: async (provider) => ApiKeyPromptDialog.prompt(provider),

  // Hook before sending messages
  onBeforeSend: async () => { /* save draft, etc. */ },

  // Handle cost display click
  onCostClick: () => { /* show cost breakdown */ },

  // Custom sandbox URL for browser extensions
  sandboxUrlProvider: () => chrome.runtime.getURL('sandbox.html'),

  // Add custom tools
  toolsFactory: (agent, agentInterface, artifactsPanel, runtimeProvidersFactory) => {
    const replTool = createJavaScriptReplTool();
    replTool.runtimeProvidersFactory = runtimeProvidersFactory;
    return [replTool];
  },
});

代理界面

用于自定义布局的下层聊天界面.

const chat = document.createElement('agent-interface') as AgentInterface;
chat.session = agent;
chat.enableAttachments = true;
chat.enableModelSelector = true;
chat.enableThinkingSelector = true;
chat.onApiKeyRequired = async (provider) => { /* ... */ };
chat.onBeforeSend = async () => { /* ... */ };

属性 :

  • session: 代理实例
  • enableAttachments: 显示附件按钮( 默认: 真)
  • enableModelSelector: 显示模式选择器( 默认: 真)
  • enableThinkingSelector: 显示思维级别选择器( 默认: 真)
  • showThemeToggle: 显示主题切换( 默认: 假)

(来自@draht/代理核心)

import { Agent } from '@draht/agent-core';

const agent = new Agent({
  initialState: {
    model: getModel('anthropic', 'claude-sonnet-5'),
    systemPrompt: 'You are helpful.',
    thinkingLevel: 'off',
    messages: [],
    tools: [],
  },
  convertToLlm: defaultConvertToLlm,
});

// Events
agent.subscribe((event) => {
  switch (event.type) {
    case 'agent_start': // Agent loop started
    case 'agent_end':   // Agent loop finished
    case 'turn_start':  // LLM call started
    case 'turn_end':    // LLM call finished
    case 'message_start':
    case 'message_update': // Streaming update
    case 'message_end':
      break;
  }
});

// Send message
await agent.prompt('Hello!');
await agent.prompt({ role: 'user-with-attachments', content: 'Check this', attachments, timestamp: Date.now() });

// Control
agent.abort();
agent.state.model = newModel;
agent.state.thinkingLevel = 'medium';
agent.state.tools = [...];
agent.followUp(customMessage);

信件类型

用户信息

文件附件的用户信件 :

const message: UserMessageWithAttachments = {
  role: 'user-with-attachments',
  content: 'Analyze this document',
  attachments: [pdfAttachment],
  timestamp: Date.now(),
};

// Type guard
if (isUserMessageWithAttachments(msg)) {
  console.log(msg.attachments);
}

人工行为

用于工艺品的持久性:

const artifact: ArtifactMessage = {
  role: 'artifact',
  action: 'create', // or 'update', 'delete'
  filename: 'chart.html',
  content: '<div>...</div>',
  timestamp: new Date().toISOString(),
};

// Type guard
if (isArtifactMessage(msg)) {
  console.log(msg.filename);
}

自定义信件类型

通过声明合并扩展 :

interface SystemNotification {
  role: 'system-notification';
  message: string;
  level: 'info' | 'warning' | 'error';
  timestamp: string;
}

declare module '@draht/agent-core' {
  interface CustomAgentMessages {
    'system-notification': SystemNotification;
  }
}

// Register renderer
registerMessageRenderer('system-notification', {
  render: (msg) => html`<div class="alert">${msg.message}</div>`,
});

// Extend convertToLlm
function myConvertToLlm(messages: AgentMessage[]): Message[] {
  const processed = messages.map((m) => {
    if (m.role === 'system-notification') {
      return { role: 'user', content: `<system>${m.message}</system>`, timestamp: Date.now() };
    }
    return m;
  });
  return defaultConvertToLlm(processed);
}

信件变形器

convertToLlm 将应用程序消息转换为 LLM 兼容格式 :

import { defaultConvertToLlm, convertAttachments } from '@draht/web-ui';

// defaultConvertToLlm handles:
// - UserMessageWithAttachments → user message with image/text content blocks
// - ArtifactMessage → filtered out (UI-only)
// - Standard messages (user, assistant, toolResult) → passed through

工具

Java脚本 REPL

在沙盒浏览器环境中执行 JavaScript :

import { createJavaScriptReplTool } from '@draht/web-ui';

const replTool = createJavaScriptReplTool();

// Configure runtime providers for artifact/attachment access
replTool.runtimeProvidersFactory = () => [
  new AttachmentsRuntimeProvider(attachments),
  new ArtifactsRuntimeProvider(artifactsPanel, agent, true), // read-write
];

agent.state.tools = [replTool];

提取文档

在 URL 从文档中提取文本 :

import { createExtractDocumentTool } from '@draht/web-ui';

const extractTool = createExtractDocumentTool();
extractTool.corsProxyUrl = 'https://corsproxy.io/?';

agent.state.tools = [extractTool];

艺术工具

被建入ArtifactsPanel,支持:HTML,SVG,Markdown,文本,JSON,图像,PDF,DOCX,XLSX.

const artifactsPanel = new ArtifactsPanel();
artifactsPanel.agent = agent;

// The tool is available as artifactsPanel.tool
agent.state.tools = [artifactsPanel.tool];

自定义工具渲染器

import { registerToolRenderer, type ToolRenderer } from '@draht/web-ui';

const myRenderer: ToolRenderer = {
  render(params, result, isStreaming) {
    return {
      content: html`<div>...</div>`,
      isCustom: false, // true = no card wrapper
    };
  },
};

registerToolRenderer('my_tool', myRenderer);

存储

设置

import {
  AppStorage,
  IndexedDBStorageBackend,
  SettingsStore,
  ProviderKeysStore,
  SessionsStore,
  CustomProvidersStore,
  setAppStorage,
  getAppStorage,
} from '@draht/web-ui';

// Create stores
const settings = new SettingsStore();
const providerKeys = new ProviderKeysStore();
const sessions = new SessionsStore();
const customProviders = new CustomProvidersStore();

// Create backend with all store configs
const backend = new IndexedDBStorageBackend({
  dbName: 'my-app',
  version: 1,
  stores: [
    settings.getConfig(),
    providerKeys.getConfig(),
    sessions.getConfig(),
    SessionsStore.getMetadataConfig(),
    customProviders.getConfig(),
  ],
});

// Wire stores to backend
settings.setBackend(backend);
providerKeys.setBackend(backend);
sessions.setBackend(backend);
customProviders.setBackend(backend);

// Create and set global storage
const storage = new AppStorage(settings, providerKeys, sessions, customProviders, backend);
setAppStorage(storage);

设置

密钥值设置 :

await storage.settings.set('proxy.enabled', true);
await storage.settings.set('proxy.url', 'https://proxy.example.com');
const enabled = await storage.settings.get<boolean>('proxy.enabled');

提供商KeysStore

API 按提供者键 :

await storage.providerKeys.set('anthropic', 'sk-ant-...');
const key = await storage.providerKeys.get('anthropic');
const providers = await storage.providerKeys.list();

会话Store

用元数据聊天会话 :

// Save session
await storage.sessions.save(sessionData, metadata);

// Load session
const data = await storage.sessions.get(sessionId);
const metadata = await storage.sessions.getMetadata(sessionId);

// List sessions (sorted by lastModified)
const allMetadata = await storage.sessions.getAllMetadata();

// Update title
await storage.sessions.updateTitle(sessionId, 'New Title');

// Delete
await storage.sessions.delete(sessionId);

自定义提供程序

自定义 LLM 提供者 :

const provider: CustomProvider = {
  id: crypto.randomUUID(),
  name: 'My Ollama',
  type: 'ollama',
  baseUrl: 'http://localhost:11434',
};

await storage.customProviders.set(provider);
const all = await storage.customProviders.getAll();

附录

装入和处理文件 :

import { loadAttachment, type Attachment } from '@draht/web-ui';

// From File input
const file = inputElement.files[0];
const attachment = await loadAttachment(file);

// From URL
const attachment = await loadAttachment('https://example.com/doc.pdf');

// From ArrayBuffer
const attachment = await loadAttachment(arrayBuffer, 'document.pdf');

// Attachment structure
interface Attachment {
  id: string;
  type: 'image' | 'document';
  fileName: string;
  mimeType: string;
  size: number;
  content: string;        // base64 encoded
  extractedText?: string; // For documents
  preview?: string;       // base64 preview image
}

所支持的格式:PDF,DOCX,XLSX,PPTX,图像,文本文件.

CORS 代理服务器

对于具有 CORS 限制的浏览器环境:

import { createStreamFn, shouldUseProxyForProvider, isCorsError } from '@draht/web-ui';

// AgentInterface auto-configures proxy from settings
// For manual setup:
agent.streamFn = createStreamFn(async () => {
  const enabled = await storage.settings.get<boolean>('proxy.enabled');
  return enabled ? await storage.settings.get<string>('proxy.url') : undefined;
});

// Providers requiring proxy:
// - zai: always
// - anthropic: only OAuth tokens (sk-ant-oat-*)

对话框

设置对话框

import { SettingsDialog, ProvidersModelsTab, ProxyTab, ApiKeysTab } from '@draht/web-ui';

SettingsDialog.open([
  new ProvidersModelsTab(), // Custom providers + model list
  new ProxyTab(),           // CORS proxy settings
  new ApiKeysTab(),         // API keys per provider
]);

会话列表

import { SessionListDialog } from '@draht/web-ui';

SessionListDialog.open(
  async (sessionId) => { /* load session */ },
  (deletedId) => { /* handle deletion */ },
);

ApiKeyPromptDialog 数据

import { ApiKeyPromptDialog } from '@draht/web-ui';

const success = await ApiKeyPromptDialog.prompt('anthropic');

模型选择器

import { ModelSelector } from '@draht/web-ui';

ModelSelector.open(currentModel, (selectedModel) => {
  agent.state.model = selectedModel;
});

样式

导入预建的 CSS :

import '@draht/web-ui/app.css';

或使用自定义配置的 Tailwind:

@import '@mariozechner/mini-lit/themes/claude.css';
@tailwind base;
@tailwind components;
@tailwind utilities;

国际化

import { i18n, setLanguage, translations } from '@draht/web-ui';

// Add translations
translations.de = {
  'Loading...': 'Laden...',
  'No sessions yet': 'Noch keine Sitzungen',
};

setLanguage('de');
console.log(i18n('Loading...')); // "Laden..."

实例

  • 实例/ - 完成网络应用程序,包括会话、文物、自定义信息
  • 站点地质学 - 使用@draht/web-ui浏览器扩展

已知问题

  • 常态成像仪: 已断开

许可证

麻省理工学院

版本与发布记录

@draht/web-ui 共发布 50 个版本,首次发布于 2026/03/03;过去 12 个月发布了 50 个版本。

版本发布日期距今
2026.11.5latest2026/11/067 天前
2026.11.5-12026/11/058 天前
2026.10.312026/11/0112 天前
2026.10.31-12026/10/3113 天前
2026.10.232026/10/2420 天前
2026.10.23-12026/10/2321 天前

其他发布通道:next → 2026.3.2-4。

关于 @draht/web-ui 的常见问题

@draht/web-ui 是什么?

由 @draht/ai提供动力的 AI 聊天界面可重复使用的 Web UI 组件

如何安装 @draht/web-ui?

在项目目录运行 npm install @draht/web-ui,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:lit(^3.3.1)、@mariozechner/mini-lit(^0.2.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@draht/web-ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@draht/web-ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@draht/web-ui 可以商用吗?

@draht/web-ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@draht/web-ui 的下载量有多少?

根据 npm 官方下载统计,@draht/web-ui 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,189 次,日均约 73 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@draht/web-ui 还在维护吗?

最新版本 2026.11.5 发布于 2026/11/06(7 天前);至今共发布 50 个版本,其中过去 12 个月发布了 50 个;首次发布于 2026/03/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。