@yak-io/vue 0.5.0 → 0.6.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createYakProvider: () => createYakProvider,
24
+ createYakServerAdapter: () => import_javascript2.createYakServerAdapter,
25
+ createYakToolset: () => import_javascript2.createYakToolset,
26
+ disableYakLogging: () => import_javascript2.disableYakLogging,
27
+ enableYakLogging: () => import_javascript2.enableYakLogging,
28
+ isYakLoggingEnabled: () => import_javascript2.isYakLoggingEnabled,
29
+ useYak: () => useYak,
30
+ useYakToolEvent: () => useYakToolEvent
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+ var import_javascript2 = require("@yak-io/javascript");
34
+
35
+ // src/composables.ts
36
+ var import_javascript = require("@yak-io/javascript");
37
+ var import_vue = require("vue");
38
+ var YAK_KEY = /* @__PURE__ */ Symbol("yak");
39
+ function createYakProvider(options) {
40
+ const isOpen = (0, import_vue.ref)(false);
41
+ const isReady = (0, import_vue.ref)(false);
42
+ const voiceMachine = (0, import_vue.ref)(import_javascript.INITIAL_VOICE_MACHINE);
43
+ const toolEventSubscribers = /* @__PURE__ */ new Set();
44
+ const handleToolCallComplete = (event) => {
45
+ import_javascript.logger.debug("Tool call completed, notifying subscribers:", {
46
+ name: event.name,
47
+ ok: event.ok,
48
+ subscriberCount: toolEventSubscribers.size
49
+ });
50
+ for (const handler of toolEventSubscribers) {
51
+ try {
52
+ handler(event);
53
+ } catch (err) {
54
+ import_javascript.logger.warn("Error in tool event subscriber:", err);
55
+ }
56
+ }
57
+ };
58
+ const resolvedRedirect = options.onRedirect ?? (typeof window !== "undefined" ? (path) => window.location.assign(path) : void 0);
59
+ const embed = new import_javascript.YakEmbed({
60
+ appId: options.appId,
61
+ mode: options.mode,
62
+ theme: options.theme,
63
+ trigger: options.trigger ?? false,
64
+ getConfig: options.getConfig,
65
+ onToolCall: options.onToolCall,
66
+ onRedirect: resolvedRedirect,
67
+ options: { disableRestartButton: options.disableRestartButton },
68
+ onToolCallComplete: handleToolCallComplete
69
+ });
70
+ embed.onStateChange((state) => {
71
+ isOpen.value = state.isOpen;
72
+ isReady.value = state.isReady;
73
+ });
74
+ embed.onVoiceStateChange((m) => {
75
+ voiceMachine.value = m;
76
+ });
77
+ (0, import_vue.onMounted)(() => {
78
+ embed.mount();
79
+ });
80
+ (0, import_vue.onUnmounted)(() => {
81
+ embed.destroy();
82
+ });
83
+ if (options.getConfig) {
84
+ const getConfig = options.getConfig;
85
+ let configFetched = false;
86
+ embed.onStateChange((state) => {
87
+ if (state.isOpen && !configFetched) {
88
+ configFetched = true;
89
+ import_javascript.logger.debug("Getting chat config");
90
+ Promise.resolve(getConfig()).then((config) => {
91
+ import_javascript.logger.debug(
92
+ `Chat config loaded with ${config.tools?.tools.length ?? 0} tools and ${config.routes?.routes.length ?? 0} routes`
93
+ );
94
+ embed.getClient().updateConfig({ chatConfig: config });
95
+ }).catch((err) => {
96
+ import_javascript.logger.warn("Error getting chat config:", err);
97
+ configFetched = false;
98
+ });
99
+ }
100
+ });
101
+ }
102
+ const voiceStart = async () => {
103
+ try {
104
+ await embed.voiceStart();
105
+ } catch (err) {
106
+ import_javascript.logger.warn("Voice start failed", err);
107
+ }
108
+ };
109
+ const chatLoading = (0, import_vue.computed)(() => isOpen.value && !isReady.value);
110
+ const voiceLoading = (0, import_vue.computed)(() => voiceMachine.value.state === "connecting");
111
+ const api = {
112
+ isOpen: (0, import_vue.readonly)(isOpen),
113
+ isReady: (0, import_vue.readonly)(isReady),
114
+ chatLoading,
115
+ open: () => embed.open(),
116
+ close: () => embed.close(),
117
+ openWithPrompt: (prompt) => embed.openWithPrompt(prompt),
118
+ subscribeToToolEvents: (handler) => {
119
+ toolEventSubscribers.add(handler);
120
+ return () => {
121
+ toolEventSubscribers.delete(handler);
122
+ };
123
+ },
124
+ voiceMachine: (0, import_vue.readonly)(voiceMachine),
125
+ voiceLoading,
126
+ voiceStart,
127
+ voiceStop: () => embed.voiceStop(),
128
+ voiceToggle: () => embed.voiceToggle()
129
+ };
130
+ (0, import_vue.provide)(YAK_KEY, api);
131
+ return api;
132
+ }
133
+ function useYak() {
134
+ const api = (0, import_vue.inject)(YAK_KEY);
135
+ if (!api) {
136
+ throw new Error("useYak() requires createYakProvider() in an ancestor component");
137
+ }
138
+ return api;
139
+ }
140
+ function useYakToolEvent(handler) {
141
+ const api = (0, import_vue.inject)(YAK_KEY);
142
+ if (!api) {
143
+ throw new Error("useYakToolEvent() requires createYakProvider() in an ancestor component");
144
+ }
145
+ (0, import_vue.onMounted)(() => {
146
+ const unsubscribe = api.subscribeToToolEvents(handler);
147
+ (0, import_vue.onUnmounted)(unsubscribe);
148
+ });
149
+ }
150
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/composables.ts"],
4
+ "sourcesContent": ["// Public API\n\n// Re-export useful types from @yak-io/javascript for consumers\nexport type {\n ChatConfigProvider,\n GraphQLRequest,\n RESTRequest,\n Theme,\n ThemeColors,\n ToolAdapter,\n ToolCallEvent,\n ToolCallHandler,\n TriggerButtonConfig,\n VoiceMachine,\n VoiceState,\n WidgetMode,\n WidgetPosition,\n YakServerAdapterConfig,\n YakToolset,\n} from \"@yak-io/javascript\";\n// Re-export tool composition + logging utilities\nexport {\n createYakServerAdapter,\n createYakToolset,\n disableYakLogging,\n enableYakLogging,\n isYakLoggingEnabled,\n} from \"@yak-io/javascript\";\nexport type { ToolCallEventHandler, YakApi, YakProviderOptions } from \"./composables.js\";\nexport { createYakProvider, useYak, useYakToolEvent } from \"./composables.js\";\n", "import {\n type ChatConfigProvider,\n INITIAL_VOICE_MACHINE,\n logger,\n type Theme,\n type ToolCallEvent,\n type ToolCallHandler,\n type TriggerButtonConfig,\n type VoiceMachine,\n type WidgetMode,\n YakEmbed,\n} from \"@yak-io/javascript\";\nimport {\n computed,\n type DeepReadonly,\n type InjectionKey,\n inject,\n onMounted,\n onUnmounted,\n provide,\n type Ref,\n readonly,\n ref,\n} from \"vue\";\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type ToolCallEventHandler = (event: ToolCallEvent) => void;\n\nexport type YakProviderOptions = {\n appId: string;\n mode?: WidgetMode;\n getConfig?: ChatConfigProvider;\n onToolCall?: ToolCallHandler;\n theme?: Theme;\n onRedirect?: (path: string) => void;\n disableRestartButton?: boolean;\n trigger?: boolean | TriggerButtonConfig;\n};\n\n/** Reactive handle for controlling the Yak widget \u2014 chat + voice \u2014 from Vue. */\nexport type YakApi = {\n // \u2500\u2500 Chat \u2500\u2500\n /** Whether the chat panel is currently open. */\n isOpen: DeepReadonly<Ref<boolean>>;\n /** Whether the chat iframe is ready to receive messages. */\n isReady: DeepReadonly<Ref<boolean>>;\n /** Whether the chat is opening but not yet interactive (`isOpen && !isReady`). */\n chatLoading: DeepReadonly<Ref<boolean>>;\n /** Open the chat panel. */\n open: () => void;\n /** Close the chat panel. */\n close: () => void;\n /** Open the chat panel and send a specific prompt. */\n openWithPrompt: (prompt: string) => void;\n /** Subscribe to tool-call completion events; returns an unsubscribe function. */\n subscribeToToolEvents: (handler: ToolCallEventHandler) => () => void;\n // \u2500\u2500 Voice \u2500\u2500\n /** Current voice state-machine snapshot. `idle` when mode is `chat`. */\n voiceMachine: DeepReadonly<Ref<VoiceMachine>>;\n /** Whether the voice session is establishing its connection (`state === \"connecting\"`). */\n voiceLoading: DeepReadonly<Ref<boolean>>;\n /** Start a voice session. Must be invoked from a user gesture. */\n voiceStart: () => Promise<void>;\n /** Stop the current voice session. */\n voiceStop: () => Promise<void>;\n /** Toggle voice: start if idle/error, stop if active. */\n voiceToggle: () => Promise<void>;\n};\n\n// \u2500\u2500 Injection key \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst YAK_KEY: InjectionKey<YakApi> = Symbol(\"yak\");\n\n// \u2500\u2500 Provider composable \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Sets up the yak widget (chat + voice) and provides it to descendants.\n * Call this once in a root/layout component.\n *\n * @example\n * ```ts\n * createYakProvider({\n * appId: \"my-app\",\n * mode: \"both\",\n * getConfig: async () => ({ routes: { routes: [], generated_at: \"\" } }),\n * });\n * ```\n */\nexport function createYakProvider(options: YakProviderOptions): YakApi {\n const isOpen = ref(false);\n const isReady = ref(false);\n const voiceMachine = ref<VoiceMachine>(INITIAL_VOICE_MACHINE);\n const toolEventSubscribers = new Set<ToolCallEventHandler>();\n\n const handleToolCallComplete = (event: ToolCallEvent) => {\n logger.debug(\"Tool call completed, notifying subscribers:\", {\n name: event.name,\n ok: event.ok,\n subscriberCount: toolEventSubscribers.size,\n });\n for (const handler of toolEventSubscribers) {\n try {\n handler(event);\n } catch (err) {\n logger.warn(\"Error in tool event subscriber:\", err);\n }\n }\n };\n\n const resolvedRedirect =\n options.onRedirect ??\n (typeof window !== \"undefined\" ? (path: string) => window.location.assign(path) : undefined);\n\n const embed = new YakEmbed({\n appId: options.appId,\n mode: options.mode,\n theme: options.theme,\n trigger: options.trigger ?? false,\n getConfig: options.getConfig,\n onToolCall: options.onToolCall,\n onRedirect: resolvedRedirect,\n options: { disableRestartButton: options.disableRestartButton },\n onToolCallComplete: handleToolCallComplete,\n });\n\n embed.onStateChange((state) => {\n isOpen.value = state.isOpen;\n isReady.value = state.isReady;\n });\n embed.onVoiceStateChange((m) => {\n voiceMachine.value = m;\n });\n\n onMounted(() => {\n embed.mount();\n });\n\n onUnmounted(() => {\n embed.destroy();\n });\n\n // Fetch chat config when widget opens\n if (options.getConfig) {\n const getConfig = options.getConfig;\n let configFetched = false;\n\n embed.onStateChange((state) => {\n if (state.isOpen && !configFetched) {\n configFetched = true;\n logger.debug(\"Getting chat config\");\n\n Promise.resolve(getConfig())\n .then((config) => {\n logger.debug(\n `Chat config loaded with ${config.tools?.tools.length ?? 0} tools and ${config.routes?.routes.length ?? 0} routes`\n );\n embed.getClient().updateConfig({ chatConfig: config });\n })\n .catch((err) => {\n logger.warn(\"Error getting chat config:\", err);\n configFetched = false;\n });\n }\n });\n }\n\n const voiceStart = async () => {\n try {\n await embed.voiceStart();\n } catch (err) {\n logger.warn(\"Voice start failed\", err);\n }\n };\n\n const chatLoading = computed(() => isOpen.value && !isReady.value);\n const voiceLoading = computed(() => voiceMachine.value.state === \"connecting\");\n\n const api: YakApi = {\n isOpen: readonly(isOpen),\n isReady: readonly(isReady),\n chatLoading,\n open: () => embed.open(),\n close: () => embed.close(),\n openWithPrompt: (prompt: string) => embed.openWithPrompt(prompt),\n subscribeToToolEvents: (handler: ToolCallEventHandler) => {\n toolEventSubscribers.add(handler);\n return () => {\n toolEventSubscribers.delete(handler);\n };\n },\n voiceMachine: readonly(voiceMachine),\n voiceLoading,\n voiceStart,\n voiceStop: () => embed.voiceStop(),\n voiceToggle: () => embed.voiceToggle(),\n };\n\n provide(YAK_KEY, api);\n\n return api;\n}\n\n// \u2500\u2500 Consumer composables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Access the yak widget API (chat + voice).\n *\n * @throws {Error} if used outside a component tree with createYakProvider\n */\nexport function useYak(): YakApi {\n const api = inject(YAK_KEY);\n if (!api) {\n throw new Error(\"useYak() requires createYakProvider() in an ancestor component\");\n }\n return api;\n}\n\n/**\n * Subscribe to tool call completion events.\n * Automatically cleans up on component unmount.\n */\nexport function useYakToolEvent(handler: ToolCallEventHandler): void {\n const api = inject(YAK_KEY);\n if (!api) {\n throw new Error(\"useYakToolEvent() requires createYakProvider() in an ancestor component\");\n }\n\n onMounted(() => {\n const unsubscribe = api.subscribeToToolEvents(handler);\n onUnmounted(unsubscribe);\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["import_javascript"]
7
+ }
package/dist/index.js CHANGED
@@ -1,4 +1,147 @@
1
- // Public API
2
- // Re-export tool composition + logging utilities
3
- export { createYakServerAdapter, createYakToolset, disableYakLogging, enableYakLogging, isYakLoggingEnabled, } from "@yak-io/javascript";
4
- export { createYakProvider, useYak, useYakToolEvent } from "./composables.js";
1
+ // src/index.ts
2
+ import {
3
+ createYakServerAdapter,
4
+ createYakToolset,
5
+ disableYakLogging,
6
+ enableYakLogging,
7
+ isYakLoggingEnabled
8
+ } from "@yak-io/javascript";
9
+
10
+ // src/composables.ts
11
+ import {
12
+ INITIAL_VOICE_MACHINE,
13
+ logger,
14
+ YakEmbed
15
+ } from "@yak-io/javascript";
16
+ import {
17
+ computed,
18
+ inject,
19
+ onMounted,
20
+ onUnmounted,
21
+ provide,
22
+ readonly,
23
+ ref
24
+ } from "vue";
25
+ var YAK_KEY = /* @__PURE__ */ Symbol("yak");
26
+ function createYakProvider(options) {
27
+ const isOpen = ref(false);
28
+ const isReady = ref(false);
29
+ const voiceMachine = ref(INITIAL_VOICE_MACHINE);
30
+ const toolEventSubscribers = /* @__PURE__ */ new Set();
31
+ const handleToolCallComplete = (event) => {
32
+ logger.debug("Tool call completed, notifying subscribers:", {
33
+ name: event.name,
34
+ ok: event.ok,
35
+ subscriberCount: toolEventSubscribers.size
36
+ });
37
+ for (const handler of toolEventSubscribers) {
38
+ try {
39
+ handler(event);
40
+ } catch (err) {
41
+ logger.warn("Error in tool event subscriber:", err);
42
+ }
43
+ }
44
+ };
45
+ const resolvedRedirect = options.onRedirect ?? (typeof window !== "undefined" ? (path) => window.location.assign(path) : void 0);
46
+ const embed = new YakEmbed({
47
+ appId: options.appId,
48
+ mode: options.mode,
49
+ theme: options.theme,
50
+ trigger: options.trigger ?? false,
51
+ getConfig: options.getConfig,
52
+ onToolCall: options.onToolCall,
53
+ onRedirect: resolvedRedirect,
54
+ options: { disableRestartButton: options.disableRestartButton },
55
+ onToolCallComplete: handleToolCallComplete
56
+ });
57
+ embed.onStateChange((state) => {
58
+ isOpen.value = state.isOpen;
59
+ isReady.value = state.isReady;
60
+ });
61
+ embed.onVoiceStateChange((m) => {
62
+ voiceMachine.value = m;
63
+ });
64
+ onMounted(() => {
65
+ embed.mount();
66
+ });
67
+ onUnmounted(() => {
68
+ embed.destroy();
69
+ });
70
+ if (options.getConfig) {
71
+ const getConfig = options.getConfig;
72
+ let configFetched = false;
73
+ embed.onStateChange((state) => {
74
+ if (state.isOpen && !configFetched) {
75
+ configFetched = true;
76
+ logger.debug("Getting chat config");
77
+ Promise.resolve(getConfig()).then((config) => {
78
+ logger.debug(
79
+ `Chat config loaded with ${config.tools?.tools.length ?? 0} tools and ${config.routes?.routes.length ?? 0} routes`
80
+ );
81
+ embed.getClient().updateConfig({ chatConfig: config });
82
+ }).catch((err) => {
83
+ logger.warn("Error getting chat config:", err);
84
+ configFetched = false;
85
+ });
86
+ }
87
+ });
88
+ }
89
+ const voiceStart = async () => {
90
+ try {
91
+ await embed.voiceStart();
92
+ } catch (err) {
93
+ logger.warn("Voice start failed", err);
94
+ }
95
+ };
96
+ const chatLoading = computed(() => isOpen.value && !isReady.value);
97
+ const voiceLoading = computed(() => voiceMachine.value.state === "connecting");
98
+ const api = {
99
+ isOpen: readonly(isOpen),
100
+ isReady: readonly(isReady),
101
+ chatLoading,
102
+ open: () => embed.open(),
103
+ close: () => embed.close(),
104
+ openWithPrompt: (prompt) => embed.openWithPrompt(prompt),
105
+ subscribeToToolEvents: (handler) => {
106
+ toolEventSubscribers.add(handler);
107
+ return () => {
108
+ toolEventSubscribers.delete(handler);
109
+ };
110
+ },
111
+ voiceMachine: readonly(voiceMachine),
112
+ voiceLoading,
113
+ voiceStart,
114
+ voiceStop: () => embed.voiceStop(),
115
+ voiceToggle: () => embed.voiceToggle()
116
+ };
117
+ provide(YAK_KEY, api);
118
+ return api;
119
+ }
120
+ function useYak() {
121
+ const api = inject(YAK_KEY);
122
+ if (!api) {
123
+ throw new Error("useYak() requires createYakProvider() in an ancestor component");
124
+ }
125
+ return api;
126
+ }
127
+ function useYakToolEvent(handler) {
128
+ const api = inject(YAK_KEY);
129
+ if (!api) {
130
+ throw new Error("useYakToolEvent() requires createYakProvider() in an ancestor component");
131
+ }
132
+ onMounted(() => {
133
+ const unsubscribe = api.subscribeToToolEvents(handler);
134
+ onUnmounted(unsubscribe);
135
+ });
136
+ }
137
+ export {
138
+ createYakProvider,
139
+ createYakServerAdapter,
140
+ createYakToolset,
141
+ disableYakLogging,
142
+ enableYakLogging,
143
+ isYakLoggingEnabled,
144
+ useYak,
145
+ useYakToolEvent
146
+ };
147
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/composables.ts"],
4
+ "sourcesContent": ["// Public API\n\n// Re-export useful types from @yak-io/javascript for consumers\nexport type {\n ChatConfigProvider,\n GraphQLRequest,\n RESTRequest,\n Theme,\n ThemeColors,\n ToolAdapter,\n ToolCallEvent,\n ToolCallHandler,\n TriggerButtonConfig,\n VoiceMachine,\n VoiceState,\n WidgetMode,\n WidgetPosition,\n YakServerAdapterConfig,\n YakToolset,\n} from \"@yak-io/javascript\";\n// Re-export tool composition + logging utilities\nexport {\n createYakServerAdapter,\n createYakToolset,\n disableYakLogging,\n enableYakLogging,\n isYakLoggingEnabled,\n} from \"@yak-io/javascript\";\nexport type { ToolCallEventHandler, YakApi, YakProviderOptions } from \"./composables.js\";\nexport { createYakProvider, useYak, useYakToolEvent } from \"./composables.js\";\n", "import {\n type ChatConfigProvider,\n INITIAL_VOICE_MACHINE,\n logger,\n type Theme,\n type ToolCallEvent,\n type ToolCallHandler,\n type TriggerButtonConfig,\n type VoiceMachine,\n type WidgetMode,\n YakEmbed,\n} from \"@yak-io/javascript\";\nimport {\n computed,\n type DeepReadonly,\n type InjectionKey,\n inject,\n onMounted,\n onUnmounted,\n provide,\n type Ref,\n readonly,\n ref,\n} from \"vue\";\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type ToolCallEventHandler = (event: ToolCallEvent) => void;\n\nexport type YakProviderOptions = {\n appId: string;\n mode?: WidgetMode;\n getConfig?: ChatConfigProvider;\n onToolCall?: ToolCallHandler;\n theme?: Theme;\n onRedirect?: (path: string) => void;\n disableRestartButton?: boolean;\n trigger?: boolean | TriggerButtonConfig;\n};\n\n/** Reactive handle for controlling the Yak widget \u2014 chat + voice \u2014 from Vue. */\nexport type YakApi = {\n // \u2500\u2500 Chat \u2500\u2500\n /** Whether the chat panel is currently open. */\n isOpen: DeepReadonly<Ref<boolean>>;\n /** Whether the chat iframe is ready to receive messages. */\n isReady: DeepReadonly<Ref<boolean>>;\n /** Whether the chat is opening but not yet interactive (`isOpen && !isReady`). */\n chatLoading: DeepReadonly<Ref<boolean>>;\n /** Open the chat panel. */\n open: () => void;\n /** Close the chat panel. */\n close: () => void;\n /** Open the chat panel and send a specific prompt. */\n openWithPrompt: (prompt: string) => void;\n /** Subscribe to tool-call completion events; returns an unsubscribe function. */\n subscribeToToolEvents: (handler: ToolCallEventHandler) => () => void;\n // \u2500\u2500 Voice \u2500\u2500\n /** Current voice state-machine snapshot. `idle` when mode is `chat`. */\n voiceMachine: DeepReadonly<Ref<VoiceMachine>>;\n /** Whether the voice session is establishing its connection (`state === \"connecting\"`). */\n voiceLoading: DeepReadonly<Ref<boolean>>;\n /** Start a voice session. Must be invoked from a user gesture. */\n voiceStart: () => Promise<void>;\n /** Stop the current voice session. */\n voiceStop: () => Promise<void>;\n /** Toggle voice: start if idle/error, stop if active. */\n voiceToggle: () => Promise<void>;\n};\n\n// \u2500\u2500 Injection key \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst YAK_KEY: InjectionKey<YakApi> = Symbol(\"yak\");\n\n// \u2500\u2500 Provider composable \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Sets up the yak widget (chat + voice) and provides it to descendants.\n * Call this once in a root/layout component.\n *\n * @example\n * ```ts\n * createYakProvider({\n * appId: \"my-app\",\n * mode: \"both\",\n * getConfig: async () => ({ routes: { routes: [], generated_at: \"\" } }),\n * });\n * ```\n */\nexport function createYakProvider(options: YakProviderOptions): YakApi {\n const isOpen = ref(false);\n const isReady = ref(false);\n const voiceMachine = ref<VoiceMachine>(INITIAL_VOICE_MACHINE);\n const toolEventSubscribers = new Set<ToolCallEventHandler>();\n\n const handleToolCallComplete = (event: ToolCallEvent) => {\n logger.debug(\"Tool call completed, notifying subscribers:\", {\n name: event.name,\n ok: event.ok,\n subscriberCount: toolEventSubscribers.size,\n });\n for (const handler of toolEventSubscribers) {\n try {\n handler(event);\n } catch (err) {\n logger.warn(\"Error in tool event subscriber:\", err);\n }\n }\n };\n\n const resolvedRedirect =\n options.onRedirect ??\n (typeof window !== \"undefined\" ? (path: string) => window.location.assign(path) : undefined);\n\n const embed = new YakEmbed({\n appId: options.appId,\n mode: options.mode,\n theme: options.theme,\n trigger: options.trigger ?? false,\n getConfig: options.getConfig,\n onToolCall: options.onToolCall,\n onRedirect: resolvedRedirect,\n options: { disableRestartButton: options.disableRestartButton },\n onToolCallComplete: handleToolCallComplete,\n });\n\n embed.onStateChange((state) => {\n isOpen.value = state.isOpen;\n isReady.value = state.isReady;\n });\n embed.onVoiceStateChange((m) => {\n voiceMachine.value = m;\n });\n\n onMounted(() => {\n embed.mount();\n });\n\n onUnmounted(() => {\n embed.destroy();\n });\n\n // Fetch chat config when widget opens\n if (options.getConfig) {\n const getConfig = options.getConfig;\n let configFetched = false;\n\n embed.onStateChange((state) => {\n if (state.isOpen && !configFetched) {\n configFetched = true;\n logger.debug(\"Getting chat config\");\n\n Promise.resolve(getConfig())\n .then((config) => {\n logger.debug(\n `Chat config loaded with ${config.tools?.tools.length ?? 0} tools and ${config.routes?.routes.length ?? 0} routes`\n );\n embed.getClient().updateConfig({ chatConfig: config });\n })\n .catch((err) => {\n logger.warn(\"Error getting chat config:\", err);\n configFetched = false;\n });\n }\n });\n }\n\n const voiceStart = async () => {\n try {\n await embed.voiceStart();\n } catch (err) {\n logger.warn(\"Voice start failed\", err);\n }\n };\n\n const chatLoading = computed(() => isOpen.value && !isReady.value);\n const voiceLoading = computed(() => voiceMachine.value.state === \"connecting\");\n\n const api: YakApi = {\n isOpen: readonly(isOpen),\n isReady: readonly(isReady),\n chatLoading,\n open: () => embed.open(),\n close: () => embed.close(),\n openWithPrompt: (prompt: string) => embed.openWithPrompt(prompt),\n subscribeToToolEvents: (handler: ToolCallEventHandler) => {\n toolEventSubscribers.add(handler);\n return () => {\n toolEventSubscribers.delete(handler);\n };\n },\n voiceMachine: readonly(voiceMachine),\n voiceLoading,\n voiceStart,\n voiceStop: () => embed.voiceStop(),\n voiceToggle: () => embed.voiceToggle(),\n };\n\n provide(YAK_KEY, api);\n\n return api;\n}\n\n// \u2500\u2500 Consumer composables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Access the yak widget API (chat + voice).\n *\n * @throws {Error} if used outside a component tree with createYakProvider\n */\nexport function useYak(): YakApi {\n const api = inject(YAK_KEY);\n if (!api) {\n throw new Error(\"useYak() requires createYakProvider() in an ancestor component\");\n }\n return api;\n}\n\n/**\n * Subscribe to tool call completion events.\n * Automatically cleans up on component unmount.\n */\nexport function useYakToolEvent(handler: ToolCallEventHandler): void {\n const api = inject(YAK_KEY);\n if (!api) {\n throw new Error(\"useYakToolEvent() requires createYakProvider() in an ancestor component\");\n }\n\n onMounted(() => {\n const unsubscribe = api.subscribeToToolEvents(handler);\n onUnmounted(unsubscribe);\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yak-io/vue",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Vue SDK for embedding yak chatbot",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -30,19 +30,20 @@
30
30
  "LICENSE"
31
31
  ],
32
32
  "sideEffects": false,
33
- "main": "./dist/index.js",
33
+ "main": "./dist/index.cjs",
34
34
  "module": "./dist/index.js",
35
35
  "types": "./dist/index.d.ts",
36
36
  "exports": {
37
37
  ".": {
38
38
  "types": "./dist/index.d.ts",
39
39
  "import": "./dist/index.js",
40
+ "require": "./dist/index.cjs",
40
41
  "default": "./dist/index.js"
41
42
  },
42
43
  "./package.json": "./package.json"
43
44
  },
44
45
  "dependencies": {
45
- "@yak-io/javascript": "0.10.0"
46
+ "@yak-io/javascript": "0.11.0"
46
47
  },
47
48
  "peerDependencies": {
48
49
  "vue": "^3.3.0"
@@ -55,7 +56,7 @@
55
56
  },
56
57
  "homepage": "https://docs.yak.io/docs/sdks/vue",
57
58
  "scripts": {
58
- "build": "tsc",
59
+ "build": "node ../../scripts/build-package.mjs && tsc --emitDeclarationOnly",
59
60
  "check-types": "tsc --noEmit",
60
61
  "test": "vitest run",
61
62
  "lint": "biome lint ./src --fix",
@@ -1,143 +0,0 @@
1
- import { INITIAL_VOICE_MACHINE, logger, YakEmbed, } from "@yak-io/javascript";
2
- import { computed, inject, onMounted, onUnmounted, provide, readonly, ref, } from "vue";
3
- // ── Injection key ───────────────────────────────────────────────────────────
4
- const YAK_KEY = Symbol("yak");
5
- // ── Provider composable ─────────────────────────────────────────────────────
6
- /**
7
- * Sets up the yak widget (chat + voice) and provides it to descendants.
8
- * Call this once in a root/layout component.
9
- *
10
- * @example
11
- * ```ts
12
- * createYakProvider({
13
- * appId: "my-app",
14
- * mode: "both",
15
- * getConfig: async () => ({ routes: { routes: [], generated_at: "" } }),
16
- * });
17
- * ```
18
- */
19
- export function createYakProvider(options) {
20
- const isOpen = ref(false);
21
- const isReady = ref(false);
22
- const voiceMachine = ref(INITIAL_VOICE_MACHINE);
23
- const toolEventSubscribers = new Set();
24
- const handleToolCallComplete = (event) => {
25
- logger.debug("Tool call completed, notifying subscribers:", {
26
- name: event.name,
27
- ok: event.ok,
28
- subscriberCount: toolEventSubscribers.size,
29
- });
30
- for (const handler of toolEventSubscribers) {
31
- try {
32
- handler(event);
33
- }
34
- catch (err) {
35
- logger.warn("Error in tool event subscriber:", err);
36
- }
37
- }
38
- };
39
- const resolvedRedirect = options.onRedirect ??
40
- (typeof window !== "undefined" ? (path) => window.location.assign(path) : undefined);
41
- const embed = new YakEmbed({
42
- appId: options.appId,
43
- mode: options.mode,
44
- theme: options.theme,
45
- trigger: options.trigger ?? false,
46
- getConfig: options.getConfig,
47
- onToolCall: options.onToolCall,
48
- onRedirect: resolvedRedirect,
49
- options: { disableRestartButton: options.disableRestartButton },
50
- onToolCallComplete: handleToolCallComplete,
51
- });
52
- embed.onStateChange((state) => {
53
- isOpen.value = state.isOpen;
54
- isReady.value = state.isReady;
55
- });
56
- embed.onVoiceStateChange((m) => {
57
- voiceMachine.value = m;
58
- });
59
- onMounted(() => {
60
- embed.mount();
61
- });
62
- onUnmounted(() => {
63
- embed.destroy();
64
- });
65
- // Fetch chat config when widget opens
66
- if (options.getConfig) {
67
- const getConfig = options.getConfig;
68
- let configFetched = false;
69
- embed.onStateChange((state) => {
70
- if (state.isOpen && !configFetched) {
71
- configFetched = true;
72
- logger.debug("Getting chat config");
73
- Promise.resolve(getConfig())
74
- .then((config) => {
75
- logger.debug(`Chat config loaded with ${config.tools?.tools.length ?? 0} tools and ${config.routes?.routes.length ?? 0} routes`);
76
- embed.getClient().updateConfig({ chatConfig: config });
77
- })
78
- .catch((err) => {
79
- logger.warn("Error getting chat config:", err);
80
- configFetched = false;
81
- });
82
- }
83
- });
84
- }
85
- const voiceStart = async () => {
86
- try {
87
- await embed.voiceStart();
88
- }
89
- catch (err) {
90
- logger.warn("Voice start failed", err);
91
- }
92
- };
93
- const chatLoading = computed(() => isOpen.value && !isReady.value);
94
- const voiceLoading = computed(() => voiceMachine.value.state === "connecting");
95
- const api = {
96
- isOpen: readonly(isOpen),
97
- isReady: readonly(isReady),
98
- chatLoading,
99
- open: () => embed.open(),
100
- close: () => embed.close(),
101
- openWithPrompt: (prompt) => embed.openWithPrompt(prompt),
102
- subscribeToToolEvents: (handler) => {
103
- toolEventSubscribers.add(handler);
104
- return () => {
105
- toolEventSubscribers.delete(handler);
106
- };
107
- },
108
- voiceMachine: readonly(voiceMachine),
109
- voiceLoading,
110
- voiceStart,
111
- voiceStop: () => embed.voiceStop(),
112
- voiceToggle: () => embed.voiceToggle(),
113
- };
114
- provide(YAK_KEY, api);
115
- return api;
116
- }
117
- // ── Consumer composables ────────────────────────────────────────────────────
118
- /**
119
- * Access the yak widget API (chat + voice).
120
- *
121
- * @throws {Error} if used outside a component tree with createYakProvider
122
- */
123
- export function useYak() {
124
- const api = inject(YAK_KEY);
125
- if (!api) {
126
- throw new Error("useYak() requires createYakProvider() in an ancestor component");
127
- }
128
- return api;
129
- }
130
- /**
131
- * Subscribe to tool call completion events.
132
- * Automatically cleans up on component unmount.
133
- */
134
- export function useYakToolEvent(handler) {
135
- const api = inject(YAK_KEY);
136
- if (!api) {
137
- throw new Error("useYakToolEvent() requires createYakProvider() in an ancestor component");
138
- }
139
- onMounted(() => {
140
- const unsubscribe = api.subscribeToToolEvents(handler);
141
- onUnmounted(unsubscribe);
142
- });
143
- }