@layla-network/sdk 0.1.1 → 0.1.3
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/index.cjs +207 -2
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +204 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,9 @@ __export(index_exports, {
|
|
|
27
27
|
LaylaBridgeUnavailableError: () => LaylaBridgeUnavailableError,
|
|
28
28
|
LaylaError: () => LaylaError,
|
|
29
29
|
LaylaSDK: () => LaylaSDK,
|
|
30
|
-
default: () => client_default
|
|
30
|
+
default: () => client_default,
|
|
31
|
+
installLaylaMock: () => installLaylaMock,
|
|
32
|
+
makeMockCharacter: () => makeMockCharacter
|
|
31
33
|
});
|
|
32
34
|
module.exports = __toCommonJS(index_exports);
|
|
33
35
|
|
|
@@ -578,6 +580,207 @@ var LaylaSDK = class {
|
|
|
578
580
|
}
|
|
579
581
|
};
|
|
580
582
|
var client_default = LaylaSDK;
|
|
583
|
+
|
|
584
|
+
// src/mock.ts
|
|
585
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
586
|
+
function makeMockCharacter(name, overrides = {}) {
|
|
587
|
+
return {
|
|
588
|
+
id: `mock-${name.toLowerCase()}`,
|
|
589
|
+
data: {
|
|
590
|
+
spec: "chara_card_v2",
|
|
591
|
+
spec_version: "2.0",
|
|
592
|
+
data: {
|
|
593
|
+
name,
|
|
594
|
+
description: `${name} is a mock character for local testing.`,
|
|
595
|
+
personality: "friendly, helpful",
|
|
596
|
+
scenario: "",
|
|
597
|
+
first_mes: `Hi, I'm ${name}.`,
|
|
598
|
+
mes_example: "",
|
|
599
|
+
creator_notes: "Generated by layla mock.",
|
|
600
|
+
system_prompt: "",
|
|
601
|
+
post_history_instructions: "",
|
|
602
|
+
alternate_greetings: [],
|
|
603
|
+
tags: ["mock"],
|
|
604
|
+
creator: "layla-mock",
|
|
605
|
+
character_version: "1.0",
|
|
606
|
+
extensions: {},
|
|
607
|
+
...overrides
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function installLaylaMock(options = {}) {
|
|
613
|
+
if (typeof window === "undefined") {
|
|
614
|
+
throw new Error(
|
|
615
|
+
"installLaylaMock needs a browser-like environment (window). In Node, run your test under jsdom or happy-dom."
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const latencyMs = options.latencyMs ?? 150;
|
|
619
|
+
const tokenDelayMs = options.tokenDelayMs ?? 40;
|
|
620
|
+
const errorRate = options.errorRate ?? 0;
|
|
621
|
+
const characters = options.characters ?? [makeMockCharacter("Aria"), makeMockCharacter("Kai")];
|
|
622
|
+
let current = null;
|
|
623
|
+
const log = (...a) => {
|
|
624
|
+
if (options.debug) console.log("[layla-mock]", ...a);
|
|
625
|
+
};
|
|
626
|
+
const emit = (event) => {
|
|
627
|
+
log("RN->web", event);
|
|
628
|
+
window.dispatchEvent(new MessageEvent("message", { data: JSON.stringify(event) }));
|
|
629
|
+
};
|
|
630
|
+
const emitError = (message) => emit({ event: "on_error", data: { message } });
|
|
631
|
+
const shouldError = () => errorRate > 0 && Math.random() < errorRate;
|
|
632
|
+
const tokenize = (text) => text.match(/\S+\s*/g) ?? (text ? [text] : []);
|
|
633
|
+
const defaultReply = (messages) => {
|
|
634
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
635
|
+
const said = lastUser?.content ?? "(nothing)";
|
|
636
|
+
return `This is a mock Layla response. You said: "${said}". Tokens stream one at a time so you can exercise your streaming UI, cancellation, and the final-content promise.`;
|
|
637
|
+
};
|
|
638
|
+
async function* tokenSource(messages) {
|
|
639
|
+
const produced = options.respond ? await options.respond(messages) : defaultReply(messages);
|
|
640
|
+
if (typeof produced === "string") {
|
|
641
|
+
yield* tokenize(produced);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (Array.isArray(produced)) {
|
|
645
|
+
yield* produced;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (produced && typeof produced === "object") {
|
|
649
|
+
if (Symbol.asyncIterator in produced) {
|
|
650
|
+
yield* produced;
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (Symbol.iterator in produced) {
|
|
654
|
+
yield* produced;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function handleSend(messages) {
|
|
659
|
+
const gen = { cancelled: false };
|
|
660
|
+
current = gen;
|
|
661
|
+
try {
|
|
662
|
+
await delay(latencyMs);
|
|
663
|
+
if (gen.cancelled) return;
|
|
664
|
+
if (shouldError()) {
|
|
665
|
+
emitError("Simulated model error");
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
let snapshot = "";
|
|
669
|
+
for await (const delta of tokenSource(messages)) {
|
|
670
|
+
if (gen.cancelled) return;
|
|
671
|
+
snapshot += delta;
|
|
672
|
+
emit({ event: "on_message", data: { msg: snapshot, delta } });
|
|
673
|
+
await delay(tokenDelayMs);
|
|
674
|
+
if (gen.cancelled) return;
|
|
675
|
+
}
|
|
676
|
+
emit({ event: "on_message_end" });
|
|
677
|
+
} finally {
|
|
678
|
+
if (current === gen) current = null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function handleCancel() {
|
|
682
|
+
if (current && !current.cancelled) {
|
|
683
|
+
current.cancelled = true;
|
|
684
|
+
queueMicrotask(() => emit({ event: "on_message_end" }));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function handleGetCharacters() {
|
|
688
|
+
await delay(latencyMs);
|
|
689
|
+
if (shouldError()) {
|
|
690
|
+
emitError("Simulated characters error");
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
emit({ event: "on_get_characters_response", data: characters });
|
|
694
|
+
}
|
|
695
|
+
async function handleGetCharacterImage(data) {
|
|
696
|
+
await delay(latencyMs);
|
|
697
|
+
if (shouldError()) {
|
|
698
|
+
emitError("Simulated character image error");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
emit({
|
|
702
|
+
event: "on_get_character_image_response",
|
|
703
|
+
data: {
|
|
704
|
+
character_id: data.character_id,
|
|
705
|
+
image_data_base64: "https://picsum.photos/id/237/200/300"
|
|
706
|
+
// we return a url for mock, since we usually use this as the "src" of an <img> tag, it should work fine as a mock
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
async function handleGenerateImage(_) {
|
|
711
|
+
await delay(latencyMs);
|
|
712
|
+
if (shouldError()) {
|
|
713
|
+
emitError("Simulated image generation error");
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const totalSteps = 5;
|
|
717
|
+
for (let step = 1; step <= totalSteps; step++) {
|
|
718
|
+
await delay(latencyMs);
|
|
719
|
+
emit({
|
|
720
|
+
event: "on_generate_image_progress",
|
|
721
|
+
data: {
|
|
722
|
+
status: `Generating image... (${step}/${totalSteps})`,
|
|
723
|
+
steps: step,
|
|
724
|
+
total_steps: totalSteps
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
emit({
|
|
729
|
+
event: "on_generate_image_response",
|
|
730
|
+
data: {
|
|
731
|
+
image_data_base64: "https://picsum.photos/200/300"
|
|
732
|
+
// Placeholder image URL for the mock
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
const fakeBridge = {
|
|
737
|
+
postMessage(raw) {
|
|
738
|
+
let msg;
|
|
739
|
+
try {
|
|
740
|
+
msg = JSON.parse(raw);
|
|
741
|
+
} catch {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
log("web->RN", msg);
|
|
745
|
+
switch (msg.cmd) {
|
|
746
|
+
case "send_message":
|
|
747
|
+
void handleSend(msg.data);
|
|
748
|
+
break;
|
|
749
|
+
case "cancel":
|
|
750
|
+
handleCancel();
|
|
751
|
+
break;
|
|
752
|
+
case "get_characters":
|
|
753
|
+
void handleGetCharacters();
|
|
754
|
+
break;
|
|
755
|
+
case "get_character_image":
|
|
756
|
+
void handleGetCharacterImage(msg.data);
|
|
757
|
+
break;
|
|
758
|
+
case "generate_image":
|
|
759
|
+
void handleGenerateImage(msg.data);
|
|
760
|
+
break;
|
|
761
|
+
default:
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
const previous = window.ReactNativeWebView;
|
|
767
|
+
if (previous) {
|
|
768
|
+
log("warning: replacing an existing window.ReactNativeWebView");
|
|
769
|
+
}
|
|
770
|
+
window.ReactNativeWebView = fakeBridge;
|
|
771
|
+
log("installed");
|
|
772
|
+
let installed = true;
|
|
773
|
+
return {
|
|
774
|
+
uninstall() {
|
|
775
|
+
if (!installed) return;
|
|
776
|
+
installed = false;
|
|
777
|
+
if (current) current.cancelled = true;
|
|
778
|
+
if (previous) window.ReactNativeWebView = previous;
|
|
779
|
+
else delete window.ReactNativeWebView;
|
|
780
|
+
log("uninstalled");
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
581
784
|
// Annotate the CommonJS export names for ESM import in node:
|
|
582
785
|
0 && (module.exports = {
|
|
583
786
|
ChatCompletionStream,
|
|
@@ -586,5 +789,7 @@ var client_default = LaylaSDK;
|
|
|
586
789
|
LaylaAbortError,
|
|
587
790
|
LaylaBridgeUnavailableError,
|
|
588
791
|
LaylaError,
|
|
589
|
-
LaylaSDK
|
|
792
|
+
LaylaSDK,
|
|
793
|
+
installLaylaMock,
|
|
794
|
+
makeMockCharacter
|
|
590
795
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -435,4 +435,53 @@ declare class LaylaSDK {
|
|
|
435
435
|
constructor(_options?: LaylaSDKOptions);
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
/**
|
|
439
|
+
* mock.ts
|
|
440
|
+
* -------
|
|
441
|
+
* A fake Layla native host for running the SDK OUTSIDE the app (Vite dev server,
|
|
442
|
+
* Storybook, a browser test, etc.). It installs a stand-in
|
|
443
|
+
* `window.ReactNativeWebView` and answers the web->RN commands by dispatching the
|
|
444
|
+
* same `MessageEvent`s the real host would, so the SDK's bridge, queue,
|
|
445
|
+
* streaming, and cancel paths all run for real against it.
|
|
446
|
+
*
|
|
447
|
+
* Usage (Vite — install in dev before you use the SDK):
|
|
448
|
+
*
|
|
449
|
+
* // main.ts
|
|
450
|
+
* if (import.meta.env.DEV) {
|
|
451
|
+
* const { installLaylaMock } = await import('layla-sdk/mock');
|
|
452
|
+
* installLaylaMock({ debug: true });
|
|
453
|
+
* }
|
|
454
|
+
*
|
|
455
|
+
* Install BEFORE the SDK posts its first message; otherwise the bridge sees no
|
|
456
|
+
* host and rejects with LaylaBridgeUnavailableError.
|
|
457
|
+
*/
|
|
458
|
+
|
|
459
|
+
type MockReply = string | string[] | Iterable<string> | AsyncIterable<string>;
|
|
460
|
+
interface LaylaMockOptions {
|
|
461
|
+
/**
|
|
462
|
+
* Produce the assistant reply for a `send_message`. Return a string (which the
|
|
463
|
+
* mock tokenises and streams), an array of pre-split tokens, or an
|
|
464
|
+
* (async) iterable of tokens for full control over timing/content. May be
|
|
465
|
+
* async. Defaults to a canned reply that echoes the last user message.
|
|
466
|
+
*/
|
|
467
|
+
respond?: (messages: LaylaChatMessage[]) => MockReply | Promise<MockReply>;
|
|
468
|
+
/** Cards returned by `get_characters`. Defaults to two sample cards. */
|
|
469
|
+
characters?: LaylaCharacter[];
|
|
470
|
+
/** Delay before the first event of a response (simulated latency). Default 150ms. */
|
|
471
|
+
latencyMs?: number;
|
|
472
|
+
/** Delay between streamed tokens. Default 40ms. */
|
|
473
|
+
tokenDelayMs?: number;
|
|
474
|
+
/** 0..1 probability that a request fails with on_error, for testing error paths. Default 0. */
|
|
475
|
+
errorRate?: number;
|
|
476
|
+
/** Log every web->RN command and RN->web event to the console. */
|
|
477
|
+
debug?: boolean;
|
|
478
|
+
}
|
|
479
|
+
interface LaylaMockHandle {
|
|
480
|
+
/** Remove the fake bridge and restore whatever was there before. */
|
|
481
|
+
uninstall(): void;
|
|
482
|
+
}
|
|
483
|
+
/** Build a valid Character Card V2 with sensible mock defaults. */
|
|
484
|
+
declare function makeMockCharacter(name: string, overrides?: Partial<TavernCardV2['data']>): LaylaCharacter;
|
|
485
|
+
declare function installLaylaMock(options?: LaylaMockOptions): LaylaMockHandle;
|
|
486
|
+
|
|
487
|
+
export { type ChatCompletion, type ChatCompletionChunk, type ChatCompletionCreateParamsBase, type ChatCompletionCreateParamsNonStreaming, type ChatCompletionCreateParamsStreaming, ChatCompletionStream, Images, LaylaSDK as Layla, LaylaAbortError, type LaylaApiCancel, type LaylaApiEvent, type LaylaApiEvent_onError, type LaylaApiEvent_onGenerateImageResponse, type LaylaApiEvent_onGetCharacterImageResponse, type LaylaApiEvent_onGetCharactersResponse, type LaylaApiEvent_onMsg, type LaylaApiEvent_onMsgEnd, type LaylaApiGenerateImage, type LaylaApiGetCharacterImage, type LaylaApiGetCharacters, type LaylaApiRequest, type LaylaApiSendMessage, LaylaBridgeUnavailableError, type LaylaCharacter, type LaylaChatMessage, type LaylaChatRole, LaylaError, LaylaSDK, type LaylaSDKOptions, type RequestOptions, type TavernCardV2, type TavernCharacterBook, LaylaSDK as default, installLaylaMock, makeMockCharacter };
|
package/dist/index.d.ts
CHANGED
|
@@ -435,4 +435,53 @@ declare class LaylaSDK {
|
|
|
435
435
|
constructor(_options?: LaylaSDKOptions);
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
/**
|
|
439
|
+
* mock.ts
|
|
440
|
+
* -------
|
|
441
|
+
* A fake Layla native host for running the SDK OUTSIDE the app (Vite dev server,
|
|
442
|
+
* Storybook, a browser test, etc.). It installs a stand-in
|
|
443
|
+
* `window.ReactNativeWebView` and answers the web->RN commands by dispatching the
|
|
444
|
+
* same `MessageEvent`s the real host would, so the SDK's bridge, queue,
|
|
445
|
+
* streaming, and cancel paths all run for real against it.
|
|
446
|
+
*
|
|
447
|
+
* Usage (Vite — install in dev before you use the SDK):
|
|
448
|
+
*
|
|
449
|
+
* // main.ts
|
|
450
|
+
* if (import.meta.env.DEV) {
|
|
451
|
+
* const { installLaylaMock } = await import('layla-sdk/mock');
|
|
452
|
+
* installLaylaMock({ debug: true });
|
|
453
|
+
* }
|
|
454
|
+
*
|
|
455
|
+
* Install BEFORE the SDK posts its first message; otherwise the bridge sees no
|
|
456
|
+
* host and rejects with LaylaBridgeUnavailableError.
|
|
457
|
+
*/
|
|
458
|
+
|
|
459
|
+
type MockReply = string | string[] | Iterable<string> | AsyncIterable<string>;
|
|
460
|
+
interface LaylaMockOptions {
|
|
461
|
+
/**
|
|
462
|
+
* Produce the assistant reply for a `send_message`. Return a string (which the
|
|
463
|
+
* mock tokenises and streams), an array of pre-split tokens, or an
|
|
464
|
+
* (async) iterable of tokens for full control over timing/content. May be
|
|
465
|
+
* async. Defaults to a canned reply that echoes the last user message.
|
|
466
|
+
*/
|
|
467
|
+
respond?: (messages: LaylaChatMessage[]) => MockReply | Promise<MockReply>;
|
|
468
|
+
/** Cards returned by `get_characters`. Defaults to two sample cards. */
|
|
469
|
+
characters?: LaylaCharacter[];
|
|
470
|
+
/** Delay before the first event of a response (simulated latency). Default 150ms. */
|
|
471
|
+
latencyMs?: number;
|
|
472
|
+
/** Delay between streamed tokens. Default 40ms. */
|
|
473
|
+
tokenDelayMs?: number;
|
|
474
|
+
/** 0..1 probability that a request fails with on_error, for testing error paths. Default 0. */
|
|
475
|
+
errorRate?: number;
|
|
476
|
+
/** Log every web->RN command and RN->web event to the console. */
|
|
477
|
+
debug?: boolean;
|
|
478
|
+
}
|
|
479
|
+
interface LaylaMockHandle {
|
|
480
|
+
/** Remove the fake bridge and restore whatever was there before. */
|
|
481
|
+
uninstall(): void;
|
|
482
|
+
}
|
|
483
|
+
/** Build a valid Character Card V2 with sensible mock defaults. */
|
|
484
|
+
declare function makeMockCharacter(name: string, overrides?: Partial<TavernCardV2['data']>): LaylaCharacter;
|
|
485
|
+
declare function installLaylaMock(options?: LaylaMockOptions): LaylaMockHandle;
|
|
486
|
+
|
|
487
|
+
export { type ChatCompletion, type ChatCompletionChunk, type ChatCompletionCreateParamsBase, type ChatCompletionCreateParamsNonStreaming, type ChatCompletionCreateParamsStreaming, ChatCompletionStream, Images, LaylaSDK as Layla, LaylaAbortError, type LaylaApiCancel, type LaylaApiEvent, type LaylaApiEvent_onError, type LaylaApiEvent_onGenerateImageResponse, type LaylaApiEvent_onGetCharacterImageResponse, type LaylaApiEvent_onGetCharactersResponse, type LaylaApiEvent_onMsg, type LaylaApiEvent_onMsgEnd, type LaylaApiGenerateImage, type LaylaApiGetCharacterImage, type LaylaApiGetCharacters, type LaylaApiRequest, type LaylaApiSendMessage, LaylaBridgeUnavailableError, type LaylaCharacter, type LaylaChatMessage, type LaylaChatRole, LaylaError, LaylaSDK, type LaylaSDKOptions, type RequestOptions, type TavernCardV2, type TavernCharacterBook, LaylaSDK as default, installLaylaMock, makeMockCharacter };
|
package/dist/index.js
CHANGED
|
@@ -545,6 +545,207 @@ var LaylaSDK = class {
|
|
|
545
545
|
}
|
|
546
546
|
};
|
|
547
547
|
var client_default = LaylaSDK;
|
|
548
|
+
|
|
549
|
+
// src/mock.ts
|
|
550
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
551
|
+
function makeMockCharacter(name, overrides = {}) {
|
|
552
|
+
return {
|
|
553
|
+
id: `mock-${name.toLowerCase()}`,
|
|
554
|
+
data: {
|
|
555
|
+
spec: "chara_card_v2",
|
|
556
|
+
spec_version: "2.0",
|
|
557
|
+
data: {
|
|
558
|
+
name,
|
|
559
|
+
description: `${name} is a mock character for local testing.`,
|
|
560
|
+
personality: "friendly, helpful",
|
|
561
|
+
scenario: "",
|
|
562
|
+
first_mes: `Hi, I'm ${name}.`,
|
|
563
|
+
mes_example: "",
|
|
564
|
+
creator_notes: "Generated by layla mock.",
|
|
565
|
+
system_prompt: "",
|
|
566
|
+
post_history_instructions: "",
|
|
567
|
+
alternate_greetings: [],
|
|
568
|
+
tags: ["mock"],
|
|
569
|
+
creator: "layla-mock",
|
|
570
|
+
character_version: "1.0",
|
|
571
|
+
extensions: {},
|
|
572
|
+
...overrides
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function installLaylaMock(options = {}) {
|
|
578
|
+
if (typeof window === "undefined") {
|
|
579
|
+
throw new Error(
|
|
580
|
+
"installLaylaMock needs a browser-like environment (window). In Node, run your test under jsdom or happy-dom."
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const latencyMs = options.latencyMs ?? 150;
|
|
584
|
+
const tokenDelayMs = options.tokenDelayMs ?? 40;
|
|
585
|
+
const errorRate = options.errorRate ?? 0;
|
|
586
|
+
const characters = options.characters ?? [makeMockCharacter("Aria"), makeMockCharacter("Kai")];
|
|
587
|
+
let current = null;
|
|
588
|
+
const log = (...a) => {
|
|
589
|
+
if (options.debug) console.log("[layla-mock]", ...a);
|
|
590
|
+
};
|
|
591
|
+
const emit = (event) => {
|
|
592
|
+
log("RN->web", event);
|
|
593
|
+
window.dispatchEvent(new MessageEvent("message", { data: JSON.stringify(event) }));
|
|
594
|
+
};
|
|
595
|
+
const emitError = (message) => emit({ event: "on_error", data: { message } });
|
|
596
|
+
const shouldError = () => errorRate > 0 && Math.random() < errorRate;
|
|
597
|
+
const tokenize = (text) => text.match(/\S+\s*/g) ?? (text ? [text] : []);
|
|
598
|
+
const defaultReply = (messages) => {
|
|
599
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
600
|
+
const said = lastUser?.content ?? "(nothing)";
|
|
601
|
+
return `This is a mock Layla response. You said: "${said}". Tokens stream one at a time so you can exercise your streaming UI, cancellation, and the final-content promise.`;
|
|
602
|
+
};
|
|
603
|
+
async function* tokenSource(messages) {
|
|
604
|
+
const produced = options.respond ? await options.respond(messages) : defaultReply(messages);
|
|
605
|
+
if (typeof produced === "string") {
|
|
606
|
+
yield* tokenize(produced);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (Array.isArray(produced)) {
|
|
610
|
+
yield* produced;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (produced && typeof produced === "object") {
|
|
614
|
+
if (Symbol.asyncIterator in produced) {
|
|
615
|
+
yield* produced;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (Symbol.iterator in produced) {
|
|
619
|
+
yield* produced;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function handleSend(messages) {
|
|
624
|
+
const gen = { cancelled: false };
|
|
625
|
+
current = gen;
|
|
626
|
+
try {
|
|
627
|
+
await delay(latencyMs);
|
|
628
|
+
if (gen.cancelled) return;
|
|
629
|
+
if (shouldError()) {
|
|
630
|
+
emitError("Simulated model error");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
let snapshot = "";
|
|
634
|
+
for await (const delta of tokenSource(messages)) {
|
|
635
|
+
if (gen.cancelled) return;
|
|
636
|
+
snapshot += delta;
|
|
637
|
+
emit({ event: "on_message", data: { msg: snapshot, delta } });
|
|
638
|
+
await delay(tokenDelayMs);
|
|
639
|
+
if (gen.cancelled) return;
|
|
640
|
+
}
|
|
641
|
+
emit({ event: "on_message_end" });
|
|
642
|
+
} finally {
|
|
643
|
+
if (current === gen) current = null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function handleCancel() {
|
|
647
|
+
if (current && !current.cancelled) {
|
|
648
|
+
current.cancelled = true;
|
|
649
|
+
queueMicrotask(() => emit({ event: "on_message_end" }));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async function handleGetCharacters() {
|
|
653
|
+
await delay(latencyMs);
|
|
654
|
+
if (shouldError()) {
|
|
655
|
+
emitError("Simulated characters error");
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
emit({ event: "on_get_characters_response", data: characters });
|
|
659
|
+
}
|
|
660
|
+
async function handleGetCharacterImage(data) {
|
|
661
|
+
await delay(latencyMs);
|
|
662
|
+
if (shouldError()) {
|
|
663
|
+
emitError("Simulated character image error");
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
emit({
|
|
667
|
+
event: "on_get_character_image_response",
|
|
668
|
+
data: {
|
|
669
|
+
character_id: data.character_id,
|
|
670
|
+
image_data_base64: "https://picsum.photos/id/237/200/300"
|
|
671
|
+
// we return a url for mock, since we usually use this as the "src" of an <img> tag, it should work fine as a mock
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
async function handleGenerateImage(_) {
|
|
676
|
+
await delay(latencyMs);
|
|
677
|
+
if (shouldError()) {
|
|
678
|
+
emitError("Simulated image generation error");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const totalSteps = 5;
|
|
682
|
+
for (let step = 1; step <= totalSteps; step++) {
|
|
683
|
+
await delay(latencyMs);
|
|
684
|
+
emit({
|
|
685
|
+
event: "on_generate_image_progress",
|
|
686
|
+
data: {
|
|
687
|
+
status: `Generating image... (${step}/${totalSteps})`,
|
|
688
|
+
steps: step,
|
|
689
|
+
total_steps: totalSteps
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
emit({
|
|
694
|
+
event: "on_generate_image_response",
|
|
695
|
+
data: {
|
|
696
|
+
image_data_base64: "https://picsum.photos/200/300"
|
|
697
|
+
// Placeholder image URL for the mock
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
const fakeBridge = {
|
|
702
|
+
postMessage(raw) {
|
|
703
|
+
let msg;
|
|
704
|
+
try {
|
|
705
|
+
msg = JSON.parse(raw);
|
|
706
|
+
} catch {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
log("web->RN", msg);
|
|
710
|
+
switch (msg.cmd) {
|
|
711
|
+
case "send_message":
|
|
712
|
+
void handleSend(msg.data);
|
|
713
|
+
break;
|
|
714
|
+
case "cancel":
|
|
715
|
+
handleCancel();
|
|
716
|
+
break;
|
|
717
|
+
case "get_characters":
|
|
718
|
+
void handleGetCharacters();
|
|
719
|
+
break;
|
|
720
|
+
case "get_character_image":
|
|
721
|
+
void handleGetCharacterImage(msg.data);
|
|
722
|
+
break;
|
|
723
|
+
case "generate_image":
|
|
724
|
+
void handleGenerateImage(msg.data);
|
|
725
|
+
break;
|
|
726
|
+
default:
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
const previous = window.ReactNativeWebView;
|
|
732
|
+
if (previous) {
|
|
733
|
+
log("warning: replacing an existing window.ReactNativeWebView");
|
|
734
|
+
}
|
|
735
|
+
window.ReactNativeWebView = fakeBridge;
|
|
736
|
+
log("installed");
|
|
737
|
+
let installed = true;
|
|
738
|
+
return {
|
|
739
|
+
uninstall() {
|
|
740
|
+
if (!installed) return;
|
|
741
|
+
installed = false;
|
|
742
|
+
if (current) current.cancelled = true;
|
|
743
|
+
if (previous) window.ReactNativeWebView = previous;
|
|
744
|
+
else delete window.ReactNativeWebView;
|
|
745
|
+
log("uninstalled");
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
548
749
|
export {
|
|
549
750
|
ChatCompletionStream,
|
|
550
751
|
Images,
|
|
@@ -553,5 +754,7 @@ export {
|
|
|
553
754
|
LaylaBridgeUnavailableError,
|
|
554
755
|
LaylaError,
|
|
555
756
|
LaylaSDK,
|
|
556
|
-
client_default as default
|
|
757
|
+
client_default as default,
|
|
758
|
+
installLaylaMock,
|
|
759
|
+
makeMockCharacter
|
|
557
760
|
};
|