@reverbia/sdk 1.0.0-next.20251202090922 → 1.0.0-next.20251202092727
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/dist/react/index.cjs +187 -86
- package/dist/react/index.d.mts +14 -1
- package/dist/react/index.d.ts +14 -1
- package/dist/react/index.mjs +186 -85
- package/package.json +1 -1
package/dist/react/index.cjs
CHANGED
|
@@ -47500,6 +47500,54 @@ var createClientConfig = (config) => ({
|
|
|
47500
47500
|
// src/client/client.gen.ts
|
|
47501
47501
|
var client = createClient(createClientConfig(createConfig()));
|
|
47502
47502
|
|
|
47503
|
+
// src/lib/chat/constants.ts
|
|
47504
|
+
var DEFAULT_LOCAL_CHAT_MODEL = "onnx-community/Qwen2.5-0.5B-Instruct";
|
|
47505
|
+
|
|
47506
|
+
// src/lib/chat/generation.ts
|
|
47507
|
+
var chatPipeline = null;
|
|
47508
|
+
var currentModel = null;
|
|
47509
|
+
async function generateLocalChatCompletion(messages, options = {}) {
|
|
47510
|
+
const {
|
|
47511
|
+
model = DEFAULT_LOCAL_CHAT_MODEL,
|
|
47512
|
+
temperature = 0.7,
|
|
47513
|
+
max_tokens = 1024,
|
|
47514
|
+
top_p = 0.9,
|
|
47515
|
+
onToken,
|
|
47516
|
+
signal
|
|
47517
|
+
} = options;
|
|
47518
|
+
const { pipeline, TextStreamer } = await Promise.resolve().then(() => (init_transformers_node(), transformers_node_exports));
|
|
47519
|
+
if (!chatPipeline || currentModel !== model) {
|
|
47520
|
+
chatPipeline = await pipeline("text-generation", model, {
|
|
47521
|
+
dtype: "fp16"
|
|
47522
|
+
});
|
|
47523
|
+
currentModel = model;
|
|
47524
|
+
}
|
|
47525
|
+
class CallbackStreamer extends TextStreamer {
|
|
47526
|
+
constructor(tokenizer, cb) {
|
|
47527
|
+
super(tokenizer, {
|
|
47528
|
+
skip_prompt: true,
|
|
47529
|
+
skip_special_tokens: true
|
|
47530
|
+
});
|
|
47531
|
+
this.cb = cb;
|
|
47532
|
+
}
|
|
47533
|
+
on_finalized_text(text) {
|
|
47534
|
+
if (signal?.aborted) {
|
|
47535
|
+
throw new Error("AbortError");
|
|
47536
|
+
}
|
|
47537
|
+
this.cb(text);
|
|
47538
|
+
}
|
|
47539
|
+
}
|
|
47540
|
+
const streamer = onToken ? new CallbackStreamer(chatPipeline.tokenizer, onToken) : void 0;
|
|
47541
|
+
const output = await chatPipeline(messages, {
|
|
47542
|
+
max_new_tokens: max_tokens,
|
|
47543
|
+
temperature,
|
|
47544
|
+
top_p,
|
|
47545
|
+
streamer,
|
|
47546
|
+
return_full_text: false
|
|
47547
|
+
});
|
|
47548
|
+
return output;
|
|
47549
|
+
}
|
|
47550
|
+
|
|
47503
47551
|
// src/react/useChat.ts
|
|
47504
47552
|
function useChat(options) {
|
|
47505
47553
|
const {
|
|
@@ -47507,7 +47555,9 @@ function useChat(options) {
|
|
|
47507
47555
|
baseUrl = BASE_URL,
|
|
47508
47556
|
onData: globalOnData,
|
|
47509
47557
|
onFinish,
|
|
47510
|
-
onError
|
|
47558
|
+
onError,
|
|
47559
|
+
chatProvider = "api",
|
|
47560
|
+
localModel = DEFAULT_LOCAL_CHAT_MODEL
|
|
47511
47561
|
} = options || {};
|
|
47512
47562
|
const [isLoading, setIsLoading] = (0, import_react.useState)(false);
|
|
47513
47563
|
const abortControllerRef = (0, import_react.useRef)(null);
|
|
@@ -47536,16 +47586,6 @@ function useChat(options) {
|
|
|
47536
47586
|
if (onError) onError(new Error(errorMsg));
|
|
47537
47587
|
return { data: null, error: errorMsg };
|
|
47538
47588
|
}
|
|
47539
|
-
if (!model) {
|
|
47540
|
-
const errorMsg = "model is required to call sendMessage.";
|
|
47541
|
-
if (onError) onError(new Error(errorMsg));
|
|
47542
|
-
return { data: null, error: errorMsg };
|
|
47543
|
-
}
|
|
47544
|
-
if (!getToken) {
|
|
47545
|
-
const errorMsg = "Token getter function is required.";
|
|
47546
|
-
if (onError) onError(new Error(errorMsg));
|
|
47547
|
-
return { data: null, error: errorMsg };
|
|
47548
|
-
}
|
|
47549
47589
|
if (abortControllerRef.current) {
|
|
47550
47590
|
abortControllerRef.current.abort();
|
|
47551
47591
|
}
|
|
@@ -47553,88 +47593,141 @@ function useChat(options) {
|
|
|
47553
47593
|
abortControllerRef.current = abortController;
|
|
47554
47594
|
setIsLoading(true);
|
|
47555
47595
|
try {
|
|
47556
|
-
|
|
47557
|
-
|
|
47558
|
-
const
|
|
47559
|
-
|
|
47560
|
-
|
|
47561
|
-
|
|
47562
|
-
|
|
47563
|
-
|
|
47564
|
-
|
|
47565
|
-
|
|
47566
|
-
|
|
47567
|
-
|
|
47568
|
-
|
|
47569
|
-
|
|
47570
|
-
},
|
|
47571
|
-
headers: {
|
|
47572
|
-
"Content-Type": "application/json",
|
|
47573
|
-
Authorization: `Bearer ${token}`
|
|
47574
|
-
},
|
|
47575
|
-
signal: abortController.signal
|
|
47576
|
-
});
|
|
47577
|
-
let accumulatedContent = "";
|
|
47578
|
-
let completionId = "";
|
|
47579
|
-
let completionModel = "";
|
|
47580
|
-
let accumulatedUsage = {};
|
|
47581
|
-
let finishReason;
|
|
47582
|
-
for await (const chunk of sseResult.stream) {
|
|
47583
|
-
if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
|
|
47584
|
-
continue;
|
|
47585
|
-
}
|
|
47586
|
-
if (chunk && typeof chunk === "object") {
|
|
47587
|
-
const chunkData = chunk;
|
|
47588
|
-
if (chunkData.id && !completionId) {
|
|
47589
|
-
completionId = chunkData.id;
|
|
47596
|
+
if (chatProvider === "local") {
|
|
47597
|
+
let accumulatedContent = "";
|
|
47598
|
+
const usedModel = localModel;
|
|
47599
|
+
const formattedMessages = messages.map((m) => ({
|
|
47600
|
+
role: m.role || "user",
|
|
47601
|
+
content: m.content || ""
|
|
47602
|
+
}));
|
|
47603
|
+
await generateLocalChatCompletion(formattedMessages, {
|
|
47604
|
+
model: usedModel,
|
|
47605
|
+
signal: abortController.signal,
|
|
47606
|
+
onToken: (token) => {
|
|
47607
|
+
accumulatedContent += token;
|
|
47608
|
+
if (onData) onData(token);
|
|
47609
|
+
if (globalOnData) globalOnData(token);
|
|
47590
47610
|
}
|
|
47591
|
-
|
|
47592
|
-
|
|
47593
|
-
}
|
|
47594
|
-
|
|
47595
|
-
|
|
47596
|
-
|
|
47597
|
-
|
|
47598
|
-
|
|
47611
|
+
});
|
|
47612
|
+
const completion = {
|
|
47613
|
+
id: `local-${Date.now()}`,
|
|
47614
|
+
model: usedModel,
|
|
47615
|
+
choices: [
|
|
47616
|
+
{
|
|
47617
|
+
index: 0,
|
|
47618
|
+
message: {
|
|
47619
|
+
role: "assistant",
|
|
47620
|
+
content: accumulatedContent
|
|
47621
|
+
},
|
|
47622
|
+
finish_reason: "stop"
|
|
47623
|
+
}
|
|
47624
|
+
],
|
|
47625
|
+
usage: {
|
|
47626
|
+
prompt_tokens: 0,
|
|
47627
|
+
// Not easily available from simple pipeline usage
|
|
47628
|
+
completion_tokens: 0,
|
|
47629
|
+
total_tokens: 0
|
|
47599
47630
|
}
|
|
47600
|
-
|
|
47601
|
-
|
|
47602
|
-
|
|
47603
|
-
|
|
47604
|
-
|
|
47605
|
-
|
|
47606
|
-
|
|
47631
|
+
};
|
|
47632
|
+
setIsLoading(false);
|
|
47633
|
+
if (onFinish) {
|
|
47634
|
+
onFinish(completion);
|
|
47635
|
+
}
|
|
47636
|
+
return { data: completion, error: null };
|
|
47637
|
+
} else {
|
|
47638
|
+
if (!model) {
|
|
47639
|
+
const errorMsg = "model is required to call sendMessage.";
|
|
47640
|
+
if (onError) onError(new Error(errorMsg));
|
|
47641
|
+
return { data: null, error: errorMsg };
|
|
47642
|
+
}
|
|
47643
|
+
if (!getToken) {
|
|
47644
|
+
const errorMsg = "Token getter function is required.";
|
|
47645
|
+
if (onError) onError(new Error(errorMsg));
|
|
47646
|
+
return { data: null, error: errorMsg };
|
|
47647
|
+
}
|
|
47648
|
+
const token = await getToken();
|
|
47649
|
+
if (!token) {
|
|
47650
|
+
const errorMsg = "No access token available.";
|
|
47651
|
+
setIsLoading(false);
|
|
47652
|
+
if (onError) onError(new Error(errorMsg));
|
|
47653
|
+
return { data: null, error: errorMsg };
|
|
47654
|
+
}
|
|
47655
|
+
const sseResult = await client.sse.post({
|
|
47656
|
+
baseUrl,
|
|
47657
|
+
url: "/api/v1/chat/completions",
|
|
47658
|
+
body: {
|
|
47659
|
+
messages,
|
|
47660
|
+
model,
|
|
47661
|
+
stream: true
|
|
47662
|
+
},
|
|
47663
|
+
headers: {
|
|
47664
|
+
"Content-Type": "application/json",
|
|
47665
|
+
Authorization: `Bearer ${token}`
|
|
47666
|
+
},
|
|
47667
|
+
signal: abortController.signal
|
|
47668
|
+
});
|
|
47669
|
+
let accumulatedContent = "";
|
|
47670
|
+
let completionId = "";
|
|
47671
|
+
let completionModel = "";
|
|
47672
|
+
let accumulatedUsage = {};
|
|
47673
|
+
let finishReason;
|
|
47674
|
+
for await (const chunk of sseResult.stream) {
|
|
47675
|
+
if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
|
|
47676
|
+
continue;
|
|
47677
|
+
}
|
|
47678
|
+
if (chunk && typeof chunk === "object") {
|
|
47679
|
+
const chunkData = chunk;
|
|
47680
|
+
if (chunkData.id && !completionId) {
|
|
47681
|
+
completionId = chunkData.id;
|
|
47682
|
+
}
|
|
47683
|
+
if (chunkData.model && !completionModel) {
|
|
47684
|
+
completionModel = chunkData.model;
|
|
47685
|
+
}
|
|
47686
|
+
if (chunkData.usage) {
|
|
47687
|
+
accumulatedUsage = {
|
|
47688
|
+
...accumulatedUsage,
|
|
47689
|
+
...chunkData.usage
|
|
47690
|
+
};
|
|
47691
|
+
}
|
|
47692
|
+
if (chunkData.choices && Array.isArray(chunkData.choices) && chunkData.choices.length > 0) {
|
|
47693
|
+
const choice = chunkData.choices[0];
|
|
47694
|
+
if (choice.delta?.content) {
|
|
47695
|
+
const content = choice.delta.content;
|
|
47696
|
+
accumulatedContent += content;
|
|
47697
|
+
if (onData) {
|
|
47698
|
+
onData(content);
|
|
47699
|
+
}
|
|
47700
|
+
if (globalOnData) {
|
|
47701
|
+
globalOnData(content);
|
|
47702
|
+
}
|
|
47607
47703
|
}
|
|
47608
|
-
if (
|
|
47609
|
-
|
|
47704
|
+
if (choice.finish_reason) {
|
|
47705
|
+
finishReason = choice.finish_reason;
|
|
47610
47706
|
}
|
|
47611
47707
|
}
|
|
47612
|
-
if (choice.finish_reason) {
|
|
47613
|
-
finishReason = choice.finish_reason;
|
|
47614
|
-
}
|
|
47615
47708
|
}
|
|
47616
47709
|
}
|
|
47710
|
+
const completion = {
|
|
47711
|
+
id: completionId,
|
|
47712
|
+
model: completionModel,
|
|
47713
|
+
choices: [
|
|
47714
|
+
{
|
|
47715
|
+
index: 0,
|
|
47716
|
+
message: {
|
|
47717
|
+
role: "assistant",
|
|
47718
|
+
content: accumulatedContent
|
|
47719
|
+
},
|
|
47720
|
+
finish_reason: finishReason
|
|
47721
|
+
}
|
|
47722
|
+
],
|
|
47723
|
+
usage: Object.keys(accumulatedUsage).length > 0 ? accumulatedUsage : void 0
|
|
47724
|
+
};
|
|
47725
|
+
setIsLoading(false);
|
|
47726
|
+
if (onFinish) {
|
|
47727
|
+
onFinish(completion);
|
|
47728
|
+
}
|
|
47729
|
+
return { data: completion, error: null };
|
|
47617
47730
|
}
|
|
47618
|
-
const completion = {
|
|
47619
|
-
id: completionId,
|
|
47620
|
-
model: completionModel,
|
|
47621
|
-
choices: [
|
|
47622
|
-
{
|
|
47623
|
-
index: 0,
|
|
47624
|
-
message: {
|
|
47625
|
-
role: "assistant",
|
|
47626
|
-
content: accumulatedContent
|
|
47627
|
-
},
|
|
47628
|
-
finish_reason: finishReason
|
|
47629
|
-
}
|
|
47630
|
-
],
|
|
47631
|
-
usage: Object.keys(accumulatedUsage).length > 0 ? accumulatedUsage : void 0
|
|
47632
|
-
};
|
|
47633
|
-
setIsLoading(false);
|
|
47634
|
-
if (onFinish) {
|
|
47635
|
-
onFinish(completion);
|
|
47636
|
-
}
|
|
47637
|
-
return { data: completion, error: null };
|
|
47638
47731
|
} catch (err) {
|
|
47639
47732
|
if (err instanceof Error && err.name === "AbortError") {
|
|
47640
47733
|
setIsLoading(false);
|
|
@@ -47653,7 +47746,15 @@ function useChat(options) {
|
|
|
47653
47746
|
}
|
|
47654
47747
|
}
|
|
47655
47748
|
},
|
|
47656
|
-
[
|
|
47749
|
+
[
|
|
47750
|
+
getToken,
|
|
47751
|
+
baseUrl,
|
|
47752
|
+
globalOnData,
|
|
47753
|
+
onFinish,
|
|
47754
|
+
onError,
|
|
47755
|
+
chatProvider,
|
|
47756
|
+
localModel
|
|
47757
|
+
]
|
|
47657
47758
|
);
|
|
47658
47759
|
return {
|
|
47659
47760
|
isLoading,
|
package/dist/react/index.d.mts
CHANGED
|
@@ -177,7 +177,7 @@ type LlmapiRole = string;
|
|
|
177
177
|
|
|
178
178
|
type SendMessageArgs = {
|
|
179
179
|
messages: LlmapiMessage[];
|
|
180
|
-
model
|
|
180
|
+
model?: string;
|
|
181
181
|
/**
|
|
182
182
|
* Per-request callback for data chunks. Called in addition to the global
|
|
183
183
|
* `onData` callback if provided in `useChat` options.
|
|
@@ -215,6 +215,17 @@ type UseChatOptions = {
|
|
|
215
215
|
* @param error - The error that occurred (never an AbortError)
|
|
216
216
|
*/
|
|
217
217
|
onError?: (error: Error) => void;
|
|
218
|
+
/**
|
|
219
|
+
* The provider to use for chat completions (default: "api")
|
|
220
|
+
* "local": Uses a local HuggingFace model (in-browser)
|
|
221
|
+
* "api": Uses the backend API
|
|
222
|
+
*/
|
|
223
|
+
chatProvider?: "api" | "local";
|
|
224
|
+
/**
|
|
225
|
+
* The model to use for local chat completions
|
|
226
|
+
* Default is "ibm-granite/Granite-4.0-Nano-WebGPU"
|
|
227
|
+
*/
|
|
228
|
+
localModel?: string;
|
|
218
229
|
};
|
|
219
230
|
type UseChatResult = {
|
|
220
231
|
isLoading: boolean;
|
|
@@ -244,6 +255,8 @@ type UseChatResult = {
|
|
|
244
255
|
* @param options.onFinish - Callback function to be called when the chat completion finishes successfully.
|
|
245
256
|
* @param options.onError - Callback function to be called when an unexpected error
|
|
246
257
|
* is encountered. Note: This is NOT called for aborted requests (see `stop()`).
|
|
258
|
+
* @param options.chatProvider - The provider to use for chat completions (default: "api").
|
|
259
|
+
* @param options.localModel - The model to use for local chat completions.
|
|
247
260
|
*
|
|
248
261
|
* @returns An object containing:
|
|
249
262
|
* - `isLoading`: A boolean indicating whether a request is currently in progress
|
package/dist/react/index.d.ts
CHANGED
|
@@ -177,7 +177,7 @@ type LlmapiRole = string;
|
|
|
177
177
|
|
|
178
178
|
type SendMessageArgs = {
|
|
179
179
|
messages: LlmapiMessage[];
|
|
180
|
-
model
|
|
180
|
+
model?: string;
|
|
181
181
|
/**
|
|
182
182
|
* Per-request callback for data chunks. Called in addition to the global
|
|
183
183
|
* `onData` callback if provided in `useChat` options.
|
|
@@ -215,6 +215,17 @@ type UseChatOptions = {
|
|
|
215
215
|
* @param error - The error that occurred (never an AbortError)
|
|
216
216
|
*/
|
|
217
217
|
onError?: (error: Error) => void;
|
|
218
|
+
/**
|
|
219
|
+
* The provider to use for chat completions (default: "api")
|
|
220
|
+
* "local": Uses a local HuggingFace model (in-browser)
|
|
221
|
+
* "api": Uses the backend API
|
|
222
|
+
*/
|
|
223
|
+
chatProvider?: "api" | "local";
|
|
224
|
+
/**
|
|
225
|
+
* The model to use for local chat completions
|
|
226
|
+
* Default is "ibm-granite/Granite-4.0-Nano-WebGPU"
|
|
227
|
+
*/
|
|
228
|
+
localModel?: string;
|
|
218
229
|
};
|
|
219
230
|
type UseChatResult = {
|
|
220
231
|
isLoading: boolean;
|
|
@@ -244,6 +255,8 @@ type UseChatResult = {
|
|
|
244
255
|
* @param options.onFinish - Callback function to be called when the chat completion finishes successfully.
|
|
245
256
|
* @param options.onError - Callback function to be called when an unexpected error
|
|
246
257
|
* is encountered. Note: This is NOT called for aborted requests (see `stop()`).
|
|
258
|
+
* @param options.chatProvider - The provider to use for chat completions (default: "api").
|
|
259
|
+
* @param options.localModel - The model to use for local chat completions.
|
|
247
260
|
*
|
|
248
261
|
* @returns An object containing:
|
|
249
262
|
* - `isLoading`: A boolean indicating whether a request is currently in progress
|
package/dist/react/index.mjs
CHANGED
|
@@ -818,6 +818,54 @@ var createClientConfig = (config) => ({
|
|
|
818
818
|
// src/client/client.gen.ts
|
|
819
819
|
var client = createClient(createClientConfig(createConfig()));
|
|
820
820
|
|
|
821
|
+
// src/lib/chat/constants.ts
|
|
822
|
+
var DEFAULT_LOCAL_CHAT_MODEL = "onnx-community/Qwen2.5-0.5B-Instruct";
|
|
823
|
+
|
|
824
|
+
// src/lib/chat/generation.ts
|
|
825
|
+
var chatPipeline = null;
|
|
826
|
+
var currentModel = null;
|
|
827
|
+
async function generateLocalChatCompletion(messages, options = {}) {
|
|
828
|
+
const {
|
|
829
|
+
model = DEFAULT_LOCAL_CHAT_MODEL,
|
|
830
|
+
temperature = 0.7,
|
|
831
|
+
max_tokens = 1024,
|
|
832
|
+
top_p = 0.9,
|
|
833
|
+
onToken,
|
|
834
|
+
signal
|
|
835
|
+
} = options;
|
|
836
|
+
const { pipeline, TextStreamer } = await import("./transformers.node-BSHUG7OY.mjs");
|
|
837
|
+
if (!chatPipeline || currentModel !== model) {
|
|
838
|
+
chatPipeline = await pipeline("text-generation", model, {
|
|
839
|
+
dtype: "fp16"
|
|
840
|
+
});
|
|
841
|
+
currentModel = model;
|
|
842
|
+
}
|
|
843
|
+
class CallbackStreamer extends TextStreamer {
|
|
844
|
+
constructor(tokenizer, cb) {
|
|
845
|
+
super(tokenizer, {
|
|
846
|
+
skip_prompt: true,
|
|
847
|
+
skip_special_tokens: true
|
|
848
|
+
});
|
|
849
|
+
this.cb = cb;
|
|
850
|
+
}
|
|
851
|
+
on_finalized_text(text) {
|
|
852
|
+
if (signal?.aborted) {
|
|
853
|
+
throw new Error("AbortError");
|
|
854
|
+
}
|
|
855
|
+
this.cb(text);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const streamer = onToken ? new CallbackStreamer(chatPipeline.tokenizer, onToken) : void 0;
|
|
859
|
+
const output = await chatPipeline(messages, {
|
|
860
|
+
max_new_tokens: max_tokens,
|
|
861
|
+
temperature,
|
|
862
|
+
top_p,
|
|
863
|
+
streamer,
|
|
864
|
+
return_full_text: false
|
|
865
|
+
});
|
|
866
|
+
return output;
|
|
867
|
+
}
|
|
868
|
+
|
|
821
869
|
// src/react/useChat.ts
|
|
822
870
|
function useChat(options) {
|
|
823
871
|
const {
|
|
@@ -825,7 +873,9 @@ function useChat(options) {
|
|
|
825
873
|
baseUrl = BASE_URL,
|
|
826
874
|
onData: globalOnData,
|
|
827
875
|
onFinish,
|
|
828
|
-
onError
|
|
876
|
+
onError,
|
|
877
|
+
chatProvider = "api",
|
|
878
|
+
localModel = DEFAULT_LOCAL_CHAT_MODEL
|
|
829
879
|
} = options || {};
|
|
830
880
|
const [isLoading, setIsLoading] = useState(false);
|
|
831
881
|
const abortControllerRef = useRef(null);
|
|
@@ -854,16 +904,6 @@ function useChat(options) {
|
|
|
854
904
|
if (onError) onError(new Error(errorMsg));
|
|
855
905
|
return { data: null, error: errorMsg };
|
|
856
906
|
}
|
|
857
|
-
if (!model) {
|
|
858
|
-
const errorMsg = "model is required to call sendMessage.";
|
|
859
|
-
if (onError) onError(new Error(errorMsg));
|
|
860
|
-
return { data: null, error: errorMsg };
|
|
861
|
-
}
|
|
862
|
-
if (!getToken) {
|
|
863
|
-
const errorMsg = "Token getter function is required.";
|
|
864
|
-
if (onError) onError(new Error(errorMsg));
|
|
865
|
-
return { data: null, error: errorMsg };
|
|
866
|
-
}
|
|
867
907
|
if (abortControllerRef.current) {
|
|
868
908
|
abortControllerRef.current.abort();
|
|
869
909
|
}
|
|
@@ -871,88 +911,141 @@ function useChat(options) {
|
|
|
871
911
|
abortControllerRef.current = abortController;
|
|
872
912
|
setIsLoading(true);
|
|
873
913
|
try {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
},
|
|
889
|
-
headers: {
|
|
890
|
-
"Content-Type": "application/json",
|
|
891
|
-
Authorization: `Bearer ${token}`
|
|
892
|
-
},
|
|
893
|
-
signal: abortController.signal
|
|
894
|
-
});
|
|
895
|
-
let accumulatedContent = "";
|
|
896
|
-
let completionId = "";
|
|
897
|
-
let completionModel = "";
|
|
898
|
-
let accumulatedUsage = {};
|
|
899
|
-
let finishReason;
|
|
900
|
-
for await (const chunk of sseResult.stream) {
|
|
901
|
-
if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
if (chunk && typeof chunk === "object") {
|
|
905
|
-
const chunkData = chunk;
|
|
906
|
-
if (chunkData.id && !completionId) {
|
|
907
|
-
completionId = chunkData.id;
|
|
914
|
+
if (chatProvider === "local") {
|
|
915
|
+
let accumulatedContent = "";
|
|
916
|
+
const usedModel = localModel;
|
|
917
|
+
const formattedMessages = messages.map((m) => ({
|
|
918
|
+
role: m.role || "user",
|
|
919
|
+
content: m.content || ""
|
|
920
|
+
}));
|
|
921
|
+
await generateLocalChatCompletion(formattedMessages, {
|
|
922
|
+
model: usedModel,
|
|
923
|
+
signal: abortController.signal,
|
|
924
|
+
onToken: (token) => {
|
|
925
|
+
accumulatedContent += token;
|
|
926
|
+
if (onData) onData(token);
|
|
927
|
+
if (globalOnData) globalOnData(token);
|
|
908
928
|
}
|
|
909
|
-
|
|
910
|
-
|
|
929
|
+
});
|
|
930
|
+
const completion = {
|
|
931
|
+
id: `local-${Date.now()}`,
|
|
932
|
+
model: usedModel,
|
|
933
|
+
choices: [
|
|
934
|
+
{
|
|
935
|
+
index: 0,
|
|
936
|
+
message: {
|
|
937
|
+
role: "assistant",
|
|
938
|
+
content: accumulatedContent
|
|
939
|
+
},
|
|
940
|
+
finish_reason: "stop"
|
|
941
|
+
}
|
|
942
|
+
],
|
|
943
|
+
usage: {
|
|
944
|
+
prompt_tokens: 0,
|
|
945
|
+
// Not easily available from simple pipeline usage
|
|
946
|
+
completion_tokens: 0,
|
|
947
|
+
total_tokens: 0
|
|
911
948
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
949
|
+
};
|
|
950
|
+
setIsLoading(false);
|
|
951
|
+
if (onFinish) {
|
|
952
|
+
onFinish(completion);
|
|
953
|
+
}
|
|
954
|
+
return { data: completion, error: null };
|
|
955
|
+
} else {
|
|
956
|
+
if (!model) {
|
|
957
|
+
const errorMsg = "model is required to call sendMessage.";
|
|
958
|
+
if (onError) onError(new Error(errorMsg));
|
|
959
|
+
return { data: null, error: errorMsg };
|
|
960
|
+
}
|
|
961
|
+
if (!getToken) {
|
|
962
|
+
const errorMsg = "Token getter function is required.";
|
|
963
|
+
if (onError) onError(new Error(errorMsg));
|
|
964
|
+
return { data: null, error: errorMsg };
|
|
965
|
+
}
|
|
966
|
+
const token = await getToken();
|
|
967
|
+
if (!token) {
|
|
968
|
+
const errorMsg = "No access token available.";
|
|
969
|
+
setIsLoading(false);
|
|
970
|
+
if (onError) onError(new Error(errorMsg));
|
|
971
|
+
return { data: null, error: errorMsg };
|
|
972
|
+
}
|
|
973
|
+
const sseResult = await client.sse.post({
|
|
974
|
+
baseUrl,
|
|
975
|
+
url: "/api/v1/chat/completions",
|
|
976
|
+
body: {
|
|
977
|
+
messages,
|
|
978
|
+
model,
|
|
979
|
+
stream: true
|
|
980
|
+
},
|
|
981
|
+
headers: {
|
|
982
|
+
"Content-Type": "application/json",
|
|
983
|
+
Authorization: `Bearer ${token}`
|
|
984
|
+
},
|
|
985
|
+
signal: abortController.signal
|
|
986
|
+
});
|
|
987
|
+
let accumulatedContent = "";
|
|
988
|
+
let completionId = "";
|
|
989
|
+
let completionModel = "";
|
|
990
|
+
let accumulatedUsage = {};
|
|
991
|
+
let finishReason;
|
|
992
|
+
for await (const chunk of sseResult.stream) {
|
|
993
|
+
if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
|
|
994
|
+
continue;
|
|
917
995
|
}
|
|
918
|
-
if (
|
|
919
|
-
const
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
996
|
+
if (chunk && typeof chunk === "object") {
|
|
997
|
+
const chunkData = chunk;
|
|
998
|
+
if (chunkData.id && !completionId) {
|
|
999
|
+
completionId = chunkData.id;
|
|
1000
|
+
}
|
|
1001
|
+
if (chunkData.model && !completionModel) {
|
|
1002
|
+
completionModel = chunkData.model;
|
|
1003
|
+
}
|
|
1004
|
+
if (chunkData.usage) {
|
|
1005
|
+
accumulatedUsage = {
|
|
1006
|
+
...accumulatedUsage,
|
|
1007
|
+
...chunkData.usage
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
if (chunkData.choices && Array.isArray(chunkData.choices) && chunkData.choices.length > 0) {
|
|
1011
|
+
const choice = chunkData.choices[0];
|
|
1012
|
+
if (choice.delta?.content) {
|
|
1013
|
+
const content = choice.delta.content;
|
|
1014
|
+
accumulatedContent += content;
|
|
1015
|
+
if (onData) {
|
|
1016
|
+
onData(content);
|
|
1017
|
+
}
|
|
1018
|
+
if (globalOnData) {
|
|
1019
|
+
globalOnData(content);
|
|
1020
|
+
}
|
|
925
1021
|
}
|
|
926
|
-
if (
|
|
927
|
-
|
|
1022
|
+
if (choice.finish_reason) {
|
|
1023
|
+
finishReason = choice.finish_reason;
|
|
928
1024
|
}
|
|
929
1025
|
}
|
|
930
|
-
if (choice.finish_reason) {
|
|
931
|
-
finishReason = choice.finish_reason;
|
|
932
|
-
}
|
|
933
1026
|
}
|
|
934
1027
|
}
|
|
1028
|
+
const completion = {
|
|
1029
|
+
id: completionId,
|
|
1030
|
+
model: completionModel,
|
|
1031
|
+
choices: [
|
|
1032
|
+
{
|
|
1033
|
+
index: 0,
|
|
1034
|
+
message: {
|
|
1035
|
+
role: "assistant",
|
|
1036
|
+
content: accumulatedContent
|
|
1037
|
+
},
|
|
1038
|
+
finish_reason: finishReason
|
|
1039
|
+
}
|
|
1040
|
+
],
|
|
1041
|
+
usage: Object.keys(accumulatedUsage).length > 0 ? accumulatedUsage : void 0
|
|
1042
|
+
};
|
|
1043
|
+
setIsLoading(false);
|
|
1044
|
+
if (onFinish) {
|
|
1045
|
+
onFinish(completion);
|
|
1046
|
+
}
|
|
1047
|
+
return { data: completion, error: null };
|
|
935
1048
|
}
|
|
936
|
-
const completion = {
|
|
937
|
-
id: completionId,
|
|
938
|
-
model: completionModel,
|
|
939
|
-
choices: [
|
|
940
|
-
{
|
|
941
|
-
index: 0,
|
|
942
|
-
message: {
|
|
943
|
-
role: "assistant",
|
|
944
|
-
content: accumulatedContent
|
|
945
|
-
},
|
|
946
|
-
finish_reason: finishReason
|
|
947
|
-
}
|
|
948
|
-
],
|
|
949
|
-
usage: Object.keys(accumulatedUsage).length > 0 ? accumulatedUsage : void 0
|
|
950
|
-
};
|
|
951
|
-
setIsLoading(false);
|
|
952
|
-
if (onFinish) {
|
|
953
|
-
onFinish(completion);
|
|
954
|
-
}
|
|
955
|
-
return { data: completion, error: null };
|
|
956
1049
|
} catch (err) {
|
|
957
1050
|
if (err instanceof Error && err.name === "AbortError") {
|
|
958
1051
|
setIsLoading(false);
|
|
@@ -971,7 +1064,15 @@ function useChat(options) {
|
|
|
971
1064
|
}
|
|
972
1065
|
}
|
|
973
1066
|
},
|
|
974
|
-
[
|
|
1067
|
+
[
|
|
1068
|
+
getToken,
|
|
1069
|
+
baseUrl,
|
|
1070
|
+
globalOnData,
|
|
1071
|
+
onFinish,
|
|
1072
|
+
onError,
|
|
1073
|
+
chatProvider,
|
|
1074
|
+
localModel
|
|
1075
|
+
]
|
|
975
1076
|
);
|
|
976
1077
|
return {
|
|
977
1078
|
isLoading,
|