akanjs 3.0.0-alpha.39 → 3.0.0-alpha.40

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.
@@ -46,6 +46,28 @@ export type CapacitorContactsModule = {
46
46
  };
47
47
  };
48
48
 
49
+ export type CapacitorSpeechRecognitionModule = {
50
+ SpeechRecognition: {
51
+ available: () => Promise<{ available: boolean }>;
52
+ checkPermissions: () => Promise<{ speechRecognition: CapacitorPermissionState }>;
53
+ requestPermissions: () => Promise<{ speechRecognition: CapacitorPermissionState }>;
54
+ start: (options: Record<string, unknown>) => Promise<{ matches?: string[] }>;
55
+ stop: () => Promise<void>;
56
+ removeAllListeners: () => Promise<void> | void;
57
+ addListener: (
58
+ eventName: string,
59
+ listenerFunc: (data: { matches?: string[] }) => void,
60
+ ) => Promise<{ remove: () => Promise<void> | void }> | { remove: () => Promise<void> | void };
61
+ };
62
+ };
63
+
64
+ export type CapacitorTextToSpeechModule = {
65
+ TextToSpeech: {
66
+ speak: (options: { text: string; lang?: string; rate?: number }) => Promise<void>;
67
+ stop: () => Promise<void>;
68
+ };
69
+ };
70
+
49
71
  export type CapacitorCoreModule = {
50
72
  CapacitorCookies: {
51
73
  setCookie: (options: { key: string; value: string; path?: string }) => Promise<void> | void;
@@ -155,6 +177,8 @@ type CapacitorModuleMap = {
155
177
  preferences: CapacitorPreferencesModule;
156
178
  pushNotifications: CapacitorPushNotificationsModule;
157
179
  safeArea: CapacitorSafeAreaModule;
180
+ speechRecognition: CapacitorSpeechRecognitionModule;
181
+ textToSpeech: CapacitorTextToSpeechModule;
158
182
  updater: CapacitorUpdaterModule;
159
183
  };
160
184
 
@@ -257,6 +281,16 @@ export const loadCapacitorSafeArea = () =>
257
281
  SafeArea: getCapacitorPlugin<CapacitorSafeAreaModule["SafeArea"]>("SafeArea"),
258
282
  }));
259
283
 
284
+ export const loadCapacitorSpeechRecognition = () =>
285
+ loadCapacitorModule("speechRecognition", async () => ({
286
+ SpeechRecognition: getCapacitorPlugin<CapacitorSpeechRecognitionModule["SpeechRecognition"]>("SpeechRecognition"),
287
+ }));
288
+
289
+ export const loadCapacitorTextToSpeech = () =>
290
+ loadCapacitorModule("textToSpeech", async () => ({
291
+ TextToSpeech: getCapacitorPlugin<CapacitorTextToSpeechModule["TextToSpeech"]>("TextToSpeech"),
292
+ }));
293
+
260
294
  export const loadCapacitorUpdater = () =>
261
295
  loadCapacitorModule("updater", async () => ({
262
296
  CapacitorUpdater: getCapacitorPlugin<CapacitorUpdaterModule["CapacitorUpdater"]>("CapacitorUpdater"),
@@ -54,6 +54,8 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
54
54
  agentClear: ["Clear conversation", "대화 비우기"],
55
55
  agentQuestion: ["The agent needs your decision", "에이전트가 결정을 요청합니다"],
56
56
  agentAnswer: ["Type your answer...", "답변을 입력하세요..."],
57
+ agentListen: ["Speak to the agent", "에이전트에게 말하기"],
58
+ agentVoiceFailed: ["The microphone could not be used.", "마이크를 사용할 수 없습니다."],
57
59
  agentAttach: ["Attach a file", "파일 첨부"],
58
60
  agentAttachRemove: ["Remove attachment", "첨부 제거"],
59
61
  agentAttachTooLarge: ["{name} is too large to attach.", "{name}은(는) 용량이 너무 커서 첨부할 수 없습니다."],
package/index.ts CHANGED
@@ -26,7 +26,7 @@ export interface AkanRouteConfig {
26
26
 
27
27
  export type DatabaseMode = "single" | "multiple" | "cluster";
28
28
  export type MobileEnv = "local" | "debug" | "develop" | "main";
29
- export type MobilePermission = "camera" | "contacts" | "location" | "push";
29
+ export type MobilePermission = "camera" | "contacts" | "location" | "push" | "speech";
30
30
 
31
31
  export interface AkanMobileTargetAssets {
32
32
  icon?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.39",
3
+ "version": "3.0.0-alpha.40",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -73,6 +73,41 @@ export type CapacitorContactsModule = {
73
73
  }>;
74
74
  };
75
75
  };
76
+ export type CapacitorSpeechRecognitionModule = {
77
+ SpeechRecognition: {
78
+ available: () => Promise<{
79
+ available: boolean;
80
+ }>;
81
+ checkPermissions: () => Promise<{
82
+ speechRecognition: CapacitorPermissionState;
83
+ }>;
84
+ requestPermissions: () => Promise<{
85
+ speechRecognition: CapacitorPermissionState;
86
+ }>;
87
+ start: (options: Record<string, unknown>) => Promise<{
88
+ matches?: string[];
89
+ }>;
90
+ stop: () => Promise<void>;
91
+ removeAllListeners: () => Promise<void> | void;
92
+ addListener: (eventName: string, listenerFunc: (data: {
93
+ matches?: string[];
94
+ }) => void) => Promise<{
95
+ remove: () => Promise<void> | void;
96
+ }> | {
97
+ remove: () => Promise<void> | void;
98
+ };
99
+ };
100
+ };
101
+ export type CapacitorTextToSpeechModule = {
102
+ TextToSpeech: {
103
+ speak: (options: {
104
+ text: string;
105
+ lang?: string;
106
+ rate?: number;
107
+ }) => Promise<void>;
108
+ stop: () => Promise<void>;
109
+ };
110
+ };
76
111
  export type CapacitorCoreModule = {
77
112
  CapacitorCookies: {
78
113
  setCookie: (options: {
@@ -226,6 +261,8 @@ type CapacitorModuleMap = {
226
261
  preferences: CapacitorPreferencesModule;
227
262
  pushNotifications: CapacitorPushNotificationsModule;
228
263
  safeArea: CapacitorSafeAreaModule;
264
+ speechRecognition: CapacitorSpeechRecognitionModule;
265
+ textToSpeech: CapacitorTextToSpeechModule;
229
266
  updater: CapacitorUpdaterModule;
230
267
  };
231
268
  type CapacitorImportCache = Partial<{
@@ -247,5 +284,7 @@ export declare const loadCapacitorKeyboard: () => Promise<CapacitorKeyboardModul
247
284
  export declare const loadCapacitorPreferences: () => Promise<CapacitorPreferencesModule>;
248
285
  export declare const loadCapacitorPushNotifications: () => Promise<CapacitorPushNotificationsModule>;
249
286
  export declare const loadCapacitorSafeArea: () => Promise<CapacitorSafeAreaModule>;
287
+ export declare const loadCapacitorSpeechRecognition: () => Promise<CapacitorSpeechRecognitionModule>;
288
+ export declare const loadCapacitorTextToSpeech: () => Promise<CapacitorTextToSpeechModule>;
250
289
  export declare const loadCapacitorUpdater: () => Promise<CapacitorUpdaterModule>;
251
290
  export {};
@@ -1 +1 @@
1
- export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", never, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">;
1
+ export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", never, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">;
@@ -1,13 +1,13 @@
1
1
  import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
2
2
  export declare const dictionary: {
3
- base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">, never>;
3
+ base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">, never>;
4
4
  agentTurn: import("./locale.d.ts").DictModule<import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc`, never>;
5
5
  agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">;
6
6
  };
7
- export declare const Err: import("./trans.d.ts").ErrConstructor<"agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
8
- info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
9
- success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
10
- error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
11
- warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
12
- loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
13
- }, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
7
+ export declare const Err: import("./trans.d.ts").ErrConstructor<"agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
8
+ info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
9
+ success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
10
+ error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
11
+ warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
12
+ loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
13
+ }, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
package/types/index.d.ts CHANGED
@@ -27,7 +27,7 @@ export interface AkanRouteConfig {
27
27
  }
28
28
  export type DatabaseMode = "single" | "multiple" | "cluster";
29
29
  export type MobileEnv = "local" | "debug" | "develop" | "main";
30
- export type MobilePermission = "camera" | "contacts" | "location" | "push";
30
+ export type MobilePermission = "camera" | "contacts" | "location" | "push" | "speech";
31
31
  export interface AkanMobileTargetAssets {
32
32
  icon?: string;
33
33
  splash?: string;
@@ -2,6 +2,7 @@ import { type ReactNode } from "react";
2
2
  import { type AgentRunner } from "../../vendor/use-agentic.d.ts";
3
3
  import { type AttachReader } from "./attachment.d.ts";
4
4
  import { type PersistOption } from "./sessionHistory.d.ts";
5
+ import { type VoiceEngine } from "./voice.d.ts";
5
6
  export interface ChatProps {
6
7
  className?: string;
7
8
  title?: string;
@@ -22,6 +23,12 @@ export interface ChatProps {
22
23
  * the built-in, so it can also replace how an image is prepared.
23
24
  */
24
25
  attach?: AttachReader;
26
+ /**
27
+ * Speech in and out. The engine listens and speaks; this component decides when — a press-to-talk microphone
28
+ * whose transcript lands in the composer for the user to correct, and a reply read aloud **only when the ask
29
+ * itself came in by voice**, so a typed question never turns the speakers on.
30
+ */
31
+ voice?: VoiceEngine;
25
32
  }
26
33
  /**
27
34
  * The user-facing half of the in-page agent: one floating chat wired to the same surface the dock inspects.
@@ -30,6 +37,6 @@ export interface ChatProps {
30
37
  * no history). An enclosing AgentProvider's session wins, which is how an app isolates a surface or swaps the
31
38
  * loop while keeping this UI.
32
39
  */
33
- export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, defaultOpen, persist, inline, attach, }: ChatProps) => ReactNode;
40
+ export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, defaultOpen, persist, inline, attach, voice, }: ChatProps) => ReactNode;
34
41
  declare const _default: import("react").ComponentType<ChatProps>;
35
42
  export default _default;
@@ -0,0 +1,8 @@
1
+ interface MicProps {
2
+ className?: string;
3
+ listening: boolean;
4
+ label: string;
5
+ onToggle: () => void;
6
+ }
7
+ export declare const Mic: ({ className, listening, label, onToggle }: MicProps) => import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,66 @@
1
+ /** A recognition run in progress. `stop` ends it; a final result may still arrive after. */
2
+ export interface VoiceListener {
3
+ stop: () => void;
4
+ }
5
+ /** One utterance being spoken. `cancel` is barge-in; `done` resolves when it finished or was cancelled. */
6
+ export interface VoiceSpeech {
7
+ cancel: () => void;
8
+ done: Promise<void>;
9
+ }
10
+ export interface VoiceHandlers {
11
+ /** Partial text while the user is still speaking. An engine without interim results simply never calls it. */
12
+ onInterim?: (text: string) => void;
13
+ onFinal: (text: string) => void;
14
+ onError: (message: string) => void;
15
+ }
16
+ /**
17
+ * The seam a voice engine fills — one contract, whatever is behind it: the browser's own recognition, a cloud
18
+ * STT/TTS pair, or a native plugin. The engine speaks and listens; **the framework owns the policy** — when to
19
+ * listen, what to speak, sentence chunking, barge-in, and whether a reply is spoken at all.
20
+ *
21
+ * Shaped as a subscription rather than `listen(): Promise<string>` on purpose. A promise fits push-to-talk and
22
+ * nothing else, and continuous listening could then only arrive as a breaking change; this shape carries both, so
23
+ * hands-free is a later policy decision instead of a later contract.
24
+ *
25
+ * `listen` must be reachable from the click that starts it: microphone permission and, on iOS, the first
26
+ * `speechSynthesis` utterance are both gated on a user gesture.
27
+ */
28
+ export interface VoiceEngine {
29
+ listen: (handlers: VoiceHandlers) => VoiceListener;
30
+ /** One sentence at a time, already stripped of markdown. The framework queues; the engine speaks. */
31
+ speak: (text: string) => VoiceSpeech;
32
+ /** Answering false hides the controls — a screen that cannot listen should not offer a microphone. */
33
+ available?: () => boolean;
34
+ }
35
+ /**
36
+ * What of a model's answer should be read aloud.
37
+ *
38
+ * Code blocks, tables, and rules are dropped rather than spoken: a fence read character by character is noise,
39
+ * and a table read cell by cell is worse than silence — in push-to-talk the user is looking at the screen that
40
+ * already renders both. Inline code keeps its content, because that is usually a name worth hearing.
41
+ *
42
+ * Monotonic over a growing source, which is what lets the reader below track one offset: new text never changes
43
+ * how earlier text was rendered, since a fence marker always arrives before the lines it swallows.
44
+ */
45
+ export declare const speechText: (markdown: string) => string;
46
+ /**
47
+ * Reads an assistant answer aloud as it streams in, one sentence at a time.
48
+ *
49
+ * Sentence-at-a-time rather than delta-at-a-time because a delta cuts words in half, and whole-message-at-a-time
50
+ * because that waits for the turn to end. The offset is over `speechText` output, not the raw source, so a code
51
+ * block that arrives mid-answer costs nothing.
52
+ */
53
+ export declare class VoiceReader {
54
+ #private;
55
+ constructor(engine: () => VoiceEngine | undefined);
56
+ get speaking(): boolean;
57
+ /** Call with the whole answer so far, as often as it changes; already-spoken text is never repeated. */
58
+ feed(source: string): void;
59
+ /**
60
+ * The turn ended: the last sentence has no whitespace behind it and would otherwise never be spoken. It still
61
+ * splits what is left, because a whole answer that arrived in one render must not be read as one long utterance.
62
+ */
63
+ flush(source: string): void;
64
+ cancel(): void;
65
+ reset(): void;
66
+ }
@@ -1,5 +1,6 @@
1
1
  export { Agent } from "./Agent.d.ts";
2
2
  export { type AttachReader, maxAttachmentBytes } from "./Agent/attachment.d.ts";
3
+ export type { VoiceEngine, VoiceHandlers, VoiceListener, VoiceSpeech } from "./Agent/voice.d.ts";
3
4
  export { agentAttrs } from "./agentAttrs.d.ts";
4
5
  export { animated } from "./animated.d.ts";
5
6
  export { Badge } from "./Badge.d.ts";
package/ui/Agent/Chat.tsx CHANGED
@@ -16,8 +16,10 @@ import Bubble from "./Bubble";
16
16
  import { type ChatCommand, ChatCommands } from "./ChatCommands";
17
17
  import { fetchRunner } from "./fetchRunner";
18
18
  import Menu from "./Menu";
19
+ import { Mic } from "./Mic";
19
20
  import Question from "./Question";
20
21
  import { type PersistOption, sessionHistoryOf } from "./sessionHistory";
22
+ import { type VoiceEngine, type VoiceListener, VoiceReader } from "./voice";
21
23
 
22
24
  export interface ChatProps {
23
25
  className?: string;
@@ -39,6 +41,12 @@ export interface ChatProps {
39
41
  * the built-in, so it can also replace how an image is prepared.
40
42
  */
41
43
  attach?: AttachReader;
44
+ /**
45
+ * Speech in and out. The engine listens and speaks; this component decides when — a press-to-talk microphone
46
+ * whose transcript lands in the composer for the user to correct, and a reply read aloud **only when the ask
47
+ * itself came in by voice**, so a typed question never turns the speakers on.
48
+ */
49
+ voice?: VoiceEngine;
42
50
  }
43
51
 
44
52
  const isApplePlatform = () => /Mac|iPhone|iPad|iPod/i.test(navigator.platform);
@@ -62,6 +70,7 @@ export const DefaultChat = ({
62
70
  persist,
63
71
  inline = false,
64
72
  attach,
73
+ voice,
65
74
  }: ChatProps) => {
66
75
  const { l } = usePage();
67
76
  const provided = useContext(SessionContext);
@@ -87,6 +96,18 @@ export const DefaultChat = ({
87
96
  const [open, setOpen] = useState(defaultOpen);
88
97
  const [draft, setDraft] = useState("");
89
98
  const [attached, setAttached] = useState<MessageAttachment[]>([]);
99
+ const [listening, setListening] = useState(false);
100
+
101
+ const engine = useRef<VoiceEngine | undefined>(voice);
102
+ engine.current = voice;
103
+ const listener = useRef<VoiceListener | null>(null);
104
+ const reader = useRef<VoiceReader | null>(null);
105
+ reader.current ??= new VoiceReader(() => engine.current);
106
+ /** Set while the draft came from the microphone, consumed by the send that carries it. */
107
+ const byVoice = useRef(false);
108
+ /** The assistant message being read aloud. Null means this turn is not one to read. */
109
+ const spoken = useRef<{ at: number } | null>(null);
110
+ const seen = useRef(0);
90
111
  const sent = useRef<string[]>([]);
91
112
  const stashed = useRef("");
92
113
  const [recall, setRecall] = useState(0);
@@ -119,6 +140,39 @@ export const DefaultChat = ({
119
140
  if (!open) return;
120
141
  inputRef.current?.focus();
121
142
  }, [open, session.pendingQuestion?.callId]);
143
+ useEffect(() => {
144
+ const speaker = reader.current;
145
+
146
+ if (session.messages.length < seen.current) {
147
+ speaker?.reset();
148
+ spoken.current = null;
149
+ }
150
+ seen.current = session.messages.length;
151
+ const reading = spoken.current;
152
+ if (!reading || !speaker) return;
153
+ const at = session.messages.findLastIndex(
154
+ (message) => message.role === "assistant" && !message.local && !!message.text,
155
+ );
156
+ if (at < 0) return;
157
+
158
+ if (at !== reading.at) {
159
+ speaker.reset();
160
+ reading.at = at;
161
+ }
162
+ const text = session.messages[at].text ?? "";
163
+ if (session.isRunning) speaker.feed(text);
164
+ else {
165
+ speaker.flush(text);
166
+ spoken.current = null;
167
+ }
168
+ }, [version]);
169
+ useEffect(
170
+ () => () => {
171
+ listener.current?.stop();
172
+ reader.current?.cancel();
173
+ },
174
+ [],
175
+ );
122
176
  const runPrompt = async (prompt: AgentPrompt, args: string[]) => {
123
177
  const usage = `/${prompt.name} ${prompt.args.map((arg) => `<${arg.name}>`).join(" ")}`.trim();
124
178
  if (args.length < prompt.args.filter((arg) => arg.required).length) {
@@ -154,6 +208,39 @@ export const DefaultChat = ({
154
208
  }
155
209
  }
156
210
  };
211
+ /** One press is one utterance, so the engine is told to stop even when it reported a final result itself. */
212
+ const endListening = () => {
213
+ const held = listener.current;
214
+ listener.current = null;
215
+ held?.stop();
216
+ setListening(false);
217
+ };
218
+ const toggleMic = () => {
219
+ if (listener.current) {
220
+ endListening();
221
+ return;
222
+ }
223
+ const voiceEngine = engine.current;
224
+ if (!voiceEngine) return;
225
+
226
+ reader.current?.cancel();
227
+ spoken.current = null;
228
+ listener.current = voiceEngine.listen({
229
+ onInterim: setDraft,
230
+ onFinal: (text) => {
231
+ setDraft(text);
232
+ byVoice.current = true;
233
+ endListening();
234
+ },
235
+ onError: (message) => {
236
+
237
+ console.warn(`[akan] voice input failed: ${message}`);
238
+ session.note(l("base.agentVoiceFailed"));
239
+ endListening();
240
+ },
241
+ });
242
+ setListening(true);
243
+ };
157
244
  const remember = (text: string) => {
158
245
  if (sent.current[sent.current.length - 1] !== text) sent.current = [...sent.current, text].slice(-30);
159
246
  setRecall(0);
@@ -188,6 +275,7 @@ export const DefaultChat = ({
188
275
  const question = session.pendingQuestion;
189
276
  if (question && text) {
190
277
  setDraft("");
278
+ byVoice.current = false;
191
279
  question.answer(question.multiple ? [text] : text);
192
280
  return;
193
281
  }
@@ -202,6 +290,9 @@ export const DefaultChat = ({
202
290
  const prompt = command ? prompts.current?.find(command.name) : null;
203
291
  setDraft("");
204
292
  if (text) remember(text);
293
+
294
+ spoken.current = byVoice.current ? { at: -1 } : null;
295
+ byVoice.current = false;
205
296
  if (command && prompt) {
206
297
  void runPrompt(prompt, command.args);
207
298
  return;
@@ -213,6 +304,8 @@ export const DefaultChat = ({
213
304
  setAttached([]);
214
305
  void session.send([{ role: "user", ...(text ? { text } : {}), attachments: attached }]);
215
306
  };
307
+
308
+ const canListen = !!voice && (voice.available?.() ?? true);
216
309
  const query = /^\/[A-Za-z0-9_-]*$/.test(draft) ? draft : "";
217
310
  const commandMenu = query ? ChatCommands.list(l).filter((command) => `/${command.name}`.startsWith(query)) : [];
218
311
  const promptMenu = query
@@ -323,6 +416,7 @@ export const DefaultChat = ({
323
416
  />
324
417
  ) : null}
325
418
  <div className="flex items-center gap-2">
419
+ {canListen ? <Mic label={l("base.agentListen")} listening={listening} onToggle={toggleMic} /> : null}
326
420
  <Attach label={l("base.agentAttach")} onPick={(files) => void attachFiles(files)} />
327
421
  <input
328
422
  className={inputRecipe({ size: "sm" }, "flex-1")}
@@ -349,7 +443,15 @@ export const DefaultChat = ({
349
443
  value={draft}
350
444
  />
351
445
  {session.isRunning && !session.pendingQuestion ? (
352
- <Button onClick={session.abort} size="sm" variant="outline">
446
+ <Button
447
+ onClick={() => {
448
+ reader.current?.cancel();
449
+ spoken.current = null;
450
+ session.abort();
451
+ }}
452
+ size="sm"
453
+ variant="outline"
454
+ >
353
455
  {l("base.stop")}
354
456
  </Button>
355
457
  ) : (
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { cn } from "akanjs/client";
3
+ import { AiOutlineAudio, AiOutlineAudioMuted } from "react-icons/ai";
4
+
5
+ interface MicProps {
6
+ className?: string;
7
+ listening: boolean;
8
+ label: string;
9
+ onToggle: () => void;
10
+ }
11
+
12
+ export const Mic = ({ className, listening, label, onToggle }: MicProps) => (
13
+ <button
14
+ aria-label={label}
15
+ aria-pressed={listening}
16
+ className={cn(
17
+ "shrink-0 hover:text-foreground",
18
+ listening ? "animate-pulse text-primary" : "text-foreground/50",
19
+ className,
20
+ )}
21
+ onClick={onToggle}
22
+ title={label}
23
+ type="button"
24
+ >
25
+ {listening ? <AiOutlineAudioMuted /> : <AiOutlineAudio />}
26
+ </button>
27
+ );
@@ -0,0 +1,168 @@
1
+ /** A recognition run in progress. `stop` ends it; a final result may still arrive after. */
2
+ export interface VoiceListener {
3
+ stop: () => void;
4
+ }
5
+
6
+ /** One utterance being spoken. `cancel` is barge-in; `done` resolves when it finished or was cancelled. */
7
+ export interface VoiceSpeech {
8
+ cancel: () => void;
9
+ done: Promise<void>;
10
+ }
11
+
12
+ export interface VoiceHandlers {
13
+ /** Partial text while the user is still speaking. An engine without interim results simply never calls it. */
14
+ onInterim?: (text: string) => void;
15
+ onFinal: (text: string) => void;
16
+ onError: (message: string) => void;
17
+ }
18
+
19
+ /**
20
+ * The seam a voice engine fills — one contract, whatever is behind it: the browser's own recognition, a cloud
21
+ * STT/TTS pair, or a native plugin. The engine speaks and listens; **the framework owns the policy** — when to
22
+ * listen, what to speak, sentence chunking, barge-in, and whether a reply is spoken at all.
23
+ *
24
+ * Shaped as a subscription rather than `listen(): Promise<string>` on purpose. A promise fits push-to-talk and
25
+ * nothing else, and continuous listening could then only arrive as a breaking change; this shape carries both, so
26
+ * hands-free is a later policy decision instead of a later contract.
27
+ *
28
+ * `listen` must be reachable from the click that starts it: microphone permission and, on iOS, the first
29
+ * `speechSynthesis` utterance are both gated on a user gesture.
30
+ */
31
+ export interface VoiceEngine {
32
+ listen: (handlers: VoiceHandlers) => VoiceListener;
33
+ /** One sentence at a time, already stripped of markdown. The framework queues; the engine speaks. */
34
+ speak: (text: string) => VoiceSpeech;
35
+ /** Answering false hides the controls — a screen that cannot listen should not offer a microphone. */
36
+ available?: () => boolean;
37
+ }
38
+
39
+ const fenced = /^ {0,3}(?:```|~~~)/;
40
+ const tableRow = /^\s*\|/;
41
+ const horizontalRule = /^ {0,3}(?:-{3,}|\*{3,}|_{3,})\s*$/;
42
+ const lineMarkers = /^\s*(?:#{1,6}\s+|>\s?|[-*+]\s+|\d{1,9}[.)]\s+)/;
43
+ const image = /!\[([^\]]*)\]\([^)]*\)/g;
44
+ const link = /\[([^\]]*)\]\([^)]*\)/g;
45
+ const emphasis = /(\*\*\*|\*\*|\*|___|__|_|~~)/g;
46
+ const inlineCode = /`([^`]*)`/g;
47
+
48
+ /**
49
+ * What of a model's answer should be read aloud.
50
+ *
51
+ * Code blocks, tables, and rules are dropped rather than spoken: a fence read character by character is noise,
52
+ * and a table read cell by cell is worse than silence — in push-to-talk the user is looking at the screen that
53
+ * already renders both. Inline code keeps its content, because that is usually a name worth hearing.
54
+ *
55
+ * Monotonic over a growing source, which is what lets the reader below track one offset: new text never changes
56
+ * how earlier text was rendered, since a fence marker always arrives before the lines it swallows.
57
+ */
58
+ export const speechText = (markdown: string): string => {
59
+ const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
60
+ const kept: string[] = [];
61
+ let inFence = false;
62
+ for (const line of lines) {
63
+ if (fenced.test(line)) {
64
+ inFence = !inFence;
65
+ continue;
66
+ }
67
+ if (inFence || tableRow.test(line) || horizontalRule.test(line)) continue;
68
+ const plain = line
69
+ .replace(lineMarkers, "")
70
+ .replace(image, "$1")
71
+ .replace(link, "$1")
72
+ .replace(inlineCode, "$1")
73
+ .replace(emphasis, "");
74
+ kept.push(plain);
75
+ }
76
+ return kept.join("\n");
77
+ };
78
+
79
+ /** A terminator counts only with whitespace behind it, so `1.5` is not a sentence and a stream is never cut early. */
80
+ const sentenceEnd = /[.!?。!?…]+\s|\n/g;
81
+
82
+ /**
83
+ * Reads an assistant answer aloud as it streams in, one sentence at a time.
84
+ *
85
+ * Sentence-at-a-time rather than delta-at-a-time because a delta cuts words in half, and whole-message-at-a-time
86
+ * because that waits for the turn to end. The offset is over `speechText` output, not the raw source, so a code
87
+ * block that arrives mid-answer costs nothing.
88
+ */
89
+ export class VoiceReader {
90
+ readonly #engine: () => VoiceEngine | undefined;
91
+ #queue: string[] = [];
92
+ #current: VoiceSpeech | null = null;
93
+ #offset = 0;
94
+
95
+ constructor(engine: () => VoiceEngine | undefined) {
96
+ this.#engine = engine;
97
+ }
98
+
99
+ get speaking() {
100
+ return !!this.#current;
101
+ }
102
+
103
+ /** Call with the whole answer so far, as often as it changes; already-spoken text is never repeated. */
104
+ feed(source: string) {
105
+ this.#chunk(source, false);
106
+ }
107
+
108
+ /**
109
+ * The turn ended: the last sentence has no whitespace behind it and would otherwise never be spoken. It still
110
+ * splits what is left, because a whole answer that arrived in one render must not be read as one long utterance.
111
+ */
112
+ flush(source: string) {
113
+ this.#chunk(source, true);
114
+ }
115
+
116
+ cancel() {
117
+ this.#queue = [];
118
+ this.#current?.cancel();
119
+ this.#current = null;
120
+ }
121
+
122
+ reset() {
123
+ this.cancel();
124
+ this.#offset = 0;
125
+ }
126
+
127
+ #chunk(source: string, final: boolean) {
128
+ const text = speechText(source);
129
+
130
+ if (text.length < this.#offset) this.reset();
131
+ const pending = text.slice(this.#offset);
132
+ sentenceEnd.lastIndex = 0;
133
+ let cut = 0;
134
+ for (let match = sentenceEnd.exec(pending); match; match = sentenceEnd.exec(pending)) {
135
+ const end = match.index + match[0].length;
136
+ this.#enqueue(pending.slice(cut, end));
137
+ cut = end;
138
+ }
139
+ if (final) {
140
+ this.#enqueue(pending.slice(cut));
141
+ cut = pending.length;
142
+ }
143
+ this.#offset += cut;
144
+ this.#pump();
145
+ }
146
+
147
+ #enqueue(chunk: string) {
148
+ const text = chunk.trim();
149
+ if (text) this.#queue.push(text);
150
+ }
151
+
152
+ #pump() {
153
+ if (this.#current || !this.#queue.length) return;
154
+ const engine = this.#engine();
155
+ if (!engine) {
156
+ this.#queue = [];
157
+ return;
158
+ }
159
+ const next = this.#queue.shift() as string;
160
+ const speech = engine.speak(next);
161
+ this.#current = speech;
162
+ void speech.done.then(() => {
163
+ if (this.#current !== speech) return;
164
+ this.#current = null;
165
+ this.#pump();
166
+ });
167
+ }
168
+ }
package/ui/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { Agent } from "./Agent";
2
2
  export { type AttachReader, maxAttachmentBytes } from "./Agent/attachment";
3
+ export type { VoiceEngine, VoiceHandlers, VoiceListener, VoiceSpeech } from "./Agent/voice";
3
4
  export { agentAttrs } from "./agentAttrs";
4
5
  export { animated } from "./animated";
5
6
  export { Badge } from "./Badge";