@xom11/whiteboard 0.28.0 → 0.29.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.
Files changed (39) hide show
  1. package/dist/ai.d.mts +236 -295
  2. package/dist/ai.d.ts +236 -295
  3. package/dist/ai.js +6019 -7612
  4. package/dist/ai.js.map +1 -1
  5. package/dist/ai.mjs +4804 -5457
  6. package/dist/ai.mjs.map +1 -1
  7. package/dist/catalog.json +2 -2
  8. package/dist/{chunk-AJAHD35N.mjs → chunk-E6EDOPGT.mjs} +3 -105
  9. package/dist/chunk-E6EDOPGT.mjs.map +1 -0
  10. package/dist/{chunk-QCZVFEN4.mjs → chunk-GEC2D2EQ.mjs} +3 -3
  11. package/dist/{chunk-QCZVFEN4.mjs.map → chunk-GEC2D2EQ.mjs.map} +1 -1
  12. package/dist/geometry-2d.d.mts +1 -2
  13. package/dist/geometry-2d.d.ts +1 -2
  14. package/dist/geometry-2d.js +888 -3623
  15. package/dist/geometry-2d.js.map +1 -1
  16. package/dist/geometry-2d.mjs +1 -1
  17. package/dist/geometry-3d.d.mts +1 -2
  18. package/dist/geometry-3d.d.ts +1 -2
  19. package/dist/graph-2d.d.mts +1 -2
  20. package/dist/graph-2d.d.ts +1 -2
  21. package/dist/{host-4P766V4J.mjs → host-HKMZSCIT.mjs} +54 -313
  22. package/dist/host-HKMZSCIT.mjs.map +1 -0
  23. package/dist/index.d.mts +2 -4
  24. package/dist/index.d.ts +2 -4
  25. package/dist/index.js +880 -3619
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +3 -4
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/latex.d.mts +1 -2
  30. package/dist/latex.d.ts +1 -2
  31. package/dist/{types-BHYC2Fiw.d.mts → types-C3FjpoUi.d.mts} +1 -232
  32. package/dist/{types-BHYC2Fiw.d.ts → types-C3FjpoUi.d.ts} +1 -232
  33. package/package.json +1 -9
  34. package/dist/chunk-AJAHD35N.mjs.map +0 -1
  35. package/dist/chunk-T3SOHYB2.mjs +0 -851
  36. package/dist/chunk-T3SOHYB2.mjs.map +0 -1
  37. package/dist/handleExtractProblem-C-U5KluK.d.mts +0 -158
  38. package/dist/handleExtractProblem-C-U5KluK.d.ts +0 -158
  39. package/dist/host-4P766V4J.mjs.map +0 -1
@@ -1,851 +0,0 @@
1
- "use client";
2
- import Anthropic from '@anthropic-ai/sdk';
3
- import { z } from 'zod';
4
- import { zodToJsonSchema } from 'zod-to-json-schema';
5
-
6
- // src/stamps/geometry-2d/ai/providers/anthropic.ts
7
- var TOOL_NAME = "emit_figure_envelope";
8
- var VISION_TOOL_NAME = "extract_problem_envelope";
9
- var AnthropicProvider = class {
10
- constructor(opts) {
11
- this.opts = opts;
12
- this.name = "anthropic";
13
- this.defaultModel = "claude-opus-4-7";
14
- if (!opts.apiKey) throw new Error("AnthropicProvider: apiKey b\u1EAFt bu\u1ED9c");
15
- }
16
- async call(req) {
17
- const enableCaching = this.opts.enableCaching !== false;
18
- const systemBlock = enableCaching ? { type: "text", text: req.systemPrompt, cache_control: { type: "ephemeral" } } : { type: "text", text: req.systemPrompt };
19
- const tool = {
20
- name: TOOL_NAME,
21
- description: 'Emit envelope JSON cho ph\xE9p v\u1EBD h\xECnh ho\u1EB7c t\u1EEB ch\u1ED1i. decision="build" k\xE8m figure (DSL h\xECnh h\u1ECDc); decision="refuse" k\xE8m reason.',
22
- input_schema: req.schema
23
- };
24
- const client = new Anthropic({ apiKey: this.opts.apiKey });
25
- let resp;
26
- try {
27
- resp = await client.messages.create(
28
- {
29
- model: req.model,
30
- max_tokens: req.maxTokens,
31
- system: [systemBlock],
32
- tools: [tool],
33
- tool_choice: { type: "tool", name: TOOL_NAME },
34
- messages: [{ role: "user", content: req.userPrompt }]
35
- },
36
- req.signal ? { signal: req.signal } : void 0
37
- );
38
- } catch (e) {
39
- const err = e;
40
- return {
41
- kind: "error",
42
- message: err.message ?? "L\u1ED7i g\u1ECDi Anthropic API",
43
- ...err.status !== void 0 ? { status: err.status } : {}
44
- };
45
- }
46
- const usage = toUsage(resp.usage);
47
- const toolUse = resp.content.find((c) => c.type === "tool_use");
48
- if (!toolUse || toolUse.type !== "tool_use") {
49
- return {
50
- kind: "error",
51
- message: "Claude kh\xF4ng g\u1ECDi tool. stop_reason=" + resp.stop_reason
52
- };
53
- }
54
- if (toolUse.name !== TOOL_NAME) {
55
- return {
56
- kind: "error",
57
- message: `Tool kh\xF4ng x\xE1c \u0111\u1ECBnh: "${toolUse.name}"`
58
- };
59
- }
60
- return { kind: "json", data: toolUse.input, usage };
61
- }
62
- async extractText(req) {
63
- const model = req.model ?? this.defaultModel;
64
- const enableCaching = this.opts.enableCaching !== false;
65
- const systemBlock = enableCaching ? { type: "text", text: req.systemPrompt, cache_control: { type: "ephemeral" } } : { type: "text", text: req.systemPrompt };
66
- const tool = {
67
- name: VISION_TOOL_NAME,
68
- description: "Tr\xEDch \u0111\u1EC1 b\xE0i h\xECnh h\u1ECDc t\u1EEB \u1EA3nh, ho\u1EB7c t\u1EEB ch\u1ED1i n\u1EBFu kh\xF4ng ph\u1EA3i \u0111\u1EC1 to\xE1n.",
69
- input_schema: req.schema
70
- };
71
- const imageBlocks = req.images.map((img) => ({
72
- type: "image",
73
- source: {
74
- type: "base64",
75
- media_type: img.mediaType,
76
- data: img.base64
77
- }
78
- }));
79
- const client = new Anthropic({ apiKey: this.opts.apiKey });
80
- let resp;
81
- try {
82
- resp = await client.messages.create(
83
- {
84
- model,
85
- max_tokens: req.maxTokens,
86
- system: [systemBlock],
87
- tools: [tool],
88
- tool_choice: { type: "tool", name: VISION_TOOL_NAME },
89
- messages: [
90
- {
91
- role: "user",
92
- content: [...imageBlocks, { type: "text", text: req.userPrompt }]
93
- }
94
- ]
95
- },
96
- req.signal ? { signal: req.signal } : void 0
97
- );
98
- } catch (e) {
99
- const err = e;
100
- return {
101
- kind: "error",
102
- message: err.message ?? "L\u1ED7i g\u1ECDi Anthropic Vision API",
103
- ...err.status !== void 0 ? { status: err.status } : {}
104
- };
105
- }
106
- const usage = toUsage(resp.usage);
107
- const toolUse = resp.content.find((c) => c.type === "tool_use");
108
- if (!toolUse || toolUse.type !== "tool_use") {
109
- return { kind: "error", message: "Claude kh\xF4ng g\u1ECDi vision tool. stop_reason=" + resp.stop_reason };
110
- }
111
- if (toolUse.name !== VISION_TOOL_NAME) {
112
- return { kind: "error", message: `Claude g\u1ECDi sai tool: ${toolUse.name}` };
113
- }
114
- return { kind: "json", data: toolUse.input, usage };
115
- }
116
- };
117
- function toUsage(u) {
118
- return {
119
- inputTokens: u.input_tokens,
120
- outputTokens: u.output_tokens,
121
- cacheReadTokens: u.cache_read_input_tokens ?? 0,
122
- cacheCreationTokens: u.cache_creation_input_tokens ?? 0
123
- };
124
- }
125
-
126
- // src/stamps/geometry-2d/ai/providers/ollama.ts
127
- var DEFAULT_BASE_URL = "http://localhost:11434";
128
- var DEFAULT_MODEL = "gemma3:4b";
129
- var OllamaProvider = class {
130
- constructor(opts = {}) {
131
- this.name = "ollama";
132
- this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
133
- this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL;
134
- this.fetchImpl = opts.fetchImpl ?? null;
135
- }
136
- resolveFetch() {
137
- if (this.fetchImpl) return this.fetchImpl;
138
- if (typeof fetch === "undefined") {
139
- throw new Error(
140
- "OllamaProvider: global `fetch` kh\xF4ng kh\u1EA3 d\u1EE5ng. Truy\u1EC1n `fetchImpl` qua constructor ho\u1EB7c ch\u1EA1y \u1EDF Node 18+ / browser."
141
- );
142
- }
143
- return fetch;
144
- }
145
- async call(req) {
146
- const body = {
147
- model: req.model,
148
- messages: [
149
- { role: "system", content: req.systemPrompt },
150
- { role: "user", content: req.userPrompt }
151
- ],
152
- format: req.schema,
153
- stream: true,
154
- options: { num_predict: req.maxTokens, temperature: 0.2 }
155
- };
156
- let doFetch;
157
- try {
158
- doFetch = this.resolveFetch();
159
- } catch (e) {
160
- const err = e;
161
- return { kind: "error", message: err.message ?? "fetch kh\xF4ng kh\u1EA3 d\u1EE5ng" };
162
- }
163
- let resp;
164
- try {
165
- resp = await doFetch(`${this.baseUrl}/api/chat`, {
166
- method: "POST",
167
- headers: { "content-type": "application/json" },
168
- body: JSON.stringify(body),
169
- signal: req.signal
170
- });
171
- } catch (e) {
172
- const err = e;
173
- return {
174
- kind: "error",
175
- message: err.message ?? `Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c Ollama \u1EDF ${this.baseUrl}`
176
- };
177
- }
178
- if (!resp.ok || !resp.body) {
179
- let detail = "";
180
- try {
181
- detail = await resp.text();
182
- } catch {
183
- }
184
- return {
185
- kind: "error",
186
- message: `Ollama HTTP ${resp.status}: ${detail || resp.statusText}`,
187
- status: resp.status
188
- };
189
- }
190
- const reader = resp.body.getReader();
191
- const decoder = new TextDecoder();
192
- let buffer = "";
193
- let content = "";
194
- let promptEvalCount = 0;
195
- let evalCount = 0;
196
- while (true) {
197
- const { value, done } = await reader.read();
198
- if (done) break;
199
- buffer += decoder.decode(value, { stream: true });
200
- let nl;
201
- while ((nl = buffer.indexOf("\n")) !== -1) {
202
- const line = buffer.slice(0, nl).trim();
203
- buffer = buffer.slice(nl + 1);
204
- if (!line) continue;
205
- try {
206
- const chunk = JSON.parse(line);
207
- if (chunk.message?.content) {
208
- content += chunk.message.content;
209
- if (req.onToken) {
210
- try {
211
- req.onToken(chunk.message.content);
212
- } catch {
213
- }
214
- }
215
- }
216
- if (chunk.done) {
217
- promptEvalCount = chunk.prompt_eval_count ?? promptEvalCount;
218
- evalCount = chunk.eval_count ?? evalCount;
219
- }
220
- } catch {
221
- }
222
- }
223
- }
224
- const trimmed = content.trim();
225
- if (!trimmed) return { kind: "error", message: "Ollama tr\u1EA3 message.content r\u1ED7ng" };
226
- let data;
227
- try {
228
- data = JSON.parse(trimmed);
229
- } catch (e) {
230
- const err = e;
231
- return {
232
- kind: "error",
233
- message: "Ollama content kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: " + (err.message ?? "?")
234
- };
235
- }
236
- return {
237
- kind: "json",
238
- data,
239
- usage: {
240
- inputTokens: promptEvalCount,
241
- outputTokens: evalCount,
242
- cacheReadTokens: 0,
243
- cacheCreationTokens: 0
244
- }
245
- };
246
- }
247
- // Vision: gửi ảnh qua images[] field trong message (Ollama multimodal API).
248
- // Model cần hỗ trợ vision (gemma3, llava, ...). Output vẫn là JSON envelope.
249
- async extractText(req) {
250
- const model = req.model ?? this.defaultModel;
251
- const body = {
252
- model,
253
- messages: [
254
- { role: "system", content: req.systemPrompt },
255
- {
256
- role: "user",
257
- content: req.userPrompt,
258
- images: req.images.map((i) => i.base64)
259
- }
260
- ],
261
- format: req.schema,
262
- stream: false,
263
- options: { num_predict: req.maxTokens, temperature: 0.2 }
264
- };
265
- let doFetch;
266
- try {
267
- doFetch = this.resolveFetch();
268
- } catch (e) {
269
- return { kind: "error", message: e.message ?? "fetch kh\xF4ng kh\u1EA3 d\u1EE5ng" };
270
- }
271
- let resp;
272
- try {
273
- resp = await doFetch(`${this.baseUrl}/api/chat`, {
274
- method: "POST",
275
- headers: { "content-type": "application/json" },
276
- body: JSON.stringify(body),
277
- signal: req.signal
278
- });
279
- } catch (e) {
280
- return {
281
- kind: "error",
282
- message: e.message ?? `Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c Ollama \u1EDF ${this.baseUrl}`
283
- };
284
- }
285
- if (!resp.ok) {
286
- let detail = "";
287
- try {
288
- detail = await resp.text();
289
- } catch {
290
- }
291
- return {
292
- kind: "error",
293
- message: `Ollama Vision HTTP ${resp.status}: ${detail || resp.statusText}`,
294
- status: resp.status
295
- };
296
- }
297
- let json;
298
- try {
299
- json = await resp.json();
300
- } catch (e) {
301
- return { kind: "error", message: "Ollama vision response kh\xF4ng ph\u1EA3i JSON: " + (e.message ?? "?") };
302
- }
303
- const content = json.message?.content?.trim();
304
- if (!content) return { kind: "error", message: "Ollama vision tr\u1EA3 content r\u1ED7ng" };
305
- let data;
306
- try {
307
- data = JSON.parse(content);
308
- } catch (e) {
309
- return { kind: "error", message: "Ollama vision content kh\xF4ng parse JSON: " + (e.message ?? "?") };
310
- }
311
- const usage = {
312
- inputTokens: json.prompt_eval_count ?? 0,
313
- outputTokens: json.eval_count ?? 0,
314
- cacheReadTokens: 0,
315
- cacheCreationTokens: 0
316
- };
317
- return { kind: "json", data, usage };
318
- }
319
- };
320
-
321
- // src/stamps/geometry-2d/ai/providers/claude-agent-sdk.ts
322
- var DEFAULT_MODEL2 = "claude-sonnet-4-6";
323
- var SCHEMA_CACHE = /* @__PURE__ */ new WeakMap();
324
- function memoSchemaJson(schema) {
325
- let s = SCHEMA_CACHE.get(schema);
326
- if (s) return s;
327
- s = JSON.stringify(schema, null, 2);
328
- SCHEMA_CACHE.set(schema, s);
329
- return s;
330
- }
331
- var ClaudeAgentSdkProvider = class {
332
- constructor(opts = {}) {
333
- this.name = "claude-agent-sdk";
334
- this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL2;
335
- this.oauthToken = opts.oauthToken;
336
- this.queryImpl = opts.queryImpl ?? null;
337
- }
338
- async resolveQuery() {
339
- if (this.queryImpl) return this.queryImpl;
340
- const mod = await import('@anthropic-ai/claude-agent-sdk');
341
- return mod.query;
342
- }
343
- async call(req) {
344
- if (this.oauthToken) {
345
- process.env.CLAUDE_CODE_OAUTH_TOKEN = this.oauthToken;
346
- }
347
- let query;
348
- try {
349
- query = await this.resolveQuery();
350
- } catch (e) {
351
- return {
352
- kind: "error",
353
- message: "ClaudeAgentSdkProvider: SDK kh\xF4ng kh\u1EA3 d\u1EE5ng. " + (e.message ?? "")
354
- };
355
- }
356
- const schemaText = memoSchemaJson(req.schema);
357
- const constrainedSystem = `${req.systemPrompt}
358
-
359
- QUAN TR\u1ECCNG: Output PH\u1EA2I l\xE0 valid JSON \u0111\xFAng schema sau. KH\xD4NG markdown wrapper, KH\xD4NG prose gi\u1EA3i th\xEDch, CH\u1EC8 raw JSON:
360
-
361
- ${schemaText}`;
362
- let assistantText = "";
363
- let usageInput = 0;
364
- let usageOutput = 0;
365
- let cacheRead = 0;
366
- let cacheCreation = 0;
367
- try {
368
- for await (const msg of query({
369
- prompt: req.userPrompt,
370
- options: {
371
- systemPrompt: constrainedSystem,
372
- allowedTools: [],
373
- model: req.model ?? this.defaultModel,
374
- ...req.signal ? { abortSignal: req.signal } : {}
375
- }
376
- })) {
377
- if (msg.type === "assistant") {
378
- const message = msg.message;
379
- for (const block of message.content) {
380
- const b = block;
381
- if (b.type === "text" && typeof b.text === "string") {
382
- assistantText += b.text;
383
- if (req.onToken) {
384
- try {
385
- req.onToken(b.text);
386
- } catch {
387
- }
388
- }
389
- }
390
- }
391
- } else if (msg.type === "result") {
392
- const r = msg;
393
- if (r.subtype && r.subtype !== "success") {
394
- return {
395
- kind: "error",
396
- message: `ClaudeAgentSdkProvider: result subtype=${r.subtype}`
397
- };
398
- }
399
- if (r.usage) {
400
- usageInput = r.usage.input_tokens ?? 0;
401
- usageOutput = r.usage.output_tokens ?? 0;
402
- cacheRead = r.usage.cache_read_input_tokens ?? 0;
403
- cacheCreation = r.usage.cache_creation_input_tokens ?? 0;
404
- }
405
- }
406
- }
407
- } catch (e) {
408
- return {
409
- kind: "error",
410
- message: "ClaudeAgentSdkProvider: query() throw: " + (e.message ?? "?")
411
- };
412
- }
413
- if (!assistantText.trim()) {
414
- return {
415
- kind: "error",
416
- message: "ClaudeAgentSdkProvider: assistant tr\u1EA3 response r\u1ED7ng."
417
- };
418
- }
419
- let cleaned = assistantText.trim();
420
- cleaned = cleaned.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?\s*```\s*$/, "");
421
- let data;
422
- try {
423
- data = JSON.parse(cleaned);
424
- } catch (e) {
425
- return {
426
- kind: "error",
427
- message: "ClaudeAgentSdkProvider: output kh\xF4ng parse JSON: " + (e.message ?? "?") + ". Output preview: " + cleaned.slice(0, 200)
428
- };
429
- }
430
- return {
431
- kind: "json",
432
- data,
433
- usage: {
434
- inputTokens: usageInput,
435
- outputTokens: usageOutput,
436
- cacheReadTokens: cacheRead,
437
- cacheCreationTokens: cacheCreation
438
- }
439
- };
440
- }
441
- };
442
-
443
- // src/stamps/geometry-2d/ai/providers/claude-cli.ts
444
- var DEFAULT_BIN = "claude";
445
- var DEFAULT_MODEL3 = "claude-sonnet-4-6";
446
- var DEFAULT_MAX_BUDGET_USD = 0.5;
447
- var ClaudeCliProvider = class {
448
- constructor(opts = {}) {
449
- this.name = "claude-cli";
450
- this.bin = opts.bin ?? DEFAULT_BIN;
451
- this.defaultModel = opts.defaultModel ?? DEFAULT_MODEL3;
452
- this.maxBudgetUsd = opts.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
453
- this.spawnImpl = opts.spawnImpl ?? null;
454
- }
455
- async resolveSpawn() {
456
- if (this.spawnImpl) return this.spawnImpl;
457
- const mod = await import('child_process');
458
- return mod.spawn;
459
- }
460
- async call(req) {
461
- let spawn;
462
- try {
463
- spawn = await this.resolveSpawn();
464
- } catch (e) {
465
- return {
466
- kind: "error",
467
- message: "ClaudeCliProvider: node:child_process kh\xF4ng kh\u1EA3 d\u1EE5ng (ch\u1EC9 ch\u1EA1y \u0111\u01B0\u1EE3c \u1EDF Node env). " + (e.message ?? "")
468
- };
469
- }
470
- const args = [
471
- "--print",
472
- "--output-format",
473
- "json",
474
- "--json-schema",
475
- JSON.stringify(req.schema),
476
- "--append-system-prompt",
477
- req.systemPrompt,
478
- "--tools",
479
- "",
480
- "--model",
481
- req.model,
482
- "--max-budget-usd",
483
- String(this.maxBudgetUsd)
484
- ];
485
- return new Promise((resolve) => {
486
- let child;
487
- try {
488
- child = spawn(this.bin, args, { stdio: ["pipe", "pipe", "pipe"] });
489
- } catch (e) {
490
- resolve({
491
- kind: "error",
492
- message: `ClaudeCliProvider: spawn '${this.bin}' th\u1EA5t b\u1EA1i. ` + (e.message ?? "Ki\u1EC3m tra claude CLI \u0111\xE3 c\xE0i ch\u01B0a.")
493
- });
494
- return;
495
- }
496
- let stdout = "";
497
- let stderr = "";
498
- let settled = false;
499
- const settle = (out) => {
500
- if (settled) return;
501
- settled = true;
502
- resolve(out);
503
- };
504
- const onAbort = () => {
505
- child.kill("SIGTERM");
506
- settle({ kind: "error", message: "ClaudeCliProvider: aborted" });
507
- };
508
- if (req.signal) {
509
- if (req.signal.aborted) {
510
- onAbort();
511
- return;
512
- }
513
- req.signal.addEventListener("abort", onAbort, { once: true });
514
- }
515
- child.stdout?.on("data", (chunk) => {
516
- stdout += chunk.toString("utf8");
517
- });
518
- child.stderr?.on("data", (chunk) => {
519
- stderr += chunk.toString("utf8");
520
- });
521
- child.on("error", (e) => {
522
- settle({
523
- kind: "error",
524
- message: `ClaudeCliProvider: subprocess error: ${e.message}`
525
- });
526
- });
527
- child.on("close", (code) => {
528
- if (settled) return;
529
- if (code !== 0) {
530
- settle({
531
- kind: "error",
532
- message: `ClaudeCliProvider: exit code ${code}. stderr: ${stderr.trim() || "(empty)"}`,
533
- ...typeof code === "number" ? { status: code } : {}
534
- });
535
- return;
536
- }
537
- let env;
538
- try {
539
- env = JSON.parse(stdout.trim());
540
- } catch (e) {
541
- settle({
542
- kind: "error",
543
- message: "ClaudeCliProvider: stdout kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: " + (e.message ?? "?")
544
- });
545
- return;
546
- }
547
- if (env.is_error) {
548
- settle({
549
- kind: "error",
550
- message: `ClaudeCliProvider: CLI b\xE1o l\u1ED7i (subtype=${env.subtype}, api_status=${env.api_error_status ?? "n/a"})`
551
- });
552
- return;
553
- }
554
- if (env.structured_output === void 0 || env.structured_output === null) {
555
- settle({
556
- kind: "error",
557
- message: `ClaudeCliProvider: thi\u1EBFu structured_output trong response. result="${(env.result ?? "").slice(0, 200)}"`
558
- });
559
- return;
560
- }
561
- const usage = toUsage2(env.usage);
562
- settle({ kind: "json", data: env.structured_output, usage });
563
- });
564
- try {
565
- child.stdin?.write(req.userPrompt);
566
- child.stdin?.end();
567
- } catch (e) {
568
- settle({
569
- kind: "error",
570
- message: "ClaudeCliProvider: ghi stdin th\u1EA5t b\u1EA1i: " + (e.message ?? "?")
571
- });
572
- }
573
- });
574
- }
575
- };
576
- function toUsage2(u) {
577
- return {
578
- inputTokens: u?.input_tokens ?? 0,
579
- outputTokens: u?.output_tokens ?? 0,
580
- cacheReadTokens: u?.cache_read_input_tokens ?? 0,
581
- cacheCreationTokens: u?.cache_creation_input_tokens ?? 0
582
- };
583
- }
584
-
585
- // src/stamps/geometry-2d/ai/providers/index.ts
586
- function selectProvider(opts = {}) {
587
- if (opts.provider) return opts.provider;
588
- if (opts.apiKey) {
589
- return new AnthropicProvider({
590
- apiKey: opts.apiKey,
591
- enableCaching: opts.enableCaching
592
- });
593
- }
594
- const env = opts.env ?? readEnv();
595
- const wanted = (env.WHITEBOARD_AI_PROVIDER ?? "claude-agent-sdk").toLowerCase();
596
- if (wanted === "anthropic") {
597
- const key = env.ANTHROPIC_API_KEY;
598
- if (!key) {
599
- throw new Error(
600
- "selectProvider: WHITEBOARD_AI_PROVIDER=anthropic nh\u01B0ng thi\u1EBFu env ANTHROPIC_API_KEY"
601
- );
602
- }
603
- return new AnthropicProvider({ apiKey: key, enableCaching: opts.enableCaching });
604
- }
605
- if (wanted === "claude-cli") {
606
- const budgetEnv = env.CLAUDE_CLI_MAX_BUDGET_USD;
607
- const budgetNum = budgetEnv !== void 0 ? Number(budgetEnv) : void 0;
608
- return new ClaudeCliProvider({
609
- bin: opts.claudeCliBin ?? env.CLAUDE_CLI_BIN,
610
- defaultModel: opts.claudeCliDefaultModel ?? env.CLAUDE_CLI_MODEL,
611
- maxBudgetUsd: opts.claudeCliMaxBudgetUsd ?? (budgetNum !== void 0 && Number.isFinite(budgetNum) ? budgetNum : void 0)
612
- });
613
- }
614
- if (wanted === "claude-agent-sdk") {
615
- return new ClaudeAgentSdkProvider({
616
- oauthToken: opts.claudeAgentSdkOauthToken ?? env.CLAUDE_CODE_OAUTH_TOKEN,
617
- defaultModel: opts.claudeAgentSdkDefaultModel ?? env.CLAUDE_AGENT_SDK_MODEL
618
- });
619
- }
620
- if (wanted === "ollama") {
621
- return new OllamaProvider({
622
- baseUrl: opts.ollamaBaseUrl ?? env.OLLAMA_BASE_URL,
623
- defaultModel: opts.ollamaDefaultModel ?? env.OLLAMA_DEFAULT_MODEL
624
- });
625
- }
626
- throw new Error(
627
- `selectProvider: WHITEBOARD_AI_PROVIDER="${wanted}" kh\xF4ng h\u1EE3p l\u1EC7 (anthropic|claude-cli|claude-agent-sdk|ollama)`
628
- );
629
- }
630
- function readEnv() {
631
- if (typeof process !== "undefined" && process.env) {
632
- return process.env;
633
- }
634
- return {};
635
- }
636
- var VisionEnvelopeZ = z.object({
637
- decision: z.enum(["extract", "refuse"]),
638
- text: z.string().optional(),
639
- confidence: z.enum(["high", "low"]).optional(),
640
- reason: z.string().optional()
641
- }).refine(
642
- (e) => e.decision === "extract" ? e.text != null && e.text.length > 0 : e.reason != null && e.reason.length > 0,
643
- { message: "extract c\u1EA7n text kh\xF4ng r\u1ED7ng; refuse c\u1EA7n reason kh\xF4ng r\u1ED7ng" }
644
- );
645
- function visionEnvelopeJsonSchema() {
646
- return zodToJsonSchema(VisionEnvelopeZ, {
647
- $refStrategy: "none",
648
- target: "jsonSchema7"
649
- });
650
- }
651
-
652
- // src/stamps/geometry-2d/ai/vision/prompt.ts
653
- function buildVisionSystemPrompt() {
654
- return [
655
- "B\u1EA1n l\xE0 OCR chuy\xEAn \u0111\u1ECDc \u0111\u1EC1 to\xE1n h\xECnh h\u1ECDc ti\u1EBFng Vi\u1EC7t t\u1EEB \u1EA3nh.",
656
- "",
657
- "NHI\u1EC6M V\u1EE4:",
658
- "1. \u0110\u1ECDc text trong \u1EA3nh, tr\u1EA3 v\u1EC1 ph\u1EA7n \u0110\u1EC0 B\xC0I (l\u1EDDi v\u0103n + c\xF4ng th\u1EE9c inline).",
659
- "2. GI\u1EEE NGUY\xCAN c\xE1c k\xFD hi\u1EC7u to\xE1n Unicode: \u0394 \u22A5 \u2225 \xB0 \u2299 \u03C0 \u2192 \u2264 \u2265 \u2208 \u2209 \u2229 \u222A.",
660
- "3. B\u1ECE QUA h\xECnh v\u1EBD minh ho\u1EA1 \u2014 ch\u1EC9 tr\u1EA3 ph\u1EA7n text.",
661
- '4. N\u1EBFu \u1EA3nh KH\xD4NG ph\u1EA3i \u0111\u1EC1 to\xE1n h\xECnh h\u1ECDc (vd: v\u0103n h\u1ECDc, \u1EA3nh \u0111\u1EDDi th\u01B0\u1EDDng, code, c\xF4ng th\u1EE9c kh\xF4ng li\xEAn quan): decision="refuse" v\u1EDBi reason c\u1EE5 th\u1EC3 b\u1EB1ng ti\u1EBFng Vi\u1EC7t.',
662
- "5. \u0110\xE1nh gi\xE1 confidence:",
663
- ' - "high": \u2265 80% k\xFD t\u1EF1 \u0111\u1ECDc r\xF5 r\xE0ng, kh\xF4ng nghi ng\u1EDD.',
664
- ' - "low": \u1EA3nh m\u1EDD, c\xF3 ch\u1EEF kh\xF4ng ch\u1EAFc ch\u1EAFn, ho\u1EB7c < 80% k\xFD t\u1EF1 confident.',
665
- "",
666
- "OUTPUT: JSON theo schema sau, kh\xF4ng markdown, kh\xF4ng gi\u1EA3i th\xEDch th\xEAm.",
667
- ' { "decision": "extract", "text": "...", "confidence": "high"|"low" }',
668
- ' { "decision": "refuse", "reason": "..." }',
669
- "",
670
- "V\xCD D\u1EE4 extract success:",
671
- ' { "decision": "extract", "text": "Cho tam gi\xE1c ABC vu\xF4ng t\u1EA1i A. K\u1EBB \u0111\u01B0\u1EDDng cao AH \u22A5 BC. Ch\u1EE9ng minh AH\xB2 = BH \xB7 CH.", "confidence": "high" }',
672
- "",
673
- "V\xCD D\u1EE4 refuse:",
674
- ' { "decision": "refuse", "reason": "\u1EA2nh kh\xF4ng ph\u1EA3i \u0111\u1EC1 to\xE1n \u2014 \u0111\xE2y l\xE0 m\u1ED9t \u0111o\u1EA1n v\u0103n v\u1EC1 Truy\u1EC7n Ki\u1EC1u." }'
675
- ].join("\n");
676
- }
677
- var VISION_USER_PROMPT = "\u0110\u1ECDc \u0111\u1EC1 b\xE0i h\xECnh h\u1ECDc trong \u1EA3nh sau.";
678
-
679
- // src/stamps/geometry-2d/ai/vision/tesseract.ts
680
- var DEFAULT_LANG = "vie+eng";
681
- async function runTesseractOcr(image, opts = {}) {
682
- if (opts.signal?.aborted) {
683
- const err = new Error("Tesseract OCR aborted");
684
- err.name = "AbortError";
685
- throw err;
686
- }
687
- const { createWorker } = await import('tesseract.js');
688
- const lang = opts.lang ?? DEFAULT_LANG;
689
- const worker = await createWorker(lang);
690
- try {
691
- const dataUrl = `data:${image.mediaType};base64,${image.base64}`;
692
- const { data } = await worker.recognize(dataUrl);
693
- return { text: data.text, confidence: data.confidence };
694
- } finally {
695
- await worker.terminate();
696
- }
697
- }
698
-
699
- // src/stamps/geometry-2d/ai/vision/extractProblem.ts
700
- var MIN_HIGH_CONFIDENCE_CHARS = 10;
701
- var MAX_TEXT_CHARS = 2e3;
702
- var TESSERACT_HIGH_CONFIDENCE_THRESHOLD = 70;
703
- function pickVisionModel(providerDefault, opts, env) {
704
- return opts.visionModel ?? env.WHITEBOARD_AI_VISION_MODEL ?? providerDefault;
705
- }
706
- async function extractProblemFromImage(image, opts = {}) {
707
- const engine = opts.engine ?? "tesseract";
708
- if (engine === "tesseract") {
709
- return extractViaTesseract(image, opts);
710
- }
711
- return extractViaLlm(image, opts);
712
- }
713
- async function extractViaTesseract(image, opts) {
714
- let raw;
715
- try {
716
- raw = await runTesseractOcr(image, {
717
- ...opts.tesseractLang ? { lang: opts.tesseractLang } : {},
718
- ...opts.signal ? { signal: opts.signal } : {}
719
- });
720
- } catch (e) {
721
- const err = e;
722
- return {
723
- ok: false,
724
- reason: "unreadable",
725
- message: "Tesseract OCR fail: " + (err.message ?? "?")
726
- };
727
- }
728
- const text = postProcess(raw.text);
729
- if (text.length === 0) {
730
- return { ok: false, reason: "empty", message: "Tesseract kh\xF4ng tr\xEDch \u0111\u01B0\u1EE3c text." };
731
- }
732
- const tooShort = text.length < MIN_HIGH_CONFIDENCE_CHARS;
733
- const lowConf = raw.confidence < TESSERACT_HIGH_CONFIDENCE_THRESHOLD;
734
- const confidence = tooShort || lowConf ? "low" : "high";
735
- return {
736
- ok: true,
737
- text,
738
- confidence,
739
- usage: { inputTokens: 0, outputTokens: 0 }
740
- };
741
- }
742
- async function extractViaLlm(image, opts) {
743
- const provider = opts.provider ?? selectProvider(opts);
744
- if (!provider.extractText) {
745
- return {
746
- ok: false,
747
- reason: "unsupported",
748
- message: `Provider "${provider.name}" kh\xF4ng h\u1ED7 tr\u1EE3 \u0111\u1ECDc \u1EA3nh.`
749
- };
750
- }
751
- const env = opts.env ?? readEnv2();
752
- const model = pickVisionModel(provider.defaultModel, opts, env);
753
- const req = {
754
- systemPrompt: buildVisionSystemPrompt(),
755
- userPrompt: VISION_USER_PROMPT,
756
- schema: visionEnvelopeJsonSchema(),
757
- images: [image],
758
- model,
759
- maxTokens: opts.maxTokens ?? 1024,
760
- ...opts.signal ? { signal: opts.signal } : {}
761
- };
762
- const out = await provider.extractText(req);
763
- if (out.kind === "error") {
764
- return { ok: false, reason: "unreadable", message: out.message };
765
- }
766
- const parsed = VisionEnvelopeZ.safeParse(out.data);
767
- if (!parsed.success) {
768
- return {
769
- ok: false,
770
- reason: "empty",
771
- message: "Kh\xF4ng parse \u0111\u01B0\u1EE3c output OCR: " + parsed.error.message
772
- };
773
- }
774
- const env_ = parsed.data;
775
- if (env_.decision === "refuse") {
776
- return {
777
- ok: false,
778
- reason: "not-math",
779
- message: env_.reason ?? "\u1EA2nh kh\xF4ng ph\u1EA3i \u0111\u1EC1 to\xE1n."
780
- };
781
- }
782
- const rawText = env_.text ?? "";
783
- const text = postProcess(rawText);
784
- if (text.length === 0) {
785
- return { ok: false, reason: "empty", message: "OCR kh\xF4ng tr\xEDch \u0111\u01B0\u1EE3c text." };
786
- }
787
- const tooShort = text.length < MIN_HIGH_CONFIDENCE_CHARS;
788
- const confidence = env_.confidence === "low" || tooShort ? "low" : "high";
789
- const usage = out.usage ?? { inputTokens: 0, outputTokens: 0 };
790
- return {
791
- ok: true,
792
- text,
793
- confidence,
794
- usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens }
795
- };
796
- }
797
- function postProcess(raw) {
798
- let t = raw.trim();
799
- t = t.replace(/\*\*(.+?)\*\*/g, "$1");
800
- t = t.replace(/\*(.+?)\*/g, "$1");
801
- t = t.replace(/_(.+?)_/g, "$1");
802
- t = t.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1");
803
- t = t.replace(/\s+/g, " ").trim();
804
- t = t.normalize("NFC");
805
- if (t.length > MAX_TEXT_CHARS) t = t.slice(0, MAX_TEXT_CHARS);
806
- return t;
807
- }
808
- function readEnv2() {
809
- if (typeof process !== "undefined" && process.env) {
810
- return process.env;
811
- }
812
- return {};
813
- }
814
-
815
- // src/stamps/geometry-2d/ai/handleExtractProblem.ts
816
- async function handleExtractProblem(image, opts = {}) {
817
- try {
818
- const r = await extractProblemFromImage(image, opts);
819
- if (r.ok) {
820
- if (r.confidence === "low") {
821
- return {
822
- kind: "low-confidence",
823
- text: r.text,
824
- warning: "OCR c\xF3 th\u1EC3 kh\xF4ng ch\xEDnh x\xE1c, ki\u1EC3m tra tr\u01B0\u1EDBc khi v\u1EBD.",
825
- usage: r.usage
826
- };
827
- }
828
- return { kind: "success", text: r.text, usage: r.usage };
829
- }
830
- if (r.reason === "not-math") {
831
- return { kind: "refused", reason: "not-math", message: r.message };
832
- }
833
- if (r.reason === "unsupported") {
834
- return { kind: "error", code: "unsupported", message: r.message };
835
- }
836
- if (r.reason === "unreadable") {
837
- return { kind: "error", code: "network", message: r.message };
838
- }
839
- return { kind: "error", code: "empty", message: r.message };
840
- } catch (e) {
841
- return {
842
- kind: "error",
843
- code: "unexpected",
844
- message: e instanceof Error ? e.message : String(e)
845
- };
846
- }
847
- }
848
-
849
- export { AnthropicProvider, OllamaProvider, VISION_USER_PROMPT, VisionEnvelopeZ, buildVisionSystemPrompt, extractProblemFromImage, handleExtractProblem, pickVisionModel, selectProvider, visionEnvelopeJsonSchema };
850
- //# sourceMappingURL=chunk-T3SOHYB2.mjs.map
851
- //# sourceMappingURL=chunk-T3SOHYB2.mjs.map