@dudko.dev/代理网络反应
反应约束 @dudko.dev/agent-web —— 说
无头,普遍 浏览器 LLM 代理。将代理放入任何反应
具有单钩和(可选的)一组前置组件的应用程序:
- 🪝
useAgent—— 从配置中构建代理, 流出其输入的事件 即时投降状态(计划、步骤、工具呼叫、流接答、符号) 使用、模型装入进度、聊天记录)run编号 :stop编号 :reset编号 :reload。 。 。 . - 💬
<AgentChat>——一来一回聊天:每个工具的呼叫及其成本,代号由 a. 每一答复,以及全部想法、副剂、同意提示 有“ +” / Ctrl+V / 拖放附件( 图像、 PDF 、 文件) 的作曲者, "/"命令,运行计时器,代理,模型+思维芯片,"QQ自动"同意 芯片和语音到文本; 在索引数据库和一个工作空间中保存聊天( 虚拟) 文件系统)面板. 一切都是道具:显示/隐藏,标签(i18n),渲染 道具、插槽、可互换组件、主题——见 文件/聊天时间。 。 。 . - 🗣️ 指导和学习 ——在代理人工作时输入(
send, (中文(简体) ),stopSubagent; 队列 / 在获得它们的步骤下交付了笔记),并观看 它保存一本笔记本:它写成技能的教训learned, (中文(简体) ),<LearnedSkills>, (中文(简体) ),forgetSkill和它获得的页面 文件/聊天时间。 。 。 . - 🧩 构件 —— 说
<AgentComposer>, (中文(简体) ),<MessageList>, (中文(简体) ),<StepList>, (中文(简体) ),<ToolApprovalPrompt>, (中文(简体) ),<ApprovalModeSwitch>, (中文(简体) ),<ChatHistoryList>, (中文(简体) ),<FilesPanel>, (中文(简体) ),<LearnedSkills>, (中文(简体) ),<ContextMeter>, (中文(简体) ),<PlanView>, (中文(简体) ),<ModelLoadBar>, (中文(简体) ),<UsageBadge>, (中文(简体) ),<ApiKeyForm>; 钩子useChatHistory, (中文(简体) ),useVirtualFiles, (中文(简体) ),useSpeechToText; MCP 钩useMcp编号 :useMcpServers住在这里@dudko.dev/agent-web-react/mcp。 。 。 . - 🔑
useCredentials——存储 BYOK API 密钥 休息时加密 (WebCrypto + 索引DB). - 🖥️
useWebLLMModel编号 :useLocalModel装入一个安装在设备上的模型(WebLLM, 浏览器的内置模式,变压器.js, 或您的运行时间) 下载进度、切换和卸载. - 🎛️ 无头第一 – 该事件– UI 逻辑是一个纯的,导出还原器
(单位:千美元)
agentStateReducer;组件为上部可选糖.
▶︎ 现场演示 - 三名探员: 一个编辑粘贴纸板,一个驱动任意数量的MCP服务器(加上 185个工具模拟目录 显示工具搜索,一个玩棋子对着你 ——你的动作触发了它,分析子剂在Web Workers运行. 云别克或 本地 WebGPU, 都在您的浏览器中 。 设置打开工具同意、思维、符号 和步入限制,收缩,技能 - 经纪人自己的 笔记本 (写下来) 在工作空间里 跨越重载)和 网络访问 有限 以列表为主机。 工作时输入代理。 来源于
demo/。 。 。 .
安装
npm install @dudko.dev/agent-web-react @dudko.dev/agent-web react react-dom
然后添加 只有您使用的模型提供者 (核心的可选同行, 动态导入 :
# cloud, pick what you need
npm install @ai-sdk/openai # or @ai-sdk/anthropic, @ai-sdk/google, @ai-sdk/xai, @ai-sdk/deepseek, @ai-sdk/openai-compatible
# local WebGPU models
npm install @browser-ai/web-llm @mlc-ai/web-llm
一次导入可选样式表( 如果您对组件进行样式化, 请选中它) 自己吗
import '@dudko.dev/agent-web-react/styles.css'
快速启动——一个即时聊天面板
<AgentProvider> 建立一个代理人并分享; <AgentChat> 让它.
import { useMemo } from 'react'
import {
AgentProvider,
AgentChat,
useCredentials,
defineTool,
} from '@dudko.dev/agent-web-react'
import { z } from 'zod'
import '@dudko.dev/agent-web-react/styles.css'
export function Assistant() {
const credentials = useCredentials() // encrypted vault (WebCrypto + IndexedDB)
// Your app's actions, as tools. Keep the object referentially stable.
const tools = useMemo(
() => ({
add_text: defineTool({
description: 'Add a text block to the page.',
inputSchema: z.object({ text: z.string() }),
execute: async ({ text }) => addTextBlock(text), // your code
}),
}),
[],
)
return (
<AgentProvider
config={{
model: { providerType: 'google', model: 'gemini-3.5-flash', credentialRef: 'google' },
credentials: credentials.store,
tools,
describeState: () => serializeMyState(), // optional grounding
}}
>
<AgentChat title="Assistant" style={{ height: 520 }} />
</AgentProvider>
)
}
使用捆绑机(Vite, Next, CRA)? 一个提供者 光谱 就像上面的那个 请求核心
import('@ai-sdk/google')运行时——但浏览器捆绑器 解决不了的,@vite-ignored 导入,所以失败 “......提供包件没有安装” (和 WebLLM 失败 “网络LLM不是 函数”) (中文(简体) ). 自己建个模型,传过去 直接 相反——见 捆绑器中的模型这是额外的 线和工作在每个捆绑.
存储用户的密钥一次( 在休息时加密), 例如从设置表单中 :
const credentials = useCredentials()
await credentials.setKey('google', userProvidedKey)
// or drop in <ApiKeyForm credentials={credentials} credentialRef="google" />
这个 useAgent 钩 (无头)
useAgent 是整个库在一个钩子中——如果需要直接使用它
自己的用户界面 :
import { useAgent } from '@dudko.dev/agent-web-react'
function Custom() {
const agent = useAgent({
// In a bundler, pass a model you built (see “Models in a bundler”); a
// provider spec like this one only resolves where dynamic imports do.
model: { providerType: 'openai', model: 'gpt-5.4-mini', credentialRef: 'openai' },
credentials,
tools,
})
return (
<>
<button disabled={!agent.isReady} onClick={() => agent.run('Add a totals row')}>
Run
</button>
{agent.isRunning && <button onClick={agent.stop}>Stop</button>}
{/* Everything below is live, derived from the event stream: */}
{agent.plan && <p>{agent.plan.thought}</p>}
{agent.steps.map((s) => (
<div key={s.id}>
{s.index}/{s.total} · {s.step.description} · {s.status}
{s.toolCalls.map((c, i) => (
<code key={i}>{c.name}</code>
))}
</div>
))}
<p>{agent.finalText}</p>
<small>{agent.usage.totalTokens} tokens</small>
</>
)
}
useAgent(config, options) 返回全数 AgentUiState 加:
| 外地 | 说明 |
|---|---|
run(goal, { images, files, attachments, label }) | 启动运行( 有附件); 解决 RunResult |
stop() | 中止飞行运行 |
agent | 核心数据 Agent 本身(或 undefined 直到建成为止 : agent.usage 编号 : agent.sessionUsage() (用完的,计为活;核心为0.0.32), agent.listLearnedSkills(), (中文(简体) ), agent.capabilities |
forgetSkill(name) | 从该代理的笔记本中删除其自己的一个教训 |
sendUndelivered() 编号 : discardUndelivered() | 发送, 或放下, 什么停止运行从未显示代理 |
send(text), (中文(简体) ), stopSubagent(id) | 向运行的代理商(在下一个边界向模型倾斜;再规划商重新检查计划)/停止一个子代理商. queued, (中文(简体) ), notes 财务报告和审定财务报表 undelivered 显示信件所在之处 文件/聊天时间 |
reset() | 清除整个对话(和经纪人的记忆) |
reload() | 重建代理( 如存储新密钥后) |
status | idle | initializing | ready | running | error |
isReady 编号 : isRunning | 方便布尔 |
messages | 记录;每个助理信息保留其步骤、工具呼叫、想法、批准、子代理、使用和持续时间 |
plan 编号 : steps | 实时计划和每步工具呼叫(附时间) |
finalText 编号 : finalReasoning | 流传的答案及其想法 |
usage 编号 : totalUsage | 按种类分类,这是对话的标志 |
pendingApprovals, (中文(简体) ), approve(id, { remember }), (中文(简体) ), deny(id) | 工具协议 |
approvalMode 编号 : setApprovalMode(mode) | 自动驾驶开关, 直播 |
compact() | 现在总结一下存储的文字记录 |
sessionId 编号 : loadChat(chat) | 内存会话; 切换对话框 |
subagents, (中文(简体) ), skills, (中文(简体) ), discoveredTools, (中文(简体) ), compactions, (中文(简体) ), budget | 剩下的运行,折叠 |
learned, (中文(简体) ), learnedNow, (中文(简体) ), fetches | 代理人的笔记本LessonView[],存活下来 reset),它写了什么这个跑,和 web_fetch 请求此运行( n)FetchView[](中文(简体) ) |
queued, (中文(简体) ), notes, (中文(简体) ), undelivered, (中文(简体) ), replan | 引导:等待、显示(和在何处)留下的信息;最后的重新规划决定(byUser 当信息引起它的时候) |
error, (中文(简体) ), stopped, (中文(简体) ), events, (中文(简体) ), seq | 运行错误文本, 是否停止, 原始事件日志及其计数器 |
modelLoad | WebLLM 下载进度, 装入时 |
选项 : deps (这些变化时重建代理人——例如,在模型上重建)
切换, onEvent (接接活物流), autoStart, (中文(简体) ), maxEvents, (中文(简体) ),
approvals (程序同意请求入钩;默认为真).
保留
tools财务报告和审定财务报表describeState优惠稳定(useMemo编号 :useCallback; 通过更改deps阵列重建 a 上的代理 模型/提供者开关.
本地 WebGPU 模型( 无密钥, 离线)
import { useAgent, useWebLLMModel, ModelLoadBar } from '@dudko.dev/agent-web-react'
import { webLLM } from '@browser-ai/web-llm' // your app's optional peer
// Build WebLLM in your own code so the bundler includes it (see note below).
const createLocalModel = (id: string, opts?: object) => Promise.resolve(webLLM(id, opts as never))
function LocalAgent() {
const local = useWebLLMModel('Qwen2.5-1.5B-Instruct-q4f16_1-MLC', { create: createLocalModel })
const agent = useAgent(
{ model: local.model!, tools },
{ deps: [local.model] }, // build once the model is loaded
)
if (!local.ready) {
return local.loading ? (
<ModelLoadBar load={{ progress: local.progress, text: local.text }} />
) : (
<button disabled={!local.supported} onClick={local.load}>
Load local model
</button>
)
}
return <AgentChat controller={agent} />
}
load()急于下载权重(因此进度栏在加载时会填充), 不默不作声地传达第一个信息)和ready只有模型可以翻转 实际回答。 这个create选项是它是如何在捆绑器下工作的—— 见下文.改变
modelId和一切的钩子报告——model, (中文(简体) ),ready, (中文(简体) ),loading, (中文(简体) ),progress, (中文(简体) ),error是关于新的,没有装入 还没有。 以前的模型留在内存中(loadedModelId; 回切换为 (即刻)直到新的一个被装入——这首先释放它,所以两个型号 从未共享 GPU —— 或者直到unload()。 。 。 .上下文窗口. WebLLM 用一个 4096-token 窗口装入其模型, 距离 在Quen3或Llama 3.x的训练之下。 过
contextWindowTokens改为 加载更多( 花费 KV- cache VRAM 而不是新下载); 您create工厂接收 并应用核心withWebLLMContextWindow(见演示)providers.ts) (中文(简体) ).local.contextWindow窗口是模型 里面装满了炸药 毒剂自己装入了炸药中, 工具列表和工具结果从中大小.视觉.
Phi-3.5-vision-instruct-q4f16_1-MLC获取图像( 贴上, 滴出) ——一种从不向任何地方发送它们的多式联运模式.
任何运行时间 useLocalModel
useWebLLMModel 实值 useLocalModel 与 WebLLM 引擎。 描述另一个
作为引擎运行时间—— create,可选择 warmUp (下载,现在,与
进展, unload, (中文(简体) ), supported, (中文(简体) ), contextWindowOf - 钩子给
不变 load 编号 : unload / 进度/ 切换 :
import { useLocalModel, createWebLLMEngine, type LocalModelEngine } from '@dudko.dev/agent-web-react'
const builtIn: LocalModelEngine = {
create: async () => (await import('@browser-ai/core')).browserAI('text', {
expectedInputs: [{ type: 'text' }, { type: 'image' }],
}),
warmUp: async (m, { onProgress }) => {
await m.createSessionWithProgress((p) => onProgress({ progress: p, text: 'Downloading' }))
},
supported: () => 'LanguageModel' in globalThis, // Chrome's Prompt API
contextWindowOf: (m) => m.getContextWindow(),
}
const webllm = createWebLLMEngine({ create: createLocalModel })
// One hook, any runtime: switching frees the previous model with its own engine.
const local = useLocalModel(option.model, option.builtIn ? builtIn : webllm, {
contextWindowTokens: option.contextWindow,
})
演示会运行WebLLM,Chrome的双子纳米和变压器. js这种方式——看
demo/src/local-engines.ts。 。 。 .
捆绑器中的模型( Vite, Next, CRA)
核心构建云提供方 import('@ai-sdk/<provider>') 和网络LLM
与 import('@browser-ai/web-llm')这些是 赤裸的, @vite-ignored 动态
进口 – 对节点/ SSR/ 导入- 映射设置很有帮助, 但也可以是浏览器捆绑器
让他们在运行时无法解决(云)- “提供一揽子......不是
已安装”)或将其分解为空模块(WebLLM →) “网络LLM不是
函数”) (中文(简体) ).
两者的修补相同: 在自己的代码中构建模型 (静态)
导入您的捆绑器可以看见),并将代理直接交给 LanguageModel。 。 。 .
云头 从库藏钥匙中建造提供者:
import { useEffect, useState } from 'react'
import { createGoogleGenerativeAI } from '@ai-sdk/google'
import type { LanguageModel } from 'ai'
import { useAgent, useCredentials } from '@dudko.dev/agent-web-react'
function CloudAgent() {
const credentials = useCredentials()
const [model, setModel] = useState<LanguageModel>()
useEffect(() => {
credentials.store.getApiKey('google').then((key) => {
setModel(key ? createGoogleGenerativeAI({ apiKey: key })('gemini-3.5-flash') : undefined)
})
}, [credentials.store, credentials.version])
const agent = useAgent({ model: model!, credentials: credentials.store, tools }, { deps: [model] })
// …render <AgentChat controller={agent} /> once model is set
}
人类需求
headers: { 'anthropic-dangerous-direct-browser-access': 'true' }转至createAnthropic({ … })用于直接浏览器的呼叫.
当地 - 通过静态进口 webLLM 工厂通过 useWebLLMModel是因为
create 选项(见上文WebLLM实例).
这个 demo/ 应用程序就是这样做的
demo/src/providers.ts。 。 。 .
MCP钩子生活在自己的亚道
import { useMcp, useMcpServers } from '@dudko.dev/agent-web-react/mcp'
MCP钩子是 没有 从 @dudko.dev/agent-web-react他们装入
核心软件 @dudko.dev/agent-web/mcp输入 @modelcontextprotocol/sdk - 一个
选项 pal,所以 npm 不为您安装它 。 捆绑者不能遵守
"可选":它甚至解决了动态 import() {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}在建设时间 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}所以宿主
仅进口 useAgent 从用于失败的 Webpack/CRA 的主条目中
Module not found: @modelcontextprotocol/sdk/… 而不接触MCP。 后面一个
子路径决定是你的:
- 别用MCP 只从主条目导入。 你楼里什么都没有 提及 SDK; 安装任何额外内容 .
- 使用 MCP 程序 • 从
…/mcp财务报告和审定财务报表npm install @modelcontextprotocol/sdk与核心相邻(它被捆绑成aync块,在连接时被加载).
从 0.0 18.
useMcp, (中文(简体) ),useMcpServers他们和他们的援助者 (单位:千美元)readCallbackParams, (中文(简体) ),claimOAuthCallback, (中文(简体) ),stripOAuthParams, (中文(简体) ),describeMcpResult, (中文(简体) ),isUnauthorizedError)和类型(McpStatus, (中文(简体) ),McpAuthMode, (中文(简体) ),McpServerView,.) 已移动:更改导入路径,无其他.
这个 useMcp 钩- 连接远程 MCP 服务器
让用户在运行时为自己的MCP服务器命名并将其工具交给 探员 Auth 要么是静态头头,要么是 OAuth 2.1 的整流 动态 客户注册 ——没有客户端ID可以配置,访问符是 中跑期满时给你刷新了.
import { useMcp } from '@dudko.dev/agent-web-react/mcp'
const mcp = useMcp({ clientName: 'my-app' })
await mcp.connect({ url, oauth: true }) // or { url, headers: { Authorization } }
// The server wants a sign-in: send the user off from a real click.
{mcp.status === 'needs-authorization' && <button onClick={mcp.authorize}>Authorize</button>}
// Then just merge the tools into any agent config.
const config = { model, tools: { ...myTools, ...mcp.tools } }
useMcp 返回时 { status, tools, catalog, error, authorizationUrl, oauthSupported, completingAuthorization, connect, disconnect, authorize, forgetAuthorization, checkOAuthSupport }。 。 。 . status 实值
idle | connecting | connected | needs-authorization | error。 。 。 .
OAuth绕行 远离你的应用 所以钩子坚持它
需要,并在返回途中完成代码交换,条 ?code=… 从
地址栏和在渲染前重新连接.. completingAuthorization
把窗户盖上.
两份说明:
- 连接器住在核心的可选位置
@dudko.dev/agent-web/mcp子路径 并装有 动态导入所以SDK不会出现在你的首字母里 捆绑到connect运行。 安装@modelcontextprotocol/sdk与 核心 —— 导入这些钩子是它放在你的建筑里. oauthSupported实值false超过@dudko.dev/[email protected], (中文(简体) ), 一、导 言BrowserOAuthProvider; 页眉 auuth 仍然在那里工作。 这个 对等层为>=0.0.30第一个有学习笔记本的核心 (单位:千美元)forgetSkill, (中文(简体) ),listLearnedSkills在虚拟文件系统之上, 附件、 同意、 工具搜索、 子代理、 引导和上下文编辑 软件包的聊天基于此.agent.usage编号 :agent.sessionUsage()编辑 窗口生成的输出盖需要>=0.0.32。 。 。 .
一些MCP服务器—— useMcpServers
import { useMcpServers } from '@dudko.dev/agent-web-react/mcp'
const mcp = useMcpServers({ clientName: 'my-app' }) // the list persists; reconnects on load
await mcp.add({ name: 'docs', url, auth: 'oauth' }) // 'none' | 'bearer' (+ token) | 'oauth'
mcp.servers // [{ name, url, status, catalog, error, … }]
const config = { model, tools: { ...myTools, ...mcp.tools } } // "<name>__<tool>"
服务器平行连接, 每个服务器都有自己的最后期限; pagized 工具列表
被读取到尾端; 工具一个服务器标记只读取“ ssk before
更改“ 提示 。 用数百个工具 核心开关 工具搜索
(简明目录+) find_tools上文) toolSearchThreshold ——见:
核心软件 能力文件。 。 。 .
构成部分
所有组件都是可选的,样式由 styles.css (类别前缀) awr-, (中文(简体) ),
可通过主题 --awr-* 自定义属性, 光 + 暗, 或 theme) (中文(简体) ). 每个
接受一个 className 财务报告和审定财务报表 labels; 数据组件取出简单的道具以便您可以
独立使用它们。 完整指南: 文件/聊天时间。 。 。 .
| 构成部分 | 目的 |
|---|---|
<AgentChat> | 完整面板:带有每回答活性+作曲+可选历史和文件面板的笔录. 读取 a controller 道具或该道具 <AgentProvider> 上下文. |
<AgentComposer> | 输入及其工具栏:附件、命令、计时器、代理、模型/思维、同意、麦克、发送/停止. |
<MessageList> | 笔录;每个答案都有其步骤,工具呼叫,思想,用法. |
<StepList> 编号 : <ToolCallRow> | 执行步骤 / 一个工具调用其成本和图像 . |
<ToolApprovalPrompt> | 同意卡(Allow一次/永远/拒绝). |
<ApprovalModeSwitch> | 部分同意-模式开关. |
<ChatHistoryList> | 保存聊天( 从 useChatHistory) (中文(简体) ). |
<FilesPanel> | A 类 VirtualFileSystem,直播:预览,下载,删除,上传. |
<LearnedSkills> | 经纪人的笔记本learning:它的教训,每一个来自哪里,忘记吧. |
<ContextMeter> | 按种类分类,预算栏,最后的收缩,契约. |
<PlanView> | 计划推理+步骤列表. |
<ModelLoadBar> | WebLLM下载/init进度. |
<UsageBadge> | 压缩符号读出 . |
<ApiKeyForm> | BYOK密钥条目写到加密金库. |
<Composer> | 最小文本区域 + 发送/ 停止( kept for own apps) . |
构建自己的用户界面
部件是一层薄薄的外出层, 纯 减少。 用这个 如果你愿意自己创造一切
import { agentStateReducer, createInitialAgentState } from '@dudko.dev/agent-web-react'
let state = createInitialAgentState()
state = agentStateReducer(state, { type: 'event', event }) // fold each AgentEvent
状态由纯度,导出视图类型组成,您可以用自己的组件渲染:
StepView (一个步骤及其 ToolCallViews),(c), ApprovalView, (中文(简体) ), SubagentView, (中文(简体) ), UserNoteView 编号 :
QueuedMessageView (偷窥), LessonView 编号 : FetchView (笔记本和取出的文件),
BudgetView, (中文(简体) ), CompactionView, (中文(简体) ), AttachmentView, (中文(简体) ), ChatMessage (记录录,保留其
(c) 使用时间和期限)。 外边 <AgentChat>, (中文(简体) ), useAgentContext() 返回控件
随函附上 <AgentProvider> (它投出一个外; useOptionalAgentContext() 返回时
undefined (第2条) useLabels() 有效的标签.
如何融为一体
@dudko.dev/agent-web (peer) → the headless agent + providers + vault
@dudko.dev/agent-web-react → useAgent / <AgentProvider> / components
your app → tools, credentials, and where the panel goes
React 软件包再导出您通常需要的核心原始
(单位:千美元)createAgent, (中文(简体) ), defineTool, (中文(简体) ), VaultCredentialStore, (中文(简体) ), createWebLLMModel, (中文(简体) ),
isWebGPUAvailable,以及关键类型),所以React app可以导入全部
从一个地方.
直接浏览器调用 & CORS : 并非每个提供者都允许 BYOK 直接通话 从浏览器来源。 Google (Gemini), Openai 兼容和网关 (a) 可靠的直接路径; OpenAI/xAI/DeepSeek通常需要一个代理. 见核心 提供者 doc。 。 。 . 只载 用户自己的 浏览器的密钥——共享/应用密钥归属 在代理或网关后面.
浏览器测试
npm run test:e2e 构建演示并用铬驱动 :
- MCP: 连接到一个和几个服务器, OAuth 全部重定向 往返(授权) ——带代码回来 ——连接),重新连接 重新装入后存储的符号, 以及无法获取的服务器;
- 代理商,针对一个被脚本化的双子座端点(
e2e/gemini.ts, (中文(简体) ), 真实@ai-sdk/google下方提供者: 工具呼叫和符号 文本中的类型, 重装后保存并恢复的聊天, 图像 附加(shown,作为文件部分发送,保存在工作空间),一个文件 页面上的任何位置,“询问”模式下的同意,斜线命令 触发代理商的棋子移动和俄罗斯标签;代理商的 笔记本(写作的一课,保存在工作空间里,跨越重载,由 规划者,应请求被遗忘)和网络访问(默认关闭;允许的页面) 读取,拒绝元数据地址)。 MCP和授权服务器 对话在回转时开始e2e/servers.ts,标准 标头包括——因此,测试还标出部署要求: 挑战头被曝光.
这些内容包括哪些单位在结构上无法测试: 调制,一个真正的导航 远离和回,和索引DB.
演示和部署
这个 demo/ 应用( Vite + React) 部署到 GitHub Pages 由
.github/workflows/deploy-demo.yml 打开
每一次推动 main。要在当地运行它:
npm install && npm run build # build the library into dist/
cd demo && npm install && npm run dev
演示别名 @dudko.dev/agent-web-react 改为: ../dist,因此重建
图书馆(npm run build)在更改出处后.
许可证
麻省理工学院