@mujian/js-sdk 0.0.6-beta.7 → 0.0.6-beta.70

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