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

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 (42) 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 +828 -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/log.d.ts +4 -0
  41. package/package.json +16 -6
  42. /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,92 @@ 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
+ class SendMessageError extends Error {
551
1070
  constructor(message, cause){
552
1071
  super(message);
553
1072
  this.name = this.constructor.name;
554
1073
  this.cause = cause;
555
1074
  }
556
1075
  }
557
- class InsufficientBalanceError extends Error {
1076
+ class UnexpectedSseEndingError extends Error {
558
1077
  constructor(message, cause){
559
1078
  super(message);
560
1079
  this.name = this.constructor.name;
@@ -569,6 +1088,7 @@ async function* callSdk(mujian, content, type = 'generate', signal) {
569
1088
  try {
570
1089
  const onData = (data)=>{
571
1090
  if (error) return;
1091
+ if ('string' != typeof data) return;
572
1092
  queue.push(data);
573
1093
  if (resolveWait) {
574
1094
  resolveWait();
@@ -603,48 +1123,52 @@ async function* callSdk(mujian, content, type = 'generate', signal) {
603
1123
  resolveWait = resolve;
604
1124
  });
605
1125
  }
606
- const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
1126
+ const NOT_SAVED_MSG_ID_PREFIX = 'not_saved';
1127
+ const FALLBACK_MESSAGE = `哎呀,AI线路好像抽风了`;
1128
+ const useChatStreaming = ({ common, setError, setMessages, getMessages, setInput, mujian })=>{
607
1129
  const { body } = common;
608
1130
  const [isStreaming, setIsStreaming] = useState(false);
609
1131
  const chatStreaming = useCallback(async ({ query, regenerate, is_continue, signal })=>{
610
1132
  try {
611
1133
  setError(void 0);
612
1134
  setIsStreaming(true);
1135
+ const backupMessages = getMessages();
613
1136
  const userMessage = {
614
- id: Date.now().toString(),
1137
+ id: `${NOT_SAVED_MSG_ID_PREFIX}_user_${Date.now()}`,
615
1138
  content: query || '',
616
1139
  role: 'user',
617
1140
  swipes: [],
618
1141
  activeSwipeId: 0,
619
1142
  isStreaming: false,
620
- sendAt: new Date()
1143
+ sendAt: new Date(),
1144
+ swipeInfo: [],
1145
+ flags: {}
621
1146
  };
622
1147
  const aiMessage = {
623
- id: Date.now().toString() + '1',
1148
+ id: `${NOT_SAVED_MSG_ID_PREFIX}_assistant_${Date.now()}`,
624
1149
  content: '',
625
1150
  role: 'assistant',
626
- swipes: [],
1151
+ swipes: [
1152
+ ''
1153
+ ],
627
1154
  activeSwipeId: 0,
628
1155
  isStreaming: true,
629
- sendAt: new Date()
1156
+ sendAt: new Date(),
1157
+ swipeInfo: [
1158
+ {}
1159
+ ],
1160
+ flags: {}
630
1161
  };
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
1162
+ if (regenerate) setMessages(produce((draft)=>{
1163
+ const lastMessage = draft[draft.length - 1];
1164
+ lastMessage.activeSwipeId = lastMessage.swipes.length;
1165
+ lastMessage.swipes.push('');
1166
+ lastMessage.content = '';
1167
+ lastMessage.isStreaming = true;
1168
+ lastMessage.flags = {
1169
+ saved: lastMessage.flags.saved
645
1170
  };
646
- return newMessages;
647
- });
1171
+ }));
648
1172
  else is_continue ? setMessages((prev)=>[
649
1173
  ...prev,
650
1174
  aiMessage
@@ -665,45 +1189,59 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
665
1189
  const data = line.slice(6);
666
1190
  try {
667
1191
  const parsedData = JSON.parse(data);
668
- if (!regenerate && parsedData.question_id) {
1192
+ console.log(parsedData);
1193
+ if (regenerate || 'meta' !== parsedData.type) {
1194
+ if ('stream' === parsedData.type) {
1195
+ const partialContent = parsedData.choices[0].delta.content;
1196
+ parsedData.choices[0].delta.reasoning;
1197
+ const chunkFinishReason = parsedData.choices[0].finish_reason;
1198
+ content += partialContent;
1199
+ setMessages(produce((draft)=>{
1200
+ const lastMessage = draft[draft.length - 1];
1201
+ lastMessage.swipes[lastMessage.activeSwipeId] = content;
1202
+ lastMessage.content = content;
1203
+ lastMessage.isStreaming = true;
1204
+ if ('content_filter' === chunkFinishReason) lastMessage.flags.contentFilter = true;
1205
+ }));
1206
+ await new Promise((resolve)=>setTimeout(resolve, 10));
1207
+ } else if ('mjv' === parsedData.type) {
1208
+ const { value, delta, schema } = parsedData;
1209
+ setMessages(produce((draft)=>{
1210
+ const lastMessage = draft[draft.length - 1];
1211
+ const mjv = {
1212
+ value,
1213
+ delta,
1214
+ schema
1215
+ };
1216
+ const swipeInfo = lastMessage.swipeInfo;
1217
+ if (swipeInfo[lastMessage.activeSwipeId]) swipeInfo[lastMessage.activeSwipeId].mjv = mjv;
1218
+ else swipeInfo[lastMessage.activeSwipeId] = {
1219
+ mjv
1220
+ };
1221
+ lastMessage.swipeInfo = swipeInfo;
1222
+ lastMessage.mjv = mjv;
1223
+ }));
1224
+ }
1225
+ } else {
669
1226
  const { question_id, reply_id } = parsedData;
670
1227
  setMessages((prev)=>{
671
1228
  const newMessages = [
672
1229
  ...prev
673
1230
  ];
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
1231
  return newMessages;
681
1232
  });
1233
+ setMessages(produce((draft)=>{
1234
+ if (question_id && !is_continue) {
1235
+ const userMsg = draft[draft.length - 2];
1236
+ userMsg.id = question_id;
1237
+ userMsg.flags.saved = true;
1238
+ }
1239
+ const lastMessage = draft[draft.length - 1];
1240
+ lastMessage.id = reply_id;
1241
+ lastMessage.flags.saved = true;
1242
+ }));
682
1243
  continue;
683
1244
  }
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
1245
  } catch {
708
1246
  if ('[DONE]' !== data) {
709
1247
  console.error('Received plain SSE message:', data);
@@ -712,22 +1250,28 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
712
1250
  }
713
1251
  }
714
1252
  }
1253
+ } catch (error) {
1254
+ Log.e('Stream error', error);
1255
+ if ('object' == typeof error && error && 'message' in error && 'string' == typeof error.message) if ('code' in error && 2011030001 === error.code) {
1256
+ setMessages(backupMessages);
1257
+ if (!regenerate && !is_continue && query) setInput(query);
1258
+ } else setError(new SendMessageError(error.message));
1259
+ else setError(error);
715
1260
  } 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
- });
1261
+ Log.i('stream end finally');
1262
+ setMessages(produce((draft)=>{
1263
+ const lastMessage = draft[draft.length - 1];
1264
+ lastMessage.isStreaming = false;
1265
+ if (!lastMessage.swipes[lastMessage.activeSwipeId]) {
1266
+ lastMessage.swipes[lastMessage.activeSwipeId] = FALLBACK_MESSAGE;
1267
+ lastMessage.content = FALLBACK_MESSAGE;
1268
+ lastMessage.flags.emptyResponse = true;
1269
+ }
1270
+ }));
727
1271
  }
728
1272
  } catch (error) {
729
1273
  console.error('Error:', error);
730
- error instanceof Error && JSON.parse(error.message || '{}')?.code === 111 ? setError(new InsufficientBalanceError()) : setError(new SendMessageError('Send message failed', error));
1274
+ setError(new SendMessageError('Send message failed', error));
731
1275
  setMessages((prev)=>prev.slice(0, -1));
732
1276
  } finally{
733
1277
  setIsStreaming(false);
@@ -741,9 +1285,8 @@ const useChatStreaming = ({ common, setError, setMessages, mujian })=>{
741
1285
  isStreaming
742
1286
  };
743
1287
  };
744
- const useChat = ({ body, onError, onFinish })=>{
1288
+ const useChat = ({ body, onError, onFinish, pageSize })=>{
745
1289
  const [error, _setError] = useState();
746
- const [messages, setMessages] = useState([]);
747
1290
  const [input, setInput] = useState('');
748
1291
  const [abortController, setAbortController] = useState();
749
1292
  const mujian = useMujian();
@@ -751,18 +1294,67 @@ const useChat = ({ body, onError, onFinish })=>{
751
1294
  _setError(error);
752
1295
  if (error) onError?.(error);
753
1296
  };
1297
+ const isLoadingMore = useRef(false);
1298
+ const { data, loadMoreAsync, loadingMore, loading, mutate, error: messageLoadError } = useInfiniteScroll(async (current)=>{
1299
+ isLoadingMore.current = true;
1300
+ if (current?.noMore) return {
1301
+ noMore: true,
1302
+ list: []
1303
+ };
1304
+ try {
1305
+ const resp = await mujian.ai.chat.message.getPage(current?.cursor, pageSize);
1306
+ return {
1307
+ cursor: resp.nextCursor,
1308
+ list: resp.messages.map((m)=>({
1309
+ ...m,
1310
+ mjv: m.swipeInfo[m.activeSwipeId]?.mjv,
1311
+ flags: {
1312
+ saved: true
1313
+ }
1314
+ })),
1315
+ noMore: !resp.nextCursor
1316
+ };
1317
+ } finally{
1318
+ isLoadingMore.current = false;
1319
+ }
1320
+ }, {
1321
+ direction: 'top'
1322
+ });
1323
+ const loadMoreMessage = async ()=>{
1324
+ if (isLoadingMore.current) return;
1325
+ try {
1326
+ isLoadingMore.current = true;
1327
+ await loadMoreAsync();
1328
+ } finally{
1329
+ isLoadingMore.current = false;
1330
+ }
1331
+ };
1332
+ const messages = data?.list ?? [];
1333
+ const latestData = useLatest(data);
1334
+ const latestListRef = useRef(messages);
1335
+ latestListRef.current = messages;
1336
+ const setMessages = (messages)=>{
1337
+ let newList;
1338
+ newList = 'function' == typeof messages ? messages(latestListRef.current) : messages;
1339
+ latestListRef.current = newList;
1340
+ mutate({
1341
+ ...latestData.current,
1342
+ list: newList
1343
+ });
1344
+ };
1345
+ const getMessages = useCallback(()=>latestListRef.current, [
1346
+ latestListRef
1347
+ ]);
754
1348
  const { chatStreaming, isStreaming } = useChatStreaming({
755
1349
  common: {
756
1350
  body
757
1351
  },
758
1352
  setError,
759
1353
  setMessages,
1354
+ getMessages,
1355
+ setInput,
760
1356
  mujian
761
1357
  });
762
- useMount(async ()=>{
763
- const resp = await mujian.ai.chat.message.getAll();
764
- setMessages(resp);
765
- });
766
1358
  useUpdateEffect(()=>{
767
1359
  if (!isStreaming) {
768
1360
  const lastMessage = messages[messages.length - 1];
@@ -771,48 +1363,79 @@ const useChat = ({ body, onError, onFinish })=>{
771
1363
  }, [
772
1364
  isStreaming
773
1365
  ]);
774
- const append = async (query)=>{
1366
+ const append = useCallback(async (query)=>{
775
1367
  if (isStreaming) throw new Error('useChat is streaming already.');
776
1368
  const controller = new AbortController();
777
1369
  setAbortController(controller);
778
- await chatStreaming({
779
- query,
780
- signal: controller.signal
781
- });
782
- setAbortController(void 0);
783
- };
784
- const regenerate = async ()=>{
1370
+ try {
1371
+ await chatStreaming({
1372
+ query,
1373
+ signal: controller.signal
1374
+ });
1375
+ } catch (e) {
1376
+ setError(e);
1377
+ throw e;
1378
+ } finally{
1379
+ setAbortController(void 0);
1380
+ }
1381
+ }, [
1382
+ isStreaming,
1383
+ chatStreaming
1384
+ ]);
1385
+ const regenerate = useCallback(async ()=>{
785
1386
  if (isStreaming) throw new Error('useChat is streaming already.');
786
1387
  const controller = new AbortController();
787
1388
  setAbortController(controller);
788
1389
  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 ()=>{
1390
+ try {
1391
+ await chatStreaming({
1392
+ query: lastMessage?.content || '',
1393
+ regenerate: true,
1394
+ signal: controller.signal
1395
+ });
1396
+ } catch (e) {
1397
+ setError(e);
1398
+ throw e;
1399
+ } finally{
1400
+ setAbortController(void 0);
1401
+ }
1402
+ }, [
1403
+ isStreaming,
1404
+ messages,
1405
+ chatStreaming
1406
+ ]);
1407
+ const continueGenerate = useCallback(async ()=>{
797
1408
  if (isStreaming) throw new Error('useChat is streaming already.');
798
1409
  const controller = new AbortController();
799
1410
  setAbortController(controller);
800
- await chatStreaming({
801
- query: '',
802
- is_continue: true,
803
- signal: controller.signal
804
- });
805
- setAbortController(void 0);
806
- };
1411
+ try {
1412
+ await chatStreaming({
1413
+ query: '',
1414
+ is_continue: true,
1415
+ signal: controller.signal
1416
+ });
1417
+ } catch (e) {
1418
+ setError(e);
1419
+ throw e;
1420
+ } finally{
1421
+ setAbortController(void 0);
1422
+ }
1423
+ }, [
1424
+ isStreaming,
1425
+ chatStreaming
1426
+ ]);
807
1427
  const stop = ()=>{
808
1428
  abortController?.abort();
809
1429
  };
810
1430
  const setSwipe = async (messageId, swipeId)=>{
811
- await mujian.ai.chat.message.swipe(messageId, swipeId);
812
- setMessages((prev)=>prev.map((msg)=>msg.id === messageId ? {
1431
+ if (swipeId < 0) return;
1432
+ setMessages((prev)=>prev.map((msg)=>msg.id === messageId && swipeId < msg.swipes.length ? {
813
1433
  ...msg,
814
- activeSwipeId: swipeId
1434
+ activeSwipeId: swipeId,
1435
+ content: msg.swipes[swipeId],
1436
+ mjv: msg.swipeInfo[swipeId]?.mjv
815
1437
  } : msg));
1438
+ await mujian.ai.chat.message.swipe(messageId, swipeId);
816
1439
  };
817
1440
  const editMessage = async (messageId, content)=>{
818
1441
  await mujian.ai.chat.message.editOne(messageId, content);
@@ -832,7 +1455,11 @@ const useChat = ({ body, onError, onFinish })=>{
832
1455
  setMessages((prev)=>prev.map((msg)=>msg.id === messageId ? patchMessage(msg) : msg));
833
1456
  };
834
1457
  const deleteMessage = async (messageId)=>{
835
- await mujian.ai.chat.message.deleteOne(messageId);
1458
+ if (!messageId.startsWith(NOT_SAVED_MSG_ID_PREFIX)) try {
1459
+ await mujian.ai.chat.message.deleteOne(messageId);
1460
+ } catch (e) {
1461
+ if (e?.code !== 2011030004) throw e;
1462
+ }
836
1463
  setMessages((prev)=>prev.filter((msg)=>msg.id !== messageId));
837
1464
  };
838
1465
  return {
@@ -855,7 +1482,9 @@ const useChat = ({ body, onError, onFinish })=>{
855
1482
  },
856
1483
  setSwipe,
857
1484
  editMessage,
858
- deleteMessage
1485
+ deleteMessage,
1486
+ loadMoreMessage,
1487
+ messagesStatus: messageLoadError ? 'error' : loadingMore || loading ? 'loading' : data?.noMore ? 'all-loaded' : 'has-more'
859
1488
  };
860
1489
  };
861
- export { MdRenderer, MujianProvider, Thread, useChat, useMujian };
1490
+ export { MdRenderer, MujianProvider, MujianSpinner, Thread, useChat, useMjEngine, useMujian };