viho-llm 1.0.8 → 1.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/index.js CHANGED
@@ -4,9 +4,11 @@ var genai = require('@google/genai');
4
4
  var mime = require('mime-types');
5
5
  var qiao_log_js = require('qiao.log.js');
6
6
  var OpenAI = require('openai');
7
+ var crypto = require('crypto');
8
+ var https = require('https');
7
9
 
8
10
  // gemini
9
- const logger$4 = qiao_log_js.Logger('gemini-util.js');
11
+ const logger$6 = qiao_log_js.Logger('gemini-util.js');
10
12
 
11
13
  /**
12
14
  * chat
@@ -20,19 +22,19 @@ const chat$1 = async (client, modelName, chatOptions) => {
20
22
 
21
23
  // check
22
24
  if (!client) {
23
- logger$4.error(methodName, 'need client');
25
+ logger$6.error(methodName, 'need client');
24
26
  return;
25
27
  }
26
28
  if (!modelName) {
27
- logger$4.error(methodName, 'need modelName');
29
+ logger$6.error(methodName, 'need modelName');
28
30
  return;
29
31
  }
30
32
  if (!chatOptions) {
31
- logger$4.error(methodName, 'need chatOptions');
33
+ logger$6.error(methodName, 'need chatOptions');
32
34
  return;
33
35
  }
34
36
  if (!chatOptions.contents) {
35
- logger$4.error(methodName, 'need chatOptions.contents');
37
+ logger$6.error(methodName, 'need chatOptions.contents');
36
38
  return;
37
39
  }
38
40
 
@@ -48,13 +50,13 @@ const chat$1 = async (client, modelName, chatOptions) => {
48
50
  // gen
49
51
  const response = await client.models.generateContent(options);
50
52
  if (!response || !response.text) {
51
- logger$4.error(methodName, 'invalid response');
53
+ logger$6.error(methodName, 'invalid response');
52
54
  return;
53
55
  }
54
56
 
55
57
  return response.text;
56
58
  } catch (error) {
57
- logger$4.error(methodName, 'error', error);
59
+ logger$6.error(methodName, 'error', error);
58
60
  }
59
61
  };
60
62
 
@@ -71,23 +73,23 @@ const chatWithStreaming$1 = async (client, modelName, chatOptions, callbackOptio
71
73
 
72
74
  // check
73
75
  if (!client) {
74
- logger$4.error(methodName, 'need client');
76
+ logger$6.error(methodName, 'need client');
75
77
  return;
76
78
  }
77
79
  if (!modelName) {
78
- logger$4.error(methodName, 'need modelName');
80
+ logger$6.error(methodName, 'need modelName');
79
81
  return;
80
82
  }
81
83
  if (!chatOptions) {
82
- logger$4.error(methodName, 'need chatOptions');
84
+ logger$6.error(methodName, 'need chatOptions');
83
85
  return;
84
86
  }
85
87
  if (!chatOptions.contents) {
86
- logger$4.error(methodName, 'need chatOptions.contents');
88
+ logger$6.error(methodName, 'need chatOptions.contents');
87
89
  return;
88
90
  }
89
91
  if (!callbackOptions) {
90
- logger$4.error(methodName, 'need callbackOptions');
92
+ logger$6.error(methodName, 'need callbackOptions');
91
93
  return;
92
94
  }
93
95
 
@@ -145,38 +147,38 @@ const cacheAdd = async (client, modelName, cacheOptions) => {
145
147
 
146
148
  // check
147
149
  if (!client) {
148
- logger$4.error(methodName, 'need client');
150
+ logger$6.error(methodName, 'need client');
149
151
  return;
150
152
  }
151
153
  if (!modelName) {
152
- logger$4.error(methodName, 'need modelName');
154
+ logger$6.error(methodName, 'need modelName');
153
155
  return;
154
156
  }
155
157
  if (!cacheOptions) {
156
- logger$4.error(methodName, 'need cacheOptions');
158
+ logger$6.error(methodName, 'need cacheOptions');
157
159
  return;
158
160
  }
159
161
  if (!cacheOptions.gsPath) {
160
- logger$4.error(methodName, 'need cacheOptions.gsPath');
162
+ logger$6.error(methodName, 'need cacheOptions.gsPath');
161
163
  return;
162
164
  }
163
165
  if (!cacheOptions.systemPrompt) {
164
- logger$4.error(methodName, 'need cacheOptions.systemPrompt');
166
+ logger$6.error(methodName, 'need cacheOptions.systemPrompt');
165
167
  return;
166
168
  }
167
169
  if (!cacheOptions.cacheName) {
168
- logger$4.error(methodName, 'need cacheOptions.cacheName');
170
+ logger$6.error(methodName, 'need cacheOptions.cacheName');
169
171
  return;
170
172
  }
171
173
  if (!cacheOptions.cacheTTL) {
172
- logger$4.error(methodName, 'need cacheOptions.cacheTTL');
174
+ logger$6.error(methodName, 'need cacheOptions.cacheTTL');
173
175
  return;
174
176
  }
175
177
 
176
178
  // const
177
179
  const mimeType = mime.lookup(cacheOptions.gsPath);
178
- logger$4.info(methodName, 'cacheOptions', cacheOptions);
179
- logger$4.info(methodName, 'mimeType', mimeType);
180
+ logger$6.info(methodName, 'cacheOptions', cacheOptions);
181
+ logger$6.info(methodName, 'mimeType', mimeType);
180
182
 
181
183
  try {
182
184
  // cache add
@@ -192,7 +194,7 @@ const cacheAdd = async (client, modelName, cacheOptions) => {
192
194
 
193
195
  return cache;
194
196
  } catch (error) {
195
- logger$4.error(methodName, 'error', error);
197
+ logger$6.error(methodName, 'error', error);
196
198
  }
197
199
  };
198
200
 
@@ -206,7 +208,7 @@ const cacheList = async (client) => {
206
208
 
207
209
  // check
208
210
  if (!client) {
209
- logger$4.error(methodName, 'need client');
211
+ logger$6.error(methodName, 'need client');
210
212
  return;
211
213
  }
212
214
 
@@ -220,7 +222,7 @@ const cacheList = async (client) => {
220
222
 
221
223
  return cacheObjs;
222
224
  } catch (error) {
223
- logger$4.error(methodName, 'error', error);
225
+ logger$6.error(methodName, 'error', error);
224
226
  }
225
227
  };
226
228
 
@@ -236,15 +238,15 @@ const cacheUpdate = async (client, cacheName, cacheOptions) => {
236
238
 
237
239
  // check
238
240
  if (!client) {
239
- logger$4.error(methodName, 'need client');
241
+ logger$6.error(methodName, 'need client');
240
242
  return;
241
243
  }
242
244
  if (!cacheName) {
243
- logger$4.error(methodName, 'need cacheName');
245
+ logger$6.error(methodName, 'need cacheName');
244
246
  return;
245
247
  }
246
248
  if (!cacheOptions) {
247
- logger$4.error(methodName, 'need cacheOptions');
249
+ logger$6.error(methodName, 'need cacheOptions');
248
250
  return;
249
251
  }
250
252
 
@@ -257,12 +259,12 @@ const cacheUpdate = async (client, cacheName, cacheOptions) => {
257
259
 
258
260
  return res;
259
261
  } catch (error) {
260
- logger$4.error(methodName, 'error', error);
262
+ logger$6.error(methodName, 'error', error);
261
263
  }
262
264
  };
263
265
 
264
266
  // gemini
265
- const logger$3 = qiao_log_js.Logger('gemini-api.js');
267
+ const logger$5 = qiao_log_js.Logger('gemini-api.js');
266
268
 
267
269
  /**
268
270
  * GeminiAPI
@@ -274,15 +276,15 @@ const GeminiAPI = (options) => {
274
276
 
275
277
  // check
276
278
  if (!options) {
277
- logger$3.error(methodName, 'need options');
279
+ logger$5.error(methodName, 'need options');
278
280
  return;
279
281
  }
280
282
  if (!options.apiKey) {
281
- logger$3.error(methodName, 'need options.apiKey');
283
+ logger$5.error(methodName, 'need options.apiKey');
282
284
  return;
283
285
  }
284
286
  if (!options.modelName) {
285
- logger$3.error(methodName, 'need options.modelName');
287
+ logger$5.error(methodName, 'need options.modelName');
286
288
  return;
287
289
  }
288
290
 
@@ -305,7 +307,7 @@ const GeminiAPI = (options) => {
305
307
  };
306
308
 
307
309
  // gemini
308
- const logger$2 = qiao_log_js.Logger('viho-llm');
310
+ const logger$4 = qiao_log_js.Logger('viho-llm');
309
311
 
310
312
  /**
311
313
  * GeminiVertex
@@ -317,19 +319,19 @@ const GeminiVertex = (options) => {
317
319
 
318
320
  // check
319
321
  if (!options) {
320
- logger$2.error(methodName, 'need options');
322
+ logger$4.error(methodName, 'need options');
321
323
  return;
322
324
  }
323
325
  if (!options.projectId) {
324
- logger$2.error(methodName, 'need options.projectId');
326
+ logger$4.error(methodName, 'need options.projectId');
325
327
  return;
326
328
  }
327
329
  if (!options.location) {
328
- logger$2.error(methodName, 'need options.location');
330
+ logger$4.error(methodName, 'need options.location');
329
331
  return;
330
332
  }
331
333
  if (!options.modelName) {
332
- logger$2.error(methodName, 'need options.modelName');
334
+ logger$4.error(methodName, 'need options.modelName');
333
335
  return;
334
336
  }
335
337
 
@@ -365,7 +367,7 @@ const GeminiVertex = (options) => {
365
367
  };
366
368
 
367
369
  // Logger
368
- const logger$1 = qiao_log_js.Logger('openai-util.js');
370
+ const logger$3 = qiao_log_js.Logger('openai-util.js');
369
371
 
370
372
  /**
371
373
  * chat
@@ -378,11 +380,11 @@ const chat = async (client, chatOptions) => {
378
380
 
379
381
  // check
380
382
  if (!client) {
381
- logger$1.error(methodName, 'need client');
383
+ logger$3.error(methodName, 'need client');
382
384
  return;
383
385
  }
384
386
  if (!chatOptions) {
385
- logger$1.error(methodName, 'need chatOptions');
387
+ logger$3.error(methodName, 'need chatOptions');
386
388
  return;
387
389
  }
388
390
 
@@ -391,7 +393,7 @@ const chat = async (client, chatOptions) => {
391
393
  const completion = await client.chat.completions.create(chatOptions);
392
394
  return completion.choices[0]?.message;
393
395
  } catch (error) {
394
- logger$1.error(methodName, 'error', error);
396
+ logger$3.error(methodName, 'error', error);
395
397
  }
396
398
  };
397
399
 
@@ -407,15 +409,15 @@ const chatWithStreaming = async (client, chatOptions, callbackOptions) => {
407
409
 
408
410
  // check
409
411
  if (!client) {
410
- logger$1.error(methodName, 'need client');
412
+ logger$3.error(methodName, 'need client');
411
413
  return;
412
414
  }
413
415
  if (!chatOptions) {
414
- logger$1.error(methodName, 'need chatOptions');
416
+ logger$3.error(methodName, 'need chatOptions');
415
417
  return;
416
418
  }
417
419
  if (!callbackOptions) {
418
- logger$1.error(methodName, 'need callbackOptions');
420
+ logger$3.error(methodName, 'need callbackOptions');
419
421
  return;
420
422
  }
421
423
 
@@ -469,7 +471,7 @@ const chatWithStreaming = async (client, chatOptions, callbackOptions) => {
469
471
  };
470
472
 
471
473
  // openai
472
- const logger = qiao_log_js.Logger('openai.js');
474
+ const logger$2 = qiao_log_js.Logger('openai.js');
473
475
 
474
476
  /**
475
477
  * OpenAI
@@ -481,15 +483,15 @@ const OpenAIAPI = (options) => {
481
483
 
482
484
  // check
483
485
  if (!options) {
484
- logger.error(methodName, 'need options');
486
+ logger$2.error(methodName, 'need options');
485
487
  return;
486
488
  }
487
489
  if (!options.apiKey) {
488
- logger.error(methodName, 'need options.apiKey');
490
+ logger$2.error(methodName, 'need options.apiKey');
489
491
  return;
490
492
  }
491
493
  if (!options.baseURL) {
492
- logger.error(methodName, 'need options.baseURL');
494
+ logger$2.error(methodName, 'need options.baseURL');
493
495
  return;
494
496
  }
495
497
 
@@ -514,6 +516,281 @@ const OpenAIAPI = (options) => {
514
516
  return openai;
515
517
  };
516
518
 
519
+ // crypto
520
+ const logger$1 = qiao_log_js.Logger('liblib-util.js');
521
+
522
+ /**
523
+ * generateSignature
524
+ * @param {*} uri
525
+ * @param {*} secretKey
526
+ * @returns
527
+ */
528
+ const generateSignature = (uri, secretKey) => {
529
+ const timestamp = String(Date.now());
530
+ const signatureNonce = crypto.randomBytes(8).toString('hex');
531
+ const content = `${uri}&${timestamp}&${signatureNonce}`;
532
+
533
+ const hmac = crypto.createHmac('sha1', secretKey);
534
+ hmac.update(content);
535
+ const signature = hmac.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
536
+
537
+ return { signature, timestamp, signatureNonce };
538
+ };
539
+
540
+ /**
541
+ * request
542
+ * @param {*} baseURL
543
+ * @param {*} accessKey
544
+ * @param {*} secretKey
545
+ * @param {*} uri
546
+ * @param {*} body
547
+ * @returns
548
+ */
549
+ const request = (baseURL, accessKey, secretKey, uri, body) => {
550
+ const methodName = 'request';
551
+
552
+ return new Promise((resolve, reject) => {
553
+ const { signature, timestamp, signatureNonce } = generateSignature(uri, secretKey);
554
+ const query =
555
+ `AccessKey=${accessKey}` +
556
+ `&Signature=${signature}` +
557
+ `&Timestamp=${timestamp}` +
558
+ `&SignatureNonce=${signatureNonce}`;
559
+
560
+ const postData = JSON.stringify(body);
561
+
562
+ const options = {
563
+ hostname: baseURL,
564
+ path: `${uri}?${query}`,
565
+ method: 'POST',
566
+ headers: {
567
+ 'Content-Type': 'application/json',
568
+ 'Content-Length': Buffer.byteLength(postData),
569
+ },
570
+ };
571
+
572
+ const req = https.request(options, (res) => {
573
+ let data = '';
574
+ res.on('data', (chunk) => {
575
+ data += chunk;
576
+ });
577
+ res.on('end', () => {
578
+ try {
579
+ resolve(JSON.parse(data));
580
+ } catch (e) {
581
+ logger$1.error(methodName, 'JSON parse error', data);
582
+ reject(new Error('JSON parse error: ' + data));
583
+ }
584
+ });
585
+ });
586
+
587
+ req.on('error', (error) => {
588
+ logger$1.error(methodName, 'request error', error);
589
+ reject(error);
590
+ });
591
+ req.write(postData);
592
+ req.end();
593
+ });
594
+ };
595
+
596
+ /**
597
+ * text2img
598
+ * @param {*} baseURL
599
+ * @param {*} accessKey
600
+ * @param {*} secretKey
601
+ * @param {*} prompt
602
+ * @param {*} options
603
+ * @returns
604
+ */
605
+ const text2img = (baseURL, accessKey, secretKey, prompt, options) => {
606
+ const methodName = 'text2img';
607
+
608
+ // check
609
+ if (!prompt) {
610
+ logger$1.error(methodName, 'need prompt');
611
+ return;
612
+ }
613
+
614
+ options = options || {};
615
+ const generateParams = {
616
+ prompt: prompt,
617
+ aspectRatio: options.aspectRatio || 'portrait',
618
+ imgCount: options.imgCount || 1,
619
+ steps: options.steps || 30,
620
+ };
621
+
622
+ if (options.width && options.height) {
623
+ delete generateParams.aspectRatio;
624
+ generateParams.imageSize = {
625
+ width: options.width,
626
+ height: options.height,
627
+ };
628
+ }
629
+
630
+ if (options.controlnet) {
631
+ generateParams.controlnet = options.controlnet;
632
+ }
633
+
634
+ return request(baseURL, accessKey, secretKey, '/api/generate/webui/text2img/ultra', {
635
+ templateUuid: '5d7e67009b344550bc1aa6ccbfa1d7f4',
636
+ generateParams: generateParams,
637
+ });
638
+ };
639
+
640
+ /**
641
+ * img2img
642
+ * @param {*} baseURL
643
+ * @param {*} accessKey
644
+ * @param {*} secretKey
645
+ * @param {*} prompt
646
+ * @param {*} sourceImage
647
+ * @param {*} options
648
+ * @returns
649
+ */
650
+ const img2img = (baseURL, accessKey, secretKey, prompt, sourceImage, options) => {
651
+ const methodName = 'img2img';
652
+
653
+ // check
654
+ if (!prompt) {
655
+ logger$1.error(methodName, 'need prompt');
656
+ return;
657
+ }
658
+ if (!sourceImage) {
659
+ logger$1.error(methodName, 'need sourceImage');
660
+ return;
661
+ }
662
+
663
+ options = options || {};
664
+ const generateParams = {
665
+ prompt: prompt,
666
+ sourceImage: sourceImage,
667
+ imgCount: options.imgCount || 1,
668
+ };
669
+
670
+ if (options.controlnet) {
671
+ generateParams.controlnet = options.controlnet;
672
+ }
673
+
674
+ return request(baseURL, accessKey, secretKey, '/api/generate/webui/img2img/ultra', {
675
+ templateUuid: '07e00af4fc464c7ab55ff906f8acf1b7',
676
+ generateParams: generateParams,
677
+ });
678
+ };
679
+
680
+ /**
681
+ * queryStatus
682
+ * @param {*} baseURL
683
+ * @param {*} accessKey
684
+ * @param {*} secretKey
685
+ * @param {*} generateUuid
686
+ * @returns
687
+ */
688
+ const queryStatus = (baseURL, accessKey, secretKey, generateUuid) => {
689
+ return request(baseURL, accessKey, secretKey, '/api/generate/webui/status', {
690
+ generateUuid: generateUuid,
691
+ });
692
+ };
693
+
694
+ /**
695
+ * waitForResult
696
+ * @param {*} baseURL
697
+ * @param {*} accessKey
698
+ * @param {*} secretKey
699
+ * @param {*} generateUuid
700
+ * @param {*} intervalMs
701
+ * @param {*} maxRetries
702
+ * @returns
703
+ */
704
+ const waitForResult = (baseURL, accessKey, secretKey, generateUuid, intervalMs, maxRetries) => {
705
+ intervalMs = intervalMs || 3000;
706
+ maxRetries = maxRetries || 60;
707
+
708
+ return new Promise((resolve, reject) => {
709
+ let attempt = 0;
710
+
711
+ function poll() {
712
+ attempt++;
713
+ queryStatus(baseURL, accessKey, secretKey, generateUuid)
714
+ .then((res) => {
715
+ const status = res.data && res.data.generateStatus;
716
+
717
+ if (status === 5) {
718
+ return resolve(res.data);
719
+ }
720
+ if (status === 6 || status === 7) {
721
+ return reject(new Error('Generation failed: ' + (res.data.generateMsg || 'unknown')));
722
+ }
723
+
724
+ if (attempt >= maxRetries) {
725
+ return reject(new Error('Polling timeout after ' + maxRetries + ' retries'));
726
+ }
727
+
728
+ setTimeout(poll, intervalMs);
729
+ })
730
+ .catch(reject);
731
+ }
732
+
733
+ poll();
734
+ });
735
+ };
736
+
737
+ // util
738
+ const logger = qiao_log_js.Logger('liblib.js');
739
+
740
+ /**
741
+ * LibLibAPI
742
+ * @param {*} options
743
+ * @returns
744
+ */
745
+ const LibLibAPI = (options) => {
746
+ const methodName = 'LibLibAPI';
747
+
748
+ // check
749
+ if (!options) {
750
+ logger.error(methodName, 'need options');
751
+ return;
752
+ }
753
+ if (!options.accessKey) {
754
+ logger.error(methodName, 'need options.accessKey');
755
+ return;
756
+ }
757
+ if (!options.secretKey) {
758
+ logger.error(methodName, 'need options.secretKey');
759
+ return;
760
+ }
761
+
762
+ // config
763
+ const baseURL = options.baseURL || 'openapi.liblibai.cloud';
764
+ const accessKey = options.accessKey;
765
+ const secretKey = options.secretKey;
766
+
767
+ // liblib
768
+ const liblib = {};
769
+
770
+ // text2img
771
+ liblib.text2img = async (prompt, imgOptions) => {
772
+ return await text2img(baseURL, accessKey, secretKey, prompt, imgOptions);
773
+ };
774
+
775
+ // img2img
776
+ liblib.img2img = async (prompt, sourceImage, imgOptions) => {
777
+ return await img2img(baseURL, accessKey, secretKey, prompt, sourceImage, imgOptions);
778
+ };
779
+
780
+ // queryStatus
781
+ liblib.queryStatus = async (generateUuid) => {
782
+ return await queryStatus(baseURL, accessKey, secretKey, generateUuid);
783
+ };
784
+
785
+ // waitForResult
786
+ liblib.waitForResult = async (generateUuid, intervalMs, maxRetries) => {
787
+ return await waitForResult(baseURL, accessKey, secretKey, generateUuid, intervalMs, maxRetries);
788
+ };
789
+
790
+ //
791
+ return liblib;
792
+ };
793
+
517
794
  /**
518
795
  * callLLM
519
796
  * @param {*} options
@@ -538,29 +815,38 @@ const callLLM = async (options) => {
538
815
  * @returns
539
816
  */
540
817
  const extractJSON = (text) => {
541
- const cleaned = text.replace(/\{\{/g, '{').replace(/\}\}/g, '}');
818
+ if (typeof text === 'object' && text !== null) return text;
819
+ const str = String(text).trim();
820
+
821
+ // 1. 尝试直接解析
542
822
  try {
543
- return JSON.parse(cleaned);
544
- } catch (e) {
545
- // try next
823
+ return JSON.parse(str);
824
+ } catch (_) {
825
+ /* ignore */
546
826
  }
547
- const codeBlock = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
548
- if (codeBlock) {
827
+
828
+ // 2. 尝试从 markdown 代码块提取
829
+ const codeBlockMatch = str.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
830
+ if (codeBlockMatch) {
549
831
  try {
550
- return JSON.parse(codeBlock[1].trim());
551
- } catch (e) {
552
- // try next
832
+ return JSON.parse(codeBlockMatch[1].trim());
833
+ } catch (_) {
834
+ /* ignore */
553
835
  }
554
836
  }
555
- const match = cleaned.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
556
- if (match) {
837
+
838
+ // 3. 尝试提取第一个 {...} 块
839
+ const firstBrace = str.indexOf('{');
840
+ const lastBrace = str.lastIndexOf('}');
841
+ if (firstBrace !== -1 && lastBrace > firstBrace) {
557
842
  try {
558
- return JSON.parse(match[1]);
559
- } catch (e) {
560
- // try next
843
+ return JSON.parse(str.slice(firstBrace, lastBrace + 1));
844
+ } catch (_) {
845
+ /* ignore */
561
846
  }
562
847
  }
563
- throw new Error(`Cannot extract JSON from: ${text.slice(0, 200)}`);
848
+
849
+ throw new Error(`Cannot parse JSON from LLM response: ${str.slice(0, 200)}`);
564
850
  };
565
851
 
566
852
  // util
@@ -599,5 +885,6 @@ const runAgents = async (agents) => {
599
885
 
600
886
  exports.GeminiAPI = GeminiAPI;
601
887
  exports.GeminiVertex = GeminiVertex;
888
+ exports.LibLibAPI = LibLibAPI;
602
889
  exports.OpenAIAPI = OpenAIAPI;
603
890
  exports.runAgents = runAgents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viho-llm",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "Utility library for working with multiple LLM providers (Google Gemini and OpenAI), providing common tools and helpers for AI interactions",
5
5
  "keywords": [
6
6
  "llm",
@@ -68,5 +68,5 @@
68
68
  }
69
69
  }
70
70
  },
71
- "gitHead": "46a9940278370dfe84caa47adcb4ebcd1f5c1393"
71
+ "gitHead": "1cf5a70d8978834ee91576c9126040a3357e34d0"
72
72
  }
@@ -22,27 +22,36 @@ export const callLLM = async (options) => {
22
22
  * @returns
23
23
  */
24
24
  export const extractJSON = (text) => {
25
- const cleaned = text.replace(/\{\{/g, '{').replace(/\}\}/g, '}');
25
+ if (typeof text === 'object' && text !== null) return text;
26
+ const str = String(text).trim();
27
+
28
+ // 1. 尝试直接解析
26
29
  try {
27
- return JSON.parse(cleaned);
28
- } catch (e) {
29
- // try next
30
+ return JSON.parse(str);
31
+ } catch (_) {
32
+ /* ignore */
30
33
  }
31
- const codeBlock = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
32
- if (codeBlock) {
34
+
35
+ // 2. 尝试从 markdown 代码块提取
36
+ const codeBlockMatch = str.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
37
+ if (codeBlockMatch) {
33
38
  try {
34
- return JSON.parse(codeBlock[1].trim());
35
- } catch (e) {
36
- // try next
39
+ return JSON.parse(codeBlockMatch[1].trim());
40
+ } catch (_) {
41
+ /* ignore */
37
42
  }
38
43
  }
39
- const match = cleaned.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
40
- if (match) {
44
+
45
+ // 3. 尝试提取第一个 {...} 块
46
+ const firstBrace = str.indexOf('{');
47
+ const lastBrace = str.lastIndexOf('}');
48
+ if (firstBrace !== -1 && lastBrace > firstBrace) {
41
49
  try {
42
- return JSON.parse(match[1]);
43
- } catch (e) {
44
- // try next
50
+ return JSON.parse(str.slice(firstBrace, lastBrace + 1));
51
+ } catch (_) {
52
+ /* ignore */
45
53
  }
46
54
  }
47
- throw new Error(`Cannot extract JSON from: ${text.slice(0, 200)}`);
55
+
56
+ throw new Error(`Cannot parse JSON from LLM response: ${str.slice(0, 200)}`);
48
57
  };
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './models/gemini-api.js';
2
2
  export * from './models/gemini-vertex.js';
3
3
  export * from './models/openai.js';
4
+ export * from './models/liblib.js';
4
5
  export * from './agents/agent.js';
@@ -0,0 +1,224 @@
1
+ // crypto
2
+ import crypto from 'crypto';
3
+
4
+ // https
5
+ import https from 'https';
6
+
7
+ // Logger
8
+ import { Logger } from 'qiao.log.js';
9
+ const logger = Logger('liblib-util.js');
10
+
11
+ /**
12
+ * generateSignature
13
+ * @param {*} uri
14
+ * @param {*} secretKey
15
+ * @returns
16
+ */
17
+ export const generateSignature = (uri, secretKey) => {
18
+ const timestamp = String(Date.now());
19
+ const signatureNonce = crypto.randomBytes(8).toString('hex');
20
+ const content = `${uri}&${timestamp}&${signatureNonce}`;
21
+
22
+ const hmac = crypto.createHmac('sha1', secretKey);
23
+ hmac.update(content);
24
+ const signature = hmac.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
25
+
26
+ return { signature, timestamp, signatureNonce };
27
+ };
28
+
29
+ /**
30
+ * request
31
+ * @param {*} baseURL
32
+ * @param {*} accessKey
33
+ * @param {*} secretKey
34
+ * @param {*} uri
35
+ * @param {*} body
36
+ * @returns
37
+ */
38
+ export const request = (baseURL, accessKey, secretKey, uri, body) => {
39
+ const methodName = 'request';
40
+
41
+ return new Promise((resolve, reject) => {
42
+ const { signature, timestamp, signatureNonce } = generateSignature(uri, secretKey);
43
+ const query =
44
+ `AccessKey=${accessKey}` +
45
+ `&Signature=${signature}` +
46
+ `&Timestamp=${timestamp}` +
47
+ `&SignatureNonce=${signatureNonce}`;
48
+
49
+ const postData = JSON.stringify(body);
50
+
51
+ const options = {
52
+ hostname: baseURL,
53
+ path: `${uri}?${query}`,
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ 'Content-Length': Buffer.byteLength(postData),
58
+ },
59
+ };
60
+
61
+ const req = https.request(options, (res) => {
62
+ let data = '';
63
+ res.on('data', (chunk) => {
64
+ data += chunk;
65
+ });
66
+ res.on('end', () => {
67
+ try {
68
+ resolve(JSON.parse(data));
69
+ } catch (e) {
70
+ logger.error(methodName, 'JSON parse error', data);
71
+ reject(new Error('JSON parse error: ' + data));
72
+ }
73
+ });
74
+ });
75
+
76
+ req.on('error', (error) => {
77
+ logger.error(methodName, 'request error', error);
78
+ reject(error);
79
+ });
80
+ req.write(postData);
81
+ req.end();
82
+ });
83
+ };
84
+
85
+ /**
86
+ * text2img
87
+ * @param {*} baseURL
88
+ * @param {*} accessKey
89
+ * @param {*} secretKey
90
+ * @param {*} prompt
91
+ * @param {*} options
92
+ * @returns
93
+ */
94
+ export const text2img = (baseURL, accessKey, secretKey, prompt, options) => {
95
+ const methodName = 'text2img';
96
+
97
+ // check
98
+ if (!prompt) {
99
+ logger.error(methodName, 'need prompt');
100
+ return;
101
+ }
102
+
103
+ options = options || {};
104
+ const generateParams = {
105
+ prompt: prompt,
106
+ aspectRatio: options.aspectRatio || 'portrait',
107
+ imgCount: options.imgCount || 1,
108
+ steps: options.steps || 30,
109
+ };
110
+
111
+ if (options.width && options.height) {
112
+ delete generateParams.aspectRatio;
113
+ generateParams.imageSize = {
114
+ width: options.width,
115
+ height: options.height,
116
+ };
117
+ }
118
+
119
+ if (options.controlnet) {
120
+ generateParams.controlnet = options.controlnet;
121
+ }
122
+
123
+ return request(baseURL, accessKey, secretKey, '/api/generate/webui/text2img/ultra', {
124
+ templateUuid: '5d7e67009b344550bc1aa6ccbfa1d7f4',
125
+ generateParams: generateParams,
126
+ });
127
+ };
128
+
129
+ /**
130
+ * img2img
131
+ * @param {*} baseURL
132
+ * @param {*} accessKey
133
+ * @param {*} secretKey
134
+ * @param {*} prompt
135
+ * @param {*} sourceImage
136
+ * @param {*} options
137
+ * @returns
138
+ */
139
+ export const img2img = (baseURL, accessKey, secretKey, prompt, sourceImage, options) => {
140
+ const methodName = 'img2img';
141
+
142
+ // check
143
+ if (!prompt) {
144
+ logger.error(methodName, 'need prompt');
145
+ return;
146
+ }
147
+ if (!sourceImage) {
148
+ logger.error(methodName, 'need sourceImage');
149
+ return;
150
+ }
151
+
152
+ options = options || {};
153
+ const generateParams = {
154
+ prompt: prompt,
155
+ sourceImage: sourceImage,
156
+ imgCount: options.imgCount || 1,
157
+ };
158
+
159
+ if (options.controlnet) {
160
+ generateParams.controlnet = options.controlnet;
161
+ }
162
+
163
+ return request(baseURL, accessKey, secretKey, '/api/generate/webui/img2img/ultra', {
164
+ templateUuid: '07e00af4fc464c7ab55ff906f8acf1b7',
165
+ generateParams: generateParams,
166
+ });
167
+ };
168
+
169
+ /**
170
+ * queryStatus
171
+ * @param {*} baseURL
172
+ * @param {*} accessKey
173
+ * @param {*} secretKey
174
+ * @param {*} generateUuid
175
+ * @returns
176
+ */
177
+ export const queryStatus = (baseURL, accessKey, secretKey, generateUuid) => {
178
+ return request(baseURL, accessKey, secretKey, '/api/generate/webui/status', {
179
+ generateUuid: generateUuid,
180
+ });
181
+ };
182
+
183
+ /**
184
+ * waitForResult
185
+ * @param {*} baseURL
186
+ * @param {*} accessKey
187
+ * @param {*} secretKey
188
+ * @param {*} generateUuid
189
+ * @param {*} intervalMs
190
+ * @param {*} maxRetries
191
+ * @returns
192
+ */
193
+ export const waitForResult = (baseURL, accessKey, secretKey, generateUuid, intervalMs, maxRetries) => {
194
+ intervalMs = intervalMs || 3000;
195
+ maxRetries = maxRetries || 60;
196
+
197
+ return new Promise((resolve, reject) => {
198
+ let attempt = 0;
199
+
200
+ function poll() {
201
+ attempt++;
202
+ queryStatus(baseURL, accessKey, secretKey, generateUuid)
203
+ .then((res) => {
204
+ const status = res.data && res.data.generateStatus;
205
+
206
+ if (status === 5) {
207
+ return resolve(res.data);
208
+ }
209
+ if (status === 6 || status === 7) {
210
+ return reject(new Error('Generation failed: ' + (res.data.generateMsg || 'unknown')));
211
+ }
212
+
213
+ if (attempt >= maxRetries) {
214
+ return reject(new Error('Polling timeout after ' + maxRetries + ' retries'));
215
+ }
216
+
217
+ setTimeout(poll, intervalMs);
218
+ })
219
+ .catch(reject);
220
+ }
221
+
222
+ poll();
223
+ });
224
+ };
@@ -0,0 +1,60 @@
1
+ // util
2
+ import { text2img, img2img, queryStatus, waitForResult } from './liblib-util.js';
3
+
4
+ // Logger
5
+ import { Logger } from 'qiao.log.js';
6
+ const logger = Logger('liblib.js');
7
+
8
+ /**
9
+ * LibLibAPI
10
+ * @param {*} options
11
+ * @returns
12
+ */
13
+ export const LibLibAPI = (options) => {
14
+ const methodName = 'LibLibAPI';
15
+
16
+ // check
17
+ if (!options) {
18
+ logger.error(methodName, 'need options');
19
+ return;
20
+ }
21
+ if (!options.accessKey) {
22
+ logger.error(methodName, 'need options.accessKey');
23
+ return;
24
+ }
25
+ if (!options.secretKey) {
26
+ logger.error(methodName, 'need options.secretKey');
27
+ return;
28
+ }
29
+
30
+ // config
31
+ const baseURL = options.baseURL || 'openapi.liblibai.cloud';
32
+ const accessKey = options.accessKey;
33
+ const secretKey = options.secretKey;
34
+
35
+ // liblib
36
+ const liblib = {};
37
+
38
+ // text2img
39
+ liblib.text2img = async (prompt, imgOptions) => {
40
+ return await text2img(baseURL, accessKey, secretKey, prompt, imgOptions);
41
+ };
42
+
43
+ // img2img
44
+ liblib.img2img = async (prompt, sourceImage, imgOptions) => {
45
+ return await img2img(baseURL, accessKey, secretKey, prompt, sourceImage, imgOptions);
46
+ };
47
+
48
+ // queryStatus
49
+ liblib.queryStatus = async (generateUuid) => {
50
+ return await queryStatus(baseURL, accessKey, secretKey, generateUuid);
51
+ };
52
+
53
+ // waitForResult
54
+ liblib.waitForResult = async (generateUuid, intervalMs, maxRetries) => {
55
+ return await waitForResult(baseURL, accessKey, secretKey, generateUuid, intervalMs, maxRetries);
56
+ };
57
+
58
+ //
59
+ return liblib;
60
+ };