ds-markdown 0.0.14-beta.0 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -180,9 +180,9 @@ Let's explore these new features together!`);
180
180
  ### Imperative API (Recommended for Streaming Scenarios)
181
181
 
182
182
  ```typescript
183
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
183
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
184
184
 
185
- interface MarkdownRef {
185
+ interface MarkdownCMDRef {
186
186
  push: (content: string, answerType: AnswerType) => void;
187
187
  clear: () => void;
188
188
  triggerWholeEnd: () => void;
@@ -194,6 +194,15 @@ interface MarkdownRef {
194
194
  | `push` | `(content: string, answerType: AnswerType)` | Add content and start typing |
195
195
  | `clear` | - | Clear all content and state |
196
196
  | `triggerWholeEnd` | - | Manually trigger end callback |
197
+ | `stop` | - | Pause typing animation |
198
+ | `resume` | - | Resume typing animation |
199
+
200
+ **Usage Example:**
201
+
202
+ ```tsx
203
+ markdownRef.current?.stop(); // Pause animation
204
+ markdownRef.current?.resume(); // Resume animation
205
+ ```
197
206
 
198
207
  ---
199
208
 
@@ -252,10 +261,10 @@ High-frequency recommends `requestAnimationFrame`, low-frequency recommends `set
252
261
 
253
262
  ````tsx
254
263
  import { useRef } from 'react';
255
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
264
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
256
265
 
257
266
  function StreamingChat() {
258
- const markdownRef = useRef<MarkdownRef>(null);
267
+ const markdownRef = useRef<MarkdownCMDRef>(null);
259
268
 
260
269
  // Simulate AI streaming response
261
270
  const simulateAIResponse = async () => {
@@ -382,7 +391,7 @@ const handleStreamingMarkdown = () => {
382
391
 
383
392
  ```tsx
384
393
  // ✅ Recommended: Imperative API
385
- const ref = useRef<MarkdownRef>(null);
394
+ const ref = useRef<MarkdownCMDRef>(null);
386
395
  useEffect(() => {
387
396
  ref.current?.push(newChunk, 'answer');
388
397
  }, [newChunk]);
@@ -395,9 +404,9 @@ const [content, setContent] = useState('');
395
404
  ### 3. Type Safety
396
405
 
397
406
  ```tsx
398
- import { MarkdownRef } from 'ds-markdown';
407
+ import { MarkdownCMDRef } from 'ds-markdown';
399
408
 
400
- const ref = useRef<MarkdownRef>(null);
409
+ const ref = useRef<MarkdownCMDRef>(null);
401
410
  // Complete TypeScript type hints
402
411
  ```
403
412
 
package/README.ja.md CHANGED
@@ -180,9 +180,9 @@ React 19 は多くのエキサイティングな新機能をもたらします
180
180
  ### 命令的 API(ストリーミングシナリオにおすすめ)
181
181
 
182
182
  ```typescript
183
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
183
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
184
184
 
185
- interface MarkdownRef {
185
+ interface MarkdownCMDRef {
186
186
  push: (content: string, answerType: AnswerType) => void;
187
187
  clear: () => void;
188
188
  triggerWholeEnd: () => void;
@@ -194,6 +194,15 @@ interface MarkdownRef {
194
194
  | `push` | `(content: string, answerType: AnswerType)` | コンテンツを追加してタイピングを開始 |
195
195
  | `clear` | - | すべてのコンテンツと状態をクリア |
196
196
  | `triggerWholeEnd` | - | 完了コールバックを手動でトリガー |
197
+ | `stop` | - | タイピングを一時停止 |
198
+ | `resume` | - | タイピングを再開 |
199
+
200
+ **使用例:**
201
+
202
+ ```tsx
203
+ markdownRef.current?.stop(); // アニメーションを一時停止
204
+ markdownRef.current?.resume(); // アニメーションを再開
205
+ ```
197
206
 
198
207
  ---
199
208
 
@@ -250,10 +259,10 @@ interface MarkdownRef {
250
259
 
251
260
  ````tsx
252
261
  import { useRef } from 'react';
253
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
262
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
254
263
 
255
264
  function StreamingChat() {
256
- const markdownRef = useRef<MarkdownRef>(null);
265
+ const markdownRef = useRef<MarkdownCMDRef>(null);
257
266
 
258
267
  // AI ストリーミング応答をシミュレート
259
268
  const simulateAIResponse = async () => {
@@ -380,7 +389,7 @@ const handleStreamingMarkdown = () => {
380
389
 
381
390
  ```tsx
382
391
  // ✅ 推奨:命令的 API
383
- const ref = useRef<MarkdownRef>(null);
392
+ const ref = useRef<MarkdownCMDRef>(null);
384
393
  useEffect(() => {
385
394
  ref.current?.push(newChunk, 'answer');
386
395
  }, [newChunk]);
@@ -393,9 +402,9 @@ const [content, setContent] = useState('');
393
402
  ### 3. 型安全
394
403
 
395
404
  ```tsx
396
- import { MarkdownRef } from 'ds-markdown';
405
+ import { MarkdownCMDRef } from 'ds-markdown';
397
406
 
398
- const ref = useRef<MarkdownRef>(null);
407
+ const ref = useRef<MarkdownCMDRef>(null);
399
408
  // 完全な TypeScript 型ヒント
400
409
  ```
401
410
 
package/README.ko.md CHANGED
@@ -180,9 +180,9 @@ React 19는 많은 흥미로운 새 기능을 제공합니다:
180
180
  ### 명령형 API (스트리밍 시나리오 추천)
181
181
 
182
182
  ```typescript
183
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
183
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
184
184
 
185
- interface MarkdownRef {
185
+ interface MarkdownCMDRef {
186
186
  push: (content: string, answerType: AnswerType) => void;
187
187
  clear: () => void;
188
188
  triggerWholeEnd: () => void;
@@ -195,6 +195,16 @@ interface MarkdownRef {
195
195
  | `clear` | - | 모든 콘텐츠와 상태 초기화 |
196
196
  | `triggerWholeEnd` | - | 완료 콜백 수동 트리거 |
197
197
 
198
+ | `stop` | - | 타이핑 애니메이션 일시정지 |
199
+ | `resume` | - | 타이핑 애니메이션 재개 |
200
+
201
+ **사용 예시:**
202
+
203
+ ```tsx
204
+ markdownRef.current?.stop(); // 애니메이션 일시정지
205
+ markdownRef.current?.resume(); // 애니메이션 재개
206
+ ```
207
+
198
208
  ---
199
209
 
200
210
  ## 🎛️ 타이머 모드 상세 설명
@@ -250,10 +260,10 @@ interface MarkdownRef {
250
260
 
251
261
  ````tsx
252
262
  import { useRef } from 'react';
253
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
263
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
254
264
 
255
265
  function StreamingChat() {
256
- const markdownRef = useRef<MarkdownRef>(null);
266
+ const markdownRef = useRef<MarkdownCMDRef>(null);
257
267
 
258
268
  // AI 스트리밍 응답 시뮬레이션
259
269
  const simulateAIResponse = async () => {
@@ -380,7 +390,7 @@ const handleStreamingMarkdown = () => {
380
390
 
381
391
  ```tsx
382
392
  // ✅ 추천: 명령형 API
383
- const ref = useRef<MarkdownRef>(null);
393
+ const ref = useRef<MarkdownCMDRef>(null);
384
394
  useEffect(() => {
385
395
  ref.current?.push(newChunk, 'answer');
386
396
  }, [newChunk]);
@@ -393,9 +403,9 @@ const [content, setContent] = useState('');
393
403
  ### 3. 타입 안전성
394
404
 
395
405
  ```tsx
396
- import { MarkdownRef } from 'ds-markdown';
406
+ import { MarkdownCMDRef } from 'ds-markdown';
397
407
 
398
- const ref = useRef<MarkdownRef>(null);
408
+ const ref = useRef<MarkdownCMDRef>(null);
399
409
  // 완전한 TypeScript 타입 힌트
400
410
  ```
401
411
 
package/README.md CHANGED
@@ -38,6 +38,7 @@
38
38
  - 高频打字支持(`requestAnimationFrame`模式下打字间隔最低可接近于`0ms`)
39
39
  - 帧同步渲染,与浏览器 60fps 完美配合
40
40
  - 智能字符批量处理,视觉效果更自然
41
+ - 支持打字的中断 `stop` 和 继续`resume`
41
42
 
42
43
  ### 🔧 **灵活易用**
43
44
 
@@ -104,6 +105,8 @@ pnpm add ds-markdown
104
105
 
105
106
  ### 基础用法
106
107
 
108
+ [DEMO](https://stackblitz.com/edit/vitejs-vite-z94syu8j?file=src%2FApp.tsx)
109
+
107
110
  ```tsx
108
111
  import DsMarkdown from 'ds-markdown';
109
112
  import 'ds-markdown/style.css';
@@ -179,21 +182,20 @@ React 19 带来了许多激动人心的新特性:
179
182
 
180
183
  ### 命令式 API (推荐流式场景)
181
184
 
182
- ```typescript
183
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
184
-
185
- interface MarkdownRef {
186
- push: (content: string, answerType: AnswerType) => void;
187
- clear: () => void;
188
- triggerWholeEnd: () => void;
189
- }
190
- ```
191
-
192
185
  | 方法 | 参数 | 说明 |
193
186
  | ----------------- | ------------------------------------------- | ------------------ |
194
187
  | `push` | `(content: string, answerType: AnswerType)` | 添加内容并开始打字 |
195
188
  | `clear` | - | 清空所有内容和状态 |
196
189
  | `triggerWholeEnd` | - | 手动触发完成回调 |
190
+ | `stop` | - | 暂停打字动画 |
191
+ | `resume` | - | 恢复打字动画 |
192
+
193
+ **用法示例:**
194
+
195
+ ```tsx
196
+ markdownRef.current?.stop(); // 暂停动画
197
+ markdownRef.current?.resume(); // 恢复动画
198
+ ```
197
199
 
198
200
  ---
199
201
 
@@ -252,10 +254,10 @@ interface MarkdownRef {
252
254
 
253
255
  ````tsx
254
256
  import { useRef } from 'react';
255
- import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
257
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
256
258
 
257
259
  function StreamingChat() {
258
- const markdownRef = useRef<MarkdownRef>(null);
260
+ const markdownRef = useRef<MarkdownCMDRef>(null);
259
261
 
260
262
  // 模拟 AI 流式响应
261
263
  const simulateAIResponse = async () => {
@@ -382,7 +384,7 @@ const handleStreamingMarkdown = () => {
382
384
 
383
385
  ```tsx
384
386
  // ✅ 推荐:命令式 API
385
- const ref = useRef<MarkdownRef>(null);
387
+ const ref = useRef<MarkdownCMDRef>(null);
386
388
  useEffect(() => {
387
389
  ref.current?.push(newChunk, 'answer');
388
390
  }, [newChunk]);
@@ -395,9 +397,9 @@ const [content, setContent] = useState('');
395
397
  ### 3. 类型安全
396
398
 
397
399
  ```tsx
398
- import { MarkdownRef } from 'ds-markdown';
400
+ import { MarkdownCMDRef } from 'ds-markdown';
399
401
 
400
- const ref = useRef<MarkdownRef>(null);
402
+ const ref = useRef<MarkdownCMDRef>(null);
401
403
  // 完整的 TypeScript 类型提示
402
404
  ```
403
405
 
@@ -5,5 +5,5 @@ interface MarkdownImplProps extends MarkdownProps {
5
5
  answerType: AnswerType;
6
6
  theme?: Theme;
7
7
  }
8
- declare const _default: React.NamedExoticComponent<MarkdownImplProps>;
8
+ declare const _default: React.NamedExoticComponent<MarkdownImplProps & React.RefAttributes<import("../defined.js").MarkdownBaseRef>>;
9
9
  export default _default;
@@ -7,7 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
8
  const constant_js_1 = require("../constant.js");
9
9
  const index_js_1 = __importDefault(require("../MarkdownCMD/index.js"));
10
- const MarkdownInner = ({ children: _children = '', answerType, onTypedChar, ...rest }) => {
10
+ const MarkdownInner = ({ children: _children = '', answerType, markdownRef, ...rest }) => {
11
11
  const cmdRef = (0, react_1.useRef)(null);
12
12
  const prefixRef = (0, react_1.useRef)('');
13
13
  const content = (0, react_1.useMemo)(() => {
@@ -38,9 +38,17 @@ const MarkdownInner = ({ children: _children = '', answerType, onTypedChar, ...r
38
38
  prefixRef.current = content;
39
39
  }
40
40
  }, [answerType, content]);
41
+ (0, react_1.useImperativeHandle)(markdownRef, () => ({
42
+ stop: () => {
43
+ cmdRef.current.stop();
44
+ },
45
+ resume: () => {
46
+ cmdRef.current.resume();
47
+ },
48
+ }));
41
49
  return (0, jsx_runtime_1.jsx)(index_js_1.default, { ref: cmdRef, ...rest });
42
50
  };
43
- const Markdown = (props) => {
51
+ const Markdown = (0, react_1.forwardRef)((props, ref) => {
44
52
  const { children = '', answerType = 'answer' } = props;
45
53
  if (constant_js_1.__DEV__) {
46
54
  if (!['thinking', 'answer'].includes(answerType)) {
@@ -50,7 +58,7 @@ const Markdown = (props) => {
50
58
  throw new Error('Markdown组件的子元素必须是一个字符串');
51
59
  }
52
60
  }
53
- return (0, jsx_runtime_1.jsx)(MarkdownInner, { ...props, answerType: answerType });
54
- };
61
+ return (0, jsx_runtime_1.jsx)(MarkdownInner, { ...props, answerType: answerType, markdownRef: ref });
62
+ });
55
63
  exports.default = (0, react_1.memo)(Markdown);
56
64
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/Markdown/index.tsx"],"names":[],"mappings":";;;;;;AAAA,iCAAgE;AAChE,gDAAyC;AAEzC,uEAAmE;AAQnE,MAAM,aAAa,GAAgC,CAAC,EAAE,QAAQ,EAAE,SAAS,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;IACpH,MAAM,MAAM,GAAG,IAAA,cAAM,EAAc,IAAK,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAA,cAAM,EAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAC3B,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,qBAAO,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAClC,IAAI,UAAU,GAAG,EAAE,CAAC;YACpB,IAAI,SAAS,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;gBAC7B,UAAU,GAAG,OAAO,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1C,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBACvD,CAAC;qBAAM,CAAC;oBACN,UAAU,GAAG,OAAO,CAAC;oBACrB,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC5C,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;QAC9B,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1B,OAAO,uBAAC,kBAAW,IAAC,GAAG,EAAE,MAAM,KAAM,IAAI,GAAI,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAgC,CAAC,KAAK,EAAE,EAAE;IACtD,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,UAAU,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEvD,IAAI,qBAAO,EAAE,CAAC;QACZ,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,uBAAC,aAAa,OAAK,KAAK,EAAE,UAAU,EAAE,UAAU,GAAI,CAAC;AAC9D,CAAC,CAAC;AAEF,kBAAe,IAAA,YAAI,EAAC,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/Markdown/index.tsx"],"names":[],"mappings":";;;;;;AAAA,iCAAiG;AACjG,gDAAyC;AAEzC,uEAAkD;AAYlD,MAAM,aAAa,GAAiC,CAAC,EAAE,QAAQ,EAAE,SAAS,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;IACrH,MAAM,MAAM,GAAG,IAAA,cAAM,EAAiB,IAAK,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAA,cAAM,EAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAC3B,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,qBAAO,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAClC,IAAI,UAAU,GAAG,EAAE,CAAC;YACpB,IAAI,SAAS,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;gBAC7B,UAAU,GAAG,OAAO,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1C,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBACvD,CAAC;qBAAM,CAAC;oBACN,UAAU,GAAG,OAAO,CAAC;oBACrB,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC5C,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;QAC9B,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1B,IAAA,2BAAmB,EAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;QACtC,IAAI,EAAE,GAAG,EAAE;YACT,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE;YACX,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,OAAO,uBAAC,kBAAW,IAAC,GAAG,EAAE,MAAM,KAAM,IAAI,GAAI,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,IAAA,kBAAU,EAAiC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACzE,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,UAAU,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEvD,IAAI,qBAAO,EAAE,CAAC;QACZ,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,uBAAC,aAAa,OAAK,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,GAAI,CAAC;AAChF,CAAC,CAAC,CAAC;AAEH,kBAAe,IAAA,YAAI,EAAC,QAAQ,CAAC,CAAC"}
@@ -1,8 +1,3 @@
1
- import { AnswerType, MarkdownProps } from '../defined.js';
2
- export interface MarkdownRef {
3
- push: (content: string, answerType: AnswerType) => void;
4
- clear: () => void;
5
- triggerWholeEnd: () => void;
6
- }
7
- declare const MarkdownCMD: import("react").ForwardRefExoticComponent<MarkdownProps & import("react").RefAttributes<MarkdownRef>>;
1
+ import { MarkdownProps, MarkdownCMDRef } from '../defined.js';
2
+ declare const MarkdownCMD: import("react").ForwardRefExoticComponent<MarkdownProps & import("react").RefAttributes<MarkdownCMDRef>>;
8
3
  export default MarkdownCMD;
@@ -7,9 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
8
  const index_js_1 = __importDefault(require("../components/HighReactMarkdown/index.js"));
9
9
  const classnames_1 = __importDefault(require("classnames"));
10
- const compiler_js_1 = require("../utils/compiler.js");
11
10
  const constant_js_1 = require("../constant.js");
12
- const deepClone_js_1 = __importDefault(require("../utils/methods/deepClone.js"));
13
11
  const useTypingTask_js_1 = require("../hooks/useTypingTask.js");
14
12
  const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, onTypedChar, timerType = 'setTimeout', theme = 'light' }, ref) => {
15
13
  /** 当前需要打字的内容 */
@@ -19,15 +17,7 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
19
17
  * 如果打字已经完全结束,则不会再触发打字效果
20
18
  */
21
19
  const isWholeTypedEndRef = (0, react_1.useRef)(false);
22
- /**
23
- * 稳定段落
24
- * 稳定段落是已经打过字,并且不会再变化的段落
25
- */
26
- const stableSegmentsRef = (0, react_1.useRef)([]);
27
- const stableSegments = stableSegmentsRef.current;
28
- /** 当前段落引用 */
29
- const currentParagraphRef = (0, react_1.useRef)(undefined);
30
- const currentSegment = currentParagraphRef.current;
20
+ const charIndexRef = (0, react_1.useRef)(0);
31
21
  /** 整个内容引用 */
32
22
  const wholeContentRef = (0, react_1.useRef)({
33
23
  thinking: {
@@ -38,8 +28,8 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
38
28
  content: '',
39
29
  length: 0,
40
30
  },
31
+ allLength: 0,
41
32
  });
42
- /** 触发更新 */
43
33
  const [, setUpdate] = (0, react_1.useState)(false);
44
34
  const triggerUpdate = () => {
45
35
  setUpdate((prev) => !prev);
@@ -48,66 +38,16 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
48
38
  * 处理字符显示逻辑
49
39
  */
50
40
  const processCharDisplay = (char) => {
51
- const currentSegment = currentParagraphRef.current;
52
- // debugger;
53
- /** 如果碰到 space,和split_segment 则需要处理成两个段落 */
54
- if (char.contentType === 'space' || char.contentType === 'split_segment') {
55
- if (currentSegment) {
56
- const newStableSegments = [...stableSegmentsRef.current];
57
- // 放入到稳定队列
58
- if (currentSegment) {
59
- newStableSegments.push({ ...currentSegment, isTyped: false });
60
- }
61
- stableSegmentsRef.current = newStableSegments;
62
- currentParagraphRef.current = undefined;
63
- triggerUpdate();
64
- }
65
- return;
66
- }
67
- // 处理当前段落
68
- const newCurrentParagraph = {
69
- content: '',
70
- isTyped: false,
71
- type: 'text',
72
- answerType: char.answerType,
73
- tokensReference: {},
74
- };
75
- let _currentParagraph = currentSegment;
76
- if (!_currentParagraph) {
77
- // 如果当前没有段落,则直接设置为新当前段落
78
- _currentParagraph = newCurrentParagraph;
79
- }
80
- else if (currentSegment && currentSegment?.answerType !== char.answerType) {
81
- // 如果当前段落和当前字符的回答类型不一致,则需要处理成两个段落
82
- const newStableSegments = [...stableSegmentsRef.current];
83
- newStableSegments.push({ ...currentSegment, isTyped: false });
84
- stableSegmentsRef.current = newStableSegments;
85
- _currentParagraph = newCurrentParagraph;
86
- }
87
- const tokensReference = (0, deepClone_js_1.default)(_currentParagraph.tokensReference);
88
- if (tokensReference[char.tokenId]) {
89
- tokensReference[char.tokenId].raw += char.content;
90
- tokensReference[char.tokenId].startIndex = currentSegment?.content?.length || 0;
41
+ if (char.answerType === 'thinking') {
42
+ wholeContentRef.current.thinking.content += char.content;
43
+ wholeContentRef.current.thinking.length += 1;
91
44
  }
92
45
  else {
93
- tokensReference[char.tokenId] = {
94
- startIndex: currentSegment?.content?.length || 0,
95
- raw: char.content,
96
- };
46
+ wholeContentRef.current.answer.content += char.content;
47
+ wholeContentRef.current.answer.length += 1;
97
48
  }
98
- const newCurrentSegment = {
99
- ..._currentParagraph,
100
- tokensReference,
101
- content: (currentSegment?.content || '') + char.content,
102
- isTyped: true,
103
- };
104
- currentParagraphRef.current = newCurrentSegment;
105
49
  triggerUpdate();
106
50
  };
107
- /** 思考段落 */
108
- const thinkingParagraphs = (0, react_1.useMemo)(() => stableSegments.filter((paragraph) => paragraph.answerType === 'thinking'), [stableSegments]);
109
- /** 回答段落 */
110
- const answerParagraphs = (0, react_1.useMemo)(() => stableSegments.filter((paragraph) => paragraph.answerType === 'answer'), [stableSegments]);
111
51
  // 使用新的打字任务 hook
112
52
  const typingTask = (0, useTypingTask_js_1.useTypingTask)({
113
53
  timerType,
@@ -119,10 +59,6 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
119
59
  processCharDisplay,
120
60
  wholeContentRef,
121
61
  });
122
- const lastSegmentRawRef = (0, react_1.useRef)({
123
- thinkingReference: null,
124
- answerReference: null,
125
- });
126
62
  /**
127
63
  * 内部推送处理逻辑
128
64
  */
@@ -130,103 +66,18 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
130
66
  if (content.length === 0) {
131
67
  return;
132
68
  }
133
- const lastSegmentReference = lastSegmentRawRef.current[`${answerType}Reference`];
134
- if (isWholeTypedEndRef.current) {
135
- if (constant_js_1.__DEV__) {
136
- console.warn('打字已经完全结束,不能再添加新的内容');
137
- }
138
- return;
139
- }
140
- const wholeContent = wholeContentRef.current[`${answerType}`] || '';
141
- let currentIndex = wholeContent.length;
142
- let currentLastSegmentReference = null;
143
- let currentLastSegmentRaw = '';
144
- let lastSegmentRaw = '';
145
- if (lastSegmentReference) {
146
- lastSegmentRaw = lastSegmentReference.raw;
147
- currentLastSegmentRaw = lastSegmentRaw + content;
148
- }
149
- else {
150
- currentLastSegmentRaw = content;
151
- }
152
- const tokens = (0, compiler_js_1.compiler)(currentLastSegmentRaw);
153
- // 如果最后一个token是space,则把lastSegmentRaw设置为空
154
- if (tokens[tokens.length - 1].type === 'space') {
155
- currentLastSegmentReference = null;
156
- }
157
- else {
158
- // 如果上一个segment存在并且当前只有一个token,则说明是同一个segment
159
- if (lastSegmentReference !== null && tokens.length === 1) {
160
- const newCurrentLastSegmentReference = lastSegmentReference;
161
- newCurrentLastSegmentReference.raw = newCurrentLastSegmentReference.raw + content;
162
- currentLastSegmentReference = newCurrentLastSegmentReference;
163
- }
164
- else {
165
- currentLastSegmentReference = tokens[tokens.length - 1];
166
- }
167
- }
168
- const pushAndSplitSegment = (raw, tokenIndex, segmentTokenId) => {
169
- const currentToken = tokens[tokenIndex];
170
- if (tokenIndex > 0) {
171
- const prevToken = tokens[tokenIndex - 1];
172
- if (prevToken.type !== 'space' && currentToken.type !== 'space') {
173
- charsRef.current.push({ content: '', answerType, contentType: 'split_segment', tokenId: currentToken.id, index: currentIndex++ });
174
- }
175
- }
176
- charsRef.current.push(...raw.split('').map((char) => ({ content: char, answerType, contentType: 'segment', tokenId: segmentTokenId, index: currentIndex++ })));
177
- };
178
- if (!lastSegmentReference) {
179
- tokens.forEach((token, i) => {
180
- if (token.type === 'space') {
181
- charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'space', tokenId: token.id, index: currentIndex++ })));
182
- }
183
- else {
184
- pushAndSplitSegment(token.raw, i, token.id);
185
- }
186
- });
187
- }
188
- else {
189
- // debugger;
190
- let str = '';
191
- let nextTokenIndex = lastSegmentRaw.length;
192
- for (let i = 0; i < tokens.length; i++) {
193
- const token = tokens[i];
194
- if (token.type === 'space') {
195
- charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'space', tokenId: token.id, index: currentIndex++ })));
196
- str += token.raw;
197
- }
198
- else {
199
- str += token.raw;
200
- if (str.length < nextTokenIndex && i == 0) {
201
- /** 如果当前字符串长度小于下一个token的索引,则需要将当前段落更新, 以修正不完整的token */
202
- const lastSegmentReferenceId = lastSegmentReference.id;
203
- const currentSegment = currentParagraphRef.current;
204
- const tokensReference = currentSegment?.tokensReference || {};
205
- const lastTokenReference = tokensReference[lastSegmentReferenceId];
206
- if (lastTokenReference) {
207
- const newTokensReference = { [lastSegmentReferenceId]: { startIndex: lastTokenReference.startIndex, raw: token.raw } };
208
- // 去除charsRef中 tokenId = lastSegmentReferenceId 的字符
209
- charsRef.current = charsRef.current.filter((char) => char.tokenId !== lastSegmentReferenceId);
210
- const newCurrentSegment = { ...currentSegment, tokensReference: newTokensReference, isTyped: false, type: 'text', answerType };
211
- newCurrentSegment.content = Object.values(newTokensReference).reduce((acc, curr) => acc + curr.raw, '');
212
- currentParagraphRef.current = newCurrentSegment;
213
- triggerUpdate();
214
- }
215
- else {
216
- // TODO
217
- }
218
- }
219
- const realRaw = str.slice(nextTokenIndex);
220
- if (realRaw.length > 0) {
221
- pushAndSplitSegment(realRaw, i, lastSegmentReference.id);
222
- }
223
- }
224
- nextTokenIndex = str.length;
225
- }
226
- }
227
- lastSegmentRawRef.current[`${answerType}Reference`] = currentLastSegmentReference;
228
- wholeContent.content = wholeContent.content + content;
229
- wholeContent.length = wholeContent.content.length;
69
+ charsRef.current.push(...content.split('').map((chatStr) => {
70
+ const index = charIndexRef.current++;
71
+ const charObj = {
72
+ content: chatStr,
73
+ answerType,
74
+ contentType: 'segment',
75
+ tokenId: 0,
76
+ index,
77
+ };
78
+ return charObj;
79
+ }));
80
+ wholeContentRef.current.allLength += content.length;
230
81
  if (!typingTask.isTyping()) {
231
82
  typingTask.start();
232
83
  }
@@ -245,20 +96,31 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
245
96
  */
246
97
  clear: () => {
247
98
  typingTask.stop();
99
+ typingTask.typedIsManualStopRef.current = false;
248
100
  charsRef.current = [];
101
+ wholeContentRef.current = {
102
+ thinking: {
103
+ content: '',
104
+ length: 0,
105
+ },
106
+ answer: {
107
+ content: '',
108
+ length: 0,
109
+ },
110
+ allLength: 0,
111
+ };
249
112
  isWholeTypedEndRef.current = false;
250
- currentParagraphRef.current = undefined;
251
- typingTask.clear();
252
- // 清理缓冲区
253
- const lastSegmentRef = lastSegmentRawRef.current;
254
- lastSegmentRef.thinkingReference = null;
255
- lastSegmentRef.answerReference = null;
256
- wholeContentRef.current.thinking.content = '';
257
- wholeContentRef.current.thinking.length = 0;
258
- wholeContentRef.current.answer.content = '';
259
- wholeContentRef.current.answer.length = 0;
113
+ charIndexRef.current = 0;
260
114
  triggerUpdate();
261
115
  },
116
+ /** 停止打字任务 */
117
+ stop: () => {
118
+ typingTask.stop();
119
+ },
120
+ /** 重新开始打字任务 */
121
+ resume: () => {
122
+ typingTask.resume();
123
+ },
262
124
  /**
263
125
  * 主动触发打字结束
264
126
  */
@@ -276,19 +138,14 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
276
138
  * 刷新缓冲区 (新增方法)
277
139
  */
278
140
  }));
279
- const getParagraphs = (paragraphs, answerType) => {
280
- return ((0, jsx_runtime_1.jsxs)("div", { className: `ds-markdown-paragraph ds-typed-${answerType}`, children: [paragraphs.map((paragraph, index) => {
281
- if (paragraph.type === 'br') {
282
- return null;
283
- }
284
- return ((0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: paragraph.content || '' }, index));
285
- }), currentSegment?.answerType === answerType && ((0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: currentSegment.content || '' }, currentSegment.content))] }));
141
+ const getParagraphs = (answerType) => {
142
+ return ((0, jsx_runtime_1.jsx)("div", { className: `ds-markdown-paragraph ds-typed-${answerType}`, children: (0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: wholeContentRef.current[answerType].content || '' }) }));
286
143
  };
287
144
  return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)({
288
145
  'ds-markdown': true,
289
146
  apple: true,
290
147
  'ds-markdown-dark': theme === 'dark',
291
- }), children: [(thinkingParagraphs.length > 0 || currentSegment?.answerType === 'thinking') && (0, jsx_runtime_1.jsx)("div", { className: "ds-markdown-thinking", children: getParagraphs(thinkingParagraphs, 'thinking') }), (answerParagraphs.length > 0 || currentSegment?.answerType === 'answer') && (0, jsx_runtime_1.jsx)("div", { className: "ds-markdown-answer", children: getParagraphs(answerParagraphs, 'answer') })] }));
148
+ }), children: [(0, jsx_runtime_1.jsx)("div", { className: "ds-markdown-thinking", children: getParagraphs('thinking') }), (0, jsx_runtime_1.jsx)("div", { className: "ds-markdown-answer", children: getParagraphs('answer') })] }));
292
149
  });
293
150
  if (constant_js_1.__DEV__) {
294
151
  MarkdownCMD.displayName = 'MarkdownCMD';