@smart-cloud/ai-kit-ui 1.3.15 → 1.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smart-cloud/ai-kit-ui",
3
- "version": "1.3.15",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -19,51 +19,54 @@
19
19
  "dependencies": {
20
20
  "@emotion/cache": "^11.14.0",
21
21
  "@emotion/react": "^11.14.0",
22
- "@mantine/colors-generator": "^8.3.15",
23
- "@smart-cloud/ai-kit-core": "^1.3.7",
24
- "@smart-cloud/wpsuite-core": "^2.2.6",
25
- "@tabler/icons-react": "^3.36.1",
22
+ "@mantine/colors-generator": "^8.3.16",
23
+ "@smart-cloud/ai-kit-core": "^1.4.3",
24
+ "@smart-cloud/wpsuite-core": "^2.2.10",
25
+ "@tabler/icons-react": "^3.40.0",
26
26
  "chroma-js": "^3.2.0",
27
27
  "react-markdown": "^10.1.0",
28
+ "rehype-parse": "^9.0.1",
28
29
  "rehype-raw": "^7.0.0",
30
+ "rehype-remark": "^10.0.0",
29
31
  "rehype-sanitize": "^6.0.0",
30
32
  "rehype-stringify": "^10.0.1",
31
33
  "remark-gfm": "^4.0.1",
32
34
  "remark-parse": "^11.0.0",
33
35
  "remark-rehype": "^11.1.2",
36
+ "remark-stringify": "^11.0.0",
34
37
  "unified": "^11.0.5"
35
38
  },
36
39
  "peerDependencies": {
37
- "@mantine/core": "^8.3.15",
38
- "@mantine/hooks": "^8.3.15",
39
- "@mantine/modals": "^8.3.15",
40
- "@wordpress/data": "^10.39.0",
41
- "aws-amplify": "^6.16.2",
40
+ "@mantine/core": "^8.3.16",
41
+ "@mantine/hooks": "^8.3.16",
42
+ "@mantine/modals": "^8.3.16",
43
+ "@wordpress/data": "^10.41.0",
44
+ "aws-amplify": "^6.16.3",
42
45
  "react": "^18.3.1",
43
46
  "react-dom": "^18.3.1"
44
47
  },
45
48
  "devDependencies": {
46
49
  "@eslint/js": "^10.0.1",
47
- "@mantine/core": "^8.3.15",
48
- "@mantine/hooks": "^8.3.15",
49
- "@mantine/modals": "^8.3.15",
50
- "@types/dom-chromium-ai": "^0.0.14",
51
- "@types/jquery": "^3.5.33",
50
+ "@mantine/core": "^8.3.16",
51
+ "@mantine/hooks": "^8.3.16",
52
+ "@mantine/modals": "^8.3.16",
53
+ "@types/dom-chromium-ai": "^0.0.15",
54
+ "@types/jquery": "^4.0.0",
52
55
  "@types/react": "^18.3.23",
53
56
  "@types/react-dom": "^18.3.7",
54
- "@typescript-eslint/eslint-plugin": "^8.56.0",
55
- "@typescript-eslint/parser": "^8.56.0",
56
- "@wordpress/data": "^10.39.0",
57
- "ajv": "^8.17.1",
58
- "aws-amplify": "^6.16.2",
59
- "eslint": "^10.0.0",
60
- "globals": "^17.3.0",
57
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
58
+ "@typescript-eslint/parser": "^8.57.0",
59
+ "@wordpress/data": "^10.41.0",
60
+ "ajv": "^8.18.0",
61
+ "aws-amplify": "^6.16.3",
62
+ "eslint": "^10.0.3",
63
+ "globals": "^17.4.0",
61
64
  "jquery": "^4.0.0",
62
65
  "react": "^18.3.1",
63
66
  "react-dom": "^18.3.1",
64
67
  "tsup": "^8.5.1",
65
68
  "typescript": "^5.9.3",
66
- "typescript-eslint": "^8.56.0"
69
+ "typescript-eslint": "^8.57.0"
67
70
  },
68
71
  "exports": {
69
72
  ".": {
@@ -55,6 +55,12 @@ import {
55
55
 
56
56
  import { translations } from "../i18n";
57
57
  import { PoweredBy } from "../poweredBy";
58
+ import { shouldChunkInput } from "./chunking-utils";
59
+ import {
60
+ chunkedSummarize,
61
+ chunkedTranslate,
62
+ chunkedRewrite,
63
+ } from "./chunked-features";
58
64
  import {
59
65
  isBackendConfigured,
60
66
  readDefaultOutputLanguage,
@@ -472,7 +478,31 @@ const AiFeatureBase: FC<AiFeatureProps & AiKitShellInjectedProps> = (props) => {
472
478
  }, [text, defaults]);
473
479
 
474
480
  const canGenerate = useMemo(() => {
475
- const text = typeof inputText === "function" ? inputText() : inputText;
481
+ // If inputText is a function (async or sync getText), we can't determine
482
+ // if it has content without calling it. Assume it's valid if provided.
483
+ const input = inputText;
484
+ if (typeof input === "function") {
485
+ switch (mode) {
486
+ case "generateImageMetadata":
487
+ return Boolean(image);
488
+ case "translate":
489
+ // For translate, we need outputLanguage check, but can't check text without calling getText
490
+ return Boolean(
491
+ !outputLanguage || detectedLanguage !== outputLanguage,
492
+ );
493
+ case "summarize":
494
+ case "proofread":
495
+ case "rewrite":
496
+ case "write":
497
+ case "generatePostMetadata":
498
+ return true; // Assume getText will provide valid content
499
+ default:
500
+ return false;
501
+ }
502
+ }
503
+
504
+ // If inputText is a string, check it directly
505
+ const text = input as string | undefined;
476
506
  switch (mode) {
477
507
  case "generateImageMetadata":
478
508
  return Boolean(image);
@@ -507,8 +537,11 @@ const AiFeatureBase: FC<AiFeatureProps & AiKitShellInjectedProps> = (props) => {
507
537
  setError(null);
508
538
  setGenerated(null);
509
539
 
540
+ const input = await inputText;
510
541
  try {
511
- const text = typeof inputText === "function" ? inputText() : inputText;
542
+ // Support both sync and async getText functions
543
+ const text =
544
+ typeof input === "function" ? await Promise.resolve(input()) : input;
512
545
  switch (mode) {
513
546
  case "summarize": {
514
547
  const res = await ai.run(async ({ signal, onStatus }) => {
@@ -524,13 +557,32 @@ const AiFeatureBase: FC<AiFeatureProps & AiKitShellInjectedProps> = (props) => {
524
557
  type: type as SummarizerType,
525
558
  outputLanguage: outLang as SummarizeArgs["outputLanguage"],
526
559
  };
527
- const out = await summarize(args, {
560
+
561
+ const featureOptions: FeatureOptions = {
528
562
  signal,
529
563
  onStatus,
530
564
  context,
531
565
  modeOverride,
532
566
  onDeviceTimeoutOverride: onDeviceTimeout,
533
- });
567
+ };
568
+
569
+ // Determine if we're using on-device mode
570
+ const isOnDevice =
571
+ modeOverride === "local-only" ||
572
+ (!modeOverride && context === "admin");
573
+
574
+ // Check if chunking is needed
575
+ if (shouldChunkInput(text!.trim(), "summarize", isOnDevice)) {
576
+ return await chunkedSummarize(
577
+ text!.trim(),
578
+ args,
579
+ featureOptions,
580
+ isOnDevice,
581
+ );
582
+ }
583
+
584
+ // Normal single-pass summarization
585
+ const out = await summarize(args, featureOptions);
534
586
  return out.result;
535
587
  });
536
588
  setGenerated((res as never) ?? "");
@@ -614,13 +666,32 @@ const AiFeatureBase: FC<AiFeatureProps & AiKitShellInjectedProps> = (props) => {
614
666
  sourceLanguage: inputLang!,
615
667
  targetLanguage: outLang,
616
668
  };
617
- const out = await translate(args, {
669
+
670
+ const featureOptions: FeatureOptions = {
618
671
  signal,
619
672
  onStatus,
620
673
  context,
621
674
  modeOverride,
622
675
  onDeviceTimeoutOverride: onDeviceTimeout,
623
- });
676
+ };
677
+
678
+ // Determine if we're using on-device mode
679
+ const isOnDevice =
680
+ modeOverride === "local-only" ||
681
+ (!modeOverride && context === "admin");
682
+
683
+ // Check if chunking is needed (both on-device quota and AWS Translate limit)
684
+ if (shouldChunkInput(text!.trim(), "translate", isOnDevice)) {
685
+ return await chunkedTranslate(
686
+ text!.trim(),
687
+ args,
688
+ featureOptions,
689
+ isOnDevice,
690
+ );
691
+ }
692
+
693
+ // Normal single-pass translation
694
+ const out = await translate(args, featureOptions);
624
695
  return out.result;
625
696
  });
626
697
  setGenerated((res as never) ?? "");
@@ -652,13 +723,32 @@ const AiFeatureBase: FC<AiFeatureProps & AiKitShellInjectedProps> = (props) => {
652
723
  length: length as RewriterLength,
653
724
  outputLanguage: outLang as RewriteArgs["outputLanguage"],
654
725
  };
655
- const out = await rewrite(args, {
726
+
727
+ const featureOptions: FeatureOptions = {
656
728
  signal,
657
729
  onStatus,
658
730
  context,
659
731
  modeOverride,
660
732
  onDeviceTimeoutOverride: onDeviceTimeout,
661
- });
733
+ };
734
+
735
+ // Determine if we're using on-device mode
736
+ const isOnDevice =
737
+ modeOverride === "local-only" ||
738
+ (!modeOverride && context === "admin");
739
+
740
+ // Check if chunking is needed
741
+ if (shouldChunkInput(text!.trim(), "rewrite", isOnDevice)) {
742
+ return await chunkedRewrite(
743
+ text!.trim(),
744
+ args,
745
+ featureOptions,
746
+ isOnDevice,
747
+ );
748
+ }
749
+
750
+ // Normal single-pass rewrite
751
+ const out = await rewrite(args, featureOptions);
662
752
  return out.result;
663
753
  });
664
754
  setGenerated((res as never) ?? "");
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Chunked versions of AI features for handling large inputs
3
+ *
4
+ * These wrappers split large inputs into smaller chunks, process them
5
+ * sequentially, and combine the results.
6
+ */
7
+
8
+ import type {
9
+ AiKitStatusEvent,
10
+ FeatureOptions,
11
+ SummarizeArgs,
12
+ SummarizeResult,
13
+ TranslateArgs,
14
+ TranslateResult,
15
+ RewriteArgs,
16
+ RewriteResult,
17
+ } from "@smart-cloud/ai-kit-core";
18
+ import { summarize, translate, rewrite } from "@smart-cloud/ai-kit-core";
19
+ import {
20
+ splitTextIntoChunks,
21
+ getChunkSize,
22
+ estimateTokenCount,
23
+ } from "./chunking-utils";
24
+
25
+ /**
26
+ * Chunked summarize implementation
27
+ *
28
+ * Strategy:
29
+ * 1. Split text into chunks
30
+ * 2. Summarize each chunk
31
+ * 3. If combined summaries are still large, recursively summarize them
32
+ * 4. Return final summary
33
+ */
34
+ export async function chunkedSummarize(
35
+ text: string,
36
+ args: SummarizeArgs,
37
+ options: FeatureOptions,
38
+ isOnDevice: boolean,
39
+ recursionLevel: number = 0,
40
+ ): Promise<SummarizeResult> {
41
+ const maxChunkSize = getChunkSize("summarize", isOnDevice);
42
+ const chunks = splitTextIntoChunks(text, maxChunkSize);
43
+
44
+ if (chunks.length === 1) {
45
+ // No chunking needed
46
+ return await summarize(args, options);
47
+ }
48
+
49
+ // Prevent infinite recursion (max 2 levels)
50
+ if (recursionLevel >= 2) {
51
+ throw new Error(
52
+ "Text is too large to summarize. Please try using backend mode or reduce the input size.",
53
+ );
54
+ }
55
+
56
+ // Phase 1: Summarize each chunk
57
+ const chunkSummaries: string[] = [];
58
+
59
+ for (let i = 0; i < chunks.length; i++) {
60
+ const chunkResult = await summarize(
61
+ {
62
+ ...args,
63
+ text: chunks[i].text,
64
+ },
65
+ {
66
+ ...options,
67
+ onStatus: (e: AiKitStatusEvent) => {
68
+ if (options.onStatus) {
69
+ // Modify progress to reflect chunking
70
+ const baseProgress =
71
+ typeof e.progress === "number" ? e.progress : 0;
72
+ const chunkProgress = (i + baseProgress) / chunks.length;
73
+
74
+ options.onStatus({
75
+ ...e,
76
+ message:
77
+ recursionLevel === 0
78
+ ? `Summarizing part ${i + 1}/${chunks.length}...`
79
+ : `Combining summaries (${i + 1}/${chunks.length})...`,
80
+ progress: chunkProgress,
81
+ });
82
+ }
83
+ },
84
+ },
85
+ );
86
+
87
+ chunkSummaries.push(chunkResult.result);
88
+ }
89
+
90
+ // Phase 2: Combine summaries
91
+ const combinedSummaries = chunkSummaries.join("\n\n");
92
+
93
+ // Check if we need another round of summarization
94
+ if (estimateTokenCount(combinedSummaries) > maxChunkSize / 3.5) {
95
+ // Recursively summarize
96
+ return await chunkedSummarize(
97
+ combinedSummaries,
98
+ {
99
+ ...args,
100
+ // Adjust length for recursive summarization
101
+ length: args.length === "short" ? "short" : "medium",
102
+ },
103
+ {
104
+ ...options,
105
+ onStatus: (e: AiKitStatusEvent) => {
106
+ if (options.onStatus) {
107
+ options.onStatus({
108
+ ...e,
109
+ message: "Creating final summary...",
110
+ });
111
+ }
112
+ },
113
+ },
114
+ isOnDevice,
115
+ recursionLevel + 1,
116
+ );
117
+ }
118
+
119
+ // Final summarization
120
+ return await summarize(
121
+ {
122
+ ...args,
123
+ text: combinedSummaries,
124
+ length: args.length === "short" ? "short" : "medium",
125
+ },
126
+ {
127
+ ...options,
128
+ onStatus: (e: AiKitStatusEvent) => {
129
+ if (options.onStatus) {
130
+ options.onStatus({
131
+ ...e,
132
+ message: "Creating final summary...",
133
+ });
134
+ }
135
+ },
136
+ },
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Chunked translate implementation
142
+ *
143
+ * Strategy:
144
+ * 1. Split text into chunks (respecting AWS Translate 10k char limit)
145
+ * 2. Translate each chunk sequentially
146
+ * 3. Join translated chunks
147
+ */
148
+ export async function chunkedTranslate(
149
+ text: string,
150
+ args: TranslateArgs,
151
+ options: FeatureOptions,
152
+ isOnDevice: boolean,
153
+ ): Promise<TranslateResult> {
154
+ const maxChunkSize = getChunkSize("translate", isOnDevice);
155
+ const chunks = splitTextIntoChunks(text, maxChunkSize);
156
+
157
+ if (chunks.length === 1) {
158
+ // No chunking needed
159
+ return await translate(args, options);
160
+ }
161
+
162
+ // Translate each chunk sequentially
163
+ const translatedChunks: string[] = [];
164
+
165
+ for (let i = 0; i < chunks.length; i++) {
166
+ const chunkResult = await translate(
167
+ {
168
+ ...args,
169
+ text: chunks[i].text,
170
+ },
171
+ {
172
+ ...options,
173
+ onStatus: (e: AiKitStatusEvent) => {
174
+ if (options.onStatus) {
175
+ const baseProgress =
176
+ typeof e.progress === "number" ? e.progress : 0;
177
+ const chunkProgress = (i + baseProgress) / chunks.length;
178
+
179
+ options.onStatus({
180
+ ...e,
181
+ message: `Translating part ${i + 1}/${chunks.length}...`,
182
+ progress: chunkProgress,
183
+ });
184
+ }
185
+ },
186
+ },
187
+ );
188
+
189
+ translatedChunks.push(chunkResult.result);
190
+ }
191
+
192
+ // Join with paragraph breaks to maintain structure
193
+ return {
194
+ result: translatedChunks.join("\n\n"),
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Chunked rewrite implementation
200
+ *
201
+ * Strategy:
202
+ * 1. Split text into chunks
203
+ * 2. Rewrite each chunk sequentially
204
+ * 3. Join rewritten chunks
205
+ */
206
+ export async function chunkedRewrite(
207
+ text: string,
208
+ args: RewriteArgs,
209
+ options: FeatureOptions,
210
+ isOnDevice: boolean,
211
+ ): Promise<RewriteResult> {
212
+ const maxChunkSize = getChunkSize("rewrite", isOnDevice);
213
+ const chunks = splitTextIntoChunks(text, maxChunkSize);
214
+
215
+ if (chunks.length === 1) {
216
+ // No chunking needed
217
+ return await rewrite(args, options);
218
+ }
219
+
220
+ // Rewrite each chunk sequentially
221
+ const rewrittenChunks: string[] = [];
222
+
223
+ for (let i = 0; i < chunks.length; i++) {
224
+ const chunkResult = await rewrite(
225
+ {
226
+ ...args,
227
+ text: chunks[i].text,
228
+ },
229
+ {
230
+ ...options,
231
+ onStatus: (e: AiKitStatusEvent) => {
232
+ if (options.onStatus) {
233
+ const baseProgress =
234
+ typeof e.progress === "number" ? e.progress : 0;
235
+ const chunkProgress = (i + baseProgress) / chunks.length;
236
+
237
+ options.onStatus({
238
+ ...e,
239
+ message: `Rewriting part ${i + 1}/${chunks.length}...`,
240
+ progress: chunkProgress,
241
+ });
242
+ }
243
+ },
244
+ },
245
+ );
246
+
247
+ rewrittenChunks.push(chunkResult.result);
248
+ }
249
+
250
+ // Join with paragraph breaks
251
+ return {
252
+ result: rewrittenChunks.join("\n\n"),
253
+ };
254
+ }