vite-plugin-prism-design 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +238 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +159 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# vite-plugin-prism-design
|
|
2
|
+
|
|
3
|
+
PrismDesign 的 Vite 插件 —— 一行配置,为你的 React / Vue / Svelte 项目接入 AI 可视化编辑能力。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 开发模式自动注入 PrismDesign Widget(悬浮聊天面板)
|
|
8
|
+
- 自动启动 AI Agent 服务,无需手动管理
|
|
9
|
+
- Agent 端口被占用时自动递增
|
|
10
|
+
- 生产构建自动跳过,零影响
|
|
11
|
+
- 支持 Claude API、GLM API 等多种 AI 后端
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add -D vite-plugin-prism-design
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// vite.config.ts
|
|
23
|
+
import { defineConfig } from 'vite'
|
|
24
|
+
import react from '@vitejs/plugin-react'
|
|
25
|
+
import prismDesign from 'vite-plugin-prism-design'
|
|
26
|
+
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
plugins: [
|
|
29
|
+
react(),
|
|
30
|
+
prismDesign()
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
启动 `pnpm dev` 后,页面右下角会出现 PrismDesign 悬浮按钮。点击展开聊天面板,描述你想要的 UI 修改,AI 会直接修改源码并自动刷新页面。
|
|
36
|
+
|
|
37
|
+
## 配置选项
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
prismDesign({
|
|
41
|
+
// ── Agent 配置 ──
|
|
42
|
+
|
|
43
|
+
// API Key(传递给 Agent 作为 ANTHROPIC_API_KEY)
|
|
44
|
+
// 也可以通过项目根目录 .env 文件配置
|
|
45
|
+
apiKey: 'sk-ant-...',
|
|
46
|
+
|
|
47
|
+
// API Base URL(可选,用于 LiteLLM 代理等场景)
|
|
48
|
+
baseUrl: 'https://your-proxy.com/v1',
|
|
49
|
+
|
|
50
|
+
// AI 模型名称(可选,默认 claude-sonnet-4-20250514)
|
|
51
|
+
model: 'claude-sonnet-4-20250514',
|
|
52
|
+
|
|
53
|
+
// Agent 类型(默认 "claude")
|
|
54
|
+
agentType: 'claude', // 'claude' | 'glm'
|
|
55
|
+
|
|
56
|
+
// Agent 服务端口(默认 9527,被占用时自动递增)
|
|
57
|
+
agentPort: 9527,
|
|
58
|
+
|
|
59
|
+
// 是否自动启动 Agent(默认 true)
|
|
60
|
+
// 设为 false 则需要手动运行 Agent
|
|
61
|
+
agentAutoStart: true,
|
|
62
|
+
|
|
63
|
+
// 直接指定已运行的 Agent URL(跳过自动启动)
|
|
64
|
+
// 适用于 Agent 在远程服务器或 Docker 容器中运行的场景
|
|
65
|
+
agentUrl: 'http://localhost:9527',
|
|
66
|
+
|
|
67
|
+
// ── Widget 配置 ──
|
|
68
|
+
|
|
69
|
+
// 悬浮按钮位置(默认 "bottom-right")
|
|
70
|
+
position: 'bottom-right', // 'bottom-right' | 'bottom-left'
|
|
71
|
+
|
|
72
|
+
// 语言(默认根据浏览器语言自动检测)
|
|
73
|
+
locale: 'zh', // 'zh' | 'en'
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 使用场景
|
|
78
|
+
|
|
79
|
+
### 基础用法(使用 .env 配置 API Key)
|
|
80
|
+
|
|
81
|
+
最简单的方式,API Key 放在项目 `.env` 文件中:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# .env
|
|
85
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// vite.config.ts
|
|
90
|
+
prismDesign()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Agent 启动时会自动读取 `.env` 文件中的环境变量。
|
|
94
|
+
|
|
95
|
+
### 使用 LiteLLM 代理
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
prismDesign({
|
|
99
|
+
apiKey: 'sk-your-key',
|
|
100
|
+
baseUrl: 'https://your-litellm-proxy.com/v1',
|
|
101
|
+
model: 'gpt-4o',
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 使用智谱 GLM
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
prismDesign({
|
|
109
|
+
agentType: 'glm',
|
|
110
|
+
apiKey: 'your-zhipu-api-key',
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 连接远程 / Docker Agent
|
|
115
|
+
|
|
116
|
+
如果 Agent 运行在远程服务器或 PrismDesign Platform 容器中:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
prismDesign({
|
|
120
|
+
agentAutoStart: false,
|
|
121
|
+
agentUrl: 'https://your-platform.com/api/workspaces/abc123/agent',
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 手动管理 Agent
|
|
126
|
+
|
|
127
|
+
如果你想自己启动 Agent 进程:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
prismDesign({
|
|
131
|
+
agentAutoStart: false,
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
然后手动运行:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npx prism-design-agent start --port 9527
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Widget 会显示连接表单,输入 Agent 地址后连接。
|
|
142
|
+
|
|
143
|
+
## Widget 功能
|
|
144
|
+
|
|
145
|
+
### 聊天
|
|
146
|
+
|
|
147
|
+
在聊天面板中描述你想要的 UI 修改,例如:
|
|
148
|
+
|
|
149
|
+
- "把标题颜色改成蓝色"
|
|
150
|
+
- "导航栏增加一个用户头像"
|
|
151
|
+
- "这个按钮改成圆角样式"
|
|
152
|
+
|
|
153
|
+
AI 会直接修改源码,修改完成后页面自动刷新。
|
|
154
|
+
|
|
155
|
+
### 评论标注
|
|
156
|
+
|
|
157
|
+
点击聊天输入框左侧的评论按钮,进入元素选择模式:
|
|
158
|
+
|
|
159
|
+
1. 鼠标移动高亮页面元素
|
|
160
|
+
2. 点击选中元素,弹出评论输入框
|
|
161
|
+
3. 输入评论后确认,评论会作为上下文附在下一条消息中
|
|
162
|
+
4. 支持重选元素(在评论框打开时点击其他元素)
|
|
163
|
+
|
|
164
|
+
评论功能会自动检测 React / Vue 组件信息和源文件位置,帮助 AI 精准定位代码。
|
|
165
|
+
|
|
166
|
+
### 移动端适配
|
|
167
|
+
|
|
168
|
+
在移动设备上(viewport < 640px),面板以底部弹窗形式展示,支持滑入/滑出动画。
|
|
169
|
+
|
|
170
|
+
## 工作原理
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
┌─────────────────────────────────────────────┐
|
|
174
|
+
│ Vite Dev Server │
|
|
175
|
+
│ ┌───────────────────────────────────────┐ │
|
|
176
|
+
│ │ vite-plugin │ │
|
|
177
|
+
│ │ ├─ transformIndexHtml: 注入 widget │ │
|
|
178
|
+
│ │ ├─ configureServer: 启动 agent │ │
|
|
179
|
+
│ │ └─ middleware: 服务 widget.js │ │
|
|
180
|
+
│ └───────────────────────────────────────┘ │
|
|
181
|
+
│ ↕ spawn │
|
|
182
|
+
│ ┌───────────────────────────────────────┐ │
|
|
183
|
+
│ │ Agent Server (port 9527) │ │
|
|
184
|
+
│ │ ├─ POST /api/chat → Claude SDK │ │
|
|
185
|
+
│ │ ├─ WebSocket /ws → 实时进度 │ │
|
|
186
|
+
│ │ └─ 读取项目 CLAUDE.md │ │
|
|
187
|
+
│ └───────────────────────────────────────┘ │
|
|
188
|
+
└─────────────────────────────────────────────┘
|
|
189
|
+
↕ HTTP + WebSocket
|
|
190
|
+
┌─────────────────────────────────────────────┐
|
|
191
|
+
│ Browser │
|
|
192
|
+
│ ┌───────────────────────────────────────┐ │
|
|
193
|
+
│ │ Widget (Shadow DOM) │ │
|
|
194
|
+
│ │ ├─ FAB 悬浮按钮 │ │
|
|
195
|
+
│ │ ├─ 聊天面板 │ │
|
|
196
|
+
│ │ └─ 评论标注 │ │
|
|
197
|
+
│ └───────────────────────────────────────┘ │
|
|
198
|
+
└─────────────────────────────────────────────┘
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## CLAUDE.md
|
|
202
|
+
|
|
203
|
+
在项目根目录创建 `CLAUDE.md` 文件可以给 AI 提供项目上下文,例如:
|
|
204
|
+
|
|
205
|
+
```markdown
|
|
206
|
+
# CLAUDE.md
|
|
207
|
+
|
|
208
|
+
## 项目信息
|
|
209
|
+
- 框架:React 19 + TypeScript
|
|
210
|
+
- 样式:Tailwind CSS v4
|
|
211
|
+
- UI 组件库:shadcn/ui
|
|
212
|
+
|
|
213
|
+
## 编码规范
|
|
214
|
+
- 使用函数组件 + Hooks
|
|
215
|
+
- 样式使用 Tailwind 工具类
|
|
216
|
+
- 组件放在 src/components/ 目录
|
|
217
|
+
|
|
218
|
+
## 工作规则
|
|
219
|
+
- 只做 UI 层面的修改
|
|
220
|
+
- 优先使用已有的组件库组件
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Agent 启动时会自动读取这个文件。
|
|
224
|
+
|
|
225
|
+
## 注意事项
|
|
226
|
+
|
|
227
|
+
- 仅在 `vite dev`(开发模式)下生效,`vite build` 时自动跳过
|
|
228
|
+
- Agent 进程随 Vite Dev Server 一起启动和关闭
|
|
229
|
+
- Widget 使用 Shadow DOM 隔离样式,不会影响你的页面
|
|
230
|
+
- API Key 建议放在 `.env` 文件中,不要提交到代码仓库
|
|
231
|
+
|
|
232
|
+
## 兼容性
|
|
233
|
+
|
|
234
|
+
- Vite 5+
|
|
235
|
+
- React / Vue / Svelte / 任何 Vite 支持的框架
|
|
236
|
+
- 现代浏览器(Chrome, Firefox, Safari, Edge)
|
|
237
|
+
|
|
238
|
+
> **Next.js 用户**:Next.js 使用 Webpack/Turbopack,不支持 Vite 插件。请关注后续的 `@prism-design/next-plugin`。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface PrismDesignOptions {
|
|
4
|
+
/** Agent server port (default: 9527, auto-increments if occupied) */
|
|
5
|
+
agentPort?: number;
|
|
6
|
+
/** Agent type: "claude" | "glm" (default: "claude") */
|
|
7
|
+
agentType?: "claude" | "glm";
|
|
8
|
+
/** Disable auto-starting agent (if you run it manually) */
|
|
9
|
+
agentAutoStart?: boolean;
|
|
10
|
+
/** Agent URL override (skips auto-start, connects to existing agent) */
|
|
11
|
+
agentUrl?: string;
|
|
12
|
+
/** API Key for the AI model (passed to agent as ANTHROPIC_API_KEY) */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/** API Base URL (passed to agent as ANTHROPIC_BASE_URL, e.g. LiteLLM proxy) */
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/** AI model name (passed to agent as ANTHROPIC_MODEL, e.g. "claude-sonnet-4-20250514") */
|
|
17
|
+
model?: string;
|
|
18
|
+
/** Widget position (default: "bottom-right") */
|
|
19
|
+
position?: "bottom-right" | "bottom-left";
|
|
20
|
+
/** Widget locale override */
|
|
21
|
+
locale?: "zh" | "en";
|
|
22
|
+
}
|
|
23
|
+
declare function prismDesign(options?: PrismDesignOptions): Plugin;
|
|
24
|
+
|
|
25
|
+
export { type PrismDesignOptions, prismDesign as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
function findWidgetScript() {
|
|
13
|
+
try {
|
|
14
|
+
const widgetPkg = __require.resolve("prism-design-widget/package.json");
|
|
15
|
+
const widgetDir = path.dirname(widgetPkg);
|
|
16
|
+
const iifeFile = path.join(widgetDir, "dist", "prism-design-widget.iife.js");
|
|
17
|
+
if (fs.existsSync(iifeFile)) return iifeFile;
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const thisDir = path.dirname(new URL(import.meta.url).pathname);
|
|
22
|
+
const candidate = path.resolve(thisDir, "../../widget/dist/prism-design-widget.iife.js");
|
|
23
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function findAgentCli() {
|
|
29
|
+
const candidates = [
|
|
30
|
+
// Monorepo sibling
|
|
31
|
+
path.resolve(new URL(import.meta.url).pathname, "../../../agent/dist/cli.js"),
|
|
32
|
+
// npm global / local
|
|
33
|
+
"prism-design-agent"
|
|
34
|
+
];
|
|
35
|
+
for (const c of candidates) {
|
|
36
|
+
try {
|
|
37
|
+
if (path.isAbsolute(c) && fs.existsSync(c)) return c;
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function prismDesign(options = {}) {
|
|
44
|
+
const {
|
|
45
|
+
agentPort: preferredPort = 9527,
|
|
46
|
+
agentType = "claude",
|
|
47
|
+
agentAutoStart = true,
|
|
48
|
+
agentUrl: agentUrlOverride,
|
|
49
|
+
apiKey,
|
|
50
|
+
baseUrl,
|
|
51
|
+
model,
|
|
52
|
+
position = "bottom-right",
|
|
53
|
+
locale
|
|
54
|
+
} = options;
|
|
55
|
+
let agentProcess = null;
|
|
56
|
+
let agentUrl = agentUrlOverride || "";
|
|
57
|
+
let widgetJs = "";
|
|
58
|
+
let config;
|
|
59
|
+
return {
|
|
60
|
+
name: "prism-design",
|
|
61
|
+
apply: "serve",
|
|
62
|
+
// Dev only
|
|
63
|
+
configResolved(resolvedConfig) {
|
|
64
|
+
config = resolvedConfig;
|
|
65
|
+
},
|
|
66
|
+
async configureServer(server) {
|
|
67
|
+
const widgetPath = findWidgetScript();
|
|
68
|
+
if (widgetPath) {
|
|
69
|
+
widgetJs = fs.readFileSync(widgetPath, "utf-8");
|
|
70
|
+
} else {
|
|
71
|
+
config.logger.warn("[PrismDesign] Widget script not found. Run: pnpm build:widget");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
server.middlewares.use("/__prism__/widget.js", (_req, res) => {
|
|
75
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
76
|
+
res.setHeader("Cache-Control", "no-store");
|
|
77
|
+
res.end(widgetJs);
|
|
78
|
+
});
|
|
79
|
+
if (!agentUrlOverride && agentAutoStart) {
|
|
80
|
+
const agentCli = findAgentCli();
|
|
81
|
+
if (agentCli) {
|
|
82
|
+
const projectRoot = config.root || process.cwd();
|
|
83
|
+
const agentEnv = { ...process.env };
|
|
84
|
+
if (apiKey) agentEnv.ANTHROPIC_API_KEY = apiKey;
|
|
85
|
+
if (baseUrl) agentEnv.ANTHROPIC_BASE_URL = baseUrl;
|
|
86
|
+
if (model) agentEnv.ANTHROPIC_MODEL = model;
|
|
87
|
+
agentProcess = spawn("node", [agentCli, "start", "--port", String(preferredPort), "--project", projectRoot, "--agent-type", agentType], {
|
|
88
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
89
|
+
env: agentEnv
|
|
90
|
+
});
|
|
91
|
+
const portPromise = new Promise((resolve) => {
|
|
92
|
+
const timeout = setTimeout(() => resolve(preferredPort), 15e3);
|
|
93
|
+
agentProcess.stdout?.on("data", (data) => {
|
|
94
|
+
const text = data.toString();
|
|
95
|
+
const match = text.match(/__PRISM_AGENT_PORT__=(\d+)/);
|
|
96
|
+
if (match) {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
resolve(parseInt(match[1], 10));
|
|
99
|
+
}
|
|
100
|
+
for (const line of text.split("\n")) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (trimmed && !trimmed.startsWith("__PRISM_AGENT_PORT__")) {
|
|
103
|
+
config.logger.info(`[PrismDesign Agent] ${trimmed}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
agentProcess.stderr?.on("data", (data) => {
|
|
109
|
+
const msg = data.toString().trim();
|
|
110
|
+
if (msg) config.logger.warn(`[PrismDesign Agent] ${msg}`);
|
|
111
|
+
});
|
|
112
|
+
agentProcess.on("exit", (code) => {
|
|
113
|
+
if (code !== null && code !== 0) {
|
|
114
|
+
config.logger.error(`[PrismDesign] Agent exited with code ${code}`);
|
|
115
|
+
}
|
|
116
|
+
agentProcess = null;
|
|
117
|
+
});
|
|
118
|
+
const actualPort = await portPromise;
|
|
119
|
+
agentUrl = `http://localhost:${actualPort}`;
|
|
120
|
+
config.logger.info(`[PrismDesign] Agent ready at ${agentUrl}`);
|
|
121
|
+
} else {
|
|
122
|
+
config.logger.warn("[PrismDesign] Agent CLI not found. Install prism-design-agent or run agent manually.");
|
|
123
|
+
config.logger.info("[PrismDesign] Widget will show connection form for manual URL input.");
|
|
124
|
+
}
|
|
125
|
+
} else if (agentUrlOverride) {
|
|
126
|
+
agentUrl = agentUrlOverride;
|
|
127
|
+
config.logger.info(`[PrismDesign] Using agent at ${agentUrl}`);
|
|
128
|
+
}
|
|
129
|
+
server.httpServer?.on("close", () => {
|
|
130
|
+
if (agentProcess) {
|
|
131
|
+
agentProcess.kill("SIGTERM");
|
|
132
|
+
agentProcess = null;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
transformIndexHtml() {
|
|
137
|
+
const initOptions = {};
|
|
138
|
+
if (agentUrl) initOptions.agentUrl = agentUrl;
|
|
139
|
+
if (position !== "bottom-right") initOptions.position = position;
|
|
140
|
+
if (locale) initOptions.locale = locale;
|
|
141
|
+
const optionsJson = JSON.stringify(initOptions);
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
tag: "script",
|
|
145
|
+
attrs: { src: "/__prism__/widget.js" },
|
|
146
|
+
injectTo: "body"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
tag: "script",
|
|
150
|
+
children: `PrismDesignWidget.init(${optionsJson});`,
|
|
151
|
+
injectTo: "body"
|
|
152
|
+
}
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
prismDesign as default
|
|
159
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-prism-design",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsup --watch"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"prism-design-widget": "workspace:*"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"vite": ">=5.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"tsup": "^8.3.0",
|
|
21
|
+
"typescript": "^5.6.0",
|
|
22
|
+
"vite": "^6.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|