@mujian/js-sdk 0.0.6-beta.8 → 0.0.6-beta.81

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 (43) hide show
  1. package/dist/events/index.d.ts +34 -2
  2. package/dist/index.d.ts +17 -39
  3. package/dist/index.js +369 -30
  4. package/dist/lite.d.ts +74 -0
  5. package/dist/lite.js +120 -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 +4 -0
  9. package/dist/modules/ai/index.d.ts +3 -2
  10. package/dist/modules/ai/openai/chat.d.ts +25 -0
  11. package/dist/modules/ai/openai/completions.d.ts +19 -0
  12. package/dist/modules/ai/openai/images.d.ts +19 -0
  13. package/dist/modules/ai/openai/index.d.ts +4 -0
  14. package/dist/modules/ai/openai/responses.d.ts +20 -0
  15. package/dist/modules/ai/text/index.d.ts +9 -2
  16. package/dist/modules/config.d.ts +18 -0
  17. package/dist/modules/utils/clipboard.d.ts +5 -0
  18. package/dist/modules/utils/index.d.ts +4 -0
  19. package/dist/react/chat/useChat/index.d.ts +34 -8
  20. package/dist/react/chat/useChat/inner/chatStreaming.d.ts +5 -2
  21. package/dist/react/chat/useChat/message.d.ts +31 -13
  22. package/dist/react/components/MdRenderer/index.d.ts +6 -4
  23. package/dist/react/components/MdRenderer/utils/height.d.ts +0 -0
  24. package/dist/react/components/MdRenderer/utils/iframe.d.ts +9 -0
  25. package/dist/react/components/MdRenderer/utils/scripts.d.ts +4 -0
  26. package/dist/react/components/MujianSpinner/index.d.ts +7 -0
  27. package/dist/react/components/index.d.ts +1 -0
  28. package/dist/react/index.d.ts +1 -0
  29. package/dist/react/mjEngine/index.d.ts +48 -0
  30. package/dist/react.css +65 -4
  31. package/dist/react.js +841 -199
  32. package/dist/types/index.d.ts +38 -0
  33. package/dist/umd/index.js +2 -0
  34. package/dist/umd/index.js.LICENSE.txt +7 -0
  35. package/dist/umd/lite.js +2 -0
  36. package/dist/umd/lite.js.LICENSE.txt +7 -0
  37. package/dist/umd/react.css +1 -0
  38. package/dist/umd/react.js +138 -0
  39. package/dist/umd/react.js.LICENSE.txt +31 -0
  40. package/dist/utils/is-network-error.d.ts +1 -0
  41. package/dist/utils/log.d.ts +4 -0
  42. package/package.json +16 -6
  43. /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,60 +406,185 @@ function messageFormatting(mes, sanitizerOverrides = {}) {
234
406
  });
235
407
  return mes;
236
408
  }
237
- const unescapeHTML = (str)=>{
238
- const named = {
239
- amp: "&",
240
- lt: "<",
241
- gt: ">",
242
- quot: '"',
243
- apos: "'",
244
- nbsp: "\u00A0"
245
- };
246
- return str.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (_m, body)=>{
247
- if ("#" === body[0]) {
248
- const isHex = body[1]?.toLowerCase() === "x";
249
- const numStr = isHex ? body.slice(2) : body.slice(1);
250
- const codePoint = parseInt(numStr, isHex ? 16 : 10);
251
- if (Number.isFinite(codePoint)) try {
252
- return String.fromCodePoint(codePoint);
253
- } catch {}
254
- return _m;
255
- }
256
- const lower = body.toLowerCase();
257
- return Object.hasOwn(named, lower) ? named[lower] : _m;
258
- });
259
- };
260
- const MdRendererBase = ({ content })=>{
261
- let mes = messageFormatting(content);
262
- mes = mes.replace(/<code(.*)>[\s\S]*?<\/code>/g, (match)=>{
263
- const code = match.replace("<code>", "").replace("</code>", "");
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
- });
409
+ const MdRendererBase = ({ content, unsafe = false, extra = {} })=>{
410
+ const containerRef = useRef(null);
411
+ const [iframeIdList, setIframeIdList] = useState([]);
412
+ useEffect(()=>{
413
+ let mes = messageFormatting(content);
414
+ mes = mes.replace(/<pre><code(.*)>[\s\S]*?<\/code><\/pre>/g, (match)=>{
415
+ if (!match.includes('&lt;body&gt;') && !match.includes('&lt;/body&gt;')) return match;
416
+ const code = match.replace(/<pre><code(.*?)>/g, '').replace(/<\/code><\/pre>/g, '');
417
+ const id = v4();
418
+ const containerId = `MJ-iframe-container-${id}`;
419
+ const iframeId = `MJ-iframe-${id}`;
420
+ const srcdoc = createSrcContent(unescapeHTML(code), iframeId, unsafe, extra);
421
+ setIframeIdList((prev)=>[
422
+ ...prev,
423
+ id
424
+ ]);
425
+ const escapedSrcdoc = escapeHtmlAttribute(srcdoc);
426
+ return `
427
+ <div class="MJ-iframe-container" id="${containerId}" style="display:flex;width:100%;height:100%;position:relative;">
428
+ <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;">
429
+ <div class="spin-inner wave-text" style="font-weight:500;font-size:16px;color:white;display:flex;justify-content:center;align-items:center;">
430
+ <span>加</span>
431
+ <span>载</span>
432
+ <span>中</span>
433
+ <span>.</span>
434
+ <span>.</span>
435
+ <span>.</span>
436
+ </div>
437
+ </div>
438
+ <iframe id="${iframeId}" class="w-full" sandbox="${unsafe ? "allow-scripts" : "allow-scripts allow-same-origin"}" loading="lazy"
439
+ referrerpolicy="no-referrer" allowTransparency="true"
440
+ style="color-scheme: none;background-color: transparent;width:100%;;border:none;" srcdoc="` + escapedSrcdoc + `"></iframe>
441
+ </div>`;
442
+ });
443
+ if (containerRef.current) containerRef.current.innerHTML = mes;
444
+ }, [
445
+ content
446
+ ]);
447
+ useEffect(()=>{
448
+ window.addEventListener('message', function(event) {
449
+ if (event.data?.type === 'MJ_ADJUST_IFRAME_HEIGHT' && event.data?.iframe_id) {
450
+ const targetIframe = containerRef.current?.querySelector('#' + event.data.iframe_id);
451
+ if (!targetIframe) return;
452
+ targetIframe.style.height = `${event.data.height}px`;
453
+ }
454
+ });
455
+ window.addEventListener('resize', ()=>{
456
+ iframeIdList.forEach((id)=>{
457
+ const iframe = containerRef.current?.querySelector('#MJ-iframe-' + id);
458
+ if (!iframe) return;
459
+ iframe.contentWindow?.postMessage({
460
+ type: 'MJ_UPDATE_VIEWPORT_HEIGHT'
461
+ }, '*');
462
+ });
463
+ });
464
+ }, []);
465
+ useEffect(()=>{
466
+ iframeIdList.forEach((id)=>{
467
+ const iframe = containerRef.current?.querySelector('#MJ-iframe-' + id);
468
+ if (!iframe) return;
469
+ const removeSpin = ()=>{
470
+ const spinElement = iframe.parentElement?.querySelector('#MJ-spin-' + id);
471
+ if (spinElement) spinElement.remove();
472
+ };
473
+ const timeout = setTimeout(removeSpin, 5000);
474
+ iframe.addEventListener('load', ()=>{
475
+ clearTimeout(timeout);
476
+ removeSpin();
477
+ });
478
+ });
479
+ return ()=>{
480
+ iframeIdList.forEach((id)=>{
481
+ const iframe = containerRef.current?.querySelector('#MJ-iframe-' + id);
482
+ if (!iframe) return;
483
+ const spinElement = iframe.parentElement?.querySelector('#MJ-spin-' + iframe.id);
484
+ if (spinElement) spinElement.remove();
485
+ iframe.removeEventListener('load', ()=>{
486
+ Log.i('iframe loaded', iframe.id);
487
+ });
488
+ });
489
+ };
490
+ }, [
491
+ iframeIdList
492
+ ]);
268
493
  return /*#__PURE__*/ jsx("div", {
269
494
  className: "mes_text",
270
- dangerouslySetInnerHTML: {
271
- __html: mes
272
- }
495
+ ref: containerRef
273
496
  });
274
497
  };
275
498
  const MdRenderer = /*#__PURE__*/ react.memo(MdRendererBase, (prev, next)=>prev.content === next.content);
276
- MdRendererBase.displayName = "MdRenderer";
277
- MdRenderer.displayName = "MdRenderer";
278
- const chat_complete = async function(message, onData, signal) {
279
- return await this.call("mujian:ai:chat:complete", {
499
+ MdRendererBase.displayName = 'MdRenderer';
500
+ MdRenderer.displayName = 'MdRenderer';
501
+ var events_EVENT = /*#__PURE__*/ function(EVENT) {
502
+ EVENT["MUJIAN_INIT"] = "mujian:init";
503
+ EVENT["MUJIAN_AI_CHAT_STOP"] = "mujian:ai:chat:stop";
504
+ EVENT["MUJIAN_AI_CHAT_COMPLETE"] = "mujian:ai:chat:complete";
505
+ EVENT["MUJIAN_AI_CHAT_APPLY_REGEX"] = "mujian:ai:chat:applyRegex";
506
+ EVENT["MUJIAN_AI_CHAT_RENDER_MESSAGE"] = "mujian:ai:chat:renderMessage";
507
+ EVENT["MUJIAN_AI_TEXT_GENERATE"] = "mujian:ai:text:generate";
508
+ EVENT["MUJIAN_AI_OPENAI_COMPLETIONS_CREATE"] = "mujian:ai:openai:completions:create";
509
+ EVENT["MUJIAN_AI_OPENAI_CHAT_COMPLETIONS_CREATE"] = "mujian:ai:openai:chat:completions:create";
510
+ EVENT["MUJIAN_AI_OPENAI_RESPONSES_CREATE"] = "mujian:ai:openai:responses:create";
511
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_GET_ALL"] = "mujian:ai:chat:message:getAll";
512
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_GET_PAGE"] = "mujian:ai:chat:message:getPage";
513
+ EVENT["MUJIAN_AI_CHAT_PROJECT_GET_INFO"] = "mujian:ai:chat:project:getInfo";
514
+ EVENT["MUJIAN_AI_SETTINGS_PERSONA_GET_ACTIVE"] = "mujian:ai:settings:persona:getActive";
515
+ EVENT["MUJIAN_AI_SETTINGS_PERSONA_SET_ACTIVE"] = "mujian:ai:settings:persona:setActive";
516
+ EVENT["MUJIAN_AI_SETTINGS_MODEL_GET_ALL"] = "mujian:ai:settings:model:getAll";
517
+ EVENT["MUJIAN_AI_SETTINGS_MODEL_SET_ACTIVE"] = "mujian:ai:settings:model:setActive";
518
+ EVENT["MUJIAN_AI_SETTINGS_MODEL_GET_ACTIVE"] = "mujian:ai:settings:model:getActive";
519
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_DELETE_ONE"] = "mujian:ai:chat:message:deleteOne";
520
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_EDIT_ONE"] = "mujian:ai:chat:message:editOne";
521
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_SWIPE"] = "mujian:ai:chat:message:swipe";
522
+ EVENT["MUJIAN_AI_CHAT_MESSAGE_GET_PROMPT"] = "mujian:ai:chat:message:getPrompt";
523
+ EVENT["MUJIAN_AI_OPENAI_IMAGES_GENERATE"] = "mujian:ai:openai:images:generate";
524
+ EVENT["MUJIAN_UTILS_CLIPBOARD_WRITE_TEXT"] = "mujian:utils:clipboard:writeText";
525
+ EVENT["MUJIAN_GET_CONFIG"] = "mujian:getConfig";
526
+ return EVENT;
527
+ }({});
528
+ function wrapOnData(onData) {
529
+ let fullContent = '';
530
+ let buffer = '';
531
+ let questionId;
532
+ let replyId;
533
+ return function(data) {
534
+ buffer += data;
535
+ const lines = buffer.split('\n');
536
+ buffer = lines.pop() || '';
537
+ for (const line of lines)if (line.startsWith('data: ')) try {
538
+ const parsedData = JSON.parse(line.slice(6));
539
+ if (parsedData.question_id) questionId = parsedData.question_id;
540
+ if (parsedData.reply_id) replyId = parsedData.reply_id;
541
+ if (parsedData.isFinished) return void onData({
542
+ isFinished: true,
543
+ deltaContent: '',
544
+ fullContent,
545
+ questionId,
546
+ replyId
547
+ });
548
+ const deltaContent = parsedData?.choices?.[0]?.delta?.content;
549
+ if (deltaContent?.length > 0) {
550
+ fullContent += deltaContent;
551
+ onData({
552
+ isFinished: false,
553
+ deltaContent: deltaContent,
554
+ fullContent,
555
+ questionId,
556
+ replyId
557
+ });
558
+ }
559
+ } catch (e) {
560
+ onData({
561
+ isFinished: true,
562
+ error: e,
563
+ deltaContent: '',
564
+ fullContent,
565
+ questionId,
566
+ replyId
567
+ });
568
+ return;
569
+ }
570
+ };
571
+ }
572
+ const chat_complete = async function(message, onData, signal, option = {}) {
573
+ await this.call(events_EVENT.MUJIAN_AI_CHAT_COMPLETE, {
280
574
  content: message
281
575
  }, {
282
- onData,
576
+ onData: option.parseContent ? wrapOnData(onData) : onData,
283
577
  signal
284
578
  });
285
579
  };
286
580
  const applyRegex = async function(props) {
287
- return await this.call("mujian:ai:chat:applyRegex", props);
581
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_APPLY_REGEX, props);
582
+ };
583
+ const renderMessage = async function(props) {
584
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_RENDER_MESSAGE, props);
288
585
  };
289
586
  const continueComplete = async function(onData, signal) {
290
- return await this.call("mujian:ai:chat:complete", {
587
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_COMPLETE, {
291
588
  isContinue: true
292
589
  }, {
293
590
  onData,
@@ -295,7 +592,7 @@ const continueComplete = async function(onData, signal) {
295
592
  });
296
593
  };
297
594
  const chat_regenerate = async function(onData, signal) {
298
- return await this.call("mujian:ai:chat:complete", {
595
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_COMPLETE, {
299
596
  isRegenerate: true
300
597
  }, {
301
598
  onData,
@@ -303,61 +600,120 @@ const chat_regenerate = async function(onData, signal) {
303
600
  });
304
601
  };
305
602
  const getAll = async function() {
306
- return await this.call("mujian:ai:chat:message:getAll");
603
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_GET_ALL);
307
604
  };
308
605
  const messageDeleteOne = async function(messageId) {
309
- return await this.call("mujian:ai:chat:message:deleteOne", {
606
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_DELETE_ONE, {
310
607
  messageId
311
608
  });
312
609
  };
313
610
  const messageEditOne = async function(messageId, content) {
314
- return await this.call("mujian:ai:chat:message:editOne", {
611
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_EDIT_ONE, {
315
612
  messageId,
316
613
  content
317
614
  });
318
615
  };
319
616
  const messageSwipe = async function(messageId, swipeId) {
320
- return await this.call("mujian:ai:chat:message:swipe", {
617
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_SWIPE, {
321
618
  messageId,
322
619
  swipeId
323
620
  });
324
621
  };
325
622
  const getPrompt = async function(messageId) {
326
- return await this.call("mujian:ai:chat:message:getPrompt", {
623
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_GET_PROMPT, {
327
624
  messageId
328
625
  });
329
626
  };
627
+ async function getPage(fromCursor, pageSize) {
628
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_MESSAGE_GET_PAGE, {
629
+ fromCursor,
630
+ pageSize
631
+ });
632
+ }
330
633
  const getInfo = async function() {
331
- return await this.call("mujian:ai:chat:project:getInfo");
634
+ return await this.call(events_EVENT.MUJIAN_AI_CHAT_PROJECT_GET_INFO);
332
635
  };
333
636
  const getActive = async function() {
334
- return await this.call("mujian:ai:settings:persona:getActive");
637
+ return await this.call(events_EVENT.MUJIAN_AI_SETTINGS_PERSONA_GET_ACTIVE);
335
638
  };
336
639
  const setActive = async function(personaId) {
337
- return await this.call("mujian:ai:settings:persona:setActive", {
640
+ return await this.call(events_EVENT.MUJIAN_AI_SETTINGS_PERSONA_SET_ACTIVE, {
338
641
  personaId
339
642
  });
340
643
  };
341
644
  const model_getActive = async function() {
342
- return await this.call("mujian:ai:settings:model:getActive");
645
+ return await this.call(events_EVENT.MUJIAN_AI_SETTINGS_MODEL_GET_ACTIVE);
343
646
  };
344
647
  const model_setActive = async function(modelId) {
345
- return await this.call("mujian:ai:settings:model:setActive", {
648
+ return await this.call(events_EVENT.MUJIAN_AI_SETTINGS_MODEL_SET_ACTIVE, {
346
649
  modelId
347
650
  });
348
651
  };
349
652
  const model_getAll = async function() {
350
- return await this.call("mujian:ai:settings:model:getAll");
653
+ return await this.call(events_EVENT.MUJIAN_AI_SETTINGS_MODEL_GET_ALL);
351
654
  };
352
- const generate = async function() {
353
- return await this.call("mujian:ai:text:generate", {
354
- content: 'q'
655
+ const generate = async function(content) {
656
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_TEXT_GENERATE, {
657
+ content
355
658
  });
356
659
  };
660
+ const chat = {
661
+ completions: {
662
+ create: async function(params, options, onData, signal) {
663
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_CHAT_COMPLETIONS_CREATE, {
664
+ params,
665
+ options
666
+ }, {
667
+ onData,
668
+ signal
669
+ });
670
+ }
671
+ }
672
+ };
673
+ const completions = {
674
+ create: async function(params, options, onData, signal) {
675
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_COMPLETIONS_CREATE, {
676
+ params,
677
+ options
678
+ }, {
679
+ onData,
680
+ signal
681
+ });
682
+ }
683
+ };
684
+ const responses = {
685
+ create: async function(params, options, onData, signal) {
686
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_RESPONSES_CREATE, {
687
+ params,
688
+ options
689
+ }, {
690
+ onData,
691
+ signal
692
+ });
693
+ }
694
+ };
695
+ const images_images = {
696
+ generate: async function(params, options, onData, signal) {
697
+ return await this.call(MujianSdk.EVENT.MUJIAN_AI_OPENAI_IMAGES_GENERATE, {
698
+ params,
699
+ options
700
+ }, {
701
+ onData,
702
+ signal
703
+ });
704
+ }
705
+ };
706
+ async function getConfig() {
707
+ return await this.call(events_EVENT.MUJIAN_GET_CONFIG);
708
+ }
357
709
  const saveGame = async function() {};
358
710
  const loadGame = async function() {};
711
+ async function writeText(text) {
712
+ return await this.call(events_EVENT.MUJIAN_UTILS_CLIPBOARD_WRITE_TEXT, text);
713
+ }
359
714
  class MujianSdk {
360
715
  constructor(){}
716
+ static EVENT = events_EVENT;
361
717
  static getInstance() {
362
718
  if (!window.$mujian) window.$mujian = new MujianSdk();
363
719
  return window.$mujian;
@@ -368,28 +724,59 @@ class MujianSdk {
368
724
  return this.ready;
369
725
  }
370
726
  pendingRequests = new Map();
727
+ _config;
728
+ get openapi() {
729
+ return this._config?.openapi;
730
+ }
731
+ initPromise = null;
371
732
  async init() {
372
- const handshake = new postmate.Model({
373
- reply: ({ id, complete, data })=>{
374
- const call = this.pendingRequests.get(id);
375
- if (!call) return;
376
- call.onData?.(data);
377
- if (complete) {
378
- call.onComplete?.();
379
- call.resolve(data);
380
- this.pendingRequests.delete(id);
733
+ if (this.initPromise) return this.initPromise;
734
+ this.initPromise = (async ()=>{
735
+ const handshake = new postmate.Model({
736
+ reply: ({ id, complete, data, error })=>{
737
+ const call = this.pendingRequests.get(id);
738
+ if (!call) return;
739
+ if (error) {
740
+ call.reject(error);
741
+ this.pendingRequests.delete(id);
742
+ return;
743
+ }
744
+ call.onData?.(data);
745
+ if (complete) {
746
+ call.onComplete?.();
747
+ call.resolve(data);
748
+ this.pendingRequests.delete(id);
749
+ }
750
+ }
751
+ });
752
+ try {
753
+ const parent = await handshake;
754
+ this.ready = true;
755
+ this.parent = parent;
756
+ Log.i('mujian sdk client init');
757
+ await this.call(events_EVENT.MUJIAN_INIT);
758
+ const projectInfo = await this.ai.chat.project.getInfo();
759
+ if (projectInfo.config?.customCss) {
760
+ const style = document.createElement('style');
761
+ style.setAttribute('type', 'text/css');
762
+ style.setAttribute('id', 'mujian-custom-css');
763
+ style.textContent = projectInfo.config.customCss;
764
+ document.head.appendChild(style);
381
765
  }
766
+ if (projectInfo.config?.customJs) {
767
+ const script = document.createElement("script");
768
+ script.setAttribute('type', "text/javascript");
769
+ script.setAttribute('id', 'mujian-custom-js');
770
+ script.textContent = projectInfo.config.customJs;
771
+ document.head.appendChild(script);
772
+ }
773
+ const config = await getConfig.call(this);
774
+ if (config) this._config = config;
775
+ } catch (error) {
776
+ Log.e('init error', error);
382
777
  }
383
- });
384
- try {
385
- const parent = await handshake;
386
- this.ready = true;
387
- this.parent = parent;
388
- console.log('mujian sdk client init');
389
- await this.call("mujian:init");
390
- } catch (error) {
391
- console.log('init error', error);
392
- }
778
+ })();
779
+ return this.initPromise;
393
780
  }
394
781
  emit(event, data) {
395
782
  if (!this.ready) throw new Error('Mujian is not initialized');
@@ -415,7 +802,7 @@ class MujianSdk {
415
802
  data
416
803
  });
417
804
  controller?.signal?.addEventListener('abort', ()=>{
418
- this.emit("mujian:ai:chat:stop", {
805
+ this.emit(events_EVENT.MUJIAN_AI_CHAT_STOP, {
419
806
  id: callId
420
807
  });
421
808
  });
@@ -439,6 +826,7 @@ class MujianSdk {
439
826
  },
440
827
  complete: chat_complete.bind(this),
441
828
  applyRegex: applyRegex.bind(this),
829
+ renderMessage: renderMessage.bind(this),
442
830
  continue: continueComplete.bind(this),
443
831
  regenerate: chat_regenerate.bind(this),
444
832
  message: {
@@ -446,17 +834,34 @@ class MujianSdk {
446
834
  deleteOne: messageDeleteOne.bind(this),
447
835
  editOne: messageEditOne.bind(this),
448
836
  swipe: messageSwipe.bind(this),
449
- getPrompt: getPrompt.bind(this)
837
+ getPrompt: getPrompt.bind(this),
838
+ getPage: getPage.bind(this)
450
839
  }
451
840
  },
452
841
  text: {
453
842
  complete: generate.bind(this)
843
+ },
844
+ openai: {
845
+ completions: {
846
+ create: completions.create.bind(this)
847
+ },
848
+ chat: {
849
+ completions: {
850
+ create: chat.completions.create.bind(this)
851
+ }
852
+ },
853
+ responses: {
854
+ create: responses.create.bind(this)
855
+ },
856
+ images: {
857
+ generate: images_images.generate.bind(this)
858
+ }
454
859
  }
455
860
  };
456
861
  ui = {};
457
- util = {
458
- cn: ()=>{
459
- console.log('cn');
862
+ utils = {
863
+ clipboard: {
864
+ writeText: writeText.bind(this)
460
865
  }
461
866
  };
462
867
  hybrid = {};
@@ -474,6 +879,43 @@ class MujianSdk {
474
879
  }
475
880
  };
476
881
  }
882
+ const styleSheet = `
883
+ @keyframes spin {
884
+ 0% { transform: rotate(0deg); }
885
+ 100% { transform: rotate(360deg); }
886
+ }
887
+ `;
888
+ const spinnerStyle = {
889
+ width: 48,
890
+ height: 48,
891
+ animation: 'spin 1s linear infinite'
892
+ };
893
+ const MujianSpinner = ({ className, style })=>/*#__PURE__*/ jsxs(Fragment, {
894
+ children: [
895
+ /*#__PURE__*/ jsx("style", {
896
+ children: styleSheet
897
+ }),
898
+ /*#__PURE__*/ jsx("svg", {
899
+ xmlns: "http://www.w3.org/2000/svg",
900
+ width: "24",
901
+ height: "24",
902
+ viewBox: "0 0 24 24",
903
+ fill: "none",
904
+ stroke: "currentColor",
905
+ strokeWidth: "2",
906
+ strokeLinecap: "round",
907
+ strokeLinejoin: "round",
908
+ className: className,
909
+ style: {
910
+ ...spinnerStyle,
911
+ ...style
912
+ },
913
+ children: /*#__PURE__*/ jsx("path", {
914
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
915
+ })
916
+ })
917
+ ]
918
+ });
477
919
  const MujianContext = /*#__PURE__*/ createContext(null);
478
920
  const MujianProvider = ({ children, loadingComponent })=>{
479
921
  const [mujian, setMujian] = useState(null);
@@ -486,7 +928,20 @@ const MujianProvider = ({ children, loadingComponent })=>{
486
928
  }, []);
487
929
  return /*#__PURE__*/ jsx(MujianContext.Provider, {
488
930
  value: mujian,
489
- children: mujian ? children : loadingComponent ?? 'Mujian is loading'
931
+ children: mujian ? children : loadingComponent ?? /*#__PURE__*/ jsx("div", {
932
+ style: {
933
+ height: '100%',
934
+ width: '100%',
935
+ display: 'flex',
936
+ justifyContent: 'center',
937
+ alignItems: 'center'
938
+ },
939
+ children: /*#__PURE__*/ jsx(MujianSpinner, {
940
+ style: {
941
+ color: '#EC4342'
942
+ }
943
+ })
944
+ })
490
945
  });
491
946
  };
492
947
  const useMujian = ()=>{
@@ -504,7 +959,7 @@ const MessageItem = (props)=>{
504
959
  const { data: renderedMessage } = useRequest(async ()=>{
505
960
  const { isStreaming } = message;
506
961
  if (isStreaming) return message;
507
- return await mujian.ai.chat.applyRegex({
962
+ return await mujian.ai.chat.renderMessage({
508
963
  message,
509
964
  depth,
510
965
  index
@@ -533,28 +988,103 @@ const Thread = {
533
988
  MessageItem: MessageItem,
534
989
  MessageList: MessageList
535
990
  };
536
- class SendMessageError extends Error {
537
- constructor(message, cause){
538
- super(message);
539
- this.name = this.constructor.name;
540
- this.cause = cause;
991
+ let $mj_ai_chat_complete;
992
+ let $mj_ai_chat_project;
993
+ let $mj_ai_chat_settings_persona;
994
+ let $mj_ai_chat_setFirstMesIndex;
995
+ let $mj_ai_chat_mjv_get;
996
+ let $mj_ai_chat_mjv_getAll;
997
+ const $mj_engine = {
998
+ ai: {
999
+ chat: {
1000
+ get complete () {
1001
+ return $mj_ai_chat_complete;
1002
+ },
1003
+ get project () {
1004
+ return $mj_ai_chat_project;
1005
+ },
1006
+ settings: {
1007
+ get persona () {
1008
+ return $mj_ai_chat_settings_persona;
1009
+ }
1010
+ },
1011
+ get setFirstMesIndex () {
1012
+ return $mj_ai_chat_setFirstMesIndex;
1013
+ }
1014
+ }
541
1015
  }
542
- }
543
- class InvalidDeltaContentError extends Error {
544
- constructor(message, cause){
545
- super(message);
546
- this.name = this.constructor.name;
547
- this.cause = cause;
1016
+ };
1017
+ window.$mj_engine = {
1018
+ ...$mj_engine,
1019
+ bind ({ messageId, swipeId }) {
1020
+ return {
1021
+ ai: {
1022
+ chat: {
1023
+ ...$mj_engine.ai.chat,
1024
+ mjv: {
1025
+ get (path) {
1026
+ return $mj_ai_chat_mjv_get?.(messageId, swipeId, path);
1027
+ },
1028
+ getAll () {
1029
+ return $mj_ai_chat_mjv_getAll?.(messageId, swipeId) ?? {};
1030
+ }
1031
+ }
1032
+ }
1033
+ }
1034
+ };
548
1035
  }
1036
+ };
1037
+ function useMjEngine({ projectInfo, activePersona, onSend, messages, setSwipe }) {
1038
+ useEffect(()=>{
1039
+ $mj_ai_chat_project = projectInfo ?? void 0;
1040
+ $mj_ai_chat_settings_persona = activePersona ?? void 0;
1041
+ }, [
1042
+ projectInfo,
1043
+ activePersona
1044
+ ]);
1045
+ useEffect(()=>{
1046
+ $mj_ai_chat_complete = async (message)=>{
1047
+ await onSend(message);
1048
+ };
1049
+ $mj_ai_chat_setFirstMesIndex = async (oneBasedIndex)=>{
1050
+ if (messages.length > 1) throw new Error('已有新消息,不允许切换开场白');
1051
+ if (0 === messages.length) throw new Error('断言失败:没有开场消息,无法切换开场白');
1052
+ const zeroBasedIndex = Math.floor(oneBasedIndex) - 1;
1053
+ const swipeLength = messages[0].swipes.length;
1054
+ if (zeroBasedIndex < 0 || zeroBasedIndex >= swipeLength) return;
1055
+ setSwipe(messages[0].id, zeroBasedIndex);
1056
+ };
1057
+ $mj_ai_chat_mjv_getAll = (messageId, swipeId)=>messages.find((m)=>m.id === messageId)?.swipeInfo[swipeId]?.mjv?.value;
1058
+ $mj_ai_chat_mjv_get = (messageId, swipeId, path)=>{
1059
+ const mjv = $mj_ai_chat_mjv_getAll?.(messageId, swipeId);
1060
+ if (!mjv) return;
1061
+ return get(mjv, path);
1062
+ };
1063
+ }, [
1064
+ onSend,
1065
+ messages,
1066
+ setSwipe
1067
+ ]);
549
1068
  }
550
- class UnexpectedSseEndingError extends Error {
1069
+ const errorMessages = [
1070
+ 'network error',
1071
+ 'NetworkError when attempting to fetch resource.',
1072
+ 'The Internet connection appears to be offline.',
1073
+ 'Network request failed',
1074
+ 'Load failed',
1075
+ 'Failed to fetch'
1076
+ ];
1077
+ function isNetworkError(message) {
1078
+ return errorMessages.some((m)=>message.includes(m));
1079
+ }
1080
+ class SendMessageError extends Error {
551
1081
  constructor(message, cause){
552
1082
  super(message);
553
1083
  this.name = this.constructor.name;
554
1084
  this.cause = cause;
555
1085
  }
556
1086
  }
557
- class InsufficientBalanceError extends Error {
1087
+ class UnexpectedSseEndingError extends Error {
558
1088
  constructor(message, cause){
559
1089
  super(message);
560
1090
  this.name = this.constructor.name;
@@ -569,6 +1099,7 @@ async function* callSdk(mujian, content, type = 'generate', signal) {
569
1099
  try {
570
1100
  const onData = (data)=>{
571
1101
  if (error) return;
1102
+ if ('string' != typeof data) return;
572
1103
  queue.push(data);
573
1104
  if (resolveWait) {
574
1105
  resolveWait();
@@ -603,48 +1134,52 @@ async function* callSdk(mujian, content, type = 'generate', signal) {
603
1134
  resolveWait = resolve;
604
1135
  });
605
1136
  }
606
- const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
1137
+ const NOT_SAVED_MSG_ID_PREFIX = 'not_saved';
1138
+ const FALLBACK_MESSAGE = `哎呀,AI线路好像抽风了`;
1139
+ const useChatStreaming = ({ common, setError, setMessages, getMessages, setInput, mujian })=>{
607
1140
  const { body } = common;
608
1141
  const [isStreaming, setIsStreaming] = useState(false);
609
1142
  const chatStreaming = useCallback(async ({ query, regenerate, is_continue, signal })=>{
610
1143
  try {
611
1144
  setError(void 0);
612
1145
  setIsStreaming(true);
1146
+ const backupMessages = getMessages();
613
1147
  const userMessage = {
614
- id: Date.now().toString(),
1148
+ id: `${NOT_SAVED_MSG_ID_PREFIX}_user_${Date.now()}`,
615
1149
  content: query || '',
616
1150
  role: 'user',
617
1151
  swipes: [],
618
1152
  activeSwipeId: 0,
619
1153
  isStreaming: false,
620
- sendAt: new Date()
1154
+ sendAt: new Date(),
1155
+ swipeInfo: [],
1156
+ flags: {}
621
1157
  };
622
1158
  const aiMessage = {
623
- id: Date.now().toString() + '1',
1159
+ id: `${NOT_SAVED_MSG_ID_PREFIX}_assistant_${Date.now()}`,
624
1160
  content: '',
625
1161
  role: 'assistant',
626
- swipes: [],
1162
+ swipes: [
1163
+ ''
1164
+ ],
627
1165
  activeSwipeId: 0,
628
1166
  isStreaming: true,
629
- sendAt: new Date()
1167
+ sendAt: new Date(),
1168
+ swipeInfo: [
1169
+ {}
1170
+ ],
1171
+ flags: {}
630
1172
  };
631
- if (regenerate) setMessages((prev)=>{
632
- const newMessages = [
633
- ...prev
634
- ];
635
- const lastMessage = newMessages[newMessages.length - 1];
636
- newMessages[newMessages.length - 1] = {
637
- ...lastMessage,
638
- activeSwipeId: lastMessage.swipes.length,
639
- swipes: [
640
- ...lastMessage.swipes,
641
- ''
642
- ],
643
- content: '',
644
- isStreaming: true
1173
+ if (regenerate) setMessages(produce((draft)=>{
1174
+ const lastMessage = draft[draft.length - 1];
1175
+ lastMessage.activeSwipeId = lastMessage.swipes.length;
1176
+ lastMessage.swipes.push('');
1177
+ lastMessage.content = '';
1178
+ lastMessage.isStreaming = true;
1179
+ lastMessage.flags = {
1180
+ saved: lastMessage.flags.saved
645
1181
  };
646
- return newMessages;
647
- });
1182
+ }));
648
1183
  else is_continue ? setMessages((prev)=>[
649
1184
  ...prev,
650
1185
  aiMessage
@@ -656,6 +1191,7 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
656
1191
  const stream = callSdk(mujian, query || '', is_continue ? 'continue' : regenerate ? 'regenerate' : 'generate', signal);
657
1192
  let buffer = '';
658
1193
  let content = '';
1194
+ let finishReason;
659
1195
  try {
660
1196
  for await (const chunk of stream){
661
1197
  buffer += chunk;
@@ -665,45 +1201,60 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
665
1201
  const data = line.slice(6);
666
1202
  try {
667
1203
  const parsedData = JSON.parse(data);
668
- if (!regenerate && parsedData.question_id) {
1204
+ console.log(parsedData);
1205
+ if (regenerate || 'meta' !== parsedData.type) {
1206
+ if ('stream' === parsedData.type) {
1207
+ const partialContent = parsedData.choices[0].delta.content;
1208
+ parsedData.choices[0].delta.reasoning;
1209
+ const chunkFinishReason = parsedData.choices[0].finish_reason;
1210
+ content += partialContent;
1211
+ setMessages(produce((draft)=>{
1212
+ const lastMessage = draft[draft.length - 1];
1213
+ lastMessage.swipes[lastMessage.activeSwipeId] = content;
1214
+ lastMessage.content = content;
1215
+ lastMessage.isStreaming = true;
1216
+ if ('content_filter' === chunkFinishReason) lastMessage.flags.contentFilter = true;
1217
+ }));
1218
+ if (chunkFinishReason) finishReason = chunkFinishReason;
1219
+ await new Promise((resolve)=>setTimeout(resolve, 10));
1220
+ } else if ('mjv' === parsedData.type) {
1221
+ const { value, delta, schema } = parsedData;
1222
+ setMessages(produce((draft)=>{
1223
+ const lastMessage = draft[draft.length - 1];
1224
+ const mjv = {
1225
+ value,
1226
+ delta,
1227
+ schema
1228
+ };
1229
+ const swipeInfo = lastMessage.swipeInfo;
1230
+ if (swipeInfo[lastMessage.activeSwipeId]) swipeInfo[lastMessage.activeSwipeId].mjv = mjv;
1231
+ else swipeInfo[lastMessage.activeSwipeId] = {
1232
+ mjv
1233
+ };
1234
+ lastMessage.swipeInfo = swipeInfo;
1235
+ lastMessage.mjv = mjv;
1236
+ }));
1237
+ }
1238
+ } else {
669
1239
  const { question_id, reply_id } = parsedData;
670
1240
  setMessages((prev)=>{
671
1241
  const newMessages = [
672
1242
  ...prev
673
1243
  ];
674
- if (question_id && !is_continue) newMessages[newMessages.length - 2].id = question_id;
675
- const lastMessage = newMessages[newMessages.length - 1];
676
- newMessages[newMessages.length - 1] = {
677
- ...lastMessage,
678
- id: reply_id
679
- };
680
1244
  return newMessages;
681
1245
  });
1246
+ setMessages(produce((draft)=>{
1247
+ if (question_id && !is_continue) {
1248
+ const userMsg = draft[draft.length - 2];
1249
+ userMsg.id = question_id;
1250
+ userMsg.flags.saved = true;
1251
+ }
1252
+ const lastMessage = draft[draft.length - 1];
1253
+ lastMessage.id = reply_id;
1254
+ lastMessage.flags.saved = true;
1255
+ }));
682
1256
  continue;
683
1257
  }
684
- if (!parsedData.choices?.[0]) continue;
685
- const partialContent = parsedData.choices[0].delta.content;
686
- parsedData.choices[0].delta.reasoning;
687
- content += partialContent;
688
- if (content || '' === content) {
689
- setMessages((prev)=>{
690
- const newMessages = [
691
- ...prev
692
- ];
693
- const lastMessage = newMessages[newMessages.length - 1];
694
- lastMessage.swipes[lastMessage.activeSwipeId] = content;
695
- newMessages[newMessages.length - 1] = {
696
- ...lastMessage,
697
- content: content,
698
- isStreaming: true
699
- };
700
- return newMessages;
701
- });
702
- await new Promise((resolve)=>setTimeout(resolve, 10));
703
- } else {
704
- console.error('data', data);
705
- setError(new InvalidDeltaContentError(data));
706
- }
707
1258
  } catch {
708
1259
  if ('[DONE]' !== data) {
709
1260
  console.error('Received plain SSE message:', data);
@@ -712,22 +1263,28 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
712
1263
  }
713
1264
  }
714
1265
  }
1266
+ } catch (error) {
1267
+ Log.e('Stream error', error);
1268
+ if ('object' == typeof error && error && 'message' in error && 'string' == typeof error.message) if ('code' in error && 2011030001 === error.code) {
1269
+ setMessages(backupMessages);
1270
+ if (!regenerate && !is_continue && query) setInput(query);
1271
+ } else isNetworkError(error.message) ? setError(new SendMessageError('网络好像断开了,检查一下再试吧~')) : setError(new SendMessageError(error.message));
1272
+ else setError(error);
715
1273
  } finally{
716
- console.log('stream end finally');
717
- setMessages((prev)=>{
718
- const newMessages = [
719
- ...prev
720
- ];
721
- newMessages[newMessages.length - 1] = {
722
- ...newMessages[newMessages.length - 1],
723
- isStreaming: false
724
- };
725
- return newMessages;
726
- });
1274
+ Log.i('stream end finally');
1275
+ setMessages(produce((draft)=>{
1276
+ const lastMessage = draft[draft.length - 1];
1277
+ lastMessage.isStreaming = false;
1278
+ if (!lastMessage.swipes[lastMessage.activeSwipeId] && !signal?.aborted && 'stop' === finishReason) {
1279
+ lastMessage.swipes[lastMessage.activeSwipeId] = FALLBACK_MESSAGE;
1280
+ lastMessage.content = FALLBACK_MESSAGE;
1281
+ lastMessage.flags.emptyResponse = true;
1282
+ }
1283
+ }));
727
1284
  }
728
1285
  } catch (error) {
729
1286
  console.error('Error:', error);
730
- error instanceof Error && JSON.parse(error.message || '{}')?.code === 111 ? setError(new InsufficientBalanceError()) : setError(new SendMessageError('Send message failed', error));
1287
+ setError(new SendMessageError('Send message failed', error));
731
1288
  setMessages((prev)=>prev.slice(0, -1));
732
1289
  } finally{
733
1290
  setIsStreaming(false);
@@ -741,9 +1298,8 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
741
1298
  isStreaming
742
1299
  };
743
1300
  };
744
- const useChat = ({ body, onError, onFinish })=>{
1301
+ const useChat = ({ body, onError, onFinish, pageSize })=>{
745
1302
  const [error, _setError] = useState();
746
- const [messages, setMessages] = useState([]);
747
1303
  const [input, setInput] = useState('');
748
1304
  const [abortController, setAbortController] = useState();
749
1305
  const mujian = useMujian();
@@ -751,18 +1307,67 @@ const useChat = ({ body, onError, onFinish })=>{
751
1307
  _setError(error);
752
1308
  if (error) onError?.(error);
753
1309
  };
1310
+ const isLoadingMore = useRef(false);
1311
+ const { data, loadMoreAsync, loadingMore, loading, mutate, error: messageLoadError } = useInfiniteScroll(async (current)=>{
1312
+ isLoadingMore.current = true;
1313
+ if (current?.noMore) return {
1314
+ noMore: true,
1315
+ list: []
1316
+ };
1317
+ try {
1318
+ const resp = await mujian.ai.chat.message.getPage(current?.cursor, pageSize);
1319
+ return {
1320
+ cursor: resp.nextCursor,
1321
+ list: resp.messages.map((m)=>({
1322
+ ...m,
1323
+ mjv: m.swipeInfo[m.activeSwipeId]?.mjv,
1324
+ flags: {
1325
+ saved: true
1326
+ }
1327
+ })),
1328
+ noMore: !resp.nextCursor
1329
+ };
1330
+ } finally{
1331
+ isLoadingMore.current = false;
1332
+ }
1333
+ }, {
1334
+ direction: 'top'
1335
+ });
1336
+ const loadMoreMessage = async ()=>{
1337
+ if (isLoadingMore.current) return;
1338
+ try {
1339
+ isLoadingMore.current = true;
1340
+ await loadMoreAsync();
1341
+ } finally{
1342
+ isLoadingMore.current = false;
1343
+ }
1344
+ };
1345
+ const messages = data?.list ?? [];
1346
+ const latestData = useLatest(data);
1347
+ const latestListRef = useRef(messages);
1348
+ latestListRef.current = messages;
1349
+ const setMessages = (messages)=>{
1350
+ let newList;
1351
+ newList = 'function' == typeof messages ? messages(latestListRef.current) : messages;
1352
+ latestListRef.current = newList;
1353
+ mutate({
1354
+ ...latestData.current,
1355
+ list: newList
1356
+ });
1357
+ };
1358
+ const getMessages = useCallback(()=>latestListRef.current, [
1359
+ latestListRef
1360
+ ]);
754
1361
  const { chatStreaming, isStreaming } = useChatStreaming({
755
1362
  common: {
756
1363
  body
757
1364
  },
758
1365
  setError,
759
1366
  setMessages,
1367
+ getMessages,
1368
+ setInput,
760
1369
  mujian
761
1370
  });
762
- useMount(async ()=>{
763
- const resp = await mujian.ai.chat.message.getAll();
764
- setMessages(resp);
765
- });
766
1371
  useUpdateEffect(()=>{
767
1372
  if (!isStreaming) {
768
1373
  const lastMessage = messages[messages.length - 1];
@@ -771,48 +1376,79 @@ const useChat = ({ body, onError, onFinish })=>{
771
1376
  }, [
772
1377
  isStreaming
773
1378
  ]);
774
- const append = async (query)=>{
1379
+ const append = useCallback(async (query)=>{
775
1380
  if (isStreaming) throw new Error('useChat is streaming already.');
776
1381
  const controller = new AbortController();
777
1382
  setAbortController(controller);
778
- await chatStreaming({
779
- query,
780
- signal: controller.signal
781
- });
782
- setAbortController(void 0);
783
- };
784
- const regenerate = async ()=>{
1383
+ try {
1384
+ await chatStreaming({
1385
+ query,
1386
+ signal: controller.signal
1387
+ });
1388
+ } catch (e) {
1389
+ setError(e);
1390
+ throw e;
1391
+ } finally{
1392
+ setAbortController(void 0);
1393
+ }
1394
+ }, [
1395
+ isStreaming,
1396
+ chatStreaming
1397
+ ]);
1398
+ const regenerate = useCallback(async ()=>{
785
1399
  if (isStreaming) throw new Error('useChat is streaming already.');
786
1400
  const controller = new AbortController();
787
1401
  setAbortController(controller);
788
1402
  const lastMessage = messages.findLast((message)=>'user' === message.role);
789
- await chatStreaming({
790
- query: lastMessage?.content || '',
791
- regenerate: true,
792
- signal: controller.signal
793
- });
794
- setAbortController(void 0);
795
- };
796
- const continueGenerate = async ()=>{
1403
+ try {
1404
+ await chatStreaming({
1405
+ query: lastMessage?.content || '',
1406
+ regenerate: true,
1407
+ signal: controller.signal
1408
+ });
1409
+ } catch (e) {
1410
+ setError(e);
1411
+ throw e;
1412
+ } finally{
1413
+ setAbortController(void 0);
1414
+ }
1415
+ }, [
1416
+ isStreaming,
1417
+ messages,
1418
+ chatStreaming
1419
+ ]);
1420
+ const continueGenerate = useCallback(async ()=>{
797
1421
  if (isStreaming) throw new Error('useChat is streaming already.');
798
1422
  const controller = new AbortController();
799
1423
  setAbortController(controller);
800
- await chatStreaming({
801
- query: '',
802
- is_continue: true,
803
- signal: controller.signal
804
- });
805
- setAbortController(void 0);
806
- };
1424
+ try {
1425
+ await chatStreaming({
1426
+ query: '',
1427
+ is_continue: true,
1428
+ signal: controller.signal
1429
+ });
1430
+ } catch (e) {
1431
+ setError(e);
1432
+ throw e;
1433
+ } finally{
1434
+ setAbortController(void 0);
1435
+ }
1436
+ }, [
1437
+ isStreaming,
1438
+ chatStreaming
1439
+ ]);
807
1440
  const stop = ()=>{
808
1441
  abortController?.abort();
809
1442
  };
810
1443
  const setSwipe = async (messageId, swipeId)=>{
811
- await mujian.ai.chat.message.swipe(messageId, swipeId);
812
- setMessages((prev)=>prev.map((msg)=>msg.id === messageId ? {
1444
+ if (swipeId < 0) return;
1445
+ setMessages((prev)=>prev.map((msg)=>msg.id === messageId && swipeId < msg.swipes.length ? {
813
1446
  ...msg,
814
- activeSwipeId: swipeId
1447
+ activeSwipeId: swipeId,
1448
+ content: msg.swipes[swipeId],
1449
+ mjv: msg.swipeInfo[swipeId]?.mjv
815
1450
  } : msg));
1451
+ await mujian.ai.chat.message.swipe(messageId, swipeId);
816
1452
  };
817
1453
  const editMessage = async (messageId, content)=>{
818
1454
  await mujian.ai.chat.message.editOne(messageId, content);
@@ -832,7 +1468,11 @@ const useChat = ({ body, onError, onFinish })=>{
832
1468
  setMessages((prev)=>prev.map((msg)=>msg.id === messageId ? patchMessage(msg) : msg));
833
1469
  };
834
1470
  const deleteMessage = async (messageId)=>{
835
- await mujian.ai.chat.message.deleteOne(messageId);
1471
+ if (!messageId.startsWith(NOT_SAVED_MSG_ID_PREFIX)) try {
1472
+ await mujian.ai.chat.message.deleteOne(messageId);
1473
+ } catch (e) {
1474
+ if (e?.code !== 2011030004) throw e;
1475
+ }
836
1476
  setMessages((prev)=>prev.filter((msg)=>msg.id !== messageId));
837
1477
  };
838
1478
  return {
@@ -855,7 +1495,9 @@ const useChat = ({ body, onError, onFinish })=>{
855
1495
  },
856
1496
  setSwipe,
857
1497
  editMessage,
858
- deleteMessage
1498
+ deleteMessage,
1499
+ loadMoreMessage,
1500
+ messagesStatus: messageLoadError ? 'error' : loadingMore || loading ? 'loading' : data?.noMore ? 'all-loaded' : 'has-more'
859
1501
  };
860
1502
  };
861
- export { MdRenderer, MujianProvider, Thread, useChat, useMujian };
1503
+ export { MdRenderer, MujianProvider, MujianSpinner, Thread, useChat, useMjEngine, useMujian };