@caoguo/maplibre-ai 0.0.2 → 0.0.5

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.
Files changed (41) hide show
  1. package/README.md +93 -39
  2. package/dist/chunk-55G7QUFA.js +293 -0
  3. package/dist/chunk-55G7QUFA.js.map +1 -0
  4. package/dist/chunk-ARYB2X4B.js +348 -0
  5. package/dist/chunk-ARYB2X4B.js.map +1 -0
  6. package/dist/chunk-HF3HJPXD.js +435 -0
  7. package/dist/chunk-HF3HJPXD.js.map +1 -0
  8. package/dist/chunk-WVEICADA.js +376 -0
  9. package/dist/chunk-WVEICADA.js.map +1 -0
  10. package/dist/chunk-ZI6ZF26Q.js +149 -0
  11. package/dist/chunk-ZI6ZF26Q.js.map +1 -0
  12. package/dist/copilot/index.cjs +302 -0
  13. package/dist/copilot/index.cjs.map +1 -0
  14. package/dist/copilot/index.d.cts +108 -0
  15. package/dist/copilot/index.d.ts +108 -0
  16. package/dist/copilot/index.js +3 -0
  17. package/dist/copilot/index.js.map +1 -0
  18. package/dist/geoai/index.cjs +449 -0
  19. package/dist/geoai/index.cjs.map +1 -0
  20. package/dist/geoai/index.d.cts +212 -0
  21. package/dist/geoai/index.d.ts +212 -0
  22. package/dist/geoai/index.js +3 -0
  23. package/dist/geoai/index.js.map +1 -0
  24. package/dist/index.cjs +1274 -0
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +4 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +4 -0
  29. package/dist/llm/index.cjs +152 -0
  30. package/dist/llm/index.cjs.map +1 -0
  31. package/dist/llm/index.d.cts +81 -0
  32. package/dist/llm/index.d.ts +81 -0
  33. package/dist/llm/index.js +3 -0
  34. package/dist/llm/index.js.map +1 -0
  35. package/dist/nlpg/index.cjs +387 -0
  36. package/dist/nlpg/index.cjs.map +1 -0
  37. package/dist/nlpg/index.d.cts +162 -0
  38. package/dist/nlpg/index.d.ts +162 -0
  39. package/dist/nlpg/index.js +3 -0
  40. package/dist/nlpg/index.js.map +1 -0
  41. package/package.json +39 -7
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @caoguo/maplibre-ai
2
2
 
3
- > 草果地图 AI 工具链 — 性能诊断 / 样式生成器 v2
3
+ > 草果地图 AI 工具链 — MapCopilot 代码生成 / GeoAI 数据入图 / NLPG 自然语言查询 / DeepSeek LLM 接入 / 性能诊断 / 样式生成器 v2
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@caoguo/maplibre-ai.svg)](https://www.npmjs.com/package/@caoguo/maplibre-ai)
6
6
  [![license](https://img.shields.io/npm/l/@caoguo/maplibre-ai.svg)](LICENSE)
@@ -13,70 +13,124 @@ npm install @caoguo/maplibre-ai
13
13
 
14
14
  ## 功能模块
15
15
 
16
- | 模块 | 导出 | 说明 |
17
- |------|------|------|
18
- | 诊断 | `aiDebug` | AI Debug 诊断工具(性能 Profiler / 瓦片监控 / 内存泄漏 / 优化建议) |
19
- | 样式生成 | `styleGenerator` | 样式生成器 v2(色板提取 / 行业模板 / 品牌定制 / 运营商主题 / 昼夜切换) |
16
+ | 模块 | 子路径 | 说明 |
17
+ |------|--------|------|
18
+ | DeepSeek | `@caoguo/maplibre-ai/llm` | DeepSeek 大模型客户端(OpenAI 兼容 / 流式 / 重试 / 降级) |
19
+ | MapCopilot | `@caoguo/maplibre-ai/copilot` | 自然语言 地图代码生成(规则引擎 + LLM 增强) |
20
+ | GeoAI | `@caoguo/maplibre-ai/geoai` | 数据入图管线(表头识别 / 中文地址解析 / 坐标系纠偏 / 批量编码) |
21
+ | NLPG | `@caoguo/maplibre-ai/nlpg` | 自然语言 → PostGIS SQL + 安全校验层(规则引擎 + LLM) |
22
+ | AI Debug | `@caoguo/maplibre-ai/debug` | 诊断工具(性能 Profiler / 瓦片监控 / 内存泄漏 / 优化建议) |
23
+ | 样式生成 | `@caoguo/maplibre-ai/stylegen` | 样式生成器 v2(色板提取 / 行业模板 / 品牌定制 / 运营商主题 / 昼夜切换) |
20
24
 
21
25
  ## 快速开始
22
26
 
23
- ### 性能诊断
27
+ ### 接入 DeepSeek 大模型
24
28
 
25
29
  ```ts
26
- import { aiDebug } from '@caoguo/maplibre-ai/debug';
30
+ import { DeepSeekClient } from '@caoguo/maplibre-ai/llm';
27
31
 
28
- // 在地图实例上启动性能 Profiler
29
- const profiler = aiDebug.startProfiler(map.instance, {
30
- tileMonitor: true, // 瓦片加载耗时监控
31
- memoryLeak: true, // 内存泄漏检测
32
- fpsSampleHz: 10,
32
+ // 浏览器环境传入你的 DeepSeek API Key(生产环境建议经服务端代理,勿在前端暴露密钥)
33
+ const client = new DeepSeekClient({
34
+ apiKey: process.env.DEEPSEEK_API_KEY!,
35
+ model: 'deepseek-chat', // 或 'deepseek-reasoner'(深度推理)
36
+ temperature: 0.3,
33
37
  });
34
38
 
35
- // 结束诊断获取报告
36
- const report = profiler.stop();
37
- console.log('平均 FPS', report.avgFps);
38
- console.log('瓦片加载 P95', report.tileLoadP95);
39
- console.log('优化建议', report.suggestions);
39
+ // 普通对话
40
+ const r = await client.chat([{ role: 'user', content: '武汉有多少个区?' }]);
41
+ console.log(r.content);
42
+
43
+ // JSON 输出(用于结构化提取)
44
+ const j = await client.chatJson<{ answer: string }>([
45
+ { role: 'user', content: '返回 JSON' },
46
+ ]);
47
+
48
+ // 流式输出
49
+ await client.chat([{ role: 'user', content: '写一首诗' }], {
50
+ onChunk: (delta) => process.stdout.write(delta),
51
+ });
40
52
  ```
41
53
 
42
- ### 样式生成器 v2
54
+ ### MapCopilot(LLM 优先 + 规则引擎降级)
43
55
 
44
56
  ```ts
45
- import { styleGenerator } from '@caoguo/maplibre-ai/stylegen';
46
-
47
- // 从品牌色板生成完整 MapLibre 样式
48
- const style = await styleGenerator.fromPalette({
49
- brandColors: ['#0a0e1a', '#4a9eff', '#e8edf3'],
50
- theme: 'dark',
51
- industry: 'pipeline', // 管网/电网/水网 等行业模板
52
- notoFonts: true,
53
- });
57
+ import { DeepSeekClient } from '@caoguo/maplibre-ai/llm';
58
+ import { LlmMapCopilot } from '@caoguo/maplibre-ai/copilot';
59
+
60
+ const copilot = new LlmMapCopilot({ client: new DeepSeekClient({ apiKey: '...' }) });
61
+ const r = await copilot.generate('创建一个武汉地图,暗色主题,缩放 12');
62
+ console.log(r.code); // LLM 生成;若 LLM 失败自动降级规则引擎
63
+ ```
64
+
65
+ ### NLPG(LLM 生成 SQL + 安全校验兜底)
66
+
67
+ ```ts
68
+ import { LlmNlpg } from '@caoguo/maplibre-ai/nlpg';
69
+
70
+ const nlpg = new LlmNlpg({ client: new DeepSeekClient({ apiKey: '...' }) });
71
+ const r = await nlpg.query('查出使用超过 20 年的铸铁燃气管');
72
+ console.log(r.query.sql); // 生成的 SQL(必经安全校验层)
73
+ console.log(r.valid); // 危险 SQL 会被拒绝并降级规则引擎
74
+ ```
75
+
76
+ ### GeoAI(数据自动入图)
54
77
 
55
- // 从图片提取色板再生成
56
- const palette = await styleGenerator.extractPalette('/path/to/logo.png');
57
- const style2 = await styleGenerator.fromPalette({ brandColors: palette });
78
+ ```ts
79
+ import { importToGeoJSON } from '@caoguo/maplibre-ai/geoai';
80
+
81
+ const headers = ['名称', '地址', '类型'];
82
+ const rows = [
83
+ ['网点A', '武汉光谷', '商业'],
84
+ ['网点B', '武汉江汉区', '政务'],
85
+ ];
86
+ const result = importToGeoJSON(headers, rows);
87
+ console.log(result.stats.successRate); // 编码成功率
88
+ // result.features 为 GeoJSON FeatureCollection
89
+ ```
90
+
91
+ ### NLPG(自然语言查询)
92
+
93
+ ```ts
94
+ import { nlpgQuery } from '@caoguo/maplibre-ai/nlpg';
95
+
96
+ const r = nlpgQuery('查出使用超过 20 年的铸铁燃气管');
97
+ console.log(r.query.sql); // SELECT * FROM pipelines WHERE ...
98
+ console.log(r.valid); // 是否通过安全校验(白名单表/危险操作拦截)
99
+ ```
100
+
101
+ ### 性能诊断
102
+
103
+ ```ts
104
+ import { diagnose } from '@caoguo/maplibre-ai/debug';
105
+
106
+ const report = diagnose({
107
+ perf: { fps: 24, drawCalls: 120, activeLayers: 60 },
108
+ tiles: { requested: 300, cached: 150, totalLoadMs: 1200 },
109
+ });
110
+ console.log(report.suggestions); // 命中的优化建议
58
111
  ```
59
112
 
60
- ### 运营商/昼夜主题
113
+ ### 样式生成器 v2
61
114
 
62
115
  ```ts
63
- import { styleGenerator } from '@caoguo/maplibre-ai/stylegen';
116
+ import { generateBrandStyle, generateIndustryStyle } from '@caoguo/maplibre-ai/stylegen';
64
117
 
65
- const dayTheme = styleGenerator.operatorTheme('china-mobile', 'day');
66
- const nightTheme = styleGenerator.operatorTheme('china-mobile', 'night');
118
+ const brand = generateBrandStyle({ brandColor: '#4a9eff', mode: 'dark' });
119
+ const industry = generateIndustryStyle('pipeline', 'dark'); // 六张网行业模板
67
120
  ```
68
121
 
69
122
  ## 设计原则
70
123
 
71
- 1. **AI 增强,非 AI 替代**:诊断与样式生成加速开发流程,不替代工程师判断。
72
- 2. **本地优先**:所有工具优先在前端运行,不依赖外部 LLM(可选项)。
73
- 3. **可解释**:所有诊断建议附原因与修复方案链接。
124
+ 1. **AI 增强,非 AI 替代**:工具加速开发流程,不替代工程师判断。
125
+ 2. **双模式架构**:规则引擎(离线、可控、可解释)+ LLM 增强(DeepSeek,复杂自由意图)。LLM 优先,失败自动降级规则引擎。
126
+ 3. **安全第一**:NLPG 生成的 SQL 无论来自规则引擎还是 LLM,都必须通过安全校验层(白名单表 / 危险操作拦截 / 注入检测)方可执行。
127
+ 4. **密钥安全**:`DeepSeekClient` 仅接收 API Key,不内置任何密钥。生产环境请在服务端代理调用,避免前端暴露密钥。
74
128
 
75
129
  ## 浏览器 / 环境要求
76
130
 
77
131
  - 现代浏览器(Chrome 90+)
78
- - 样式生成器需支持 `OffscreenCanvas`(Chrome 69+)
79
- - 核心模块在 Node.js 可测(已含 16 个 vitest 用例)
132
+ - Node.js 18+(`DeepSeekClient` 依赖原生 `fetch`/`ReadableStream`,或注入自定义 `fetchImpl`)
133
+ - 所有模块在 Node.js 可测(已含 89 个 vitest 用例,LLM 相关测试使用 mock fetch,不产生真实 API 调用)
80
134
 
81
135
  ## 许可
82
136
 
@@ -0,0 +1,293 @@
1
+ // src/copilot/copilot.ts
2
+ var PLACE_COORDINATES = {
3
+ \u6B66\u6C49: [114.305, 30.593],
4
+ \u5149\u8C37: [114.428, 30.507],
5
+ \u6C49\u53E3: [114.268, 30.586],
6
+ \u6B66\u660C: [114.316, 30.554],
7
+ \u5317\u4EAC: [116.407, 39.904],
8
+ \u4E0A\u6D77: [121.474, 31.23],
9
+ \u5E7F\u5DDE: [113.264, 23.129],
10
+ \u6DF1\u5733: [114.058, 22.543],
11
+ \u6210\u90FD: [104.067, 30.573],
12
+ \u676D\u5DDE: [120.155, 30.274],
13
+ \u5357\u4EAC: [118.796, 32.06],
14
+ \u91CD\u5E86: [106.551, 29.563],
15
+ \u897F\u5B89: [108.94, 34.341],
16
+ \u5929\u6D25: [117.2, 39.084]
17
+ };
18
+ var COLOR_NAMES = {
19
+ \u7EA2: "#ef4444",
20
+ \u7EA2\u8272: "#ef4444",
21
+ \u6A59: "#f97316",
22
+ \u6A59\u8272: "#f97316",
23
+ \u9EC4: "#f59e0b",
24
+ \u9EC4\u8272: "#f59e0b",
25
+ \u7EFF: "#22c55e",
26
+ \u7EFF\u8272: "#22c55e",
27
+ \u9752: "#22d3ee",
28
+ \u84DD\u8272: "#3b82f6",
29
+ \u84DD: "#3b82f6",
30
+ \u7D2B: "#8b5cf6",
31
+ \u7D2B\u8272: "#8b5cf6",
32
+ \u7C89: "#ec4899",
33
+ \u7070: "#6b7280",
34
+ \u9ED1\u8272: "#111827",
35
+ \u767D\u8272: "#ffffff"
36
+ };
37
+ var INTENT_PATTERNS = [
38
+ { intent: "popup_interaction", re: /弹窗|信息窗|弹出|popup|点击.*?显示|点击.*?弹出|点击.*?信息/, confidence: 0.92 },
39
+ { intent: "heatmap", re: /热力|heatmap|热度|密度图|热图/, confidence: 0.9 },
40
+ { intent: "add_line_polygon", re: /路线|连线|线段|画.{0,2}(线|面)|多边形|折线|面要素|LineString|polygon/, confidence: 0.88 },
41
+ { intent: "add_marker", re: /标记|marker|点标记|加点|标注|mark/, confidence: 0.86 },
42
+ { intent: "create_map", re: /地图|初始化|创建|生成|画布|渲染|map/, confidence: 0.8 }
43
+ ];
44
+ function classifyIntent(query) {
45
+ let best = { intent: "unknown", confidence: 0 };
46
+ for (const p of INTENT_PATTERNS) {
47
+ const m = query.match(p.re);
48
+ if (m) {
49
+ const c = p.confidence * (1 + 0.03 * Math.min(m[0].length / query.length, 1));
50
+ if (c > best.confidence) best = { intent: p.intent, confidence: Math.min(c, 1) };
51
+ }
52
+ }
53
+ return best;
54
+ }
55
+ function extractParams(query, intent) {
56
+ const params = {};
57
+ for (const [name, coord] of Object.entries(PLACE_COORDINATES)) {
58
+ if (query.includes(name)) {
59
+ params.place = name;
60
+ params.center = coord;
61
+ break;
62
+ }
63
+ }
64
+ const zoomMatch = query.match(/缩放\s*(\d{1,2})|zoom\s*(\d{1,2})|级别\s*(\d{1,2})/i);
65
+ if (zoomMatch) {
66
+ const z = parseInt(zoomMatch[1] ?? zoomMatch[2] ?? zoomMatch[3]);
67
+ if (!Number.isNaN(z) && z >= 0 && z <= 22) params.zoom = z;
68
+ }
69
+ if (/暗色|暗黑|深色|dark|夜间/.test(query)) params.style = "caoguo-dark";
70
+ else if (/亮色|浅色|白色|light|白天/.test(query)) params.style = "caoguo-light";
71
+ for (const [name, hex] of Object.entries(COLOR_NAMES)) {
72
+ if (query.includes(name)) {
73
+ params.color = hex;
74
+ break;
75
+ }
76
+ }
77
+ const sizeMatch = query.match(/(?:宽度|半径|大小|width|size)\s*(\d+)/i);
78
+ if (sizeMatch) params.size = parseInt(sizeMatch[1]);
79
+ const layerMatch = query.match(/(?:图层|layer)\s*["']?([\w-]+)["']?/i);
80
+ if (layerMatch) params.layerId = layerMatch[1];
81
+ const textMatch = query.match(/["「『]([^"」』]+)["」』]/);
82
+ if (textMatch) params.text = textMatch[1];
83
+ return params;
84
+ }
85
+ function generateCode(intent, p) {
86
+ const center = p.center ?? [114.305, 30.593];
87
+ const zoom = p.zoom ?? 12;
88
+ const style = p.style ?? "caoguo-dark";
89
+ const color = p.color ?? "#ef4444";
90
+ const size = p.size ?? 8;
91
+ const layerId = p.layerId ?? "my-layer";
92
+ switch (intent) {
93
+ case "create_map":
94
+ return [
95
+ `const map = new CaoguoMap.Map({`,
96
+ ` container: 'map',`,
97
+ ` center: [${center[0]}, ${center[1]}],`,
98
+ ` zoom: ${zoom},`,
99
+ ` style: '${style}',`,
100
+ `});`
101
+ ].join("\n");
102
+ case "add_marker":
103
+ return [
104
+ `map.addSource('marker-src', {`,
105
+ ` type: 'geojson',`,
106
+ ` data: { type: 'Feature', geometry: { type: 'Point', coordinates: [${center[0]}, ${center[1]}] }, properties: {} },`,
107
+ `});`,
108
+ `map.addLayer({`,
109
+ ` id: '${layerId}',`,
110
+ ` type: 'circle',`,
111
+ ` source: 'marker-src',`,
112
+ ` paint: { 'circle-radius': ${size}, 'circle-color': '${color}' },`,
113
+ `});`
114
+ ].join("\n");
115
+ case "add_line_polygon":
116
+ return [
117
+ `map.addSource('line-src', {`,
118
+ ` type: 'geojson',`,
119
+ ` data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [[${center[0]}, ${center[1]}], [${(center[0] + 0.05).toFixed(3)}, ${(center[1] + 0.03).toFixed(3)}]] }, properties: {} },`,
120
+ `});`,
121
+ `map.addLayer({`,
122
+ ` id: '${layerId}',`,
123
+ ` type: 'line',`,
124
+ ` source: 'line-src',`,
125
+ ` paint: { 'line-color': '${color}', 'line-width': ${Math.max(2, size)} },`,
126
+ `});`
127
+ ].join("\n");
128
+ case "heatmap":
129
+ return [
130
+ `map.addSource('heat-src', {`,
131
+ ` type: 'geojson',`,
132
+ ` data: { type: 'FeatureCollection', features: [] },`,
133
+ `});`,
134
+ `map.addLayer({`,
135
+ ` id: '${layerId}',`,
136
+ ` type: 'heatmap',`,
137
+ ` source: 'heat-src',`,
138
+ ` paint: {`,
139
+ ` 'heatmap-weight': 1,`,
140
+ ` 'heatmap-color': ['interpolate', ['linear'], ['heatmap-density'], 0, 'rgba(33,102,172,0)', 0.5, '${color}', 1, 'rgb(239,68,68)'],`,
141
+ ` 'heatmap-radius': ${Math.max(20, size * 4)},`,
142
+ ` },`,
143
+ `});`
144
+ ].join("\n");
145
+ case "popup_interaction":
146
+ return [
147
+ `map.on('click', '${layerId}', (e) => {`,
148
+ ` const coords = e.features[0].geometry.coordinates.slice();`,
149
+ ` new maplibregl.Popup()`,
150
+ ` .setLngLat(coords)`,
151
+ ` .setHTML('${p.text ?? "\u8981\u7D20\u4FE1\u606F"}')`,
152
+ ` .addTo(map.instance);`,
153
+ `});`
154
+ ].join("\n");
155
+ default:
156
+ return `// \u65E0\u6CD5\u8BC6\u522B\u7684\u610F\u56FE\uFF1A${intent}`;
157
+ }
158
+ }
159
+ function generateFromQuery(query) {
160
+ const { intent, confidence } = classifyIntent(query);
161
+ const params = extractParams(query);
162
+ const code = generateCode(intent, params);
163
+ return {
164
+ intent,
165
+ params,
166
+ code,
167
+ confidence,
168
+ description: buildDescription(intent, params)
169
+ };
170
+ }
171
+ function buildDescription(intent, p) {
172
+ const parts = [];
173
+ switch (intent) {
174
+ case "create_map":
175
+ parts.push("\u521B\u5EFA\u57FA\u7840\u5730\u56FE");
176
+ break;
177
+ case "add_marker":
178
+ parts.push("\u6DFB\u52A0\u70B9\u6807\u8BB0");
179
+ break;
180
+ case "add_line_polygon":
181
+ parts.push("\u6DFB\u52A0\u7EBF/\u9762\u8981\u7D20");
182
+ break;
183
+ case "heatmap":
184
+ parts.push("\u751F\u6210\u70ED\u529B\u56FE");
185
+ break;
186
+ case "popup_interaction":
187
+ parts.push("\u7ED1\u5B9A\u70B9\u51FB\u5F39\u7A97");
188
+ break;
189
+ default:
190
+ parts.push("\u672A\u77E5\u610F\u56FE");
191
+ }
192
+ if (p.place) parts.push(`\u5730\u70B9:${p.place}`);
193
+ if (p.color) parts.push(`\u989C\u8272:${p.color}`);
194
+ return parts.join(" ");
195
+ }
196
+ var MapCopilot = class {
197
+ lastIntent = "create_map";
198
+ lastParams = {};
199
+ /** 生成代码(支持上下文增量修改) */
200
+ generate(query) {
201
+ const { intent, confidence } = classifyIntent(query);
202
+ const isModification = /改成|改为|换成|变成|调整为|设置成/.test(query);
203
+ if (isModification && this.lastIntent !== "unknown") {
204
+ const delta = extractParams(query, this.lastIntent);
205
+ this.lastParams = { ...this.lastParams, ...delta };
206
+ const code2 = generateCode(this.lastIntent, this.lastParams);
207
+ return {
208
+ intent: this.lastIntent,
209
+ params: { ...this.lastParams },
210
+ code: code2,
211
+ confidence,
212
+ description: buildDescription(this.lastIntent, this.lastParams)
213
+ };
214
+ }
215
+ this.lastIntent = intent;
216
+ this.lastParams = extractParams(query);
217
+ const code = generateCode(intent, this.lastParams);
218
+ return {
219
+ intent,
220
+ params: { ...this.lastParams },
221
+ code,
222
+ confidence,
223
+ description: buildDescription(intent, this.lastParams)
224
+ };
225
+ }
226
+ /** 重置上下文 */
227
+ reset() {
228
+ this.lastIntent = "create_map";
229
+ this.lastParams = {};
230
+ }
231
+ /** 获取当前上下文 */
232
+ get context() {
233
+ return { intent: this.lastIntent, params: { ...this.lastParams } };
234
+ }
235
+ };
236
+
237
+ // src/copilot/llmCopilot.ts
238
+ var SYSTEM_PROMPT = `\u4F60\u662F"\u8349\u679C\u5730\u56FE"(CaoguoMap) \u7684\u5730\u56FE\u4EE3\u7801\u751F\u6210\u52A9\u624B\u3002\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u5730\u56FE\u9700\u6C42\uFF0C\u4F60\u751F\u6210\u5BF9\u5E94\u7684 MapLibre GL JS \u4EE3\u7801\u7247\u6BB5\u3002
239
+
240
+ \u8349\u679C\u5730\u56FE API \u7EA6\u5B9A\uFF1A
241
+ - \u5730\u56FE\u5B9E\u4F8B\uFF1Anew CaoguoMap.Map({ container, center, zoom, style })
242
+ - \u6DFB\u52A0\u70B9\uFF1Amap.addSource + map.addLayer({ type: 'circle' })
243
+ - \u6DFB\u52A0\u7EBF\uFF1Amap.addLayer({ type: 'line' })
244
+ - \u70ED\u529B\u56FE\uFF1Amap.addLayer({ type: 'heatmap' })
245
+ - \u5F39\u7A97\uFF1Amap.on('click', layerId, cb) + new maplibregl.Popup()
246
+
247
+ \u8981\u6C42\uFF1A
248
+ 1. \u53EA\u8FD4\u56DE JSON\uFF0C\u683C\u5F0F\uFF1A{"intent":"create_map|add_marker|add_line_polygon|heatmap|popup_interaction","code":"...","description":"..."}
249
+ 2. code \u5FC5\u987B\u662F\u53EF\u76F4\u63A5\u8FD0\u884C\u7684 JavaScript \u4EE3\u7801\u7247\u6BB5\uFF08\u4E0D\u542B import\uFF09\uFF0C\u7981\u6B62\u5305\u542B\u4EFB\u4F55 fetch/\u7F51\u7EDC\u8BF7\u6C42/\u6587\u4EF6\u7CFB\u7EDF/\u5371\u9669\u64CD\u4F5C\u3002
250
+ 3. \u82E5\u9700\u6C42\u8D85\u51FA\u5730\u56FE\u4EE3\u7801\u8303\u7574\uFF0C\u8FD4\u56DE {"intent":"unknown","code":"","description":"\u65E0\u6CD5\u8BC6\u522B\u7684\u9700\u6C42"}\u3002
251
+ 4. \u4E0D\u8981\u8F93\u51FA JSON \u4EE5\u5916\u7684\u4EFB\u4F55\u6587\u5B57\u3002`;
252
+ var LlmMapCopilot = class {
253
+ client;
254
+ enabled;
255
+ constructor(config) {
256
+ this.client = config.client;
257
+ this.enabled = config.enabled ?? true;
258
+ }
259
+ /** 生成代码:LLM 优先,失败降级规则引擎 */
260
+ async generate(query) {
261
+ if (!this.enabled) {
262
+ return generateFromQuery(query);
263
+ }
264
+ try {
265
+ return await this.generateWithLlm(query);
266
+ } catch {
267
+ return generateFromQuery(query);
268
+ }
269
+ }
270
+ async generateWithLlm(query) {
271
+ const messages = [
272
+ { role: "system", content: SYSTEM_PROMPT },
273
+ { role: "user", content: query }
274
+ ];
275
+ const { data } = await this.client.chatJson(messages);
276
+ const intent = data.intent ?? "unknown";
277
+ const code = data.code ?? "";
278
+ if (intent === "unknown" || !code.trim()) {
279
+ return generateFromQuery(query);
280
+ }
281
+ return {
282
+ intent,
283
+ params: {},
284
+ code,
285
+ confidence: 0.9,
286
+ description: data.description ?? `LLM \u751F\u6210\uFF08${intent}\uFF09`
287
+ };
288
+ }
289
+ };
290
+
291
+ export { COLOR_NAMES, LlmMapCopilot, MapCopilot, PLACE_COORDINATES, classifyIntent, extractParams, generateCode, generateFromQuery };
292
+ //# sourceMappingURL=chunk-55G7QUFA.js.map
293
+ //# sourceMappingURL=chunk-55G7QUFA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/copilot/copilot.ts","../src/copilot/llmCopilot.ts"],"names":["code"],"mappings":"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","file":"chunk-55G7QUFA.js","sourcesContent":["/**\n * MapCopilot v1(PRD phase-0 §5.5)\n *\n * 自然语言 → 草果地图 API 代码生成。\n *\n * v1 采用「意图识别 + 参数提取 + 模板生成」的规则引擎方案(不依赖大模型),\n * 覆盖 PRD 定义的 5 类核心交互意图:\n * I-1 create_map 创建基础地图\n * I-2 add_marker 添加点标记\n * I-3 add_line_polygon 添加线/面\n * I-4 heatmap 数据可视化(热力图)\n * I-5 popup_interaction 交互功能(点击弹窗)\n *\n * 全部为纯函数,可离线运行。支持上下文增量修改(\"把颜色改成红色\")。\n */\n\n// ============================================================\n// 一、类型定义\n// ============================================================\nexport type CopilotIntent =\n | 'create_map'\n | 'add_marker'\n | 'add_line_polygon'\n | 'heatmap'\n | 'popup_interaction'\n | 'unknown';\n\n/** 提取出的地图参数 */\nexport interface CopilotParams {\n /** 城市/地点名(用于映射中心坐标) */\n place?: string;\n /** 地图中心 [lng, lat] */\n center?: [number, number];\n /** 缩放级别 */\n zoom?: number;\n /** 主题样式 */\n style?: 'caoguo-dark' | 'caoguo-light';\n /** 要素颜色(hex) */\n color?: string;\n /** 线宽 / 半径 */\n size?: number;\n /** 图层 ID(用于交互意图) */\n layerId?: string;\n /** 文本内容(弹窗等) */\n text?: string;\n}\n\nexport interface CopilotResult {\n intent: CopilotIntent;\n params: CopilotParams;\n /** 生成的代码 */\n code: string;\n /** 识别置信度 0-1 */\n confidence: number;\n /** 可读说明 */\n description: string;\n}\n\n// ============================================================\n// 二、知识库(城市坐标 / 颜色 / 意图关键词)\n// ============================================================\n/** 常见城市/地点中心坐标(WGS84) */\nexport const PLACE_COORDINATES: Record<string, [number, number]> = {\n 武汉: [114.305, 30.593],\n 光谷: [114.428, 30.507],\n 汉口: [114.268, 30.586],\n 武昌: [114.316, 30.554],\n 北京: [116.407, 39.904],\n 上海: [121.474, 31.23],\n 广州: [113.264, 23.129],\n 深圳: [114.058, 22.543],\n 成都: [104.067, 30.573],\n 杭州: [120.155, 30.274],\n 南京: [118.796, 32.06],\n 重庆: [106.551, 29.563],\n 西安: [108.94, 34.341],\n 天津: [117.2, 39.084],\n};\n\n/** 颜色名 → hex */\nexport const COLOR_NAMES: Record<string, string> = {\n 红: '#ef4444',\n 红色: '#ef4444',\n 橙: '#f97316',\n 橙色: '#f97316',\n 黄: '#f59e0b',\n 黄色: '#f59e0b',\n 绿: '#22c55e',\n 绿色: '#22c55e',\n 青: '#22d3ee',\n 蓝色: '#3b82f6',\n 蓝: '#3b82f6',\n 紫: '#8b5cf6',\n 紫色: '#8b5cf6',\n 粉: '#ec4899',\n 灰: '#6b7280',\n 黑色: '#111827',\n 白色: '#ffffff',\n};\n\n/** 意图关键词(按优先级匹配) */\nconst INTENT_PATTERNS: Array<{ intent: CopilotIntent; re: RegExp; confidence: number }> = [\n { intent: 'popup_interaction', re: /弹窗|信息窗|弹出|popup|点击.*?显示|点击.*?弹出|点击.*?信息/, confidence: 0.92 },\n { intent: 'heatmap', re: /热力|heatmap|热度|密度图|热图/, confidence: 0.9 },\n { intent: 'add_line_polygon', re: /路线|连线|线段|画.{0,2}(线|面)|多边形|折线|面要素|LineString|polygon/, confidence: 0.88 },\n { intent: 'add_marker', re: /标记|marker|点标记|加点|标注|mark/, confidence: 0.86 },\n { intent: 'create_map', re: /地图|初始化|创建|生成|画布|渲染|map/, confidence: 0.8 },\n];\n\n// ============================================================\n// 三、意图识别\n// ============================================================\nexport function classifyIntent(query: string): { intent: CopilotIntent; confidence: number } {\n let best: { intent: CopilotIntent; confidence: number } = { intent: 'unknown', confidence: 0 };\n for (const p of INTENT_PATTERNS) {\n const m = query.match(p.re);\n if (m) {\n const c = p.confidence * (1 + 0.03 * Math.min(m[0].length / query.length, 1));\n if (c > best.confidence) best = { intent: p.intent, confidence: Math.min(c, 1) };\n }\n }\n return best;\n}\n\n// ============================================================\n// 四、参数提取\n// ============================================================\nexport function extractParams(query: string, intent: CopilotIntent): CopilotParams {\n const params: CopilotParams = {};\n\n // 地点 → 中心坐标\n for (const [name, coord] of Object.entries(PLACE_COORDINATES)) {\n if (query.includes(name)) {\n params.place = name;\n params.center = coord;\n break;\n }\n }\n\n // 缩放级别\n const zoomMatch = query.match(/缩放\\s*(\\d{1,2})|zoom\\s*(\\d{1,2})|级别\\s*(\\d{1,2})/i);\n if (zoomMatch) {\n const z = parseInt(zoomMatch[1] ?? zoomMatch[2] ?? zoomMatch[3]);\n if (!Number.isNaN(z) && z >= 0 && z <= 22) params.zoom = z;\n }\n\n // 主题\n if (/暗色|暗黑|深色|dark|夜间/.test(query)) params.style = 'caoguo-dark';\n else if (/亮色|浅色|白色|light|白天/.test(query)) params.style = 'caoguo-light';\n\n // 颜色\n for (const [name, hex] of Object.entries(COLOR_NAMES)) {\n if (query.includes(name)) {\n params.color = hex;\n break;\n }\n }\n\n // 尺寸(线宽/半径)\n const sizeMatch = query.match(/(?:宽度|半径|大小|width|size)\\s*(\\d+)/i);\n if (sizeMatch) params.size = parseInt(sizeMatch[1]);\n\n // 图层 ID(交互意图用)\n const layerMatch = query.match(/(?:图层|layer)\\s*[\"']?([\\w-]+)[\"']?/i);\n if (layerMatch) params.layerId = layerMatch[1];\n\n // 文本内容(弹窗用)\n const textMatch = query.match(/[\"「『]([^\"」』]+)[\"」』]/);\n if (textMatch) params.text = textMatch[1];\n\n return params;\n}\n\n// ============================================================\n// 五、代码模板生成\n// ============================================================\nexport function generateCode(intent: CopilotIntent, p: CopilotParams): string {\n const center = p.center ?? [114.305, 30.593];\n const zoom = p.zoom ?? 12;\n const style = p.style ?? 'caoguo-dark';\n const color = p.color ?? '#ef4444';\n const size = p.size ?? 8;\n const layerId = p.layerId ?? 'my-layer';\n\n switch (intent) {\n case 'create_map':\n return [\n `const map = new CaoguoMap.Map({`,\n ` container: 'map',`,\n ` center: [${center[0]}, ${center[1]}],`,\n ` zoom: ${zoom},`,\n ` style: '${style}',`,\n `});`,\n ].join('\\n');\n\n case 'add_marker':\n return [\n `map.addSource('marker-src', {`,\n ` type: 'geojson',`,\n ` data: { type: 'Feature', geometry: { type: 'Point', coordinates: [${center[0]}, ${center[1]}] }, properties: {} },`,\n `});`,\n `map.addLayer({`,\n ` id: '${layerId}',`,\n ` type: 'circle',`,\n ` source: 'marker-src',`,\n ` paint: { 'circle-radius': ${size}, 'circle-color': '${color}' },`,\n `});`,\n ].join('\\n');\n\n case 'add_line_polygon':\n return [\n `map.addSource('line-src', {`,\n ` type: 'geojson',`,\n ` data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [[${center[0]}, ${center[1]}], [${(center[0] + 0.05).toFixed(3)}, ${(center[1] + 0.03).toFixed(3)}]] }, properties: {} },`,\n `});`,\n `map.addLayer({`,\n ` id: '${layerId}',`,\n ` type: 'line',`,\n ` source: 'line-src',`,\n ` paint: { 'line-color': '${color}', 'line-width': ${Math.max(2, size)} },`,\n `});`,\n ].join('\\n');\n\n case 'heatmap':\n return [\n `map.addSource('heat-src', {`,\n ` type: 'geojson',`,\n ` data: { type: 'FeatureCollection', features: [] },`,\n `});`,\n `map.addLayer({`,\n ` id: '${layerId}',`,\n ` type: 'heatmap',`,\n ` source: 'heat-src',`,\n ` paint: {`,\n ` 'heatmap-weight': 1,`,\n ` 'heatmap-color': ['interpolate', ['linear'], ['heatmap-density'], 0, 'rgba(33,102,172,0)', 0.5, '${color}', 1, 'rgb(239,68,68)'],`,\n ` 'heatmap-radius': ${Math.max(20, size * 4)},`,\n ` },`,\n `});`,\n ].join('\\n');\n\n case 'popup_interaction':\n return [\n `map.on('click', '${layerId}', (e) => {`,\n ` const coords = e.features[0].geometry.coordinates.slice();`,\n ` new maplibregl.Popup()`,\n ` .setLngLat(coords)`,\n ` .setHTML('${p.text ?? '要素信息'}')`,\n ` .addTo(map.instance);`,\n `});`,\n ].join('\\n');\n\n default:\n return `// 无法识别的意图:${intent}`;\n }\n}\n\n// ============================================================\n// 六、MapCopilot 主入口(含上下文增量修改)\n// ============================================================\nexport function generateFromQuery(query: string): CopilotResult {\n const { intent, confidence } = classifyIntent(query);\n const params = extractParams(query, intent);\n const code = generateCode(intent, params);\n return {\n intent,\n params,\n code,\n confidence,\n description: buildDescription(intent, params),\n };\n}\n\nfunction buildDescription(intent: CopilotIntent, p: CopilotParams): string {\n const parts: string[] = [];\n switch (intent) {\n case 'create_map':\n parts.push('创建基础地图');\n break;\n case 'add_marker':\n parts.push('添加点标记');\n break;\n case 'add_line_polygon':\n parts.push('添加线/面要素');\n break;\n case 'heatmap':\n parts.push('生成热力图');\n break;\n case 'popup_interaction':\n parts.push('绑定点击弹窗');\n break;\n default:\n parts.push('未知意图');\n }\n if (p.place) parts.push(`地点:${p.place}`);\n if (p.color) parts.push(`颜色:${p.color}`);\n return parts.join(' ');\n}\n\n/**\n * MapCopilot 组件类:维护当前会话上下文,支持增量修改。\n *\n * 用法:\n * const copilot = new MapCopilot();\n * copilot.generate('创建一个武汉地图,暗色主题,缩放 12'); // → 代码\n * copilot.generate('把颜色改成红色'); // → 增量修改后代码\n */\nexport class MapCopilot {\n private lastIntent: CopilotIntent = 'create_map';\n private lastParams: CopilotParams = {};\n\n /** 生成代码(支持上下文增量修改) */\n generate(query: string): CopilotResult {\n const { intent, confidence } = classifyIntent(query);\n const isModification = /改成|改为|换成|变成|调整为|设置成/.test(query);\n\n if (isModification && this.lastIntent !== 'unknown') {\n // 增量修改:提取新参数,合并到上一轮参数\n const delta = extractParams(query, this.lastIntent);\n this.lastParams = { ...this.lastParams, ...delta };\n const code = generateCode(this.lastIntent, this.lastParams);\n return {\n intent: this.lastIntent,\n params: { ...this.lastParams },\n code,\n confidence,\n description: buildDescription(this.lastIntent, this.lastParams),\n };\n }\n\n // 全新意图\n this.lastIntent = intent;\n this.lastParams = extractParams(query, intent);\n const code = generateCode(intent, this.lastParams);\n return {\n intent,\n params: { ...this.lastParams },\n code,\n confidence,\n description: buildDescription(intent, this.lastParams),\n };\n }\n\n /** 重置上下文 */\n reset(): void {\n this.lastIntent = 'create_map';\n this.lastParams = {};\n }\n\n /** 获取当前上下文 */\n get context(): { intent: CopilotIntent; params: CopilotParams } {\n return { intent: this.lastIntent, params: { ...this.lastParams } };\n }\n}\n","/**\n * MapCopilot LLM 模式(PRD phase-0 §5.5 I-6「LLM 增强」)\n *\n * 在规则引擎(copilot.ts)基础上,接入 DeepSeek 大模型实现:\n * - 自然语言 → 地图代码生成的「自由意图」支持(突破 5 类模板限制)\n * - LLM 优先,失败自动降级到规则引擎\n *\n * 安全设计:\n * - 生成的代码片段仅返回文本,由上层决定是否执行(不在本模块 eval)\n * - 通过 system prompt 约束输出格式与 API 白名单\n */\n\nimport type { DeepSeekClient, ChatMessage } from '../llm/deepseek';\nimport { generateFromQuery, type CopilotResult, type CopilotIntent } from './copilot';\n\nexport interface LlmCopilotConfig {\n /** DeepSeek 客户端 */\n client: DeepSeekClient;\n /** 是否启用 LLM(false 则退化为纯规则引擎) */\n enabled?: boolean;\n}\n\n/** LLM 返回的代码生成结果结构 */\ninterface LlmCopilotOutput {\n intent?: string;\n code: string;\n description?: string;\n}\n\n/** 系统提示词:约束模型只生成草果地图 API 代码 */\nconst SYSTEM_PROMPT = `你是\"草果地图\"(CaoguoMap) 的地图代码生成助手。用户用自然语言描述地图需求,你生成对应的 MapLibre GL JS 代码片段。\n\n草果地图 API 约定:\n- 地图实例:new CaoguoMap.Map({ container, center, zoom, style })\n- 添加点:map.addSource + map.addLayer({ type: 'circle' })\n- 添加线:map.addLayer({ type: 'line' })\n- 热力图:map.addLayer({ type: 'heatmap' })\n- 弹窗:map.on('click', layerId, cb) + new maplibregl.Popup()\n\n要求:\n1. 只返回 JSON,格式:{\"intent\":\"create_map|add_marker|add_line_polygon|heatmap|popup_interaction\",\"code\":\"...\",\"description\":\"...\"}\n2. code 必须是可直接运行的 JavaScript 代码片段(不含 import),禁止包含任何 fetch/网络请求/文件系统/危险操作。\n3. 若需求超出地图代码范畴,返回 {\"intent\":\"unknown\",\"code\":\"\",\"description\":\"无法识别的需求\"}。\n4. 不要输出 JSON 以外的任何文字。`;\n\nexport class LlmMapCopilot {\n private client: DeepSeekClient;\n private enabled: boolean;\n\n constructor(config: LlmCopilotConfig) {\n this.client = config.client;\n this.enabled = config.enabled ?? true;\n }\n\n /** 生成代码:LLM 优先,失败降级规则引擎 */\n async generate(query: string): Promise<CopilotResult> {\n if (!this.enabled) {\n return generateFromQuery(query);\n }\n try {\n return await this.generateWithLlm(query);\n } catch {\n // LLM 失败降级到规则引擎\n return generateFromQuery(query);\n }\n }\n\n private async generateWithLlm(query: string): Promise<CopilotResult> {\n const messages: ChatMessage[] = [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: query },\n ];\n const { data } = await this.client.chatJson<LlmCopilotOutput>(messages);\n\n const intent = (data.intent ?? 'unknown') as CopilotIntent;\n const code = data.code ?? '';\n // 若 LLM 返回未知意图或空代码,降级规则引擎\n if (intent === 'unknown' || !code.trim()) {\n return generateFromQuery(query);\n }\n return {\n intent,\n params: {},\n code,\n confidence: 0.9,\n description: data.description ?? `LLM 生成(${intent})`,\n };\n }\n}\n"]}