DSH 插件的 Host 与 Client:为什么「只加载了 Host」,以及要不要自己写 Client 加载器

DSH 插件的 Host 与 Client:为什么「只加载了 Host」,以及要不要自己写 Client 加载器

本文基于 @deepseek-ai/dsh@0.2.1-rc.5 与 dsh-starter 起步模板(pnpm@10.34.5、Node ^22.19.0 || >=24.0.0)整理。不同版本的插槽名与 SlotMap 声明请以官方包为准。

TL;DR

  • Host + Client 不是两个插件,而是同一个 npm 包的两个「面」:Host 跑在服务端 Node 进程,Client 跑在浏览器里的官方 Web 页面。
  • 项目源码里找不到 Client 加载逻辑是正常的 。装载由官方包里的 ClientModuleRegistry、Host 路由 /plugins/<id>/client.js、浏览器里的 __ModuleLoader__ 与 cordis Loader 完成,这些代码都在 node_modules/@deepseek-ai/* 和官方 Web 前端里,不在你的仓库中。
  • 因此,绝大多数情况下不需要自己写加载器,只需要让插件包符合官方约定的格式,并重新打包注册。
  • 排查的第一步永远是:页面 boot 清单里有没有 <包名>/client.js。没有 → 查声明 / 构建 / 注册;有 → 查客户端代码执行与插槽注册。

1. 背景:这是什么项目

dsh-starter 是一个不 Fork 官方源码 的 DeepSeek Harness 扩展起步项目。官方 @deepseek-ai/dsh-* 包按精确版本从 npm 安装,项目自己只提供 Host 插件与 Web 客户端插件,通过官方 UI 插槽(slots)挂进原生界面:

arduino 复制代码
官方 @deepseek-ai/dsh-*  (npm 精确版本 0.2.1-rc.5)
        │
        ├─ Host 侧:cordis 插件树 ── 你的插件行(cordis.patch.yml)
        │
        └─ Web 侧:官方 Composer / Sidebar / Conversation
                     └─ 插槽 ← 你的客户端模块(slots.register)

pnpm dev 会依次:构建插件 → 用官方 CLI 从官方 Web 默认配置 脚手架出 Profile(.dsh-home/profiles/starter)→ 把每个插件打成内容寻址的 .tgz 并注册 → 启动官方 dsh web。项目里没有任何一份 harness 服务端实现。

模板自带三个插件,正好覆盖三种组合:

插件 包名 形态 效果
starter/ dsh-starter-plugin Host + Client 侧栏「示例」图标 + main 面板「第一个插槽面板」
expert-team/ dsh-expert-team-plugin Host + Client(Host 仅占位) 输入框左侧「专家团」浮层,召唤后跳转 Starter 面板
host-only/ dsh-host-only-plugin 仅 Host 提供 hostHeartbeat 服务与生命周期,无任何 UI

构建与注册都是自动发现的:packages/plugins/ 下每个含 package.json 的目录算一个插件 (pnpm-workspace.yaml 里声明了 packages/plugins/*),不需要在根 package.json 里逐个登记。


2. Host + Client 到底是什么

2.1 一个包,两个面

bash 复制代码
packages/plugins/starter/              ← 一个插件、一个包
├── package.json                       ← 声明两个入口
├── cordis.patch.yml                   ← 让官方把包插进 Profile 插件树
├── src/index.ts      ──→ dist/index.js            (Host,Node ESM)
└── src/client.tsx    ──→ dist/client.browser.js  (Client,浏览器 CJS)
Host 面 Client 面
运行位置 dsh web 的 Node 进程(cordis 插件树) 浏览器里的官方 React Web 壳,同一个页面
入口 exports["."] → dist/index.js exports["./client"] → dist/client.browser.js
打包 format: esm、platform: node、packages: external format: cjs、platform: browser,React 与 @deepseek-ai/* 全部外置
装载体 cordis.patch.yml 写进 Profile,官方加载器调用 apply(ctx) window.__ModuleLoader__.load({ id, factory }),官方 client-modules 列进 boot 清单
声明字段 dsh.bundle.patch dsh.client
典型能力 ctx.provide 服务、ctx.effect 生命周期、logger、文件 / 进程 / 会话 ctx.slots 注册 UI、ctx.layout.selectPanel
可观测证据 终端 stdout 日志 页面 boot 清单里的 <包名>/client.js

两边代码结构很像(都是 name / inject / apply(ctx),都 import type { Context } from '@deepseek-ai/cordis'),但两个 ctx 属于不同的运行时 :Host 的 ctx 来自服务端 cordis 上下文,Client 的 ctx 是浏览器侧上下文,注入的是 slots、layout 这类浏览器能力。

2.2 一个便于记住的比方

把官方 dsh web 想象成一家已经装修好的店:侧栏、输入框、主区域都是店里的固定挂钩(插槽)。想在店里挂一块自己的牌子,不能直接冲进去挂,而要:

  1. 先去后台管理处登记:「我这个包要在店里挂东西」→ Host 面;
  2. 管理处查登记册,把那块牌子送进店里挂上 → Client 面。

所以:Host 让官方认识你,Client 让用户看见你。

2.3 为什么不能只有 Client

关键在发现机制:官方 ClientModuleRegistry 是扫描 Host loader entries 来找 dsh.client 声明的,不是自己扫描浏览器目录。少了 cordis.patch.yml 与 src/index.ts,浏览器模块永远不会出现在 boot 清单里------哪怕包里确实有 client.browser.js。

这就是模板里「没有仅 Client 形态 」的由来。expert-team 的 UI 全在浏览器端,仍保留了一个几乎空的 Host 入口:

ts 复制代码
// packages/plugins/expert-team/src/index.ts(节选)
export const name = 'dsh-expert-team-plugin'
export const inject = [] as const

export function apply(ctx: Context): void {
  ctx.effect(() => {
    ctx.logger('dsh-expert-team-plugin').debug('expert team UI mounted from dsh-expert-team-plugin/client')
    return () => {}
  }, 'dsh-expert-team-plugin.lifecycle')
}

2.4 两面不能直接互相调用

一个在服务器上,一个在浏览器里,是两个独立程序。starter 的 Host 面提供了 greeting() 服务,浏览器组件调不到;要做 client ↔ host 数据交换,需要官方的 remote / API 层,模板刻意没有实现。

插件之间也不互相 import,而是靠运行时约定 协作。expert-team 想跳到 starter 的面板,做法是查 main 插槽里有没有约定好的 key:

tsx 复制代码
const available = ctx.slots
  .entriesOfSlot('main')
  .some(entry => entry.options.key === STARTER_PANEL)
if (!available) return false
ctx.layout.selectPanel(STARTER_PANEL as MainPanelId)

3. 怎么区分一段代码是 Host 还是 Client

不是靠「代码看起来像不像」,项目里留有明确标记,按可靠程度看三处。

3.1 看 package.json(最权威)

字段 含义 属于哪面
exports["."] → dist/index.js 主入口 Host
exports["./client"] → dist/client.browser.js 浏览器入口 Client
dsh.bundle.patch 「把我这行写进 Profile 登记册」 Host
dsh.client 「我还有界面代码,请发给浏览器」 Client

starter/package.json 四个字段都有 → 双面;host-only/package.json 只有 exports["."] 与 dsh.bundle.patch → 仅 Host。

3.2 看源文件与所用 API

约定很死:src/index.ts 是 Host,src/client.tsx 是 Client。构建脚本就是按这两个文件是否存在来判定产出:

js 复制代码
const hasHost = existsSync(join(pluginDir, 'src/index.ts'))
const hasClient = existsSync(join(pluginDir, 'src/client.tsx'))
Host 面会出现 Client 面会出现
ctx.provide(...)、ctx.effect(...)、ctx.logger ctx.slots.register(...)、ctx.layout.selectPanel(...)
process.stdout、setInterval 等 Node 能力 JSX / <svg>、window、document
inject = [] inject = ['slots']、['slots', 'layout']
文件头 Host entry 文件头 Browser entry、type-only 的 dsh-client-ui-*/client 导入

3.3 看产物与运行时证据

  • dist/index.js:ESM、Node、依赖外置 → Host;
  • dist/client.browser.js:被 window.__ModuleLoader__.load({...}) 包起来的 CJS → Client;
  • 跑起来后:终端日志 是 Host 的证据,页面加载清单里出现 <包名>/client.js 是 Client 的证据。模板的 pnpm smoke 正是分别验证这两条。

3.4 两个易错点

  1. 别用 apply(ctx) 或 import type { Context } from '@deepseek-ai/cordis' 判断 :两面都这么写,区别在 ctx 里用了什么能力。
  2. 别被注释内容骗了 :expert-team/src/index.ts 通篇在解释客户端模块如何被发现,但它仍是 Host 面。判断看字段与产物,不看它谈论了什么。

严格来说应交叉验证三处:package.json 是「声明」、源文件是「实际写了什么」、dist 是「最终打出来的东西」。三者对不上(例如删了 client.tsx 却忘了删 dsh.client),就会出现「声明有 Client、浏览器收不到」的情况。


4. Client 模块究竟是怎么被加载的

这条链路很重要,因为它直接回答了「要不要自己写加载器」。

scss 复制代码
┌─ 你的仓库 ─────────────────────────────────────────────────┐
│ cordis.patch.yml   → 告诉官方「把我加进 Host 插件树」          │
│ package.json       → dsh.client 声明 + exports["./client"]    │
│ src/index.ts       → Host 面,构建成 dist/index.js             │
│ src/client.tsx     → 构建成带 __ModuleLoader__ 外壳的 CJS      │
└────────────────────────────────────────────────────────────┘
        │  scripts/dev.mjs:pack → dsh plugin add -w → 注册进 Profile
        ▼
┌─ 官方 @deepseek-ai/dsh(node_modules,不在你的仓库)─────────┐
│ ① runProfile() 按 Profile 依赖 + patch 装配 cordis 插件树     │
│    → 每个插件的 Host apply(ctx) 在这里执行                     │
│ ② ClientModuleRegistry(inject: ['loader'])扫 loader entries │
│    → 挑出声明了 dsh.client 的包,算出模块顺序                  │
│ ③ 生成 window.__DSH_BOOT__,并把 script / global 行注入 HTML  │
│ ④ Host 路由 /plugins/<id>/client.js 返回打包产物(cookie 认证)│
└────────────────────────────────────────────────────────────┘
        │  HTTP
        ▼
┌─ 官方 Web 前端(浏览器)────────────────────────────────────┐
│ ⑤ 每个 client.js 只执行 window.__ModuleLoader__.load(...)     │
│    → 登记 { id, factory },模块体(含 CSS)此时并不执行        │
│ ⑥ cordis Loader 按 inject 顺序 import → 执行 factory          │
│    → 得到 module.exports,调用客户端模块的 apply(ctx)           │
│ ⑦ ctx.slots.register() 把组件写进对应插槽的条目表               │
│ ⑧ 官方 owner 组件渲染到那一格时按 order / priority 取条目       │
│    → React 挂载,界面出现                                      │
└────────────────────────────────────────────────────────────┘

几个关键细节:

  • Host 是入场券 :ClientModuleRegistry 的输入是 Host 插件树,没有 loader entry 就拼不出模块表。
  • 前端代码也是 Host 给的 :/plugins/<id>/client.js 由 Host 路由提供,浏览器不会自己去磁盘找插件包。
  • 惰性工厂 :脚本执行时只登记 factory,模块体在第一次 import 时才执行并缓存。
  • 模块 id 是包名 :外壳里的 id 用的是 package.json 的 name,不是 client.tsx 里 export const name 的值;页面清单里出现的字符串是 <包名>/client.js,而不是 client.browser.js。
  • 共享单例 :客户端打包时 react / react-dom / @deepseek-ai/* 全部外置,由官方页面提供,绝不能内联第二份,否则 hooks 报错。

一次点击「示例」图标的完整链路,就是第 ⑧ 步的官方壳渲染 <button>、调用 layout.selectPanel(id),再由 main 插槽按 key 找到你的面板组件。这也是为什么 sidebar.panellist 的 id 必须与 main 的 key 完全一致。


5. 「项目完全没有处理 Client 插件的逻辑」→ 要不要自己写

5.1 结论先说

大概率不用写,而且仓库里搜不到加载逻辑是正常的。 在模板项目里,跟 Client 装载有关的自研代码只有三处,且都不是加载器:

  • scripts/build-plugin.mjs:把 client.tsx 打包成 window.__ModuleLoader__.load({...}) 外壳;
  • scripts/dev.mjs:打包 tgz、注册进 Profile、启动官方 dsh web;
  • scripts/smoke.mjs:检查 页面清单里有没有 <包名>/client.js。

真正执行加载的是官方包与官方 Web 前端。你要写的不是「加载逻辑」,而是「一个符合官方格式的插件」。

5.2 30 秒自测

在目标项目里确认三件事:

  1. package.json 里有没有 @deepseek-ai/dsh(以及对应的 dsh-client-ui-*);
  2. 启动命令是不是 dsh ... web,或 node node_modules/@deepseek-ai/dsh/lib/bin.js --profile ... --port ...;
  3. 页面源代码里有没有 window.__DSH_BOOT__ 或 __ModuleLoader__。

5.3 三种情况对照

实际情况 要不要自己写 具体做什么
A. 跑的是官方 dsh web,只是插件只有 Host 面 不用写加载器 补 dsh.client 声明、src/client.tsx、依赖与 tsconfig,重新构建注册(见第 6 节)
B. 跑官方 dsh web,但构建脚本只会打 Host、不产出 client.browser.js 要写,但只是打包脚本 照 build-plugin.mjs 第 60--93 行补一段 esbuild 配置:CJS + browser + external React/@deepseek-ai/* + window.__ModuleLoader__.load({...}) 外壳,不是写运行时加载器
C. 项目自己用 cordis 起 Node 服务、页面也是自己画的,没跑官方 Web 壳 要写,但代价很大 见 5.4

5.4 情况 C 为什么通常不划算

如果连官方 Web 壳都没有,需要自己实现的远不止「加载 client」:

  1. Host 侧:扫已加载插件的 dsh.client → 生成模块清单 → 注入 HTML → 提供 /plugins/<id>/client.js 静态路由与认证;
  2. 浏览器侧:实现 window.__ModuleLoader__(load({ id, factory }) 惰性登记 + import 执行缓存),并实现 require 对 react / @deepseek-ai/* 外置依赖的解析;
  3. 按 inject 顺序实例化客户端 cordis fiber,调用每个模块的 apply(ctx);
  4. 提供浏览器侧 ctx.slots / ctx.layout,以及会按 order 取条目并渲染的官方 owner 组件------否则 ctx.slots.register() 注册了也没人接收。

这基本等于重写官方 Web 客户端。更务实的做法是接入官方 dsh web + Profile 机制,让自己的插件走标准插槽。


6. 实操:把「仅 Host」插件改成 Host + Client

以模板里的 host-only/ → 双面为例,照 starter/ 抄即可。

6.1 保留 Host 那一半

src/index.ts 与 cordis.patch.yml 必须留着(原因见 2.3)。

6.2 改 package.json,补齐四处

jsonc 复制代码
{
  "exports": {
    ".":         { "types": "./dist/index.d.ts",  "default": "./dist/index.js" },
    "./client":  { "types": "./dist/client.d.ts", "default": "./dist/client.browser.js" },
    "./package.json": "./package.json"
  },
  "files": ["dist", "cordis.patch.yml"],
  "dsh": {
    "bundle": { "patch": "./cordis.patch.yml" },
    "client": { "platform": "web", "inject": [] }
  }
}

devDependencies 里补 react、@types/react 和用到的 @deepseek-ai/dsh-client-ui-*,版本必须与官方包完全一致 (此处 0.2.1-rc.5)。files 必须包含 dist,否则产物进不了 tgz。

6.3 改 tsconfig.json

host-only/tsconfig.json 只有 "lib": ["ES2022"] 与 "types": ["node"],写 JSX 会直接编译失败。复制 starter/tsconfig.json,关键是:

jsonc 复制代码
{
  "compilerOptions": {
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "jsx": "react-jsx",
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

构建脚本每构建完一个插件都会跑一次 tsc,编译失败会中断整个 build,dist 可能只剩一半产物。

6.4 新建 src/client.tsx

照 starter/src/client.tsx 的固定外壳:type-only 导入对应插槽包以合并 SlotMap,export const inject = ['slots'] as const,在 apply(ctx) 里用 ctx.slots.inject(插槽名, () => ctx.slots.register(...)) 包住注册。三条硬规则:

  1. sidebar.panellist 的 id === main 的 key;
  2. 只画 UI,不要自己造按钮(官方壳拥有 button、tooltip、选中态与无障碍属性);
  3. 成对注册、成对回收,卸载后界面回到官方原样。

6.5 安装依赖并构建

bash 复制代码
pnpm install
pnpm build
pnpm typecheck

成功标志 :终端出现 built <包名> [host+client]。只有 [host] 说明 src/client.tsx 没被识别(文件名或路径不对)。

6.6 重新注册并验收

bash 复制代码
pnpm clean
pnpm dev
pnpm smoke

看到 registering <包名> ... 才是真的重新注册;smoke: ok --- ... served N client module(s) 表示客户端模块已进入页面清单。最后打开带 token 的链接,检查侧栏图标与主面板。

客户端模块没有 HMR:官方 client-modules 在 boot 时按清单装载。改动后的流程固定是「重建 → 重新打包 → 重启服务 → 刷新页面」。


7. 排查手册

7.1 第一步:先分叉,别混着查

在页面 HTML 或 DevTools → Network 里搜 <包名>/client.js:

  • 搜不到 → 服务端根本没把模块发给浏览器,问题在声明 / 构建 / 注册(7.2 的 L0--L3);
  • 搜得到、界面没变化 → 模块已送达,问题在模块执行 / 插槽注册 / React(L4)。

模板的 smoke.mjs 就是自动做这个判断:从所有 package.json 里筛出 manifest.dsh?.client,逐个断言 <name>/client.js 出现在 HTML 中。

7.2 分层检查表

层 检查什么 成功标志 / 常见失败
L0 声明 package.json 的 dsh.client、exports["./client"]、files 最常见的漏项就是没有 dsh.client
L1 产物 dist/client.browser.js 是否存在、开头是不是 window.__ModuleLoader__.load;pnpm pack 的 tgz 里有没有它 不在 dist → build 没跑或 tsc 挂了;在 dist 不在 tgz → files 漏了 dist
L2 注册 pnpm dev 日志是否出现 registering <包名>;Profile 的 package.json 里 dependencies[包名] 的 file: 路径 一直显示 all N plugin(s) already registered → 改了源码没重新 build/pack
L3 传输 页面清单 / Network 里有没有 <包名>/client.js;dsh --profile starter --dump-config 看插件 id 没有 → 回到 L0/L2;Profile 有 id 但清单没有 → 检查包是否真的被重新解包
L4 执行 Console 有无报错;window.__ModuleLoader__ 里有没有模块 id;slots.register 的插槽名 / id 是否正确 React 被内联导致 hooks 报错、插槽名拼错、id 与 key 不一致、inject 少写了 'slots'

按阶段看故障现象(来自模板的 docs/tech-share-slots.html 时序表):

阶段 典型现象 优先检查
组前端清单 Host 正常、页面正常,就是没模块 loader entry 是否有 dsh.client、bundle 路径是否存在
浏览器装配 Console 报 loaded without registering / 白屏 打包外壳是不是 load({ id, factory })、模块 id 是否与清单一致
插槽注册 模块加载了,但界面没变化 插槽名拼写、sidebar.panellist.id 与 main.key 是否一致

7.3 最常见的坑

  1. 只改源码不重新 pack :dev.mjs 用 tgz 的 sha256 内容哈希 当路径,只有内容变了才重新执行 dsh plugin add。必须 pnpm build 后再 pnpm dev,只刷新浏览器无效。
  2. pnpm 命中旧解包目录 :同名同版本会复用旧目录。dev.mjs 已主动删除 profileDir/node_modules/<包名>;手动 add 时要自己删,且必须带 -w(Profile 是单包 pnpm workspace),否则报 ERR_PNPM_ADDING_TO_ROOT。
  3. 旧产物残留 :build-plugin.mjs 每次先删 index.js、client.browser.js 再生成;中途 tsc 失败会留下「只有一半」的 dist。拿不准就 pnpm clean(会删 .dsh-home、.artifacts、各插件 dist)后全量重建。
  4. 删掉 Host 入口做「纯客户端插件」:不支持,L0 全对也不会进清单。
  5. React 被内联 :客户端打包必须外置 react / react-dom / @deepseek-ai/*,否则模块一执行就报 hooks 错误,表现为「加载了但没 UI」(属于 L4 而非 L3)。
  6. 找错字符串 :boot 清单里是 <包名>/client.js,不是 client.browser.js。
  7. Profile 残缺 / 服务起不来 :删掉 .dsh-home 重新 pnpm dev,脚本会重新脚手架。

7.4 两条独立的验收证据

pnpm smoke 把 Host 与 Client 分开验证,设计思路值得借鉴:

  • Client :所有声明了 dsh.client 的插件,<name>/client.js 都出现在官方 boot 清单里;
  • Host :终端输出里出现 [dsh-host-only-plugin] applied(host-only 没有 UI,特意打印一行标记让加载可观测)。

8. 速查清单

ini 复制代码
[ ] 插件包保留 src/index.ts + cordis.patch.yml(Host 入场券)
[ ] package.json 有 exports["./client"]、dsh.client、files 含 dist
[ ] 依赖版本与官方 @deepseek-ai/dsh-* 完全一致
[ ] tsconfig 有 jsx: react-jsx 和 DOM lib
[ ] src/client.tsx 用 slots.inject → slots.register,id === main.key
[ ] pnpm build 输出 [host+client]
[ ] 客户端打包 external 了 react / @deepseek-ai/*
[ ] pnpm dev 日志出现 registering <包名>
[ ] 页面清单能搜到 <包名>/client.js
[ ] pnpm smoke 通过,界面回到官方原样的验收反向成立(删插件无残留)

9. 小结

Host + Client 描述的是同一个包在两个运行时里的两份入口,不是两个插件,也不是传统意义上的前后端通信:

  • Host 负责登记与服务端能力(服务、生命周期),也是官方发现浏览器模块的唯一入口;
  • Client 负责界面,通过官方插槽把组件填进原生骨架;
  • 装载链路完全由官方包和官方 Web 前端实现,业务项目只需要产出符合约定的包并注册进 Profile。

所以遇到「只加载了 Host」,正确的顺序是:先分清「模块没送到浏览器」还是「送到了但没渲染」,再按声明 → 产物 → 注册 → 传输 → 执行逐层排查;只有在根本没有使用官方 dsh web 与 Web 壳时,才需要考虑自己实现加载器,而那通常意味着要重写半个官方客户端,不划算。


附录 A:术语速查

术语 含义
Profile 官方 CLI 脚手架出的独立 pnpm 项目,插件以 file: 依赖注册进去
loader entry cordis.patch.yml 插入 Profile 的那一行,官方据此加载插件 Host 入口
ClientModuleRegistry 官方 Host 侧服务,扫 loader entries 的 dsh.client,产出前端模块清单
__DSH_BOOT__ 注入 HTML 的浏览器模块清单(rev / release / entries / batches)
__ModuleLoader__ 浏览器侧惰性 CJS 模块表,load({ id, factory }) 只登记不执行
插槽(slot) 官方 UI 壳预留的挂载点,如 sidebar.panellist(list)、main(keyed)
list / keyed list 槽用 id + order 排序去重;keyed 槽用 key 命中,两者需成对

附录 B:文中涉及的文件

文件 作用
README.md 项目定位、三个关键契约、常见问题
packages/plugins/README.md 插件形态约定、最小清单、常用插槽表
package.json / pnpm-workspace.yaml 官方包精确版本、工作区与 onlyBuiltDependencies
scripts/build-plugin.mjs 按源文件判定两面、esbuild 打包、__ModuleLoader__ 外壳、tsc
scripts/dev.mjs Profile 脚手架、内容哈希打包、注册、启动 dsh web
scripts/smoke.mjs 无头验收:boot 清单 + Host stdout 标记
scripts/clean.mjs 清理 .dsh-home、.artifacts、各插件 dist
docs/tech-share-slots.html 启动时序与浏览器装配的完整架构图、故障阶段表
相关推荐
parade岁月25 分钟前
AI 写的前端代码为什么总像 5 年前的?Chrome 官方出手了
前端
tryCbest1 小时前
Vue系列——第 15 篇:内置组件全家桶——Transition、Teleport、Suspense、KeepAlive
前端·vue.js
易师傅1 小时前
技术面试已死?AI 时代,真正拉开程序员差距的是什么?
前端·后端·程序员
jay神1 小时前
基于深度学习的交通流量预测系统(Web 版)
前端·人工智能·深度学习·yolo·计算机毕业设计
AiMan2 小时前
别再把秘塔会话逐个点开:这可把秘塔多个会话一次导出?AI导出鸭
前端·后端·面试
AiMan2 小时前
你好,请问可以一键导出Dola多个对话框内容吗?一键导出一整年哦
前端
Moment2 小时前
写给迷茫的应届生,怎样在 AI 时代争取更多机会
前端·后端·面试
fengci.3 小时前
CTF2MISC中等部分
开发语言·前端·学习
涛涛ing3 小时前
那个PR太干净了,干净到我差点直接点了合并
前端