@caoguo/maplibre-ai 0.0.4 → 0.0.6
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/dist/chunk-AIQSXKZQ.js +311 -0
- package/dist/chunk-AIQSXKZQ.js.map +1 -0
- package/dist/{chunk-ARYB2X4B.js → chunk-WVEICADA.js} +43 -15
- package/dist/chunk-WVEICADA.js.map +1 -0
- package/dist/copilot/index.d.cts +1 -1
- package/dist/copilot/index.d.ts +1 -1
- package/dist/deepseek-rSUSDXri.d.cts +81 -0
- package/dist/deepseek-rSUSDXri.d.ts +81 -0
- package/dist/index.cjs +205 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/llm/index.cjs +164 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +64 -59
- package/dist/llm/index.d.ts +64 -59
- package/dist/llm/index.js +1 -1
- package/dist/nlpg/index.cjs +41 -13
- package/dist/nlpg/index.cjs.map +1 -1
- package/dist/nlpg/index.d.cts +5 -3
- package/dist/nlpg/index.d.ts +5 -3
- package/dist/nlpg/index.js +1 -1
- package/package.json +11 -11
- package/dist/chunk-ARYB2X4B.js.map +0 -1
- package/dist/chunk-ZI6ZF26Q.js +0 -149
- package/dist/chunk-ZI6ZF26Q.js.map +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek LLM 客户端(PRD phase-0 §5.5 / §5.7 的 v1 后端能力)
|
|
3
|
+
*
|
|
4
|
+
* DeepSeek API 与 OpenAI Chat Completions 协议兼容:
|
|
5
|
+
* base_url: https://api.deepseek.com
|
|
6
|
+
* 模型: deepseek-chat(通用对话)/ deepseek-reasoner(深度推理)
|
|
7
|
+
* 鉴权: Authorization: Bearer <API_KEY>
|
|
8
|
+
*
|
|
9
|
+
* 设计:
|
|
10
|
+
* - 依赖注入的 fetch(浏览器/Node 均可运行,Node 18+ 原生 fetch)
|
|
11
|
+
* - 自动重试(指数退避)
|
|
12
|
+
* - 失败降级(可传入 fallback,规则引擎兜底)
|
|
13
|
+
* - 支持流式(onChunk 回调)与 JSON 输出
|
|
14
|
+
*/
|
|
15
|
+
interface DeepSeekConfig {
|
|
16
|
+
/** API Key */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/** 基础地址,默认 https://api.deepseek.com */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
/** 模型,默认 deepseek-chat */
|
|
21
|
+
model?: 'deepseek-chat' | 'deepseek-reasoner' | (string & {});
|
|
22
|
+
/** 温度 0-2 */
|
|
23
|
+
temperature?: number;
|
|
24
|
+
/** 最大 token */
|
|
25
|
+
maxTokens?: number;
|
|
26
|
+
/** 超时(ms),默认 30s */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/** 重试次数,默认 2 */
|
|
29
|
+
retries?: number;
|
|
30
|
+
/** 注入 fetch(测试/Node 环境),默认 globalThis.fetch */
|
|
31
|
+
fetchImpl?: typeof fetch;
|
|
32
|
+
}
|
|
33
|
+
interface ChatMessage {
|
|
34
|
+
role: 'system' | 'user' | 'assistant';
|
|
35
|
+
content: string;
|
|
36
|
+
}
|
|
37
|
+
interface ChatOptions {
|
|
38
|
+
/** 是否启用 JSON 模式(要求模型返回纯 JSON) */
|
|
39
|
+
json?: boolean;
|
|
40
|
+
/** 流式回调 */
|
|
41
|
+
onChunk?: (delta: string) => void;
|
|
42
|
+
}
|
|
43
|
+
interface ChatResult {
|
|
44
|
+
/** 完整回复文本 */
|
|
45
|
+
content: string;
|
|
46
|
+
/** 消耗 token */
|
|
47
|
+
usage?: {
|
|
48
|
+
promptTokens: number;
|
|
49
|
+
completionTokens: number;
|
|
50
|
+
totalTokens: number;
|
|
51
|
+
};
|
|
52
|
+
/** 使用的模型 */
|
|
53
|
+
model: string;
|
|
54
|
+
}
|
|
55
|
+
/** 已解析的 JSON 回复 */
|
|
56
|
+
interface JsonChatResult<T> {
|
|
57
|
+
/** 解析出的 JSON 对象 */
|
|
58
|
+
data: T;
|
|
59
|
+
/** 原始文本 */
|
|
60
|
+
raw: string;
|
|
61
|
+
model: string;
|
|
62
|
+
}
|
|
63
|
+
declare class DeepSeekClient {
|
|
64
|
+
private config;
|
|
65
|
+
private lastRequestId;
|
|
66
|
+
constructor(config: DeepSeekConfig);
|
|
67
|
+
/** 发起一次聊天补全 */
|
|
68
|
+
chat(messages: ChatMessage[], opts?: ChatOptions): Promise<ChatResult>;
|
|
69
|
+
/** 聊天 + JSON 解析 */
|
|
70
|
+
chatJson<T>(messages: ChatMessage[]): Promise<JsonChatResult<T>>;
|
|
71
|
+
private requestWithRetry;
|
|
72
|
+
private isRetryable;
|
|
73
|
+
private requestOnce;
|
|
74
|
+
/** 解析 SSE 流式响应 */
|
|
75
|
+
private parseStream;
|
|
76
|
+
private sleep;
|
|
77
|
+
}
|
|
78
|
+
/** 便捷工厂:创建默认配置的客户端 */
|
|
79
|
+
declare function createDeepSeekClient(config: DeepSeekConfig): DeepSeekClient;
|
|
80
|
+
|
|
81
|
+
export { type ChatMessage as C, DeepSeekClient as D, type JsonChatResult as J, type ChatOptions as a, type ChatResult as b, type DeepSeekConfig as c, createDeepSeekClient as d };
|
package/dist/index.cjs
CHANGED
|
@@ -1043,17 +1043,30 @@ function detectSpatial(text, geometryColumn = "geom") {
|
|
|
1043
1043
|
if (/公里|km|千米/.test(nearby[2])) radius *= 1e3;
|
|
1044
1044
|
return { relation: "dwithin", radius, geometryColumn };
|
|
1045
1045
|
}
|
|
1046
|
+
const point = detectReferencePoint(text);
|
|
1046
1047
|
if (/缓冲区|缓冲|范围内|区域.{0,2}内/.test(text)) {
|
|
1047
|
-
return { relation: "buffer", geometryColumn };
|
|
1048
|
+
return { relation: "buffer", point, geometryColumn };
|
|
1048
1049
|
}
|
|
1049
|
-
if (
|
|
1050
|
-
return { relation: "
|
|
1050
|
+
if (/包含|覆盖|涵盖|包围|圈住/.test(text)) {
|
|
1051
|
+
return { relation: "contains", point, geometryColumn };
|
|
1052
|
+
}
|
|
1053
|
+
if (/在.{0,2}内|内部|以内|之中/.test(text)) {
|
|
1054
|
+
return { relation: "within", point, geometryColumn };
|
|
1051
1055
|
}
|
|
1052
1056
|
if (/相交|叠加|重叠|交叉/.test(text)) {
|
|
1053
|
-
return { relation: "intersects", geometryColumn };
|
|
1057
|
+
return { relation: "intersects", point, geometryColumn };
|
|
1054
1058
|
}
|
|
1055
1059
|
return null;
|
|
1056
1060
|
}
|
|
1061
|
+
function detectReferencePoint(text) {
|
|
1062
|
+
const m = text.match(/([1-2]?\d{2,3}(?:\.\d+)?)\s*[,,]\s*(\d{1,3}(?:\.\d+)?)/);
|
|
1063
|
+
if (m) {
|
|
1064
|
+
const lng = parseFloat(m[1]);
|
|
1065
|
+
const lat = parseFloat(m[2]);
|
|
1066
|
+
if (lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90) return [lng, lat];
|
|
1067
|
+
}
|
|
1068
|
+
return void 0;
|
|
1069
|
+
}
|
|
1057
1070
|
function quoteValue(v) {
|
|
1058
1071
|
return typeof v === "string" ? `'${v.replace(/'/g, "''")}'` : String(v);
|
|
1059
1072
|
}
|
|
@@ -1063,14 +1076,26 @@ function buildWhere(conditions, spatial) {
|
|
|
1063
1076
|
parts.push(`${c.field} ${c.operator} ${quoteValue(c.value)}`);
|
|
1064
1077
|
}
|
|
1065
1078
|
if (spatial) {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1079
|
+
const col = spatial.geometryColumn;
|
|
1080
|
+
const refGeom = spatial.referenceColumn ? spatial.referenceColumn : spatial.point ? `ST_Buffer(ST_SetSRID(ST_MakePoint(${spatial.point[0]}, ${spatial.point[1]}), 4326), ${spatial.radius ?? 0})` : null;
|
|
1081
|
+
switch (spatial.relation) {
|
|
1082
|
+
case "dwithin":
|
|
1083
|
+
if (spatial.point && spatial.radius !== void 0) {
|
|
1084
|
+
parts.push(`ST_DWithin(${col}, ST_SetSRID(ST_MakePoint(${spatial.point[0]}, ${spatial.point[1]}), 4326), ${spatial.radius})`);
|
|
1085
|
+
}
|
|
1086
|
+
break;
|
|
1087
|
+
case "buffer":
|
|
1088
|
+
if (refGeom) parts.push(`ST_Intersects(${col}, ${refGeom})`);
|
|
1089
|
+
break;
|
|
1090
|
+
case "within":
|
|
1091
|
+
if (refGeom) parts.push(`ST_Within(${col}, ${refGeom})`);
|
|
1092
|
+
break;
|
|
1093
|
+
case "contains":
|
|
1094
|
+
if (refGeom) parts.push(`ST_Contains(${refGeom}, ${col})`);
|
|
1095
|
+
break;
|
|
1096
|
+
case "intersects":
|
|
1097
|
+
if (refGeom) parts.push(`ST_Intersects(${col}, ${refGeom})`);
|
|
1098
|
+
break;
|
|
1074
1099
|
}
|
|
1075
1100
|
}
|
|
1076
1101
|
return parts.length > 0 ? `WHERE ${parts.join(" AND ")}` : "";
|
|
@@ -1080,9 +1105,12 @@ function generatePostGISQuery(text, opts = {}) {
|
|
|
1080
1105
|
const geometryColumn = opts.geometryColumn ?? "geom";
|
|
1081
1106
|
const table = detectTable(text);
|
|
1082
1107
|
const spatial = detectSpatial(text, geometryColumn);
|
|
1083
|
-
if (spatial &&
|
|
1108
|
+
if (spatial && !spatial.point) {
|
|
1084
1109
|
spatial.point = center;
|
|
1085
1110
|
}
|
|
1111
|
+
if (spatial && spatial.relation !== "dwithin" && spatial.radius === void 0) {
|
|
1112
|
+
spatial.radius = 1e3;
|
|
1113
|
+
}
|
|
1086
1114
|
const conditions = [];
|
|
1087
1115
|
const field = detectField(text);
|
|
1088
1116
|
if (field) {
|
|
@@ -1450,6 +1478,168 @@ function createDeepSeekClient(config) {
|
|
|
1450
1478
|
return new DeepSeekClient(config);
|
|
1451
1479
|
}
|
|
1452
1480
|
|
|
1481
|
+
// src/llm/openaiCompatible.ts
|
|
1482
|
+
function normalizeError2(body, provider) {
|
|
1483
|
+
const err = new Error(body || `${provider} API \u8BF7\u6C42\u5931\u8D25`);
|
|
1484
|
+
err.name = `${provider}Error`;
|
|
1485
|
+
return err;
|
|
1486
|
+
}
|
|
1487
|
+
var OpenAICompatibleClient = class {
|
|
1488
|
+
apiKey;
|
|
1489
|
+
baseUrl;
|
|
1490
|
+
model;
|
|
1491
|
+
temperature;
|
|
1492
|
+
maxTokens;
|
|
1493
|
+
timeoutMs;
|
|
1494
|
+
retries;
|
|
1495
|
+
fetchImpl;
|
|
1496
|
+
authScheme;
|
|
1497
|
+
extraHeaders;
|
|
1498
|
+
providerLabel;
|
|
1499
|
+
constructor(config) {
|
|
1500
|
+
if (!config.apiKey) throw new Error("OpenAICompatibleClient: apiKey \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1501
|
+
if (!config.baseUrl) throw new Error("OpenAICompatibleClient: baseUrl \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1502
|
+
if (!config.model) throw new Error("OpenAICompatibleClient: model \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1503
|
+
this.apiKey = config.apiKey;
|
|
1504
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
1505
|
+
this.model = config.model;
|
|
1506
|
+
this.temperature = config.temperature ?? 0.3;
|
|
1507
|
+
this.maxTokens = config.maxTokens ?? 2048;
|
|
1508
|
+
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
1509
|
+
this.retries = config.retries ?? 2;
|
|
1510
|
+
this.fetchImpl = config.fetchImpl ?? (globalThis.fetch ?? fetch);
|
|
1511
|
+
this.authScheme = config.authScheme ?? "Bearer";
|
|
1512
|
+
this.extraHeaders = config.extraHeaders ?? {};
|
|
1513
|
+
this.providerLabel = (() => {
|
|
1514
|
+
try {
|
|
1515
|
+
return new URL(this.baseUrl).hostname || "LLM";
|
|
1516
|
+
} catch {
|
|
1517
|
+
return "LLM";
|
|
1518
|
+
}
|
|
1519
|
+
})();
|
|
1520
|
+
}
|
|
1521
|
+
async chat(messages, opts = {}) {
|
|
1522
|
+
return this.requestWithRetry(messages, opts);
|
|
1523
|
+
}
|
|
1524
|
+
async chatJson(messages) {
|
|
1525
|
+
const result = await this.chat(messages, { json: true });
|
|
1526
|
+
const raw = result.content.trim();
|
|
1527
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|
|
1528
|
+
let data;
|
|
1529
|
+
try {
|
|
1530
|
+
data = JSON.parse(cleaned);
|
|
1531
|
+
} catch {
|
|
1532
|
+
const match = cleaned.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
|
|
1533
|
+
if (match) {
|
|
1534
|
+
data = JSON.parse(match[0]);
|
|
1535
|
+
} else {
|
|
1536
|
+
throw new Error(`${this.providerLabel} \u8FD4\u56DE\u4E86\u975E JSON \u5185\u5BB9: ${raw.slice(0, 200)}`);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return { data, raw, model: result.model };
|
|
1540
|
+
}
|
|
1541
|
+
async requestWithRetry(messages, opts) {
|
|
1542
|
+
let lastError = null;
|
|
1543
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
1544
|
+
try {
|
|
1545
|
+
return await this.requestOnce(messages, opts);
|
|
1546
|
+
} catch (e) {
|
|
1547
|
+
lastError = e;
|
|
1548
|
+
if (attempt < this.retries) {
|
|
1549
|
+
await this.sleep(Math.min(1e3 * 2 ** attempt, 8e3));
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
throw lastError ?? new Error(`${this.providerLabel} \u8BF7\u6C42\u5931\u8D25`);
|
|
1554
|
+
}
|
|
1555
|
+
async requestOnce(messages, opts) {
|
|
1556
|
+
const controller = new AbortController();
|
|
1557
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1558
|
+
const body = {
|
|
1559
|
+
model: this.model,
|
|
1560
|
+
messages,
|
|
1561
|
+
temperature: this.temperature,
|
|
1562
|
+
max_tokens: this.maxTokens,
|
|
1563
|
+
stream: Boolean(opts.onChunk)
|
|
1564
|
+
};
|
|
1565
|
+
if (opts.json) {
|
|
1566
|
+
body.response_format = { type: "json_object" };
|
|
1567
|
+
}
|
|
1568
|
+
const headers = {
|
|
1569
|
+
"Content-Type": "application/json",
|
|
1570
|
+
...this.extraHeaders
|
|
1571
|
+
};
|
|
1572
|
+
if (this.authScheme === "Bearer") {
|
|
1573
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
1574
|
+
} else {
|
|
1575
|
+
headers["X-Api-Key"] = this.apiKey;
|
|
1576
|
+
}
|
|
1577
|
+
try {
|
|
1578
|
+
const res = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
1579
|
+
method: "POST",
|
|
1580
|
+
headers,
|
|
1581
|
+
body: JSON.stringify(body),
|
|
1582
|
+
signal: controller.signal
|
|
1583
|
+
});
|
|
1584
|
+
if (!res.ok) {
|
|
1585
|
+
const text = await res.text().catch(() => "");
|
|
1586
|
+
throw normalizeError2(`${this.providerLabel} HTTP ${res.status}: ${text}`, this.providerLabel);
|
|
1587
|
+
}
|
|
1588
|
+
if (opts.onChunk && res.body) {
|
|
1589
|
+
return this.parseStream(res.body, opts.onChunk);
|
|
1590
|
+
}
|
|
1591
|
+
const json = await res.json();
|
|
1592
|
+
const content = json.choices?.[0]?.message?.content ?? "";
|
|
1593
|
+
return {
|
|
1594
|
+
content,
|
|
1595
|
+
model: json.model ?? this.model,
|
|
1596
|
+
usage: json.usage ? {
|
|
1597
|
+
promptTokens: json.usage.prompt_tokens,
|
|
1598
|
+
completionTokens: json.usage.completion_tokens,
|
|
1599
|
+
totalTokens: json.usage.prompt_tokens + json.usage.completion_tokens
|
|
1600
|
+
} : void 0
|
|
1601
|
+
};
|
|
1602
|
+
} finally {
|
|
1603
|
+
clearTimeout(timer);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
async parseStream(body, onChunk) {
|
|
1607
|
+
const reader = body.getReader();
|
|
1608
|
+
const decoder = new TextDecoder();
|
|
1609
|
+
let content = "";
|
|
1610
|
+
let buffer = "";
|
|
1611
|
+
while (true) {
|
|
1612
|
+
const { done, value } = await reader.read();
|
|
1613
|
+
if (done) break;
|
|
1614
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1615
|
+
const lines = buffer.split("\n");
|
|
1616
|
+
buffer = lines.pop() ?? "";
|
|
1617
|
+
for (const line of lines) {
|
|
1618
|
+
const trimmed = line.trim();
|
|
1619
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1620
|
+
const data = trimmed.slice(5).trim();
|
|
1621
|
+
if (data === "[DONE]") continue;
|
|
1622
|
+
try {
|
|
1623
|
+
const json = JSON.parse(data);
|
|
1624
|
+
const delta = json.choices?.[0]?.delta?.content ?? "";
|
|
1625
|
+
if (delta) {
|
|
1626
|
+
content += delta;
|
|
1627
|
+
onChunk(delta);
|
|
1628
|
+
}
|
|
1629
|
+
} catch {
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return { content, model: this.model };
|
|
1634
|
+
}
|
|
1635
|
+
sleep(ms) {
|
|
1636
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
function createOpenAICompatibleClient(config) {
|
|
1640
|
+
return new OpenAICompatibleClient(config);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1453
1643
|
exports.CARRIER_STYLES = CARRIER_STYLES;
|
|
1454
1644
|
exports.CHINA_BOUNDS = CHINA_BOUNDS;
|
|
1455
1645
|
exports.COLOR_NAMES = COLOR_NAMES;
|
|
@@ -1461,6 +1651,7 @@ exports.LOCAL_GEO_DB = LOCAL_GEO_DB;
|
|
|
1461
1651
|
exports.LlmMapCopilot = LlmMapCopilot;
|
|
1462
1652
|
exports.LlmNlpg = LlmNlpg;
|
|
1463
1653
|
exports.MapCopilot = MapCopilot;
|
|
1654
|
+
exports.OpenAICompatibleClient = OpenAICompatibleClient;
|
|
1464
1655
|
exports.PLACE_COORDINATES = PLACE_COORDINATES;
|
|
1465
1656
|
exports.adjustBrightness = adjustBrightness;
|
|
1466
1657
|
exports.analyzePerformance = analyzePerformance;
|
|
@@ -1468,6 +1659,7 @@ exports.analyzeTiles = analyzeTiles;
|
|
|
1468
1659
|
exports.batchGeocode = batchGeocode;
|
|
1469
1660
|
exports.classifyIntent = classifyIntent;
|
|
1470
1661
|
exports.createDeepSeekClient = createDeepSeekClient;
|
|
1662
|
+
exports.createOpenAICompatibleClient = createOpenAICompatibleClient;
|
|
1471
1663
|
exports.detectCRS = detectCRS;
|
|
1472
1664
|
exports.detectField = detectField;
|
|
1473
1665
|
exports.detectHeaders = detectHeaders;
|