@mujian/js-sdk 0.0.6-beta.9 → 0.0.6-beta.90

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 (46) hide show
  1. package/dist/events/index.d.ts +31 -3
  2. package/dist/index.d.ts +16 -39
  3. package/dist/index.js +360 -62
  4. package/dist/lite.d.ts +73 -0
  5. package/dist/lite.js +119 -0
  6. package/dist/modules/ai/chat/chat.d.ts +46 -4
  7. package/dist/modules/ai/chat/index.d.ts +1 -1
  8. package/dist/modules/ai/chat/message/index.d.ts +3 -7
  9. package/dist/modules/ai/chat/settings/index.d.ts +1 -2
  10. package/dist/modules/ai/chat/settings/persona.d.ts +0 -8
  11. package/dist/modules/ai/index.d.ts +3 -2
  12. package/dist/modules/ai/openai/chat.d.ts +22 -0
  13. package/dist/modules/ai/openai/completions.d.ts +17 -0
  14. package/dist/modules/ai/openai/images.d.ts +17 -0
  15. package/dist/modules/ai/openai/index.d.ts +4 -0
  16. package/dist/modules/ai/openai/responses.d.ts +18 -0
  17. package/dist/modules/ai/text/index.d.ts +9 -2
  18. package/dist/modules/config.d.ts +17 -0
  19. package/dist/modules/utils/clipboard.d.ts +5 -0
  20. package/dist/modules/utils/index.d.ts +4 -0
  21. package/dist/react/chat/useChat/index.d.ts +34 -8
  22. package/dist/react/chat/useChat/inner/chatStreaming.d.ts +4 -2
  23. package/dist/react/chat/useChat/message.d.ts +31 -13
  24. package/dist/react/components/MdRenderer/index.d.ts +5 -3
  25. package/dist/react/components/MdRenderer/utils/height.d.ts +0 -0
  26. package/dist/react/components/MdRenderer/utils/iframe.d.ts +9 -0
  27. package/dist/react/components/MdRenderer/utils/scripts.d.ts +4 -0
  28. package/dist/react/components/MujianSpinner/index.d.ts +7 -0
  29. package/dist/react/components/index.d.ts +1 -0
  30. package/dist/react/index.d.ts +1 -0
  31. package/dist/react/mjEngine/index.d.ts +48 -0
  32. package/dist/react.css +65 -4
  33. package/dist/react.js +834 -188
  34. package/dist/types/index.d.ts +39 -0
  35. package/dist/umd/index.js +2 -0
  36. package/dist/umd/index.js.LICENSE.txt +7 -0
  37. package/dist/umd/lite.js +2 -0
  38. package/dist/umd/lite.js.LICENSE.txt +7 -0
  39. package/dist/umd/react.css +1 -0
  40. package/dist/umd/react.js +133 -0
  41. package/dist/umd/react.js.LICENSE.txt +31 -0
  42. package/dist/utils/is-network-error.d.ts +1 -0
  43. package/dist/utils/log.d.ts +4 -0
  44. package/package.json +16 -6
  45. package/dist/modules/ai/chat/settings/model.d.ts +0 -24
  46. /package/dist/react/components/MdRenderer/{utils.d.ts → utils/md.d.ts} +0 -0
package/dist/react.js CHANGED
@@ -1,11 +1,183 @@
1
- import { useMount, useRequest, useUpdateEffect } from "ahooks";
2
- import react, { createContext, forwardRef, useCallback, useContext, useEffect, useState } from "react";
3
- import { jsx } from "react/jsx-runtime";
1
+ import { useInfiniteScroll, useLatest, useRequest, useUpdateEffect } from "ahooks";
2
+ import react, { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import css_tools from "@adobe/css-tools";
5
5
  import dompurify from "dompurify";
6
6
  import showdown from "showdown";
7
+ import { v4 } from "uuid";
7
8
  import postmate from "postmate";
8
9
  import { Virtualizer } from "virtua";
10
+ import { get } from "lodash-es";
11
+ import { produce } from "immer";
12
+ const adjustIframeHeight = (iframeId)=>`
13
+ (function () {
14
+ let scheduled = false;
15
+ function measureAndPost() {
16
+ scheduled = false;
17
+ try {
18
+ const doc = window.document;
19
+ const body = doc.body;
20
+ const html = doc.documentElement;
21
+ if (!body || !html) {
22
+ return;
23
+ }
24
+ let height = 0;
25
+ // srcdoc 模式: 只用 body.scrollHeight
26
+ height = body.scrollHeight;
27
+ if (!Number.isFinite(height) || height <= 0) {
28
+ return;
29
+ }
30
+ window.parent.postMessage({ type: 'MJ_ADJUST_IFRAME_HEIGHT', iframe_id: \`${iframeId}\`, height: height }, '*');
31
+ } catch {
32
+ console.error('Error measuring iframe height');
33
+ }
34
+ }
35
+
36
+ function postIframeHeight() {
37
+ if (scheduled) {
38
+ return;
39
+ }
40
+ scheduled = true;
41
+ if (typeof window.requestAnimationFrame === 'function') {
42
+ window.requestAnimationFrame(measureAndPost);
43
+ } else {
44
+ setTimeout(measureAndPost, 500);
45
+ }
46
+ }
47
+
48
+ function observeHeightChange() {
49
+ const body = document.body;
50
+ if (!body) {
51
+ return;
52
+ }
53
+ const observer = new ResizeObserver(entries => {
54
+ postIframeHeight();
55
+ });
56
+ observer.observe(body);
57
+ }
58
+
59
+ function init() {
60
+ postIframeHeight();
61
+ observeHeightChange();
62
+ }
63
+
64
+ if (window.document.readyState === 'loading') {
65
+ window.document.addEventListener('DOMContentLoaded', init, { once: true });
66
+ } else {
67
+ init();
68
+ }
69
+ })();
70
+ `;
71
+ const iframeAdjustViewport = (parentHeight)=>`
72
+ $('html').css('--MJ-viewport-height', \`${parentHeight}px\`);
73
+ window.addEventListener('message', function (event) {
74
+ if (event.data?.type === 'MJ_UPDATE_VIEWPORT_HEIGHT') {
75
+ $('html').css('--MJ-viewport-height', \`${parentHeight}px\`);
76
+ }
77
+ });
78
+ `;
79
+ const init = (_iframeId, unsafe, extra)=>unsafe ? '' : `
80
+ (async function () {
81
+ const extra = JSON.parse(${JSON.stringify(JSON.stringify(extra))})
82
+ window.$mujian = window.parent.$mj_engine.bind(extra);
83
+ })();
84
+ `;
85
+ const thirdParty = `
86
+ <script src="https://cdn.jsdmirror.com/npm/@fortawesome/fontawesome-free/js/all.min.js"></script>
87
+ <script src="https://cdn.jsdmirror.com/npm/@tailwindcss/browser/dist/index.global.min.js"></script>
88
+ <script src="https://cdn.jsdmirror.com/npm/jquery/dist/jquery.min.js"></script>
89
+ <script src="https://cdn.jsdmirror.com/npm/jquery-ui/dist/jquery-ui.min.js"></script>
90
+ <link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/jquery-ui/themes/base/theme.min.css" />
91
+ <script src="https://cdn.jsdmirror.com/npm/jquery-ui-touch-punch"></script>
92
+ `;
93
+ const unescapeHTML = (str)=>{
94
+ const named = {
95
+ amp: '&',
96
+ lt: '<',
97
+ gt: '>',
98
+ quot: '"',
99
+ apos: "'",
100
+ nbsp: '\u00A0'
101
+ };
102
+ return str.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (_m, body)=>{
103
+ if ('#' === body[0]) {
104
+ const isHex = body[1]?.toLowerCase() === 'x';
105
+ const numStr = isHex ? body.slice(2) : body.slice(1);
106
+ const codePoint = parseInt(numStr, isHex ? 16 : 10);
107
+ if (Number.isFinite(codePoint)) try {
108
+ return String.fromCodePoint(codePoint);
109
+ } catch {}
110
+ return _m;
111
+ }
112
+ const lower = body.toLowerCase();
113
+ return Object.hasOwn(named, lower) ? named[lower] : _m;
114
+ });
115
+ };
116
+ const replaceVhInContent = (content)=>{
117
+ const has_css_min_vh = /min-height\s*:\s*[^;{}]*\d+(?:\.\d+)?vh/gi.test(content);
118
+ const has_inline_style_vh = /style\s*=\s*(["'])[\s\S]*?min-height\s*:\s*[^;]*?\d+(?:\.\d+)?vh[\s\S]*?\1/gi.test(content);
119
+ const has_js_vh = /(\.style\.minHeight\s*=\s*(["']))([\s\S]*?vh)(\2)/gi.test(content) || /(setProperty\s*\(\s*(["'])min-height\2\s*,\s*(["']))([\s\S]*?vh)(\3\s*\))/gi.test(content);
120
+ if (!has_css_min_vh && !has_inline_style_vh && !has_js_vh) return content;
121
+ const convertVhToVariable = (value)=>value.replace(/(\d+(?:\.\d+)?)vh\b/gi, (match, value)=>{
122
+ const parsed = parseFloat(value);
123
+ if (!isFinite(parsed)) return match;
124
+ const VARIABLE_EXPRESSION = "var(--MJ-viewport-height)";
125
+ if (100 === parsed) return VARIABLE_EXPRESSION;
126
+ return `calc(${VARIABLE_EXPRESSION} * ${parsed / 100})`;
127
+ });
128
+ content = content.replace(/(min-height\s*:\s*)([^;{}]*?\d+(?:\.\d+)?vh)(?=\s*[;}])/gi, (_m, prefix, value)=>`${prefix}${convertVhToVariable(value)}`);
129
+ content = content.replace(/(style\s*=\s*(["']))([^"'"]*?)(\2)/gi, (match, prefix, _quote, styleContent, suffix)=>{
130
+ if (!/min-height\s*:\s*[^;]*vh/i.test(styleContent)) return match;
131
+ const replaced = styleContent.replace(/(min-height\s*:\s*)([^;]*?\d+(?:\.\d+)?vh)/gi, (_m, p1, p2)=>`${p1}${convertVhToVariable(p2)}`);
132
+ return `${prefix}${replaced}${suffix}`;
133
+ });
134
+ content = content.replace(/(\.style\.minHeight\s*=\s*(["']))([\s\S]*?)(\2)/gi, (match, prefix, _q, val, suffix)=>{
135
+ if (!/\b\d+(?:\.\d+)?vh\b/i.test(val)) return match;
136
+ const converted = convertVhToVariable(val);
137
+ return `${prefix}${converted}${suffix}`;
138
+ });
139
+ content = content.replace(/(setProperty\s*\(\s*(["'])min-height\2\s*,\s*(["']))([\s\S]*?)(\3\s*\))/gi, (match, prefix, _q1, _q2, val, suffix)=>{
140
+ if (!/\b\d+(?:\.\d+)?vh\b/i.test(val)) return match;
141
+ const converted = convertVhToVariable(val);
142
+ return `${prefix}${converted}${suffix}`;
143
+ });
144
+ return content;
145
+ };
146
+ function escapeHtmlAttribute(value) {
147
+ return value.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
148
+ }
149
+ function createSrcContent(content, iframeId, unsafe, extra) {
150
+ content = replaceVhInContent(content);
151
+ return `
152
+ <html>
153
+ <head>
154
+ <meta charset="utf-8">
155
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
156
+ <style>
157
+ *, *::before, *::after { box-sizing: border-box; }
158
+ html,body{margin:0!important;padding:0;overflow:hidden!important;max-width:100%!important;}
159
+ </style>
160
+ ${thirdParty}
161
+ <script>
162
+ ${init(iframeId, unsafe, extra)}
163
+ ${adjustIframeHeight(iframeId)}
164
+ ${iframeAdjustViewport(window.innerHeight)}
165
+ </script>
166
+ </head>
167
+ <body>
168
+ ${content}
169
+ </body>
170
+ </html>
171
+ `;
172
+ }
173
+ const Log = {
174
+ i (...msg) {
175
+ console.log('[MujianSDK] ', ...msg);
176
+ },
177
+ e (...msg) {
178
+ console.error('[MujianSDK] ', ...msg);
179
+ }
180
+ };
9
181
  addDOMPurifyHooks();
10
182
  function canUseNegativeLookbehind() {
11
183
  try {
@@ -95,7 +267,7 @@ function addDOMPurifyHooks() {
95
267
  * @returns {string} Encoded message text
96
268
  * @copyright https://github.com/kwaroran/risuAI
97
269
  */ function encodeStyleTags(text) {
98
- const styleRegex = /<style>(.+?)<\/style>/gi;
270
+ const styleRegex = /<style>([\s\S]+?)<\/style>/gi;
99
271
  return text.replaceAll(styleRegex, (_, match)=>`<custom-style>${encodeURIComponent(match)}</custom-style>`);
100
272
  }
101
273
  /**
@@ -159,7 +331,7 @@ function addDOMPurifyHooks() {
159
331
  const markdownUnderscoreExt = ()=>{
160
332
  try {
161
333
  if (!canUseNegativeLookbehind()) {
162
- console.log('Showdown-underscore extension: Negative lookbehind not supported. Skipping.');
334
+ Log.i('Showdown-underscore extension: Negative lookbehind not supported. Skipping.');
163
335
  return [];
164
336
  }
165
337
  return [
@@ -234,31 +406,187 @@ function messageFormatting(mes, sanitizerOverrides = {}) {
234
406
  });
235
407
  return mes;
236
408
  }
237
- const MdRendererBase = ({ content })=>{
238
- const mes = messageFormatting(content);
409
+ const loadingTranslates = {
410
+ 'zh-hans': '加载中...',
411
+ 'zh-hant': '載入中...',
412
+ en: 'Loading...',
413
+ ja: '読み込み中...',
414
+ th: 'กำลังโหลด...'
415
+ };
416
+ const MdRendererBase = ({ content, unsafe = false, extra = {}, lang })=>{
417
+ const containerRef = useRef(null);
418
+ const [iframeIdList, setIframeIdList] = useState([]);
419
+ useEffect(()=>{
420
+ let mes = messageFormatting(content);
421
+ mes = mes.replace(/<pre><code(.*)>[\s\S]*?<\/code><\/pre>/g, (match)=>{
422
+ if (!match.includes('&lt;body&gt;') && !match.includes('&lt;/body&gt;')) return match;
423
+ const code = match.replace(/<pre><code(.*?)>/g, '').replace(/<\/code><\/pre>/g, '');
424
+ const id = v4();
425
+ const containerId = `MJ-iframe-container-${id}`;
426
+ const iframeId = `MJ-iframe-${id}`;
427
+ const srcdoc = createSrcContent(unescapeHTML(code), iframeId, unsafe, extra);
428
+ setIframeIdList((prev)=>[
429
+ ...prev,
430
+ id
431
+ ]);
432
+ const escapedSrcdoc = escapeHtmlAttribute(srcdoc);
433
+ const loadingText = loadingTranslates[lang ?? 'zh-hans'] || loadingTranslates['zh-hans'];
434
+ return `
435
+ <div class="MJ-iframe-container" id="${containerId}" style="display:flex;width:100%;height:100%;position:relative;">
436
+ <div class="spin-overlay" id="MJ-spin-${id}" style="width:100%;height:100%;position:absolute;top:0;left:0;z-index:1000;background-color:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;">
437
+ <div class="spin-inner wave-text" style="font-weight:500;font-size:16px;color:white;display:flex;justify-content:center;align-items:center;">
438
+ ${loadingText.split('').map((t)=>`<span>${t}</span>`).join('')}
439
+ </div>
440
+ </div>
441
+ <iframe id="${iframeId}" class="w-full" sandbox="${unsafe ? "allow-scripts" : "allow-scripts allow-same-origin"}" loading="lazy"
442
+ referrerpolicy="no-referrer" allowTransparency="true"
443
+ style="color-scheme: none;background-color: transparent;width:100%;;border:none;" srcdoc="` + escapedSrcdoc + `"></iframe>
444
+ </div>`;
445
+ });
446
+ if (containerRef.current) containerRef.current.innerHTML = mes;
447
+ }, [
448
+ content
449
+ ]);
450
+ useEffect(()=>{
451
+ window.addEventListener('message', function(event) {
452
+ if (event.data?.type === 'MJ_ADJUST_IFRAME_HEIGHT' && event.data?.iframe_id) {
453
+ const targetIframe = containerRef.current?.querySelector('#' + event.data.iframe_id);
454
+ if (!targetIframe) return;
455
+ targetIframe.style.height = `${event.data.height}px`;
456
+ }
457
+ });
458
+ window.addEventListener('resize', ()=>{
459
+ iframeIdList.forEach((id)=>{
460
+ const iframe = containerRef.current?.querySelector('#MJ-iframe-' + id);
461
+ if (!iframe) return;
462
+ iframe.contentWindow?.postMessage({
463
+ type: 'MJ_UPDATE_VIEWPORT_HEIGHT'
464
+ }, '*');
465
+ });
466
+ });
467
+ }, []);
468
+ useEffect(()=>{
469
+ iframeIdList.forEach((id)=>{
470
+ const iframe = containerRef.current?.querySelector('#MJ-iframe-' + id);
471
+ if (!iframe) return;
472
+ const removeSpin = ()=>{
473
+ const spinElement = iframe.parentElement?.querySelector('#MJ-spin-' + id);
474
+ if (spinElement) spinElement.remove();
475
+ };
476
+ const timeout = setTimeout(removeSpin, 5000);
477
+ iframe.addEventListener('load', ()=>{
478
+ clearTimeout(timeout);
479
+ removeSpin();
480
+ });
481
+ });
482
+ return ()=>{
483
+ iframeIdList.forEach((id)=>{
484
+ const iframe = containerRef.current?.querySelector('#MJ-iframe-' + id);
485
+ if (!iframe) return;
486
+ const spinElement = iframe.parentElement?.querySelector('#MJ-spin-' + iframe.id);
487
+ if (spinElement) spinElement.remove();
488
+ iframe.removeEventListener('load', ()=>{
489
+ Log.i('iframe loaded', iframe.id);
490
+ });
491
+ });
492
+ };
493
+ }, [
494
+ iframeIdList
495
+ ]);
239
496
  return /*#__PURE__*/ jsx("div", {
240
497
  className: "mes_text",
241
- dangerouslySetInnerHTML: {
242
- __html: mes
243
- }
498
+ ref: containerRef
244
499
  });
245
500
  };
246
501
  const MdRenderer = /*#__PURE__*/ react.memo(MdRendererBase, (prev, next)=>prev.content === next.content);
247
502
  MdRendererBase.displayName = 'MdRenderer';
248
503
  MdRenderer.displayName = 'MdRenderer';
249
- const chat_complete = async function(message, onData, signal) {
250
- return await this.call("mujian:ai:chat:complete", {
504
+ var events_EVENT = /*#__PURE__*/ function(EVENT) {
505
+ EVENT["MUJIAN_INIT"] = "mujian:init";
506
+ EVENT["MUJIAN_AI_CHAT_STOP"] = "mujian:ai:chat:stop";
507
+ EVENT["MUJIAN_AI_CHAT_COMPLETE"] = "mujian:ai:chat:complete";
508
+ EVENT["MUJIAN_AI_CHAT_APPLY_REGEX"] = "mujian:ai:chat:applyRegex";
509
+ EVENT["MUJIAN_AI_CHAT_RENDER_MESSAGE"] = "mujian:ai:chat:renderMessage";
510
+ EVENT["MUJIAN_AI_TEXT_GENERATE"] = "mujian:ai:text:generate";
511
+ EVENT["MUJIAN_AI_OPENAI_COMPLETIONS_CREATE"] = "mujian:ai:openai:completions:create";
512
+ EVENT["MUJIAN_AI_OPENAI_CHAT_COMPLETIONS_CREATE"] = "mujian:ai:openai:chat:completions:create";
513
+ EVENT["MUJIAN_AI_OPENAI_RESPONSES_CREATE"] = "mujian:ai:openai:responses:create";
514
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_GET_ALL"] = "mujian:ai:chat:message:getAll";
515
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_GET_PAGE"] = "mujian:ai:chat:message:getPage";
516
+ EVENT["MUJIAN_AI_CHAT_PROJECT_GET_INFO"] = "mujian:ai:chat:project:getInfo";
517
+ EVENT["MUJIAN_AI_SETTINGS_PERSONA_GET_ACTIVE"] = "mujian:ai:settings:persona:getActive";
518
+ EVENT["MUJIAN_AI_SETTINGS_PERSONA_SET_ACTIVE"] = "mujian:ai:settings:persona:setActive";
519
+ EVENT["MUJIAN_AI_SETTINGS_MODEL_GET_ALL"] = "mujian:ai:settings:model:getAll";
520
+ EVENT["MUJIAN_AI_SETTINGS_MODEL_SET_ACTIVE"] = "mujian:ai:settings:model:setActive";
521
+ EVENT["MUJIAN_AI_SETTINGS_MODEL_GET_ACTIVE"] = "mujian:ai:settings:model:getActive";
522
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_DELETE_ONE"] = "mujian:ai:chat:message:deleteOne";
523
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_EDIT_ONE"] = "mujian:ai:chat:message:editOne";
524
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_SWIPE"] = "mujian:ai:chat:message:swipe";
525
+ EVENT["MUJIAN_AI_OPENAI_IMAGES_GENERATE"] = "mujian:ai:openai:images:generate";
526
+ EVENT["MUJIAN_UTILS_CLIPBOARD_WRITE_TEXT"] = "mujian:utils:clipboard:writeText";
527
+ EVENT["MUJIAN_GET_CONFIG"] = "mujian:getConfig";
528
+ return EVENT;
529
+ }({});
530
+ function wrapOnData(onData) {
531
+ let fullContent = '';
532
+ let buffer = '';
533
+ let questionId;
534
+ let replyId;
535
+ return function(data) {
536
+ buffer += data;
537
+ const lines = buffer.split('\n');
538
+ buffer = lines.pop() || '';
539
+ for (const line of lines)if (line.startsWith('data: ')) try {
540
+ const parsedData = JSON.parse(line.slice(6));
541
+ if (parsedData.question_id) questionId = parsedData.question_id;
542
+ if (parsedData.reply_id) replyId = parsedData.reply_id;
543
+ if (parsedData.isFinished) return void onData({
544
+ isFinished: true,
545
+ deltaContent: '',
546
+ fullContent,
547
+ questionId,
548
+ replyId
549
+ });
550
+ const deltaContent = parsedData?.choices?.[0]?.delta?.content;
551
+ if (deltaContent?.length > 0) {
552
+ fullContent += deltaContent;
553
+ onData({
554
+ isFinished: false,
555
+ deltaContent: deltaContent,
556
+ fullContent,
557
+ questionId,
558
+ replyId
559
+ });
560
+ }
561
+ } catch (e) {
562
+ onData({
563
+ isFinished: true,
564
+ error: e,
565
+ deltaContent: '',
566
+ fullContent,
567
+ questionId,
568
+ replyId
569
+ });
570
+ return;
571
+ }
572
+ };
573
+ }
574
+ const chat_complete = async function(message, onData, signal, option = {}) {
575
+ await this.call(events_EVENT.MUJIAN_AI_CHAT_COMPLETE, {
251
576
  content: message
252
577
  }, {
253
- onData,
578
+ onData: option.parseContent ? wrapOnData(onData) : onData,
254
579
  signal
255
580
  });
256
581
  };
257
582
  const applyRegex = async function(props) {
258
- return await this.call("mujian:ai:chat:applyRegex", props);
583
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_APPLY_REGEX, props);
584
+ };
585
+ const renderMessage = async function(props) {
586
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_RENDER_MESSAGE, props);
259
587
  };
260
588
  const continueComplete = async function(onData, signal) {
261
- return await this.call("mujian:ai:chat:complete", {
589
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_COMPLETE, {
262
590
  isContinue: true
263
591
  }, {
264
592
  onData,
@@ -266,7 +594,7 @@ const continueComplete = async function(onData, signal) {
266
594
  });
267
595
  };
268
596
  const chat_regenerate = async function(onData, signal) {
269
- return await this.call("mujian:ai:chat:complete", {
597
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_COMPLETE, {
270
598
  isRegenerate: true
271
599
  }, {
272
600
  onData,
@@ -274,61 +602,99 @@ const chat_regenerate = async function(onData, signal) {
274
602
  });
275
603
  };
276
604
  const getAll = async function() {
277
- return await this.call("mujian:ai:chat:message:getAll");
605
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_GET_ALL);
278
606
  };
279
607
  const messageDeleteOne = async function(messageId) {
280
- return await this.call("mujian:ai:chat:message:deleteOne", {
608
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_DELETE_ONE, {
281
609
  messageId
282
610
  });
283
611
  };
284
612
  const messageEditOne = async function(messageId, content) {
285
- return await this.call("mujian:ai:chat:message:editOne", {
613
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_EDIT_ONE, {
286
614
  messageId,
287
615
  content
288
616
  });
289
617
  };
290
618
  const messageSwipe = async function(messageId, swipeId) {
291
- return await this.call("mujian:ai:chat:message:swipe", {
619
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_SWIPE, {
292
620
  messageId,
293
621
  swipeId
294
622
  });
295
623
  };
296
- const getPrompt = async function(messageId) {
297
- return await this.call("mujian:ai:chat:message:getPrompt", {
298
- messageId
624
+ async function getPage(fromCursor, pageSize) {
625
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_GET_PAGE, {
626
+ fromCursor,
627
+ pageSize
299
628
  });
300
- };
629
+ }
301
630
  const getInfo = async function() {
302
- return await this.call("mujian:ai:chat:project:getInfo");
631
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_PROJECT_GET_INFO);
303
632
  };
304
633
  const getActive = async function() {
305
- return await this.call("mujian:ai:settings:persona:getActive");
634
+ return await this.call(events_EVENT.MUJIAN_AI_SETTINGS_PERSONA_GET_ACTIVE);
306
635
  };
307
- const setActive = async function(personaId) {
308
- return await this.call("mujian:ai:settings:persona:setActive", {
309
- personaId
636
+ const generate = async function(content) {
637
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_TEXT_GENERATE, {
638
+ content
310
639
  });
311
640
  };
312
- const model_getActive = async function() {
313
- return await this.call("mujian:ai:settings:model:getActive");
641
+ const chat = {
642
+ completions: {
643
+ create: async function(params, options, onData, signal) {
644
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_CHAT_COMPLETIONS_CREATE, {
645
+ params,
646
+ options
647
+ }, {
648
+ onData,
649
+ signal
650
+ });
651
+ }
652
+ }
314
653
  };
315
- const model_setActive = async function(modelId) {
316
- return await this.call("mujian:ai:settings:model:setActive", {
317
- modelId
318
- });
654
+ const completions = {
655
+ create: async function(params, options, onData, signal) {
656
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_COMPLETIONS_CREATE, {
657
+ params,
658
+ options
659
+ }, {
660
+ onData,
661
+ signal
662
+ });
663
+ }
319
664
  };
320
- const model_getAll = async function() {
321
- return await this.call("mujian:ai:settings:model:getAll");
665
+ const responses = {
666
+ create: async function(params, options, onData, signal) {
667
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_RESPONSES_CREATE, {
668
+ params,
669
+ options
670
+ }, {
671
+ onData,
672
+ signal
673
+ });
674
+ }
322
675
  };
323
- const generate = async function() {
324
- return await this.call("mujian:ai:text:generate", {
325
- content: 'q'
326
- });
676
+ const images_images = {
677
+ generate: async function(params, options, onData, signal) {
678
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_IMAGES_GENERATE, {
679
+ params,
680
+ options
681
+ }, {
682
+ onData,
683
+ signal
684
+ });
685
+ }
327
686
  };
687
+ async function getConfig() {
688
+ return await this.call(events_EVENT.MUJIAN_GET_CONFIG);
689
+ }
328
690
  const saveGame = async function() {};
329
691
  const loadGame = async function() {};
692
+ async function writeText(text) {
693
+ return await this.call(events_EVENT.MUJIAN_UTILS_CLIPBOARD_WRITE_TEXT, text);
694
+ }
330
695
  class MujianSdk {
331
696
  constructor(){}
697
+ static EVENT = events_EVENT;
332
698
  static getInstance() {
333
699
  if (!window.$mujian) window.$mujian = new MujianSdk();
334
700
  return window.$mujian;
@@ -339,33 +705,59 @@ class MujianSdk {
339
705
  return this.ready;
340
706
  }
341
707
  pendingRequests = new Map();
708
+ _config;
709
+ get openapi() {
710
+ return this._config?.openapi;
711
+ }
712
+ initPromise = null;
342
713
  async init() {
343
- const handshake = new postmate.Model({
344
- reply: ({ id, complete, data, error })=>{
345
- const call = this.pendingRequests.get(id);
346
- if (!call) return;
347
- if (error) {
348
- call.reject(error);
349
- this.pendingRequests.delete(id);
350
- return;
714
+ if (this.initPromise) return this.initPromise;
715
+ this.initPromise = (async ()=>{
716
+ const handshake = new postmate.Model({
717
+ reply: ({ id, complete, data, error })=>{
718
+ const call = this.pendingRequests.get(id);
719
+ if (!call) return;
720
+ if (error) {
721
+ call.reject(error);
722
+ this.pendingRequests.delete(id);
723
+ return;
724
+ }
725
+ call.onData?.(data);
726
+ if (complete) {
727
+ call.onComplete?.();
728
+ call.resolve(data);
729
+ this.pendingRequests.delete(id);
730
+ }
351
731
  }
352
- call.onData?.(data);
353
- if (complete) {
354
- call.onComplete?.();
355
- call.resolve(data);
356
- this.pendingRequests.delete(id);
732
+ });
733
+ try {
734
+ const parent = await handshake;
735
+ this.ready = true;
736
+ this.parent = parent;
737
+ Log.i('mujian sdk client init');
738
+ await this.call(events_EVENT.MUJIAN_INIT);
739
+ const projectInfo = await this.ai.chat.project.getInfo();
740
+ if (projectInfo.config?.customCss) {
741
+ const style = document.createElement('style');
742
+ style.setAttribute('type', 'text/css');
743
+ style.setAttribute('id', 'mujian-custom-css');
744
+ style.textContent = projectInfo.config.customCss;
745
+ document.head.appendChild(style);
746
+ }
747
+ if (projectInfo.config?.customJs) {
748
+ const script = document.createElement("script");
749
+ script.setAttribute('type', "text/javascript");
750
+ script.setAttribute('id', 'mujian-custom-js');
751
+ script.textContent = projectInfo.config.customJs;
752
+ document.head.appendChild(script);
357
753
  }
754
+ const config = await getConfig.call(this);
755
+ if (config) this._config = config;
756
+ } catch (error) {
757
+ Log.e('init error', error);
358
758
  }
359
- });
360
- try {
361
- const parent = await handshake;
362
- this.ready = true;
363
- this.parent = parent;
364
- console.log('mujian sdk client init');
365
- await this.call("mujian:init");
366
- } catch (error) {
367
- console.log('init error', error);
368
- }
759
+ })();
760
+ return this.initPromise;
369
761
  }
370
762
  emit(event, data) {
371
763
  if (!this.ready) throw new Error('Mujian is not initialized');
@@ -391,7 +783,7 @@ class MujianSdk {
391
783
  data
392
784
  });
393
785
  controller?.signal?.addEventListener('abort', ()=>{
394
- this.emit("mujian:ai:chat:stop", {
786
+ this.emit(events_EVENT.MUJIAN_AI_CHAT_STOP, {
395
787
  id: callId
396
788
  });
397
789
  });
@@ -403,18 +795,13 @@ class MujianSdk {
403
795
  getInfo: getInfo.bind(this)
404
796
  },
405
797
  settings: {
406
- model: {
407
- getAll: model_getAll.bind(this),
408
- getActive: model_getActive.bind(this),
409
- setActive: model_setActive.bind(this)
410
- },
411
798
  persona: {
412
- getActive: getActive.bind(this),
413
- setActive: setActive.bind(this)
799
+ getActive: getActive.bind(this)
414
800
  }
415
801
  },
416
802
  complete: chat_complete.bind(this),
417
803
  applyRegex: applyRegex.bind(this),
804
+ renderMessage: renderMessage.bind(this),
418
805
  continue: continueComplete.bind(this),
419
806
  regenerate: chat_regenerate.bind(this),
420
807
  message: {
@@ -422,17 +809,33 @@ class MujianSdk {
422
809
  deleteOne: messageDeleteOne.bind(this),
423
810
  editOne: messageEditOne.bind(this),
424
811
  swipe: messageSwipe.bind(this),
425
- getPrompt: getPrompt.bind(this)
812
+ getPage: getPage.bind(this)
426
813
  }
427
814
  },
428
815
  text: {
429
816
  complete: generate.bind(this)
817
+ },
818
+ openai: {
819
+ completions: {
820
+ create: completions.create.bind(this)
821
+ },
822
+ chat: {
823
+ completions: {
824
+ create: chat.completions.create.bind(this)
825
+ }
826
+ },
827
+ responses: {
828
+ create: responses.create.bind(this)
829
+ },
830
+ images: {
831
+ generate: images_images.generate.bind(this)
832
+ }
430
833
  }
431
834
  };
432
835
  ui = {};
433
- util = {
434
- cn: ()=>{
435
- console.log('cn');
836
+ utils = {
837
+ clipboard: {
838
+ writeText: writeText.bind(this)
436
839
  }
437
840
  };
438
841
  hybrid = {};
@@ -450,6 +853,43 @@ class MujianSdk {
450
853
  }
451
854
  };
452
855
  }
856
+ const styleSheet = `
857
+ @keyframes spin {
858
+ 0% { transform: rotate(0deg); }
859
+ 100% { transform: rotate(360deg); }
860
+ }
861
+ `;
862
+ const spinnerStyle = {
863
+ width: 48,
864
+ height: 48,
865
+ animation: 'spin 1s linear infinite'
866
+ };
867
+ const MujianSpinner = ({ className, style })=>/*#__PURE__*/ jsxs(Fragment, {
868
+ children: [
869
+ /*#__PURE__*/ jsx("style", {
870
+ children: styleSheet
871
+ }),
872
+ /*#__PURE__*/ jsx("svg", {
873
+ xmlns: "http://www.w3.org/2000/svg",
874
+ width: "24",
875
+ height: "24",
876
+ viewBox: "0 0 24 24",
877
+ fill: "none",
878
+ stroke: "currentColor",
879
+ strokeWidth: "2",
880
+ strokeLinecap: "round",
881
+ strokeLinejoin: "round",
882
+ className: className,
883
+ style: {
884
+ ...spinnerStyle,
885
+ ...style
886
+ },
887
+ children: /*#__PURE__*/ jsx("path", {
888
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
889
+ })
890
+ })
891
+ ]
892
+ });
453
893
  const MujianContext = /*#__PURE__*/ createContext(null);
454
894
  const MujianProvider = ({ children, loadingComponent })=>{
455
895
  const [mujian, setMujian] = useState(null);
@@ -462,7 +902,20 @@ const MujianProvider = ({ children, loadingComponent })=>{
462
902
  }, []);
463
903
  return /*#__PURE__*/ jsx(MujianContext.Provider, {
464
904
  value: mujian,
465
- children: mujian ? children : loadingComponent ?? 'Mujian is loading'
905
+ children: mujian ? children : loadingComponent ?? /*#__PURE__*/ jsx("div", {
906
+ style: {
907
+ height: '100%',
908
+ width: '100%',
909
+ display: 'flex',
910
+ justifyContent: 'center',
911
+ alignItems: 'center'
912
+ },
913
+ children: /*#__PURE__*/ jsx(MujianSpinner, {
914
+ style: {
915
+ color: '#EC4342'
916
+ }
917
+ })
918
+ })
466
919
  });
467
920
  };
468
921
  const useMujian = ()=>{
@@ -480,7 +933,7 @@ const MessageItem = (props)=>{
480
933
  const { data: renderedMessage } = useRequest(async ()=>{
481
934
  const { isStreaming } = message;
482
935
  if (isStreaming) return message;
483
- return await mujian.ai.chat.applyRegex({
936
+ return await mujian.ai.chat.renderMessage({
484
937
  message,
485
938
  depth,
486
939
  index
@@ -509,28 +962,116 @@ const Thread = {
509
962
  MessageItem: MessageItem,
510
963
  MessageList: MessageList
511
964
  };
512
- class SendMessageError extends Error {
513
- constructor(message, cause){
514
- super(message);
515
- this.name = this.constructor.name;
516
- this.cause = cause;
965
+ let $mj_ai_chat_complete;
966
+ let $mj_ai_chat_project;
967
+ let $mj_ai_chat_settings_persona;
968
+ let $mj_ai_chat_setFirstMesIndex;
969
+ let $mj_ai_chat_mjv_get;
970
+ let $mj_ai_chat_mjv_getAll;
971
+ const $mj_engine = {
972
+ ai: {
973
+ chat: {
974
+ get complete () {
975
+ return $mj_ai_chat_complete;
976
+ },
977
+ get project () {
978
+ return $mj_ai_chat_project;
979
+ },
980
+ settings: {
981
+ get persona () {
982
+ return $mj_ai_chat_settings_persona;
983
+ }
984
+ },
985
+ get setFirstMesIndex () {
986
+ return $mj_ai_chat_setFirstMesIndex;
987
+ }
988
+ }
517
989
  }
518
- }
519
- class InvalidDeltaContentError extends Error {
520
- constructor(message, cause){
521
- super(message);
522
- this.name = this.constructor.name;
523
- this.cause = cause;
990
+ };
991
+ window.$mj_engine = {
992
+ ...$mj_engine,
993
+ bind ({ messageId, swipeId }) {
994
+ return {
995
+ ai: {
996
+ chat: {
997
+ mjv: {
998
+ get (path) {
999
+ return $mj_ai_chat_mjv_get?.(messageId, swipeId, path);
1000
+ },
1001
+ getAll () {
1002
+ return $mj_ai_chat_mjv_getAll?.(messageId, swipeId) ?? {};
1003
+ }
1004
+ },
1005
+ get complete () {
1006
+ return $mj_ai_chat_complete;
1007
+ },
1008
+ get project () {
1009
+ return $mj_ai_chat_project;
1010
+ },
1011
+ settings: {
1012
+ get persona () {
1013
+ return $mj_ai_chat_settings_persona;
1014
+ }
1015
+ },
1016
+ get setFirstMesIndex () {
1017
+ return $mj_ai_chat_setFirstMesIndex;
1018
+ }
1019
+ }
1020
+ }
1021
+ };
524
1022
  }
1023
+ };
1024
+ function useMjEngine({ projectInfo, activePersona, onSend, messages, setSwipe }) {
1025
+ useEffect(()=>{
1026
+ $mj_ai_chat_project = projectInfo ?? void 0;
1027
+ $mj_ai_chat_settings_persona = activePersona ?? void 0;
1028
+ }, [
1029
+ projectInfo,
1030
+ activePersona
1031
+ ]);
1032
+ useEffect(()=>{
1033
+ $mj_ai_chat_complete = async (message)=>{
1034
+ await onSend(message);
1035
+ };
1036
+ $mj_ai_chat_setFirstMesIndex = async (oneBasedIndex)=>{
1037
+ if (messages.length > 1) throw new Error('已有新消息,不允许切换开场白');
1038
+ if (0 === messages.length) throw new Error('断言失败:没有开场消息,无法切换开场白');
1039
+ const zeroBasedIndex = Math.floor(oneBasedIndex) - 1;
1040
+ const swipeLength = messages[0].swipes.length;
1041
+ if (zeroBasedIndex < 0 || zeroBasedIndex >= swipeLength) return;
1042
+ setSwipe(messages[0].id, zeroBasedIndex);
1043
+ };
1044
+ $mj_ai_chat_mjv_getAll = (messageId, swipeId)=>messages.find((m)=>m.id === messageId)?.swipeInfo[swipeId]?.mjv?.value;
1045
+ $mj_ai_chat_mjv_get = (messageId, swipeId, path)=>{
1046
+ const mjv = $mj_ai_chat_mjv_getAll?.(messageId, swipeId);
1047
+ if (!mjv) return;
1048
+ return get(mjv, path);
1049
+ };
1050
+ }, [
1051
+ onSend,
1052
+ messages,
1053
+ setSwipe
1054
+ ]);
525
1055
  }
526
- class UnexpectedSseEndingError extends Error {
1056
+ const errorMessages = [
1057
+ 'network error',
1058
+ 'NetworkError when attempting to fetch resource.',
1059
+ 'The Internet connection appears to be offline.',
1060
+ 'Network request failed',
1061
+ 'Load failed',
1062
+ 'Failed to fetch'
1063
+ ];
1064
+ function isNetworkError(message) {
1065
+ return errorMessages.some((m)=>message.includes(m));
1066
+ }
1067
+ class SendMessageError extends Error {
527
1068
  constructor(message, cause){
528
1069
  super(message);
529
1070
  this.name = this.constructor.name;
530
1071
  this.cause = cause;
531
1072
  }
532
1073
  }
533
- class InsufficientBalanceError extends Error {
1074
+ class UnexpectedSseEndingError extends Error {
534
1075
  constructor(message, cause){
535
1076
  super(message);
536
1077
  this.name = this.constructor.name;
@@ -545,6 +1086,7 @@ async function* callSdk(mujian, content, type = 'generate', signal) {
545
1086
  try {
546
1087
  const onData = (data)=>{
547
1088
  if (error) return;
1089
+ if ('string' != typeof data) return;
548
1090
  queue.push(data);
549
1091
  if (resolveWait) {
550
1092
  resolveWait();
@@ -580,13 +1122,14 @@ async function* callSdk(mujian, content, type = 'generate', signal) {
580
1122
  });
581
1123
  }
582
1124
  const NOT_SAVED_MSG_ID_PREFIX = 'not_saved';
583
- const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
1125
+ const useChatStreaming = ({ common, setError, setMessages, getMessages, setInput, mujian })=>{
584
1126
  const { body } = common;
585
1127
  const [isStreaming, setIsStreaming] = useState(false);
586
1128
  const chatStreaming = useCallback(async ({ query, regenerate, is_continue, signal })=>{
587
1129
  try {
588
1130
  setError(void 0);
589
1131
  setIsStreaming(true);
1132
+ const backupMessages = getMessages();
590
1133
  const userMessage = {
591
1134
  id: `${NOT_SAVED_MSG_ID_PREFIX}_user_${Date.now()}`,
592
1135
  content: query || '',
@@ -594,34 +1137,35 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
594
1137
  swipes: [],
595
1138
  activeSwipeId: 0,
596
1139
  isStreaming: false,
597
- sendAt: new Date()
1140
+ sendAt: new Date(),
1141
+ swipeInfo: [],
1142
+ flags: {}
598
1143
  };
599
1144
  const aiMessage = {
600
1145
  id: `${NOT_SAVED_MSG_ID_PREFIX}_assistant_${Date.now()}`,
601
1146
  content: '',
602
1147
  role: 'assistant',
603
- swipes: [],
1148
+ swipes: [
1149
+ ''
1150
+ ],
604
1151
  activeSwipeId: 0,
605
1152
  isStreaming: true,
606
- sendAt: new Date()
1153
+ sendAt: new Date(),
1154
+ swipeInfo: [
1155
+ {}
1156
+ ],
1157
+ flags: {}
607
1158
  };
608
- if (regenerate) setMessages((prev)=>{
609
- const newMessages = [
610
- ...prev
611
- ];
612
- const lastMessage = newMessages[newMessages.length - 1];
613
- newMessages[newMessages.length - 1] = {
614
- ...lastMessage,
615
- activeSwipeId: lastMessage.swipes.length,
616
- swipes: [
617
- ...lastMessage.swipes,
618
- ''
619
- ],
620
- content: '',
621
- isStreaming: true
1159
+ if (regenerate) setMessages(produce((draft)=>{
1160
+ const lastMessage = draft[draft.length - 1];
1161
+ lastMessage.activeSwipeId = lastMessage.swipes.length;
1162
+ lastMessage.swipes.push('');
1163
+ lastMessage.content = '';
1164
+ lastMessage.isStreaming = true;
1165
+ lastMessage.flags = {
1166
+ saved: lastMessage.flags.saved
622
1167
  };
623
- return newMessages;
624
- });
1168
+ }));
625
1169
  else is_continue ? setMessages((prev)=>[
626
1170
  ...prev,
627
1171
  aiMessage
@@ -642,45 +1186,58 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
642
1186
  const data = line.slice(6);
643
1187
  try {
644
1188
  const parsedData = JSON.parse(data);
645
- if (!regenerate && parsedData.question_id) {
1189
+ if (regenerate || 'meta' !== parsedData.type) {
1190
+ if ('stream' === parsedData.type) {
1191
+ const partialContent = parsedData.choices[0].delta.content;
1192
+ parsedData.choices[0].delta.reasoning;
1193
+ const chunkFinishReason = parsedData.choices[0].finish_reason;
1194
+ content += partialContent;
1195
+ setMessages(produce((draft)=>{
1196
+ const lastMessage = draft[draft.length - 1];
1197
+ lastMessage.swipes[lastMessage.activeSwipeId] = content;
1198
+ lastMessage.content = content;
1199
+ lastMessage.isStreaming = true;
1200
+ if ('content_filter' === chunkFinishReason) lastMessage.flags.contentFilter = true;
1201
+ }));
1202
+ await new Promise((resolve)=>setTimeout(resolve, 10));
1203
+ } else if ('mjv' === parsedData.type) {
1204
+ const { value, delta, schema } = parsedData;
1205
+ setMessages(produce((draft)=>{
1206
+ const lastMessage = draft[draft.length - 1];
1207
+ const mjv = {
1208
+ value,
1209
+ delta,
1210
+ schema
1211
+ };
1212
+ const swipeInfo = lastMessage.swipeInfo;
1213
+ if (swipeInfo[lastMessage.activeSwipeId]) swipeInfo[lastMessage.activeSwipeId].mjv = mjv;
1214
+ else swipeInfo[lastMessage.activeSwipeId] = {
1215
+ mjv
1216
+ };
1217
+ lastMessage.swipeInfo = swipeInfo;
1218
+ lastMessage.mjv = mjv;
1219
+ }));
1220
+ }
1221
+ } else {
646
1222
  const { question_id, reply_id } = parsedData;
647
1223
  setMessages((prev)=>{
648
1224
  const newMessages = [
649
1225
  ...prev
650
1226
  ];
651
- if (question_id && !is_continue) newMessages[newMessages.length - 2].id = question_id;
652
- const lastMessage = newMessages[newMessages.length - 1];
653
- newMessages[newMessages.length - 1] = {
654
- ...lastMessage,
655
- id: reply_id
656
- };
657
1227
  return newMessages;
658
1228
  });
1229
+ setMessages(produce((draft)=>{
1230
+ if (question_id && !is_continue) {
1231
+ const userMsg = draft[draft.length - 2];
1232
+ userMsg.id = question_id;
1233
+ userMsg.flags.saved = true;
1234
+ }
1235
+ const lastMessage = draft[draft.length - 1];
1236
+ lastMessage.id = reply_id;
1237
+ lastMessage.flags.saved = true;
1238
+ }));
659
1239
  continue;
660
1240
  }
661
- if (!parsedData.choices?.[0]) continue;
662
- const partialContent = parsedData.choices[0].delta.content;
663
- parsedData.choices[0].delta.reasoning;
664
- content += partialContent;
665
- if (content || '' === content) {
666
- setMessages((prev)=>{
667
- const newMessages = [
668
- ...prev
669
- ];
670
- const lastMessage = newMessages[newMessages.length - 1];
671
- lastMessage.swipes[lastMessage.activeSwipeId] = content;
672
- newMessages[newMessages.length - 1] = {
673
- ...lastMessage,
674
- content: content,
675
- isStreaming: true
676
- };
677
- return newMessages;
678
- });
679
- await new Promise((resolve)=>setTimeout(resolve, 10));
680
- } else {
681
- console.error('data', data);
682
- setError(new InvalidDeltaContentError(data));
683
- }
684
1241
  } catch {
685
1242
  if ('[DONE]' !== data) {
686
1243
  console.error('Received plain SSE message:', data);
@@ -689,22 +1246,26 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
689
1246
  }
690
1247
  }
691
1248
  }
1249
+ setMessages(produce((draft)=>{
1250
+ const lastMessage = draft[draft.length - 1];
1251
+ if (!lastMessage.swipes[lastMessage.activeSwipeId] && !signal?.aborted && !lastMessage.flags.contentFilter) lastMessage.flags.emptyResponse = true;
1252
+ }));
1253
+ } catch (error) {
1254
+ Log.e('Stream error', error);
1255
+ if ('object' == typeof error && error && 'message' in error && 'string' == typeof error.message) if ('code' in error && 2011030001 === error.code) {
1256
+ setMessages(backupMessages);
1257
+ if (!regenerate && !is_continue && query) setInput(query);
1258
+ } else isNetworkError(error.message) ? setError(new SendMessageError('网络好像断开了,检查一下再试吧~')) : setError(new SendMessageError(error.message));
1259
+ else setError(error);
692
1260
  } finally{
693
- console.log('stream end finally');
694
- setMessages((prev)=>{
695
- const newMessages = [
696
- ...prev
697
- ];
698
- newMessages[newMessages.length - 1] = {
699
- ...newMessages[newMessages.length - 1],
700
- isStreaming: false
701
- };
702
- return newMessages;
703
- });
1261
+ setMessages(produce((draft)=>{
1262
+ const lastMessage = draft[draft.length - 1];
1263
+ lastMessage.isStreaming = false;
1264
+ }));
704
1265
  }
705
1266
  } catch (error) {
706
1267
  console.error('Error:', error);
707
- error instanceof Error && JSON.parse(error.message || '{}')?.code === 111 ? setError(new InsufficientBalanceError()) : setError(new SendMessageError('Send message failed', error));
1268
+ setError(new SendMessageError('Send message failed', error));
708
1269
  setMessages((prev)=>prev.slice(0, -1));
709
1270
  } finally{
710
1271
  setIsStreaming(false);
@@ -718,9 +1279,8 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
718
1279
  isStreaming
719
1280
  };
720
1281
  };
721
- const useChat = ({ body, onError, onFinish })=>{
1282
+ const useChat = ({ body, onError, onFinish, pageSize })=>{
722
1283
  const [error, _setError] = useState();
723
- const [messages, setMessages] = useState([]);
724
1284
  const [input, setInput] = useState('');
725
1285
  const [abortController, setAbortController] = useState();
726
1286
  const mujian = useMujian();
@@ -728,18 +1288,67 @@ const useChat = ({ body, onError, onFinish })=>{
728
1288
  _setError(error);
729
1289
  if (error) onError?.(error);
730
1290
  };
1291
+ const isLoadingMore = useRef(false);
1292
+ const { data, loadMoreAsync, loadingMore, loading, mutate, error: messageLoadError } = useInfiniteScroll(async (current)=>{
1293
+ isLoadingMore.current = true;
1294
+ if (current?.noMore) return {
1295
+ noMore: true,
1296
+ list: []
1297
+ };
1298
+ try {
1299
+ const resp = await mujian.ai.chat.message.getPage(current?.cursor, pageSize);
1300
+ return {
1301
+ cursor: resp.nextCursor,
1302
+ list: resp.messages.map((m)=>({
1303
+ ...m,
1304
+ mjv: m.swipeInfo[m.activeSwipeId]?.mjv,
1305
+ flags: {
1306
+ saved: true
1307
+ }
1308
+ })),
1309
+ noMore: !resp.nextCursor
1310
+ };
1311
+ } finally{
1312
+ isLoadingMore.current = false;
1313
+ }
1314
+ }, {
1315
+ direction: 'top'
1316
+ });
1317
+ const loadMoreMessage = async ()=>{
1318
+ if (isLoadingMore.current) return;
1319
+ try {
1320
+ isLoadingMore.current = true;
1321
+ await loadMoreAsync();
1322
+ } finally{
1323
+ isLoadingMore.current = false;
1324
+ }
1325
+ };
1326
+ const messages = data?.list ?? [];
1327
+ const latestData = useLatest(data);
1328
+ const latestListRef = useRef(messages);
1329
+ latestListRef.current = messages;
1330
+ const setMessages = (messages)=>{
1331
+ let newList;
1332
+ newList = 'function' == typeof messages ? messages(latestListRef.current) : messages;
1333
+ latestListRef.current = newList;
1334
+ mutate({
1335
+ ...latestData.current,
1336
+ list: newList
1337
+ });
1338
+ };
1339
+ const getMessages = useCallback(()=>latestListRef.current, [
1340
+ latestListRef
1341
+ ]);
731
1342
  const { chatStreaming, isStreaming } = useChatStreaming({
732
1343
  common: {
733
1344
  body
734
1345
  },
735
1346
  setError,
736
1347
  setMessages,
1348
+ getMessages,
1349
+ setInput,
737
1350
  mujian
738
1351
  });
739
- useMount(async ()=>{
740
- const resp = await mujian.ai.chat.message.getAll();
741
- setMessages(resp);
742
- });
743
1352
  useUpdateEffect(()=>{
744
1353
  if (!isStreaming) {
745
1354
  const lastMessage = messages[messages.length - 1];
@@ -748,48 +1357,79 @@ const useChat = ({ body, onError, onFinish })=>{
748
1357
  }, [
749
1358
  isStreaming
750
1359
  ]);
751
- const append = async (query)=>{
1360
+ const append = useCallback(async (query)=>{
752
1361
  if (isStreaming) throw new Error('useChat is streaming already.');
753
1362
  const controller = new AbortController();
754
1363
  setAbortController(controller);
755
- await chatStreaming({
756
- query,
757
- signal: controller.signal
758
- });
759
- setAbortController(void 0);
760
- };
761
- const regenerate = async ()=>{
1364
+ try {
1365
+ await chatStreaming({
1366
+ query,
1367
+ signal: controller.signal
1368
+ });
1369
+ } catch (e) {
1370
+ setError(e);
1371
+ throw e;
1372
+ } finally{
1373
+ setAbortController(void 0);
1374
+ }
1375
+ }, [
1376
+ isStreaming,
1377
+ chatStreaming
1378
+ ]);
1379
+ const regenerate = useCallback(async ()=>{
762
1380
  if (isStreaming) throw new Error('useChat is streaming already.');
763
1381
  const controller = new AbortController();
764
1382
  setAbortController(controller);
765
1383
  const lastMessage = messages.findLast((message)=>'user' === message.role);
766
- await chatStreaming({
767
- query: lastMessage?.content || '',
768
- regenerate: true,
769
- signal: controller.signal
770
- });
771
- setAbortController(void 0);
772
- };
773
- const continueGenerate = async ()=>{
1384
+ try {
1385
+ await chatStreaming({
1386
+ query: lastMessage?.content || '',
1387
+ regenerate: true,
1388
+ signal: controller.signal
1389
+ });
1390
+ } catch (e) {
1391
+ setError(e);
1392
+ throw e;
1393
+ } finally{
1394
+ setAbortController(void 0);
1395
+ }
1396
+ }, [
1397
+ isStreaming,
1398
+ messages,
1399
+ chatStreaming
1400
+ ]);
1401
+ const continueGenerate = useCallback(async ()=>{
774
1402
  if (isStreaming) throw new Error('useChat is streaming already.');
775
1403
  const controller = new AbortController();
776
1404
  setAbortController(controller);
777
- await chatStreaming({
778
- query: '',
779
- is_continue: true,
780
- signal: controller.signal
781
- });
782
- setAbortController(void 0);
783
- };
1405
+ try {
1406
+ await chatStreaming({
1407
+ query: '',
1408
+ is_continue: true,
1409
+ signal: controller.signal
1410
+ });
1411
+ } catch (e) {
1412
+ setError(e);
1413
+ throw e;
1414
+ } finally{
1415
+ setAbortController(void 0);
1416
+ }
1417
+ }, [
1418
+ isStreaming,
1419
+ chatStreaming
1420
+ ]);
784
1421
  const stop = ()=>{
785
1422
  abortController?.abort();
786
1423
  };
787
1424
  const setSwipe = async (messageId, swipeId)=>{
788
- await mujian.ai.chat.message.swipe(messageId, swipeId);
789
- setMessages((prev)=>prev.map((msg)=>msg.id === messageId ? {
1425
+ if (swipeId < 0) return;
1426
+ setMessages((prev)=>prev.map((msg)=>msg.id === messageId && swipeId < msg.swipes.length ? {
790
1427
  ...msg,
791
- activeSwipeId: swipeId
1428
+ activeSwipeId: swipeId,
1429
+ content: msg.swipes[swipeId],
1430
+ mjv: msg.swipeInfo[swipeId]?.mjv
792
1431
  } : msg));
1432
+ await mujian.ai.chat.message.swipe(messageId, swipeId);
793
1433
  };
794
1434
  const editMessage = async (messageId, content)=>{
795
1435
  await mujian.ai.chat.message.editOne(messageId, content);
@@ -809,7 +1449,11 @@ const useChat = ({ body, onError, onFinish })=>{
809
1449
  setMessages((prev)=>prev.map((msg)=>msg.id === messageId ? patchMessage(msg) : msg));
810
1450
  };
811
1451
  const deleteMessage = async (messageId)=>{
812
- if (!messageId.startsWith(NOT_SAVED_MSG_ID_PREFIX)) await mujian.ai.chat.message.deleteOne(messageId);
1452
+ if (!messageId.startsWith(NOT_SAVED_MSG_ID_PREFIX)) try {
1453
+ await mujian.ai.chat.message.deleteOne(messageId);
1454
+ } catch (e) {
1455
+ if (e?.code !== 2011030004) throw e;
1456
+ }
813
1457
  setMessages((prev)=>prev.filter((msg)=>msg.id !== messageId));
814
1458
  };
815
1459
  return {
@@ -832,7 +1476,9 @@ const useChat = ({ body, onError, onFinish })=>{
832
1476
  },
833
1477
  setSwipe,
834
1478
  editMessage,
835
- deleteMessage
1479
+ deleteMessage,
1480
+ loadMoreMessage,
1481
+ messagesStatus: messageLoadError ? 'error' : loadingMore || loading ? 'loading' : data?.noMore ? 'all-loaded' : 'has-more'
836
1482
  };
837
1483
  };
838
- export { MdRenderer, MujianProvider, Thread, useChat, useMujian };
1484
+ export { MdRenderer, MujianProvider, MujianSpinner, Thread, useChat, useMjEngine, useMujian };