cactus-react-native 1.0.1 → 1.1.0

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 (128) hide show
  1. package/README.md +609 -56
  2. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusCrypto.kt +23 -15
  3. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusDeviceInfo.kt +12 -9
  4. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusFileSystem.kt +42 -41
  5. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusImage.kt +81 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libcactus.a +0 -0
  7. package/cpp/HybridCactus.cpp +161 -44
  8. package/cpp/HybridCactus.hpp +34 -14
  9. package/cpp/HybridCactusUtil.cpp +13 -11
  10. package/cpp/HybridCactusUtil.hpp +9 -9
  11. package/cpp/cactus_ffi.h +28 -1
  12. package/ios/HybridCactusImage.swift +53 -0
  13. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +28 -1
  14. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/engine.h +237 -7
  15. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/ffi_utils.h +158 -43
  16. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/graph.h +23 -2
  17. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel.h +52 -0
  18. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  19. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_ffi.h +28 -1
  20. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/engine.h +237 -7
  21. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/ffi_utils.h +158 -43
  22. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/graph.h +23 -2
  23. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel.h +52 -0
  24. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/cactus +0 -0
  25. package/lib/module/api/Database.js +23 -0
  26. package/lib/module/api/Database.js.map +1 -1
  27. package/lib/module/api/RemoteLM.js +201 -0
  28. package/lib/module/api/RemoteLM.js.map +1 -0
  29. package/lib/module/classes/CactusLM.js +56 -28
  30. package/lib/module/classes/CactusLM.js.map +1 -1
  31. package/lib/module/classes/CactusSTT.js +137 -0
  32. package/lib/module/classes/CactusSTT.js.map +1 -0
  33. package/lib/module/config/CactusConfig.js +4 -0
  34. package/lib/module/config/CactusConfig.js.map +1 -1
  35. package/lib/module/constants/packageVersion.js +1 -1
  36. package/lib/module/hooks/useCactusLM.js +44 -16
  37. package/lib/module/hooks/useCactusLM.js.map +1 -1
  38. package/lib/module/hooks/useCactusSTT.js +234 -0
  39. package/lib/module/hooks/useCactusSTT.js.map +1 -0
  40. package/lib/module/index.js +2 -0
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/native/Cactus.js +52 -3
  43. package/lib/module/native/Cactus.js.map +1 -1
  44. package/lib/module/native/CactusFileSystem.js +2 -3
  45. package/lib/module/native/CactusFileSystem.js.map +1 -1
  46. package/lib/module/native/CactusImage.js +13 -0
  47. package/lib/module/native/CactusImage.js.map +1 -0
  48. package/lib/module/native/index.js +1 -0
  49. package/lib/module/native/index.js.map +1 -1
  50. package/lib/module/specs/CactusImage.nitro.js +4 -0
  51. package/lib/module/specs/CactusImage.nitro.js.map +1 -0
  52. package/lib/module/telemetry/Telemetry.js +53 -1
  53. package/lib/module/telemetry/Telemetry.js.map +1 -1
  54. package/lib/module/types/CactusSTT.js +2 -0
  55. package/lib/module/types/CactusSTT.js.map +1 -0
  56. package/lib/typescript/src/api/Database.d.ts +1 -0
  57. package/lib/typescript/src/api/Database.d.ts.map +1 -1
  58. package/lib/typescript/src/api/RemoteLM.d.ts +14 -0
  59. package/lib/typescript/src/api/RemoteLM.d.ts.map +1 -0
  60. package/lib/typescript/src/classes/CactusLM.d.ts +8 -5
  61. package/lib/typescript/src/classes/CactusLM.d.ts.map +1 -1
  62. package/lib/typescript/src/classes/CactusSTT.d.ts +25 -0
  63. package/lib/typescript/src/classes/CactusSTT.d.ts.map +1 -0
  64. package/lib/typescript/src/config/CactusConfig.d.ts +1 -0
  65. package/lib/typescript/src/config/CactusConfig.d.ts.map +1 -1
  66. package/lib/typescript/src/constants/packageVersion.d.ts +1 -1
  67. package/lib/typescript/src/hooks/useCactusLM.d.ts +5 -4
  68. package/lib/typescript/src/hooks/useCactusLM.d.ts.map +1 -1
  69. package/lib/typescript/src/hooks/useCactusSTT.d.ts +20 -0
  70. package/lib/typescript/src/hooks/useCactusSTT.d.ts.map +1 -0
  71. package/lib/typescript/src/index.d.ts +4 -1
  72. package/lib/typescript/src/index.d.ts.map +1 -1
  73. package/lib/typescript/src/native/Cactus.d.ts +10 -3
  74. package/lib/typescript/src/native/Cactus.d.ts.map +1 -1
  75. package/lib/typescript/src/native/CactusFileSystem.d.ts +1 -1
  76. package/lib/typescript/src/native/CactusFileSystem.d.ts.map +1 -1
  77. package/lib/typescript/src/native/CactusImage.d.ts +6 -0
  78. package/lib/typescript/src/native/CactusImage.d.ts.map +1 -0
  79. package/lib/typescript/src/native/index.d.ts +1 -0
  80. package/lib/typescript/src/native/index.d.ts.map +1 -1
  81. package/lib/typescript/src/specs/Cactus.nitro.d.ts +4 -1
  82. package/lib/typescript/src/specs/Cactus.nitro.d.ts.map +1 -1
  83. package/lib/typescript/src/specs/CactusImage.nitro.d.ts +9 -0
  84. package/lib/typescript/src/specs/CactusImage.nitro.d.ts.map +1 -0
  85. package/lib/typescript/src/telemetry/Telemetry.d.ts +5 -1
  86. package/lib/typescript/src/telemetry/Telemetry.d.ts.map +1 -1
  87. package/lib/typescript/src/types/CactusLM.d.ts +11 -6
  88. package/lib/typescript/src/types/CactusLM.d.ts.map +1 -1
  89. package/lib/typescript/src/types/CactusSTT.d.ts +37 -0
  90. package/lib/typescript/src/types/CactusSTT.d.ts.map +1 -0
  91. package/nitro.json +4 -0
  92. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.cpp +81 -0
  93. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.hpp +66 -0
  94. package/nitrogen/generated/android/cactus+autolinking.cmake +2 -0
  95. package/nitrogen/generated/android/cactusOnLoad.cpp +10 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusImageSpec.kt +62 -0
  97. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.cpp +17 -0
  98. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.hpp +17 -0
  99. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Umbrella.hpp +5 -0
  100. package/nitrogen/generated/ios/CactusAutolinking.mm +8 -0
  101. package/nitrogen/generated/ios/CactusAutolinking.swift +15 -0
  102. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.cpp +11 -0
  103. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.hpp +85 -0
  104. package/nitrogen/generated/ios/swift/HybridCactusImageSpec.swift +58 -0
  105. package/nitrogen/generated/ios/swift/HybridCactusImageSpec_cxx.swift +158 -0
  106. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.cpp +22 -0
  107. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.hpp +64 -0
  108. package/nitrogen/generated/shared/c++/HybridCactusSpec.cpp +3 -0
  109. package/nitrogen/generated/shared/c++/HybridCactusSpec.hpp +4 -1
  110. package/package.json +1 -1
  111. package/src/api/Database.ts +27 -0
  112. package/src/api/RemoteLM.ts +273 -0
  113. package/src/classes/CactusLM.ts +76 -40
  114. package/src/classes/CactusSTT.ts +182 -0
  115. package/src/config/CactusConfig.ts +4 -0
  116. package/src/constants/packageVersion.ts +1 -1
  117. package/src/hooks/useCactusLM.ts +53 -22
  118. package/src/hooks/useCactusSTT.ts +285 -0
  119. package/src/index.tsx +14 -2
  120. package/src/native/Cactus.ts +100 -6
  121. package/src/native/CactusFileSystem.ts +2 -2
  122. package/src/native/CactusImage.ts +20 -0
  123. package/src/native/index.ts +1 -0
  124. package/src/specs/Cactus.nitro.ts +14 -1
  125. package/src/specs/CactusImage.nitro.ts +12 -0
  126. package/src/telemetry/Telemetry.ts +78 -1
  127. package/src/types/CactusLM.ts +12 -6
  128. package/src/types/CactusSTT.ts +42 -0
@@ -5,7 +5,8 @@ import type {
5
5
  CactusLMCompleteResult,
6
6
  CactusLMEmbedParams,
7
7
  CactusLMEmbedResult,
8
- CactusLMGetModelsParams,
8
+ CactusLMImageEmbedParams,
9
+ CactusLMImageEmbedResult,
9
10
  CactusLMParams,
10
11
  } from '../types/CactusLM';
11
12
  import type { CactusModel } from '../types/CactusModel';
@@ -13,12 +14,14 @@ import { Telemetry } from '../telemetry/Telemetry';
13
14
  import { CactusConfig } from '../config/CactusConfig';
14
15
  import { Database } from '../api/Database';
15
16
  import { getErrorMessage } from '../utils/error';
17
+ import { RemoteLM } from '../api/RemoteLM';
16
18
 
17
19
  export class CactusLM {
18
20
  private readonly cactus = new Cactus();
19
21
 
20
22
  private readonly model: string;
21
23
  private readonly contextSize: number;
24
+ private readonly corpusDir?: string;
22
25
 
23
26
  private isDownloading = false;
24
27
  private isInitialized = false;
@@ -29,13 +32,17 @@ export class CactusLM {
29
32
  private static readonly defaultCompleteOptions = {
30
33
  maxTokens: 512,
31
34
  };
35
+ private static readonly defaultCompleteMode = 'local';
32
36
  private static readonly defaultEmbedBufferSize = 2048;
33
37
 
34
- private static readonly modelsInfoPath = 'models/info.json';
38
+ private static cactusModelsCache: CactusModel[] | null = null;
39
+
40
+ constructor({ model, contextSize, corpusDir }: CactusLMParams = {}) {
41
+ Telemetry.init(CactusConfig.telemetryToken);
35
42
 
36
- constructor({ model, contextSize }: CactusLMParams = {}) {
37
43
  this.model = model ?? CactusLM.defaultModel;
38
44
  this.contextSize = contextSize ?? CactusLM.defaultContextSize;
45
+ this.corpusDir = corpusDir;
39
46
  }
40
47
 
41
48
  public async download({
@@ -52,8 +59,12 @@ export class CactusLM {
52
59
 
53
60
  this.isDownloading = true;
54
61
  try {
55
- await CactusFileSystem.downloadModel(this.model, onProgress);
56
- await this.getModels({ forceRefresh: true });
62
+ const model = await Database.getModel(this.model);
63
+ await CactusFileSystem.downloadModel(
64
+ this.model,
65
+ model.downloadUrl,
66
+ onProgress
67
+ );
57
68
  } finally {
58
69
  this.isDownloading = false;
59
70
  }
@@ -64,10 +75,6 @@ export class CactusLM {
64
75
  return;
65
76
  }
66
77
 
67
- if (!Telemetry.isInitialized()) {
68
- await Telemetry.init(CactusConfig.telemetryToken);
69
- }
70
-
71
78
  if (!(await CactusFileSystem.modelExists(this.model))) {
72
79
  throw new Error(`Model "${this.model}" is not downloaded`);
73
80
  }
@@ -75,7 +82,7 @@ export class CactusLM {
75
82
  const modelPath = await CactusFileSystem.getModelPath(this.model);
76
83
 
77
84
  try {
78
- await this.cactus.init(modelPath, this.contextSize);
85
+ await this.cactus.init(modelPath, this.contextSize, this.corpusDir);
79
86
  Telemetry.logInit(this.model, true);
80
87
  this.isInitialized = true;
81
88
  } catch (error) {
@@ -89,25 +96,32 @@ export class CactusLM {
89
96
  options,
90
97
  tools,
91
98
  onToken,
99
+ mode,
92
100
  }: CactusLMCompleteParams): Promise<CactusLMCompleteResult> {
93
101
  if (this.isGenerating) {
94
102
  throw new Error('CactusLM is already generating');
95
103
  }
96
104
 
97
- await this.init();
98
-
99
105
  options = { ...CactusLM.defaultCompleteOptions, ...options };
106
+ const toolsInternal = tools?.map((tool) => ({
107
+ type: 'function' as const,
108
+ function: tool,
109
+ }));
110
+ mode = mode ?? CactusLM.defaultCompleteMode;
111
+
100
112
  const responseBufferSize =
101
113
  8 * (options.maxTokens ?? CactusLM.defaultCompleteOptions.maxTokens) +
102
114
  256;
103
115
 
104
- this.isGenerating = true;
105
116
  try {
117
+ await this.init();
118
+
119
+ this.isGenerating = true;
106
120
  const result = await this.cactus.complete(
107
121
  messages,
108
122
  responseBufferSize,
109
123
  options,
110
- tools,
124
+ toolsInternal,
111
125
  onToken
112
126
  );
113
127
  Telemetry.logCompletion(
@@ -117,9 +131,25 @@ export class CactusLM {
117
131
  result
118
132
  );
119
133
  return result;
120
- } catch (error) {
121
- Telemetry.logCompletion(this.model, false, getErrorMessage(error));
122
- throw error;
134
+ } catch (localError) {
135
+ if (mode === 'local') {
136
+ Telemetry.logCompletion(this.model, false, getErrorMessage(localError));
137
+ throw localError;
138
+ }
139
+
140
+ Telemetry.logCompletion(
141
+ this.model,
142
+ false,
143
+ `Local completion error: ${getErrorMessage(localError)}. Falling back to remote completion.`
144
+ );
145
+
146
+ try {
147
+ return RemoteLM.complete(messages, options, toolsInternal, onToken);
148
+ } catch (remoteError) {
149
+ throw new Error(
150
+ `Remote completion error: ${getErrorMessage(remoteError)}`
151
+ );
152
+ }
123
153
  } finally {
124
154
  this.isGenerating = false;
125
155
  }
@@ -150,6 +180,31 @@ export class CactusLM {
150
180
  }
151
181
  }
152
182
 
183
+ public async imageEmbed({
184
+ imagePath,
185
+ }: CactusLMImageEmbedParams): Promise<CactusLMImageEmbedResult> {
186
+ if (this.isGenerating) {
187
+ throw new Error('CactusLM is already generating');
188
+ }
189
+
190
+ await this.init();
191
+
192
+ this.isGenerating = true;
193
+ try {
194
+ const embedding = await this.cactus.imageEmbed(
195
+ imagePath,
196
+ CactusLM.defaultEmbedBufferSize
197
+ );
198
+ Telemetry.logImageEmbedding(this.model, true);
199
+ return { embedding };
200
+ } catch (error) {
201
+ Telemetry.logImageEmbedding(this.model, false, getErrorMessage(error));
202
+ throw error;
203
+ } finally {
204
+ this.isGenerating = false;
205
+ }
206
+ }
207
+
153
208
  public stop(): Promise<void> {
154
209
  return this.cactus.stop();
155
210
  }
@@ -170,34 +225,15 @@ export class CactusLM {
170
225
  this.isInitialized = false;
171
226
  }
172
227
 
173
- public async getModels({
174
- forceRefresh = false,
175
- }: CactusLMGetModelsParams = {}): Promise<CactusModel[]> {
176
- if (
177
- !forceRefresh &&
178
- (await CactusFileSystem.fileExists(CactusLM.modelsInfoPath))
179
- ) {
180
- try {
181
- return JSON.parse(
182
- await CactusFileSystem.readFile(CactusLM.modelsInfoPath)
183
- );
184
- } catch {
185
- // Delete corrupted models info
186
- await CactusFileSystem.deleteFile(CactusLM.modelsInfoPath);
187
- }
228
+ public async getModels(): Promise<CactusModel[]> {
229
+ if (CactusLM.cactusModelsCache) {
230
+ return CactusLM.cactusModelsCache;
188
231
  }
189
-
190
232
  const models = await Database.getModels();
191
-
192
233
  for (const model of models) {
193
234
  model.isDownloaded = await CactusFileSystem.modelExists(model.slug);
194
235
  }
195
-
196
- await CactusFileSystem.writeFile(
197
- CactusLM.modelsInfoPath,
198
- JSON.stringify(models)
199
- );
200
-
236
+ CactusLM.cactusModelsCache = models;
201
237
  return models;
202
238
  }
203
239
  }
@@ -0,0 +1,182 @@
1
+ import { Cactus, CactusFileSystem } from '../native';
2
+ import type {
3
+ CactusSTTDownloadParams,
4
+ CactusSTTTranscribeParams,
5
+ CactusSTTTranscribeResult,
6
+ CactusSTTParams,
7
+ CactusSTTAudioEmbedParams,
8
+ CactusSTTAudioEmbedResult,
9
+ } from '../types/CactusSTT';
10
+ import type { CactusModel } from '../types/CactusModel';
11
+ import { Telemetry } from '../telemetry/Telemetry';
12
+ import { CactusConfig } from '../config/CactusConfig';
13
+ import { Database } from '../api/Database';
14
+ import { getErrorMessage } from '../utils/error';
15
+
16
+ export class CactusSTT {
17
+ private readonly cactus = new Cactus();
18
+
19
+ private readonly model: string;
20
+ private readonly contextSize: number;
21
+
22
+ private isDownloading = false;
23
+ private isInitialized = false;
24
+ private isGenerating = false;
25
+
26
+ private static readonly defaultModel = 'whisper-small';
27
+ private static readonly defaultContextSize = 2048;
28
+ private static readonly defaultTranscribeOptions = {
29
+ maxTokens: 512,
30
+ };
31
+ private static readonly defaultEmbedBufferSize = 32768;
32
+
33
+ private static cactusModelsCache: CactusModel[] | null = null;
34
+
35
+ constructor({ model, contextSize }: CactusSTTParams = {}) {
36
+ Telemetry.init(CactusConfig.telemetryToken);
37
+
38
+ this.model = model ?? CactusSTT.defaultModel;
39
+ this.contextSize = contextSize ?? CactusSTT.defaultContextSize;
40
+ }
41
+
42
+ public async download({
43
+ onProgress,
44
+ }: CactusSTTDownloadParams = {}): Promise<void> {
45
+ if (this.isDownloading) {
46
+ throw new Error('CactusSTT is already downloading');
47
+ }
48
+
49
+ if (await CactusFileSystem.modelExists(this.model)) {
50
+ onProgress?.(1.0);
51
+ return;
52
+ }
53
+
54
+ this.isDownloading = true;
55
+ try {
56
+ await CactusFileSystem.downloadModel(
57
+ this.model,
58
+ `https://vlqqczxwyaodtcdmdmlw.supabase.co/storage/v1/object/public/voice-models/${this.model}.zip`,
59
+ onProgress
60
+ );
61
+ } finally {
62
+ this.isDownloading = false;
63
+ }
64
+ }
65
+
66
+ public async init(): Promise<void> {
67
+ if (this.isInitialized) {
68
+ return;
69
+ }
70
+
71
+ if (!(await CactusFileSystem.modelExists(this.model))) {
72
+ throw new Error(`Model "${this.model}" is not downloaded`);
73
+ }
74
+
75
+ const modelPath = await CactusFileSystem.getModelPath(this.model);
76
+
77
+ try {
78
+ await this.cactus.init(modelPath, this.contextSize);
79
+ Telemetry.logInit(this.model, true);
80
+ this.isInitialized = true;
81
+ } catch (error) {
82
+ Telemetry.logInit(this.model, false, getErrorMessage(error));
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ public async transcribe({
88
+ audioFilePath,
89
+ prompt = '<|startoftranscript|><|en|><|transcribe|><|notimestamps|>',
90
+ options,
91
+ onToken,
92
+ }: CactusSTTTranscribeParams): Promise<CactusSTTTranscribeResult> {
93
+ if (this.isGenerating) {
94
+ throw new Error('CactusSTT is already generating');
95
+ }
96
+
97
+ await this.init();
98
+
99
+ options = { ...CactusSTT.defaultTranscribeOptions, ...options };
100
+ const responseBufferSize = 32768;
101
+
102
+ this.isGenerating = true;
103
+ try {
104
+ const result = await this.cactus.transcribe(
105
+ audioFilePath,
106
+ prompt,
107
+ responseBufferSize,
108
+ options,
109
+ onToken
110
+ );
111
+ Telemetry.logTranscribe(
112
+ this.model,
113
+ result.success,
114
+ result.success ? undefined : result.response,
115
+ result
116
+ );
117
+ return result;
118
+ } catch (error) {
119
+ Telemetry.logTranscribe(this.model, false, getErrorMessage(error));
120
+ throw error;
121
+ } finally {
122
+ this.isGenerating = false;
123
+ }
124
+ }
125
+
126
+ public async audioEmbed({
127
+ audioPath,
128
+ }: CactusSTTAudioEmbedParams): Promise<CactusSTTAudioEmbedResult> {
129
+ if (this.isGenerating) {
130
+ throw new Error('CactusSTT is already generating');
131
+ }
132
+
133
+ await this.init();
134
+
135
+ this.isGenerating = true;
136
+ try {
137
+ const embedding = await this.cactus.audioEmbed(
138
+ audioPath,
139
+ CactusSTT.defaultEmbedBufferSize
140
+ );
141
+ Telemetry.logAudioEmbedding(this.model, true);
142
+ return { embedding };
143
+ } catch (error) {
144
+ Telemetry.logAudioEmbedding(this.model, false, getErrorMessage(error));
145
+ throw error;
146
+ } finally {
147
+ this.isGenerating = false;
148
+ }
149
+ }
150
+
151
+ public stop(): Promise<void> {
152
+ return this.cactus.stop();
153
+ }
154
+
155
+ public async reset(): Promise<void> {
156
+ await this.stop();
157
+ return this.cactus.reset();
158
+ }
159
+
160
+ public async destroy(): Promise<void> {
161
+ if (!this.isInitialized) {
162
+ return;
163
+ }
164
+
165
+ await this.stop();
166
+ await this.cactus.destroy();
167
+
168
+ this.isInitialized = false;
169
+ }
170
+
171
+ public async getModels(): Promise<CactusModel[]> {
172
+ if (CactusSTT.cactusModelsCache) {
173
+ return CactusSTT.cactusModelsCache;
174
+ }
175
+ const models = await Database.getModels();
176
+ for (const model of models) {
177
+ model.isDownloaded = await CactusFileSystem.modelExists(model.slug);
178
+ }
179
+ CactusSTT.cactusModelsCache = models;
180
+ return models;
181
+ }
182
+ }
@@ -1,4 +1,8 @@
1
1
  export class CactusConfig {
2
+ // Telemetry
2
3
  public static telemetryToken?: string;
3
4
  public static isTelemetryEnabled: boolean = true;
5
+
6
+ // Hybrid mode
7
+ public static cactusToken?: string;
4
8
  }
@@ -1 +1 @@
1
- export const packageVersion = '1.0.1';
1
+ export const packageVersion = '1.1.0';
@@ -7,7 +7,8 @@ import type {
7
7
  CactusLMCompleteResult,
8
8
  CactusLMEmbedParams,
9
9
  CactusLMEmbedResult,
10
- CactusLMGetModelsParams,
10
+ CactusLMImageEmbedParams,
11
+ CactusLMImageEmbedResult,
11
12
  CactusLMCompleteParams,
12
13
  CactusLMDownloadParams,
13
14
  } from '../types/CactusLM';
@@ -16,9 +17,10 @@ import type { CactusModel } from '../types/CactusModel';
16
17
  export const useCactusLM = ({
17
18
  model = 'qwen3-0.6',
18
19
  contextSize = 2048,
20
+ corpusDir = undefined,
19
21
  }: CactusLMParams = {}) => {
20
22
  const [cactusLM, setCactusLM] = useState(
21
- () => new CactusLM({ model, contextSize })
23
+ () => new CactusLM({ model, contextSize, corpusDir })
22
24
  );
23
25
 
24
26
  // State
@@ -38,7 +40,7 @@ export const useCactusLM = ({
38
40
  }, [model]);
39
41
 
40
42
  useEffect(() => {
41
- setCactusLM(new CactusLM({ model, contextSize }));
43
+ setCactusLM(new CactusLM({ model, contextSize, corpusDir }));
42
44
 
43
45
  setCompletion('');
44
46
  setIsGenerating(false);
@@ -67,7 +69,7 @@ export const useCactusLM = ({
67
69
  return () => {
68
70
  mounted = false;
69
71
  };
70
- }, [model, contextSize]);
72
+ }, [model, contextSize, corpusDir]);
71
73
 
72
74
  useEffect(() => {
73
75
  return () => {
@@ -83,10 +85,15 @@ export const useCactusLM = ({
83
85
  throw new Error(message);
84
86
  }
85
87
 
88
+ setError(null);
89
+
90
+ if (isDownloaded) {
91
+ return;
92
+ }
93
+
86
94
  const thisModel = currentModelRef.current;
87
95
  const thisDownloadId = ++currentDownloadIdRef.current;
88
96
 
89
- setError(null);
90
97
  setDownloadProgress(0);
91
98
  setIsDownloading(true);
92
99
  try {
@@ -134,7 +141,7 @@ export const useCactusLM = ({
134
141
  setDownloadProgress(0);
135
142
  }
136
143
  },
137
- [cactusLM, isDownloading]
144
+ [cactusLM, isDownloading, isDownloaded]
138
145
  );
139
146
 
140
147
  const init = useCallback(async () => {
@@ -162,6 +169,7 @@ export const useCactusLM = ({
162
169
  options,
163
170
  tools,
164
171
  onToken,
172
+ mode,
165
173
  }: CactusLMCompleteParams): Promise<CactusLMCompleteResult> => {
166
174
  if (isGenerating) {
167
175
  const message = 'CactusLM is already generating';
@@ -181,6 +189,7 @@ export const useCactusLM = ({
181
189
  setCompletion((prev) => prev + token);
182
190
  onToken?.(token);
183
191
  },
192
+ mode,
184
193
  });
185
194
  } catch (e) {
186
195
  setError(getErrorMessage(e));
@@ -214,6 +223,30 @@ export const useCactusLM = ({
214
223
  [cactusLM, isGenerating]
215
224
  );
216
225
 
226
+ const imageEmbed = useCallback(
227
+ async ({
228
+ imagePath,
229
+ }: CactusLMImageEmbedParams): Promise<CactusLMImageEmbedResult> => {
230
+ if (isGenerating) {
231
+ const message = 'CactusLM is already generating';
232
+ setError(message);
233
+ throw new Error(message);
234
+ }
235
+
236
+ setError(null);
237
+ setIsGenerating(true);
238
+ try {
239
+ return await cactusLM.imageEmbed({ imagePath });
240
+ } catch (e) {
241
+ setError(getErrorMessage(e));
242
+ throw e;
243
+ } finally {
244
+ setIsGenerating(false);
245
+ }
246
+ },
247
+ [cactusLM, isGenerating]
248
+ );
249
+
217
250
  const stop = useCallback(async () => {
218
251
  setError(null);
219
252
  try {
@@ -226,40 +259,37 @@ export const useCactusLM = ({
226
259
 
227
260
  const reset = useCallback(async () => {
228
261
  setError(null);
229
- setCompletion('');
230
262
  try {
231
263
  await cactusLM.reset();
232
264
  } catch (e) {
233
265
  setError(getErrorMessage(e));
234
266
  throw e;
267
+ } finally {
268
+ setCompletion('');
235
269
  }
236
270
  }, [cactusLM]);
237
271
 
238
272
  const destroy = useCallback(async () => {
239
273
  setError(null);
240
- setCompletion('');
241
274
  try {
242
275
  await cactusLM.destroy();
243
276
  } catch (e) {
244
277
  setError(getErrorMessage(e));
245
278
  throw e;
279
+ } finally {
280
+ setCompletion('');
246
281
  }
247
282
  }, [cactusLM]);
248
283
 
249
- const getModels = useCallback(
250
- async ({ forceRefresh }: CactusLMGetModelsParams = {}): Promise<
251
- CactusModel[]
252
- > => {
253
- setError(null);
254
- try {
255
- return await cactusLM.getModels({ forceRefresh });
256
- } catch (e) {
257
- setError(getErrorMessage(e));
258
- throw e;
259
- }
260
- },
261
- [cactusLM]
262
- );
284
+ const getModels = useCallback(async (): Promise<CactusModel[]> => {
285
+ setError(null);
286
+ try {
287
+ return await cactusLM.getModels();
288
+ } catch (e) {
289
+ setError(getErrorMessage(e));
290
+ throw e;
291
+ }
292
+ }, [cactusLM]);
263
293
 
264
294
  return {
265
295
  completion,
@@ -274,6 +304,7 @@ export const useCactusLM = ({
274
304
  init,
275
305
  complete,
276
306
  embed,
307
+ imageEmbed,
277
308
  reset,
278
309
  stop,
279
310
  destroy,