@athenaintel/react 0.23.2 → 0.23.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/dist/chat/AthenaChat.cjs +129 -29
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +32 -1
- package/dist/chat/AthenaChat.js +130 -30
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/athena-composer-controls.js +1 -1
- package/dist/chat/statewire-client-tools.cjs +20 -0
- package/dist/chat/statewire-client-tools.cjs.map +1 -1
- package/dist/chat/statewire-client-tools.d.ts +27 -0
- package/dist/chat/statewire-client-tools.js +20 -0
- package/dist/chat/statewire-client-tools.js.map +1 -1
- package/dist/chat/use-athena-run-live.cjs.map +1 -1
- package/dist/chat/use-athena-run-live.d.ts +7 -0
- package/dist/chat/use-athena-run-live.js.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs +16 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.js +16 -1
- package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
- package/dist/composer/composer-handoff.cjs +121 -0
- package/dist/composer/composer-handoff.cjs.map +1 -0
- package/dist/composer/composer-handoff.d.ts +89 -0
- package/dist/composer/composer-handoff.js +121 -0
- package/dist/composer/composer-handoff.js.map +1 -0
- package/dist/composer/index.d.ts +2 -0
- package/dist/composer/useAppendToComposer.cjs +19 -7
- package/dist/composer/useAppendToComposer.cjs.map +1 -1
- package/dist/composer/useAppendToComposer.d.ts +22 -4
- package/dist/composer/useAppendToComposer.js +20 -8
- package/dist/composer/useAppendToComposer.js.map +1 -1
- package/dist/composer/useSendMessage.cjs +26 -8
- package/dist/composer/useSendMessage.cjs.map +1 -1
- package/dist/composer/useSendMessage.d.ts +8 -0
- package/dist/composer/useSendMessage.js +27 -9
- package/dist/composer/useSendMessage.js.map +1 -1
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/diagnostics/errors.cjs +2 -0
- package/dist/diagnostics/errors.cjs.map +1 -1
- package/dist/diagnostics/errors.d.ts +2 -0
- package/dist/diagnostics/errors.js +2 -0
- package/dist/diagnostics/errors.js.map +1 -1
- package/dist/dictation/DictationStrip.cjs +60 -0
- package/dist/dictation/DictationStrip.cjs.map +1 -0
- package/dist/dictation/DictationStrip.d.ts +25 -0
- package/dist/dictation/DictationStrip.js +60 -0
- package/dist/dictation/DictationStrip.js.map +1 -0
- package/dist/dictation/audio-level-history.cjs +68 -0
- package/dist/dictation/audio-level-history.cjs.map +1 -0
- package/dist/dictation/audio-level-history.d.ts +27 -0
- package/dist/dictation/audio-level-history.js +68 -0
- package/dist/dictation/audio-level-history.js.map +1 -0
- package/dist/dictation/dictation-controls.cjs +60 -0
- package/dist/dictation/dictation-controls.cjs.map +1 -0
- package/dist/dictation/dictation-controls.d.ts +15 -0
- package/dist/dictation/dictation-controls.js +60 -0
- package/dist/dictation/dictation-controls.js.map +1 -0
- package/dist/dictation/dictation-engine.cjs +128 -0
- package/dist/dictation/dictation-engine.cjs.map +1 -0
- package/dist/dictation/dictation-engine.d.ts +87 -0
- package/dist/dictation/dictation-engine.js +128 -0
- package/dist/dictation/dictation-engine.js.map +1 -0
- package/dist/dictation/dictation-state.cjs +68 -0
- package/dist/dictation/dictation-state.cjs.map +1 -0
- package/dist/dictation/dictation-state.d.ts +58 -0
- package/dist/dictation/dictation-state.js +68 -0
- package/dist/dictation/dictation-state.js.map +1 -0
- package/dist/dictation/use-dictation.cjs +204 -0
- package/dist/dictation/use-dictation.cjs.map +1 -0
- package/dist/dictation/use-dictation.d.ts +47 -0
- package/dist/dictation/use-dictation.js +204 -0
- package/dist/dictation/use-dictation.js.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaProvider.cjs +42 -3
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +1 -1
- package/dist/provider/AthenaProvider.js +43 -4
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sources":["../../src/diagnostics/errors.ts"],"sourcesContent":["/**\n * Typed SDK errors. Every failure the SDK surfaces to a consumer carries a\n * stable `code` (matchable in code), a human `message`, and a `hint` — the\n * one-line \"what to do about it\" that turns a silent failure into a debuggable\n * one. Backend rejections keep their structured `{ status, detail }` payload.\n */\n\nexport const ATHENA_SDK_ERROR_CODES = {\n /** No token and no API key resolved before a request had to be sent. */\n auth_missing: 'auth_missing',\n /** The server refused the credential (401 / statewire `unauthorized` fin). */\n auth_rejected: 'auth_rejected',\n /** `/_athena/auth` or the iframe bridge did not answer within the timeout. */\n auth_bridge_timeout: 'auth_bridge_timeout',\n /** `/_athena/auth` answered with a non-404 error. */\n auth_bridge_failed: 'auth_bridge_failed',\n /** The statewire sync URL could not be derived from the configuration. */\n config_invalid: 'config_invalid',\n /** The thread list request failed. */\n threads_list_failed: 'threads_list_failed',\n /** Loading a thread's history failed. */\n thread_load_failed: 'thread_load_failed',\n /**\n * Pinning / unpinning a thread failed. `status` 404 / 405 means the backend\n * predates `POST /api/conversations/threads/favorite` (see\n * `isThreadFavoriteUnavailableError`); the pin then stays read-only.\n */\n thread_favorite_failed: 'thread_favorite_failed',\n /**\n * Renaming a thread failed. `status` 404 / 405 means the backend predates\n * `POST /api/conversations/threads/rename` (see\n * `isThreadRenameUnavailableError`); the list then offers no rename.\n */\n thread_rename_failed: 'thread_rename_failed',\n /**\n * Archiving a thread failed. `status` 404 / 405 means the backend has no\n * archive route (see `isThreadArchiveUnavailableError`); the list then\n * offers no archive action.\n */\n thread_archive_failed: 'thread_archive_failed',\n /** The statewire host rejected a command (structured `{status, detail}`). */\n statewire_rejected: 'statewire_rejected',\n /** The statewire connection is gone and will not reconnect on its own. */\n statewire_gone: 'statewire_gone',\n /** The selected collab agent / channel was refused by the backend. */\n collab_agent_rejected: 'collab_agent_rejected',\n /** A React render error crashed the chat surface (caught by the SDK's error boundary). */\n chat_render_crash: 'chat_render_crash',\n /** A pasted or dropped file could not be uploaded as a composer attachment. */\n attachment_failed: 'attachment_failed',\n /** Mention suggestions for a scope failed after every retry. */\n mentions_fetch_failed: 'mentions_fetch_failed',\n /** The repository code search request (`/api/search/repositories`) failed. */\n repo_search_failed: 'repo_search_failed',\n /** Anything else. */\n unknown: 'unknown',\n} as const;\n\nexport type AthenaSdkErrorCode =\n (typeof ATHENA_SDK_ERROR_CODES)[keyof typeof ATHENA_SDK_ERROR_CODES];\n\nexport interface AthenaSdkErrorOptions {\n code: AthenaSdkErrorCode;\n message: string;\n /** One line telling the integrator what to check or change. */\n hint?: string;\n /** HTTP-ish status when the failure came from a server. */\n status?: number;\n /** Structured server detail (e.g. `{ code, message }` from agora). */\n detail?: unknown;\n cause?: unknown;\n /** Extra structured context safe to log (never credentials). */\n context?: Record<string, unknown>;\n}\n\nexport class AthenaSdkError extends Error {\n readonly code: AthenaSdkErrorCode;\n readonly hint: string | undefined;\n readonly status: number | undefined;\n readonly detail: unknown;\n readonly context: Record<string, unknown>;\n\n constructor(options: AthenaSdkErrorOptions) {\n super(options.message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = 'AthenaSdkError';\n this.code = options.code;\n this.hint = options.hint;\n this.status = options.status;\n this.detail = options.detail;\n this.context = options.context ?? {};\n }\n\n /** A log/console-friendly one-liner: `[code] message — hint`. */\n describe(): string {\n return `[${this.code}] ${this.message}${this.hint ? ` — ${this.hint}` : ''}`;\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n hint: this.hint,\n status: this.status,\n detail: this.detail,\n context: this.context,\n };\n }\n}\n\nexport function isAthenaSdkError(value: unknown): value is AthenaSdkError {\n return value instanceof AthenaSdkError;\n}\n\n/** Wrap any thrown value as an `AthenaSdkError` (idempotent for SDK errors). */\nexport function toAthenaSdkError(\n value: unknown,\n fallback: Omit<AthenaSdkErrorOptions, 'cause'>,\n): AthenaSdkError {\n if (isAthenaSdkError(value)) return value;\n const message =\n value instanceof Error && value.message ? value.message : fallback.message;\n return new AthenaSdkError({ ...fallback, message, cause: value });\n}\n\n/** Structured `{ code, message }` detail from an agora rejection, if present. */\nexport function readRejectionDetail(\n detail: unknown,\n): { code: string | null; message: string | null } {\n if (typeof detail === 'string') return { code: null, message: detail || null };\n if (!detail || typeof detail !== 'object') return { code: null, message: null };\n const record = detail as { code?: unknown; message?: unknown };\n return {\n code: typeof record.code === 'string' ? record.code : null,\n message: typeof record.message === 'string' ? record.message : null,\n };\n}\n"],"names":[],"mappings":";;;AAOO,MAAM,yBAAyB;AAAA;AAAA,EAEpC,cAAc;AAAA;AAAA,EAEd,eAAe;AAAA;AAAA,EAEf,qBAAqB;AAAA;AAAA,EAErB,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAAA;AAAA,EAEhB,qBAAqB;AAAA;AAAA,EAErB,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtB,uBAAuB;AAAA;AAAA,EAEvB,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAAA;AAAA,EAEhB,uBAAuB;AAAA;AAAA,EAEvB,mBAAmB;AAAA;AAAA,EAEnB,mBAAmB;AAAA;AAAA,EAEnB,uBAAuB;AAAA;AAAA,EAEvB,oBAAoB;AAAA;AAAA,EAEpB,SAAS;AACX;AAmBO,MAAM,uBAAuB,MAAM;AAAA,EAOxC,YAAY,SAAgC;AAC1C,UAAM,QAAQ,SAAS,QAAQ,UAAU,SAAY,SAAY,EAAE,OAAO,QAAQ,MAAA,CAAO;AAPlF;AACA;AACA;AACA;AACA;AAIP,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,WAAW,CAAA;AAAA,EACpC;AAAA;AAAA,EAGA,WAAmB;AACjB,WAAO,IAAI,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG,KAAK,OAAO,MAAM,KAAK,IAAI,KAAK,EAAE;AAAA,EAC5E;AAAA,EAEA,SAAkC;AAChC,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,IAAA;AAAA,EAElB;AACF;AAEO,SAAS,iBAAiB,OAAyC;AACxE,SAAO,iBAAiB;AAC1B;AAGO,SAAS,iBACd,OACA,UACgB;AAChB,MAAI,iBAAiB,KAAK,EAAG,QAAO;AACpC,QAAM,UACJ,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU,SAAS;AACrE,SAAO,IAAI,eAAe,EAAE,GAAG,UAAU,SAAS,OAAO,OAAO;AAClE;AAGO,SAAS,oBACd,QACiD;AACjD,MAAI,OAAO,WAAW,SAAU,QAAO,EAAE,MAAM,MAAM,SAAS,UAAU,KAAA;AACxE,MAAI,CAAC,UAAU,OAAO,WAAW,iBAAiB,EAAE,MAAM,MAAM,SAAS,KAAA;AACzE,QAAM,SAAS;AACf,SAAO;AAAA,IACL,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,IACtD,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,EAAA;AAEnE;"}
|
|
1
|
+
{"version":3,"file":"errors.js","sources":["../../src/diagnostics/errors.ts"],"sourcesContent":["/**\n * Typed SDK errors. Every failure the SDK surfaces to a consumer carries a\n * stable `code` (matchable in code), a human `message`, and a `hint` — the\n * one-line \"what to do about it\" that turns a silent failure into a debuggable\n * one. Backend rejections keep their structured `{ status, detail }` payload.\n */\n\nexport const ATHENA_SDK_ERROR_CODES = {\n /** No token and no API key resolved before a request had to be sent. */\n auth_missing: 'auth_missing',\n /** The server refused the credential (401 / statewire `unauthorized` fin). */\n auth_rejected: 'auth_rejected',\n /** `/_athena/auth` or the iframe bridge did not answer within the timeout. */\n auth_bridge_timeout: 'auth_bridge_timeout',\n /** `/_athena/auth` answered with a non-404 error. */\n auth_bridge_failed: 'auth_bridge_failed',\n /** The statewire sync URL could not be derived from the configuration. */\n config_invalid: 'config_invalid',\n /** The thread list request failed. */\n threads_list_failed: 'threads_list_failed',\n /** Loading a thread's history failed. */\n thread_load_failed: 'thread_load_failed',\n /**\n * Pinning / unpinning a thread failed. `status` 404 / 405 means the backend\n * predates `POST /api/conversations/threads/favorite` (see\n * `isThreadFavoriteUnavailableError`); the pin then stays read-only.\n */\n thread_favorite_failed: 'thread_favorite_failed',\n /**\n * Renaming a thread failed. `status` 404 / 405 means the backend predates\n * `POST /api/conversations/threads/rename` (see\n * `isThreadRenameUnavailableError`); the list then offers no rename.\n */\n thread_rename_failed: 'thread_rename_failed',\n /**\n * Archiving a thread failed. `status` 404 / 405 means the backend has no\n * archive route (see `isThreadArchiveUnavailableError`); the list then\n * offers no archive action.\n */\n thread_archive_failed: 'thread_archive_failed',\n /** The statewire host rejected a command (structured `{status, detail}`). */\n statewire_rejected: 'statewire_rejected',\n /** The statewire connection is gone and will not reconnect on its own. */\n statewire_gone: 'statewire_gone',\n /** The selected collab agent / channel was refused by the backend. */\n collab_agent_rejected: 'collab_agent_rejected',\n /** A React render error crashed the chat surface (caught by the SDK's error boundary). */\n chat_render_crash: 'chat_render_crash',\n /** A pasted or dropped file could not be uploaded as a composer attachment. */\n attachment_failed: 'attachment_failed',\n /** Mention suggestions for a scope failed after every retry. */\n mentions_fetch_failed: 'mentions_fetch_failed',\n /** A composer write queued before the chat was ready can no longer land (thread switched, identity changed, provider unmounted). */\n composer_write_dropped: 'composer_write_dropped',\n /** The repository code search request (`/api/search/repositories`) failed. */\n repo_search_failed: 'repo_search_failed',\n /** Anything else. */\n unknown: 'unknown',\n} as const;\n\nexport type AthenaSdkErrorCode =\n (typeof ATHENA_SDK_ERROR_CODES)[keyof typeof ATHENA_SDK_ERROR_CODES];\n\nexport interface AthenaSdkErrorOptions {\n code: AthenaSdkErrorCode;\n message: string;\n /** One line telling the integrator what to check or change. */\n hint?: string;\n /** HTTP-ish status when the failure came from a server. */\n status?: number;\n /** Structured server detail (e.g. `{ code, message }` from agora). */\n detail?: unknown;\n cause?: unknown;\n /** Extra structured context safe to log (never credentials). */\n context?: Record<string, unknown>;\n}\n\nexport class AthenaSdkError extends Error {\n readonly code: AthenaSdkErrorCode;\n readonly hint: string | undefined;\n readonly status: number | undefined;\n readonly detail: unknown;\n readonly context: Record<string, unknown>;\n\n constructor(options: AthenaSdkErrorOptions) {\n super(options.message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = 'AthenaSdkError';\n this.code = options.code;\n this.hint = options.hint;\n this.status = options.status;\n this.detail = options.detail;\n this.context = options.context ?? {};\n }\n\n /** A log/console-friendly one-liner: `[code] message — hint`. */\n describe(): string {\n return `[${this.code}] ${this.message}${this.hint ? ` — ${this.hint}` : ''}`;\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n hint: this.hint,\n status: this.status,\n detail: this.detail,\n context: this.context,\n };\n }\n}\n\nexport function isAthenaSdkError(value: unknown): value is AthenaSdkError {\n return value instanceof AthenaSdkError;\n}\n\n/** Wrap any thrown value as an `AthenaSdkError` (idempotent for SDK errors). */\nexport function toAthenaSdkError(\n value: unknown,\n fallback: Omit<AthenaSdkErrorOptions, 'cause'>,\n): AthenaSdkError {\n if (isAthenaSdkError(value)) return value;\n const message =\n value instanceof Error && value.message ? value.message : fallback.message;\n return new AthenaSdkError({ ...fallback, message, cause: value });\n}\n\n/** Structured `{ code, message }` detail from an agora rejection, if present. */\nexport function readRejectionDetail(\n detail: unknown,\n): { code: string | null; message: string | null } {\n if (typeof detail === 'string') return { code: null, message: detail || null };\n if (!detail || typeof detail !== 'object') return { code: null, message: null };\n const record = detail as { code?: unknown; message?: unknown };\n return {\n code: typeof record.code === 'string' ? record.code : null,\n message: typeof record.message === 'string' ? record.message : null,\n };\n}\n"],"names":[],"mappings":";;;AAOO,MAAM,yBAAyB;AAAA;AAAA,EAEpC,cAAc;AAAA;AAAA,EAEd,eAAe;AAAA;AAAA,EAEf,qBAAqB;AAAA;AAAA,EAErB,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAAA;AAAA,EAEhB,qBAAqB;AAAA;AAAA,EAErB,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtB,uBAAuB;AAAA;AAAA,EAEvB,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAAA;AAAA,EAEhB,uBAAuB;AAAA;AAAA,EAEvB,mBAAmB;AAAA;AAAA,EAEnB,mBAAmB;AAAA;AAAA,EAEnB,uBAAuB;AAAA;AAAA,EAEvB,wBAAwB;AAAA;AAAA,EAExB,oBAAoB;AAAA;AAAA,EAEpB,SAAS;AACX;AAmBO,MAAM,uBAAuB,MAAM;AAAA,EAOxC,YAAY,SAAgC;AAC1C,UAAM,QAAQ,SAAS,QAAQ,UAAU,SAAY,SAAY,EAAE,OAAO,QAAQ,MAAA,CAAO;AAPlF;AACA;AACA;AACA;AACA;AAIP,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,WAAW,CAAA;AAAA,EACpC;AAAA;AAAA,EAGA,WAAmB;AACjB,WAAO,IAAI,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG,KAAK,OAAO,MAAM,KAAK,IAAI,KAAK,EAAE;AAAA,EAC5E;AAAA,EAEA,SAAkC;AAChC,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,IAAA;AAAA,EAElB;AACF;AAEO,SAAS,iBAAiB,OAAyC;AACxE,SAAO,iBAAiB;AAC1B;AAGO,SAAS,iBACd,OACA,UACgB;AAChB,MAAI,iBAAiB,KAAK,EAAG,QAAO;AACpC,QAAM,UACJ,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU,SAAS;AACrE,SAAO,IAAI,eAAe,EAAE,GAAG,UAAU,SAAS,OAAO,OAAO;AAClE;AAGO,SAAS,oBACd,QACiD;AACjD,MAAI,OAAO,WAAW,SAAU,QAAO,EAAE,MAAM,MAAM,SAAS,UAAU,KAAA;AACxE,MAAI,CAAC,UAAU,OAAO,WAAW,iBAAiB,EAAE,MAAM,MAAM,SAAS,KAAA;AACzE,QAAM,SAAS;AACf,SAAO;AAAA,IACL,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,IACtD,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,EAAA;AAEnE;"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const audioLevelHistory = require("./audio-level-history.cjs");
|
|
5
|
+
const cnfast = require("cnfast");
|
|
6
|
+
const MIN_BAR_HEIGHT_PX = 3;
|
|
7
|
+
const MAX_BAR_HEIGHT_PX = 24;
|
|
8
|
+
function barHeight(level) {
|
|
9
|
+
return Math.round(MIN_BAR_HEIGHT_PX + level * (MAX_BAR_HEIGHT_PX - MIN_BAR_HEIGHT_PX));
|
|
10
|
+
}
|
|
11
|
+
const DictationWaveform = ({
|
|
12
|
+
history,
|
|
13
|
+
className
|
|
14
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
className: cnfast.cn("aui-dictation-waveform flex h-6 items-center gap-[3px]", className),
|
|
18
|
+
"aria-hidden": "true",
|
|
19
|
+
children: history.map((level, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
20
|
+
"span",
|
|
21
|
+
{
|
|
22
|
+
className: cnfast.cn(
|
|
23
|
+
"aui-dictation-bar w-[3px] shrink-0 rounded-full transition-[height] duration-75",
|
|
24
|
+
level === null ? "bg-muted-foreground/30" : "bg-foreground/80"
|
|
25
|
+
),
|
|
26
|
+
style: { height: level === null ? MIN_BAR_HEIGHT_PX : barHeight(level) }
|
|
27
|
+
},
|
|
28
|
+
index
|
|
29
|
+
))
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
const DictationStrip = ({
|
|
33
|
+
status,
|
|
34
|
+
transcript,
|
|
35
|
+
stream,
|
|
36
|
+
bars = 48,
|
|
37
|
+
className
|
|
38
|
+
}) => {
|
|
39
|
+
const history = audioLevelHistory.useAudioLevelHistory(stream, { size: bars });
|
|
40
|
+
const caption = [transcript.final, transcript.interim].filter(Boolean).join(" ");
|
|
41
|
+
const statusLabel = status === "starting" ? "Starting microphone…" : status === "stopping" ? "Finishing…" : "Listening…";
|
|
42
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
className: cnfast.cn("aui-dictation-strip flex min-h-10 flex-col gap-1 px-1.5 py-2", className),
|
|
46
|
+
role: "status",
|
|
47
|
+
"aria-live": "polite",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
50
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "aui-dictation-status text-muted-foreground shrink-0 text-xs", children: statusLabel }),
|
|
51
|
+
/* @__PURE__ */ jsxRuntime.jsx(DictationWaveform, { history, className: "min-w-0 flex-1 justify-end overflow-hidden" })
|
|
52
|
+
] }),
|
|
53
|
+
caption && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "aui-dictation-caption text-muted-foreground line-clamp-2 text-sm", children: caption })
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
exports.DictationStrip = DictationStrip;
|
|
59
|
+
exports.DictationWaveform = DictationWaveform;
|
|
60
|
+
//# sourceMappingURL=DictationStrip.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DictationStrip.cjs","sources":["../../src/dictation/DictationStrip.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { cn } from '../lib/utils';\nimport { type AudioLevelHistory, useAudioLevelHistory } from './audio-level-history';\nimport type { DictationStatus, DictationTranscript } from './dictation-state';\n\nexport interface DictationStripProps {\n status: DictationStatus;\n transcript: DictationTranscript;\n /** Microphone stream to visualize; null draws the resting baseline. */\n stream: MediaStream | null;\n /** Number of bars across the strip. Defaults to 48. */\n bars?: number;\n className?: string;\n}\n\nconst MIN_BAR_HEIGHT_PX = 3;\nconst MAX_BAR_HEIGHT_PX = 24;\n\nfunction barHeight(level: number): number {\n return Math.round(MIN_BAR_HEIGHT_PX + level * (MAX_BAR_HEIGHT_PX - MIN_BAR_HEIGHT_PX));\n}\n\n/**\n * The bars. A rolling level history renders newest-right: unfilled slots are\n * the dotted baseline, filled slots grow with the microphone level.\n */\nexport const DictationWaveform: FC<{ history: AudioLevelHistory; className?: string }> = ({\n history,\n className,\n}) => (\n <div\n className={cn('aui-dictation-waveform flex h-6 items-center gap-[3px]', className)}\n aria-hidden=\"true\"\n >\n {history.map((level, index) => (\n <span\n // Slots are positional; the history is a fixed-size window.\n key={index}\n className={cn(\n 'aui-dictation-bar w-[3px] shrink-0 rounded-full transition-[height] duration-75',\n level === null ? 'bg-muted-foreground/30' : 'bg-foreground/80',\n )}\n style={{ height: level === null ? MIN_BAR_HEIGHT_PX : barHeight(level) }}\n />\n ))}\n </div>\n);\n\n/**\n * The composer's recording mode: a live waveform in place of the text editor\n * and the interim transcript as a caption under it.\n */\nexport const DictationStrip: FC<DictationStripProps> = ({\n status,\n transcript,\n stream,\n bars = 48,\n className,\n}) => {\n const history = useAudioLevelHistory(stream, { size: bars });\n const caption = [transcript.final, transcript.interim].filter(Boolean).join(' ');\n const statusLabel =\n status === 'starting' ? 'Starting microphone…' : status === 'stopping' ? 'Finishing…' : 'Listening…';\n\n return (\n <div\n className={cn('aui-dictation-strip flex min-h-10 flex-col gap-1 px-1.5 py-2', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div className=\"flex items-center gap-3\">\n <span className=\"aui-dictation-status text-muted-foreground shrink-0 text-xs\">{statusLabel}</span>\n <DictationWaveform history={history} className=\"min-w-0 flex-1 justify-end overflow-hidden\" />\n </div>\n {caption && (\n <p className=\"aui-dictation-caption text-muted-foreground line-clamp-2 text-sm\">{caption}</p>\n )}\n </div>\n );\n};\n"],"names":["jsx","cn","useAudioLevelHistory","jsxs"],"mappings":";;;;;AAeA,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAE1B,SAAS,UAAU,OAAuB;AACxC,SAAO,KAAK,MAAM,oBAAoB,SAAS,oBAAoB,kBAAkB;AACvF;AAMO,MAAM,oBAA4E,CAAC;AAAA,EACxF;AAAA,EACA;AACF,MACEA,2BAAAA;AAAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAWC,OAAAA,GAAG,0DAA0D,SAAS;AAAA,IACjF,eAAY;AAAA,IAEX,UAAA,QAAQ,IAAI,CAAC,OAAO,UACnBD,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QAGC,WAAWC,OAAAA;AAAAA,UACT;AAAA,UACA,UAAU,OAAO,2BAA2B;AAAA,QAAA;AAAA,QAE9C,OAAO,EAAE,QAAQ,UAAU,OAAO,oBAAoB,UAAU,KAAK,EAAA;AAAA,MAAE;AAAA,MALlE;AAAA,IAAA,CAOR;AAAA,EAAA;AACH;AAOK,MAAM,iBAA0C,CAAC;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,MAAM;AACJ,QAAM,UAAUC,kBAAAA,qBAAqB,QAAQ,EAAE,MAAM,MAAM;AAC3D,QAAM,UAAU,CAAC,WAAW,OAAO,WAAW,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAC/E,QAAM,cACJ,WAAW,aAAa,yBAAyB,WAAW,aAAa,eAAe;AAE1F,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWF,OAAAA,GAAG,gEAAgE,SAAS;AAAA,MACvF,MAAK;AAAA,MACL,aAAU;AAAA,MAEV,UAAA;AAAA,QAAAE,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,UAAAH,2BAAAA,IAAC,QAAA,EAAK,WAAU,+DAA+D,UAAA,aAAY;AAAA,UAC3FA,2BAAAA,IAAC,mBAAA,EAAkB,SAAkB,WAAU,6CAAA,CAA6C;AAAA,QAAA,GAC9F;AAAA,QACC,WACCA,2BAAAA,IAAC,KAAA,EAAE,WAAU,oEAAoE,UAAA,QAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIjG;;;"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { type AudioLevelHistory } from './audio-level-history';
|
|
3
|
+
import type { DictationStatus, DictationTranscript } from './dictation-state';
|
|
4
|
+
export interface DictationStripProps {
|
|
5
|
+
status: DictationStatus;
|
|
6
|
+
transcript: DictationTranscript;
|
|
7
|
+
/** Microphone stream to visualize; null draws the resting baseline. */
|
|
8
|
+
stream: MediaStream | null;
|
|
9
|
+
/** Number of bars across the strip. Defaults to 48. */
|
|
10
|
+
bars?: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The bars. A rolling level history renders newest-right: unfilled slots are
|
|
15
|
+
* the dotted baseline, filled slots grow with the microphone level.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DictationWaveform: FC<{
|
|
18
|
+
history: AudioLevelHistory;
|
|
19
|
+
className?: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* The composer's recording mode: a live waveform in place of the text editor
|
|
23
|
+
* and the interim transcript as a caption under it.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DictationStrip: FC<DictationStripProps>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useAudioLevelHistory } from "./audio-level-history.js";
|
|
3
|
+
import { cn } from "cnfast";
|
|
4
|
+
const MIN_BAR_HEIGHT_PX = 3;
|
|
5
|
+
const MAX_BAR_HEIGHT_PX = 24;
|
|
6
|
+
function barHeight(level) {
|
|
7
|
+
return Math.round(MIN_BAR_HEIGHT_PX + level * (MAX_BAR_HEIGHT_PX - MIN_BAR_HEIGHT_PX));
|
|
8
|
+
}
|
|
9
|
+
const DictationWaveform = ({
|
|
10
|
+
history,
|
|
11
|
+
className
|
|
12
|
+
}) => /* @__PURE__ */ jsx(
|
|
13
|
+
"div",
|
|
14
|
+
{
|
|
15
|
+
className: cn("aui-dictation-waveform flex h-6 items-center gap-[3px]", className),
|
|
16
|
+
"aria-hidden": "true",
|
|
17
|
+
children: history.map((level, index) => /* @__PURE__ */ jsx(
|
|
18
|
+
"span",
|
|
19
|
+
{
|
|
20
|
+
className: cn(
|
|
21
|
+
"aui-dictation-bar w-[3px] shrink-0 rounded-full transition-[height] duration-75",
|
|
22
|
+
level === null ? "bg-muted-foreground/30" : "bg-foreground/80"
|
|
23
|
+
),
|
|
24
|
+
style: { height: level === null ? MIN_BAR_HEIGHT_PX : barHeight(level) }
|
|
25
|
+
},
|
|
26
|
+
index
|
|
27
|
+
))
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
const DictationStrip = ({
|
|
31
|
+
status,
|
|
32
|
+
transcript,
|
|
33
|
+
stream,
|
|
34
|
+
bars = 48,
|
|
35
|
+
className
|
|
36
|
+
}) => {
|
|
37
|
+
const history = useAudioLevelHistory(stream, { size: bars });
|
|
38
|
+
const caption = [transcript.final, transcript.interim].filter(Boolean).join(" ");
|
|
39
|
+
const statusLabel = status === "starting" ? "Starting microphone…" : status === "stopping" ? "Finishing…" : "Listening…";
|
|
40
|
+
return /* @__PURE__ */ jsxs(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: cn("aui-dictation-strip flex min-h-10 flex-col gap-1 px-1.5 py-2", className),
|
|
44
|
+
role: "status",
|
|
45
|
+
"aria-live": "polite",
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
48
|
+
/* @__PURE__ */ jsx("span", { className: "aui-dictation-status text-muted-foreground shrink-0 text-xs", children: statusLabel }),
|
|
49
|
+
/* @__PURE__ */ jsx(DictationWaveform, { history, className: "min-w-0 flex-1 justify-end overflow-hidden" })
|
|
50
|
+
] }),
|
|
51
|
+
caption && /* @__PURE__ */ jsx("p", { className: "aui-dictation-caption text-muted-foreground line-clamp-2 text-sm", children: caption })
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
export {
|
|
57
|
+
DictationStrip,
|
|
58
|
+
DictationWaveform
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=DictationStrip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DictationStrip.js","sources":["../../src/dictation/DictationStrip.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { cn } from '../lib/utils';\nimport { type AudioLevelHistory, useAudioLevelHistory } from './audio-level-history';\nimport type { DictationStatus, DictationTranscript } from './dictation-state';\n\nexport interface DictationStripProps {\n status: DictationStatus;\n transcript: DictationTranscript;\n /** Microphone stream to visualize; null draws the resting baseline. */\n stream: MediaStream | null;\n /** Number of bars across the strip. Defaults to 48. */\n bars?: number;\n className?: string;\n}\n\nconst MIN_BAR_HEIGHT_PX = 3;\nconst MAX_BAR_HEIGHT_PX = 24;\n\nfunction barHeight(level: number): number {\n return Math.round(MIN_BAR_HEIGHT_PX + level * (MAX_BAR_HEIGHT_PX - MIN_BAR_HEIGHT_PX));\n}\n\n/**\n * The bars. A rolling level history renders newest-right: unfilled slots are\n * the dotted baseline, filled slots grow with the microphone level.\n */\nexport const DictationWaveform: FC<{ history: AudioLevelHistory; className?: string }> = ({\n history,\n className,\n}) => (\n <div\n className={cn('aui-dictation-waveform flex h-6 items-center gap-[3px]', className)}\n aria-hidden=\"true\"\n >\n {history.map((level, index) => (\n <span\n // Slots are positional; the history is a fixed-size window.\n key={index}\n className={cn(\n 'aui-dictation-bar w-[3px] shrink-0 rounded-full transition-[height] duration-75',\n level === null ? 'bg-muted-foreground/30' : 'bg-foreground/80',\n )}\n style={{ height: level === null ? MIN_BAR_HEIGHT_PX : barHeight(level) }}\n />\n ))}\n </div>\n);\n\n/**\n * The composer's recording mode: a live waveform in place of the text editor\n * and the interim transcript as a caption under it.\n */\nexport const DictationStrip: FC<DictationStripProps> = ({\n status,\n transcript,\n stream,\n bars = 48,\n className,\n}) => {\n const history = useAudioLevelHistory(stream, { size: bars });\n const caption = [transcript.final, transcript.interim].filter(Boolean).join(' ');\n const statusLabel =\n status === 'starting' ? 'Starting microphone…' : status === 'stopping' ? 'Finishing…' : 'Listening…';\n\n return (\n <div\n className={cn('aui-dictation-strip flex min-h-10 flex-col gap-1 px-1.5 py-2', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div className=\"flex items-center gap-3\">\n <span className=\"aui-dictation-status text-muted-foreground shrink-0 text-xs\">{statusLabel}</span>\n <DictationWaveform history={history} className=\"min-w-0 flex-1 justify-end overflow-hidden\" />\n </div>\n {caption && (\n <p className=\"aui-dictation-caption text-muted-foreground line-clamp-2 text-sm\">{caption}</p>\n )}\n </div>\n );\n};\n"],"names":[],"mappings":";;;AAeA,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAE1B,SAAS,UAAU,OAAuB;AACxC,SAAO,KAAK,MAAM,oBAAoB,SAAS,oBAAoB,kBAAkB;AACvF;AAMO,MAAM,oBAA4E,CAAC;AAAA,EACxF;AAAA,EACA;AACF,MACE;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAW,GAAG,0DAA0D,SAAS;AAAA,IACjF,eAAY;AAAA,IAEX,UAAA,QAAQ,IAAI,CAAC,OAAO,UACnB;AAAA,MAAC;AAAA,MAAA;AAAA,QAGC,WAAW;AAAA,UACT;AAAA,UACA,UAAU,OAAO,2BAA2B;AAAA,QAAA;AAAA,QAE9C,OAAO,EAAE,QAAQ,UAAU,OAAO,oBAAoB,UAAU,KAAK,EAAA;AAAA,MAAE;AAAA,MALlE;AAAA,IAAA,CAOR;AAAA,EAAA;AACH;AAOK,MAAM,iBAA0C,CAAC;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,MAAM;AACJ,QAAM,UAAU,qBAAqB,QAAQ,EAAE,MAAM,MAAM;AAC3D,QAAM,UAAU,CAAC,WAAW,OAAO,WAAW,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAC/E,QAAM,cACJ,WAAW,aAAa,yBAAyB,WAAW,aAAa,eAAe;AAE1F,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,GAAG,gEAAgE,SAAS;AAAA,MACvF,MAAK;AAAA,MACL,aAAU;AAAA,MAEV,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,+DAA+D,UAAA,aAAY;AAAA,UAC3F,oBAAC,mBAAA,EAAkB,SAAkB,WAAU,6CAAA,CAA6C;AAAA,QAAA,GAC9F;AAAA,QACC,WACC,oBAAC,KAAA,EAAE,WAAU,oEAAoE,UAAA,QAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIjG;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
function emptyAudioLevelHistory(size) {
|
|
5
|
+
return new Array(Math.max(0, size)).fill(null);
|
|
6
|
+
}
|
|
7
|
+
function pushAudioLevel(history, level) {
|
|
8
|
+
if (history.length === 0) return history;
|
|
9
|
+
const clamped = Math.min(1, Math.max(0, level));
|
|
10
|
+
return [...history.slice(1), clamped];
|
|
11
|
+
}
|
|
12
|
+
function displayLevelFromSamples(samples, gain = 4) {
|
|
13
|
+
if (samples.length === 0) return 0;
|
|
14
|
+
let sum = 0;
|
|
15
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
16
|
+
const sample = samples[i] ?? 0;
|
|
17
|
+
sum += sample * sample;
|
|
18
|
+
}
|
|
19
|
+
const rms = Math.sqrt(sum / samples.length);
|
|
20
|
+
return Math.min(1, rms * gain);
|
|
21
|
+
}
|
|
22
|
+
function useAudioLevelHistory(stream, { size, intervalMs = 50 }) {
|
|
23
|
+
const [history, setHistory] = react.useState(() => emptyAudioLevelHistory(size));
|
|
24
|
+
react.useEffect(() => {
|
|
25
|
+
setHistory(emptyAudioLevelHistory(size));
|
|
26
|
+
if (!stream || typeof window === "undefined") return;
|
|
27
|
+
const AudioContextCtor = window.AudioContext ?? window.webkitAudioContext;
|
|
28
|
+
if (!AudioContextCtor) return;
|
|
29
|
+
let audioContext;
|
|
30
|
+
try {
|
|
31
|
+
audioContext = new AudioContextCtor();
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const analyser = audioContext.createAnalyser();
|
|
36
|
+
analyser.fftSize = 1024;
|
|
37
|
+
analyser.smoothingTimeConstant = 0;
|
|
38
|
+
let source = null;
|
|
39
|
+
try {
|
|
40
|
+
source = audioContext.createMediaStreamSource(stream);
|
|
41
|
+
source.connect(analyser);
|
|
42
|
+
} catch {
|
|
43
|
+
void audioContext.close();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const samples = new Float32Array(analyser.fftSize);
|
|
47
|
+
const interval = window.setInterval(() => {
|
|
48
|
+
analyser.getFloatTimeDomainData(samples);
|
|
49
|
+
const level = displayLevelFromSamples(samples);
|
|
50
|
+
setHistory((current) => pushAudioLevel(current, level));
|
|
51
|
+
}, intervalMs);
|
|
52
|
+
return () => {
|
|
53
|
+
window.clearInterval(interval);
|
|
54
|
+
try {
|
|
55
|
+
source == null ? void 0 : source.disconnect();
|
|
56
|
+
analyser.disconnect();
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
void audioContext.close();
|
|
60
|
+
};
|
|
61
|
+
}, [stream, size, intervalMs]);
|
|
62
|
+
return history;
|
|
63
|
+
}
|
|
64
|
+
exports.displayLevelFromSamples = displayLevelFromSamples;
|
|
65
|
+
exports.emptyAudioLevelHistory = emptyAudioLevelHistory;
|
|
66
|
+
exports.pushAudioLevel = pushAudioLevel;
|
|
67
|
+
exports.useAudioLevelHistory = useAudioLevelHistory;
|
|
68
|
+
//# sourceMappingURL=audio-level-history.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-level-history.cjs","sources":["../../src/dictation/audio-level-history.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * A rolling window of microphone levels (0..1), newest last, for the\n * composer's waveform strip. `size` slots are always present: unfilled slots\n * are `null` so the strip can draw them as the resting dotted baseline.\n */\nexport type AudioLevelHistory = ReadonlyArray<number | null>;\n\nexport function emptyAudioLevelHistory(size: number): AudioLevelHistory {\n return new Array<number | null>(Math.max(0, size)).fill(null);\n}\n\n/** Append one level, dropping the oldest slot. Pure. */\nexport function pushAudioLevel(history: AudioLevelHistory, level: number): AudioLevelHistory {\n if (history.length === 0) return history;\n const clamped = Math.min(1, Math.max(0, level));\n return [...history.slice(1), clamped];\n}\n\n/**\n * RMS of a PCM sample window scaled to 0..1 for display. Speech peaks sit\n * around 0.1-0.3 RMS, so a gain lifts them into the visible range while the\n * noise floor (about 0.005) stays flat.\n */\nexport function displayLevelFromSamples(samples: ArrayLike<number>, gain = 4): number {\n if (samples.length === 0) return 0;\n let sum = 0;\n for (let i = 0; i < samples.length; i += 1) {\n const sample = samples[i] ?? 0;\n sum += sample * sample;\n }\n const rms = Math.sqrt(sum / samples.length);\n return Math.min(1, rms * gain);\n}\n\ninterface UseAudioLevelHistoryOptions {\n /** Number of bars in the strip. */\n size: number;\n /** Sample cadence in ms; 50ms gives ~20 bars per second. */\n intervalMs?: number;\n}\n\n/**\n * Sample a `MediaStream` through an `AnalyserNode` into a rolling level\n * history. Returns the resting baseline when `stream` is null.\n */\nexport function useAudioLevelHistory(\n stream: MediaStream | null,\n { size, intervalMs = 50 }: UseAudioLevelHistoryOptions,\n): AudioLevelHistory {\n const [history, setHistory] = useState<AudioLevelHistory>(() => emptyAudioLevelHistory(size));\n\n useEffect(() => {\n setHistory(emptyAudioLevelHistory(size));\n if (!stream || typeof window === 'undefined') return;\n const AudioContextCtor =\n window.AudioContext ??\n (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioContextCtor) return;\n\n let audioContext: AudioContext;\n try {\n audioContext = new AudioContextCtor();\n } catch {\n return;\n }\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = 1024;\n analyser.smoothingTimeConstant = 0;\n let source: MediaStreamAudioSourceNode | null = null;\n try {\n source = audioContext.createMediaStreamSource(stream);\n source.connect(analyser);\n } catch {\n void audioContext.close();\n return;\n }\n const samples = new Float32Array(analyser.fftSize);\n const interval = window.setInterval(() => {\n analyser.getFloatTimeDomainData(samples);\n const level = displayLevelFromSamples(samples);\n setHistory((current) => pushAudioLevel(current, level));\n }, intervalMs);\n\n return () => {\n window.clearInterval(interval);\n try {\n source?.disconnect();\n analyser.disconnect();\n } catch {\n // Already disconnected.\n }\n void audioContext.close();\n };\n }, [stream, size, intervalMs]);\n\n return history;\n}\n"],"names":["useState","useEffect"],"mappings":";;;AASO,SAAS,uBAAuB,MAAiC;AACtE,SAAO,IAAI,MAAqB,KAAK,IAAI,GAAG,IAAI,CAAC,EAAE,KAAK,IAAI;AAC9D;AAGO,SAAS,eAAe,SAA4B,OAAkC;AAC3F,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAC9C,SAAO,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,OAAO;AACtC;AAOO,SAAS,wBAAwB,SAA4B,OAAO,GAAW;AACpF,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GAAG;AAC1C,UAAM,SAAS,QAAQ,CAAC,KAAK;AAC7B,WAAO,SAAS;AAAA,EAClB;AACA,QAAM,MAAM,KAAK,KAAK,MAAM,QAAQ,MAAM;AAC1C,SAAO,KAAK,IAAI,GAAG,MAAM,IAAI;AAC/B;AAaO,SAAS,qBACd,QACA,EAAE,MAAM,aAAa,MACF;AACnB,QAAM,CAAC,SAAS,UAAU,IAAIA,MAAAA,SAA4B,MAAM,uBAAuB,IAAI,CAAC;AAE5FC,QAAAA,UAAU,MAAM;AACd,eAAW,uBAAuB,IAAI,CAAC;AACvC,QAAI,CAAC,UAAU,OAAO,WAAW,YAAa;AAC9C,UAAM,mBACJ,OAAO,gBACN,OAAwE;AAC3E,QAAI,CAAC,iBAAkB;AAEvB,QAAI;AACJ,QAAI;AACF,qBAAe,IAAI,iBAAA;AAAA,IACrB,QAAQ;AACN;AAAA,IACF;AACA,UAAM,WAAW,aAAa,eAAA;AAC9B,aAAS,UAAU;AACnB,aAAS,wBAAwB;AACjC,QAAI,SAA4C;AAChD,QAAI;AACF,eAAS,aAAa,wBAAwB,MAAM;AACpD,aAAO,QAAQ,QAAQ;AAAA,IACzB,QAAQ;AACN,WAAK,aAAa,MAAA;AAClB;AAAA,IACF;AACA,UAAM,UAAU,IAAI,aAAa,SAAS,OAAO;AACjD,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,eAAS,uBAAuB,OAAO;AACvC,YAAM,QAAQ,wBAAwB,OAAO;AAC7C,iBAAW,CAAC,YAAY,eAAe,SAAS,KAAK,CAAC;AAAA,IACxD,GAAG,UAAU;AAEb,WAAO,MAAM;AACX,aAAO,cAAc,QAAQ;AAC7B,UAAI;AACF,yCAAQ;AACR,iBAAS,WAAA;AAAA,MACX,QAAQ;AAAA,MAER;AACA,WAAK,aAAa,MAAA;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,UAAU,CAAC;AAE7B,SAAO;AACT;;;;;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A rolling window of microphone levels (0..1), newest last, for the
|
|
3
|
+
* composer's waveform strip. `size` slots are always present: unfilled slots
|
|
4
|
+
* are `null` so the strip can draw them as the resting dotted baseline.
|
|
5
|
+
*/
|
|
6
|
+
export type AudioLevelHistory = ReadonlyArray<number | null>;
|
|
7
|
+
export declare function emptyAudioLevelHistory(size: number): AudioLevelHistory;
|
|
8
|
+
/** Append one level, dropping the oldest slot. Pure. */
|
|
9
|
+
export declare function pushAudioLevel(history: AudioLevelHistory, level: number): AudioLevelHistory;
|
|
10
|
+
/**
|
|
11
|
+
* RMS of a PCM sample window scaled to 0..1 for display. Speech peaks sit
|
|
12
|
+
* around 0.1-0.3 RMS, so a gain lifts them into the visible range while the
|
|
13
|
+
* noise floor (about 0.005) stays flat.
|
|
14
|
+
*/
|
|
15
|
+
export declare function displayLevelFromSamples(samples: ArrayLike<number>, gain?: number): number;
|
|
16
|
+
interface UseAudioLevelHistoryOptions {
|
|
17
|
+
/** Number of bars in the strip. */
|
|
18
|
+
size: number;
|
|
19
|
+
/** Sample cadence in ms; 50ms gives ~20 bars per second. */
|
|
20
|
+
intervalMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Sample a `MediaStream` through an `AnalyserNode` into a rolling level
|
|
24
|
+
* history. Returns the resting baseline when `stream` is null.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useAudioLevelHistory(stream: MediaStream | null, { size, intervalMs }: UseAudioLevelHistoryOptions): AudioLevelHistory;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
function emptyAudioLevelHistory(size) {
|
|
3
|
+
return new Array(Math.max(0, size)).fill(null);
|
|
4
|
+
}
|
|
5
|
+
function pushAudioLevel(history, level) {
|
|
6
|
+
if (history.length === 0) return history;
|
|
7
|
+
const clamped = Math.min(1, Math.max(0, level));
|
|
8
|
+
return [...history.slice(1), clamped];
|
|
9
|
+
}
|
|
10
|
+
function displayLevelFromSamples(samples, gain = 4) {
|
|
11
|
+
if (samples.length === 0) return 0;
|
|
12
|
+
let sum = 0;
|
|
13
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
14
|
+
const sample = samples[i] ?? 0;
|
|
15
|
+
sum += sample * sample;
|
|
16
|
+
}
|
|
17
|
+
const rms = Math.sqrt(sum / samples.length);
|
|
18
|
+
return Math.min(1, rms * gain);
|
|
19
|
+
}
|
|
20
|
+
function useAudioLevelHistory(stream, { size, intervalMs = 50 }) {
|
|
21
|
+
const [history, setHistory] = useState(() => emptyAudioLevelHistory(size));
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setHistory(emptyAudioLevelHistory(size));
|
|
24
|
+
if (!stream || typeof window === "undefined") return;
|
|
25
|
+
const AudioContextCtor = window.AudioContext ?? window.webkitAudioContext;
|
|
26
|
+
if (!AudioContextCtor) return;
|
|
27
|
+
let audioContext;
|
|
28
|
+
try {
|
|
29
|
+
audioContext = new AudioContextCtor();
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const analyser = audioContext.createAnalyser();
|
|
34
|
+
analyser.fftSize = 1024;
|
|
35
|
+
analyser.smoothingTimeConstant = 0;
|
|
36
|
+
let source = null;
|
|
37
|
+
try {
|
|
38
|
+
source = audioContext.createMediaStreamSource(stream);
|
|
39
|
+
source.connect(analyser);
|
|
40
|
+
} catch {
|
|
41
|
+
void audioContext.close();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const samples = new Float32Array(analyser.fftSize);
|
|
45
|
+
const interval = window.setInterval(() => {
|
|
46
|
+
analyser.getFloatTimeDomainData(samples);
|
|
47
|
+
const level = displayLevelFromSamples(samples);
|
|
48
|
+
setHistory((current) => pushAudioLevel(current, level));
|
|
49
|
+
}, intervalMs);
|
|
50
|
+
return () => {
|
|
51
|
+
window.clearInterval(interval);
|
|
52
|
+
try {
|
|
53
|
+
source == null ? void 0 : source.disconnect();
|
|
54
|
+
analyser.disconnect();
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
void audioContext.close();
|
|
58
|
+
};
|
|
59
|
+
}, [stream, size, intervalMs]);
|
|
60
|
+
return history;
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
displayLevelFromSamples,
|
|
64
|
+
emptyAudioLevelHistory,
|
|
65
|
+
pushAudioLevel,
|
|
66
|
+
useAudioLevelHistory
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=audio-level-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-level-history.js","sources":["../../src/dictation/audio-level-history.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * A rolling window of microphone levels (0..1), newest last, for the\n * composer's waveform strip. `size` slots are always present: unfilled slots\n * are `null` so the strip can draw them as the resting dotted baseline.\n */\nexport type AudioLevelHistory = ReadonlyArray<number | null>;\n\nexport function emptyAudioLevelHistory(size: number): AudioLevelHistory {\n return new Array<number | null>(Math.max(0, size)).fill(null);\n}\n\n/** Append one level, dropping the oldest slot. Pure. */\nexport function pushAudioLevel(history: AudioLevelHistory, level: number): AudioLevelHistory {\n if (history.length === 0) return history;\n const clamped = Math.min(1, Math.max(0, level));\n return [...history.slice(1), clamped];\n}\n\n/**\n * RMS of a PCM sample window scaled to 0..1 for display. Speech peaks sit\n * around 0.1-0.3 RMS, so a gain lifts them into the visible range while the\n * noise floor (about 0.005) stays flat.\n */\nexport function displayLevelFromSamples(samples: ArrayLike<number>, gain = 4): number {\n if (samples.length === 0) return 0;\n let sum = 0;\n for (let i = 0; i < samples.length; i += 1) {\n const sample = samples[i] ?? 0;\n sum += sample * sample;\n }\n const rms = Math.sqrt(sum / samples.length);\n return Math.min(1, rms * gain);\n}\n\ninterface UseAudioLevelHistoryOptions {\n /** Number of bars in the strip. */\n size: number;\n /** Sample cadence in ms; 50ms gives ~20 bars per second. */\n intervalMs?: number;\n}\n\n/**\n * Sample a `MediaStream` through an `AnalyserNode` into a rolling level\n * history. Returns the resting baseline when `stream` is null.\n */\nexport function useAudioLevelHistory(\n stream: MediaStream | null,\n { size, intervalMs = 50 }: UseAudioLevelHistoryOptions,\n): AudioLevelHistory {\n const [history, setHistory] = useState<AudioLevelHistory>(() => emptyAudioLevelHistory(size));\n\n useEffect(() => {\n setHistory(emptyAudioLevelHistory(size));\n if (!stream || typeof window === 'undefined') return;\n const AudioContextCtor =\n window.AudioContext ??\n (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioContextCtor) return;\n\n let audioContext: AudioContext;\n try {\n audioContext = new AudioContextCtor();\n } catch {\n return;\n }\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = 1024;\n analyser.smoothingTimeConstant = 0;\n let source: MediaStreamAudioSourceNode | null = null;\n try {\n source = audioContext.createMediaStreamSource(stream);\n source.connect(analyser);\n } catch {\n void audioContext.close();\n return;\n }\n const samples = new Float32Array(analyser.fftSize);\n const interval = window.setInterval(() => {\n analyser.getFloatTimeDomainData(samples);\n const level = displayLevelFromSamples(samples);\n setHistory((current) => pushAudioLevel(current, level));\n }, intervalMs);\n\n return () => {\n window.clearInterval(interval);\n try {\n source?.disconnect();\n analyser.disconnect();\n } catch {\n // Already disconnected.\n }\n void audioContext.close();\n };\n }, [stream, size, intervalMs]);\n\n return history;\n}\n"],"names":[],"mappings":";AASO,SAAS,uBAAuB,MAAiC;AACtE,SAAO,IAAI,MAAqB,KAAK,IAAI,GAAG,IAAI,CAAC,EAAE,KAAK,IAAI;AAC9D;AAGO,SAAS,eAAe,SAA4B,OAAkC;AAC3F,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAC9C,SAAO,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,OAAO;AACtC;AAOO,SAAS,wBAAwB,SAA4B,OAAO,GAAW;AACpF,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GAAG;AAC1C,UAAM,SAAS,QAAQ,CAAC,KAAK;AAC7B,WAAO,SAAS;AAAA,EAClB;AACA,QAAM,MAAM,KAAK,KAAK,MAAM,QAAQ,MAAM;AAC1C,SAAO,KAAK,IAAI,GAAG,MAAM,IAAI;AAC/B;AAaO,SAAS,qBACd,QACA,EAAE,MAAM,aAAa,MACF;AACnB,QAAM,CAAC,SAAS,UAAU,IAAI,SAA4B,MAAM,uBAAuB,IAAI,CAAC;AAE5F,YAAU,MAAM;AACd,eAAW,uBAAuB,IAAI,CAAC;AACvC,QAAI,CAAC,UAAU,OAAO,WAAW,YAAa;AAC9C,UAAM,mBACJ,OAAO,gBACN,OAAwE;AAC3E,QAAI,CAAC,iBAAkB;AAEvB,QAAI;AACJ,QAAI;AACF,qBAAe,IAAI,iBAAA;AAAA,IACrB,QAAQ;AACN;AAAA,IACF;AACA,UAAM,WAAW,aAAa,eAAA;AAC9B,aAAS,UAAU;AACnB,aAAS,wBAAwB;AACjC,QAAI,SAA4C;AAChD,QAAI;AACF,eAAS,aAAa,wBAAwB,MAAM;AACpD,aAAO,QAAQ,QAAQ;AAAA,IACzB,QAAQ;AACN,WAAK,aAAa,MAAA;AAClB;AAAA,IACF;AACA,UAAM,UAAU,IAAI,aAAa,SAAS,OAAO;AACjD,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,eAAS,uBAAuB,OAAO;AACvC,YAAM,QAAQ,wBAAwB,OAAO;AAC7C,iBAAW,CAAC,YAAY,eAAe,SAAS,KAAK,CAAC;AAAA,IACxD,GAAG,UAAU;AAEb,WAAO,MAAM;AACX,aAAO,cAAc,QAAQ;AAC7B,UAAI;AACF,yCAAQ;AACR,iBAAS,WAAA;AAAA,MACX,QAAQ;AAAA,MAER;AACA,WAAK,aAAa,MAAA;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,UAAU,CAAC;AAE7B,SAAO;AACT;"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const lucideReact = require("lucide-react");
|
|
5
|
+
const tooltipIconButton = require("../chat/tooltip-icon-button.cjs");
|
|
6
|
+
const cnfast = require("cnfast");
|
|
7
|
+
const DictationMicButton = ({
|
|
8
|
+
onClick,
|
|
9
|
+
disabled,
|
|
10
|
+
className,
|
|
11
|
+
title = "Dictate a message"
|
|
12
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
13
|
+
tooltipIconButton.TooltipIconButton,
|
|
14
|
+
{
|
|
15
|
+
tooltip: title,
|
|
16
|
+
side: "bottom",
|
|
17
|
+
type: "button",
|
|
18
|
+
variant: "ghost",
|
|
19
|
+
size: "icon",
|
|
20
|
+
className: cnfast.cn("aui-composer-dictate size-8 rounded-full text-muted-foreground hover:text-foreground", className),
|
|
21
|
+
"aria-label": title,
|
|
22
|
+
onClick,
|
|
23
|
+
disabled,
|
|
24
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicIcon, { className: "aui-composer-dictate-icon size-4" })
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
const DictationCancelButton = ({ onClick, disabled, className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
28
|
+
tooltipIconButton.TooltipIconButton,
|
|
29
|
+
{
|
|
30
|
+
tooltip: "Cancel dictation",
|
|
31
|
+
side: "bottom",
|
|
32
|
+
type: "button",
|
|
33
|
+
variant: "outline",
|
|
34
|
+
size: "icon",
|
|
35
|
+
className: cnfast.cn("aui-composer-dictate-cancel size-8 rounded-full", className),
|
|
36
|
+
"aria-label": "Cancel dictation",
|
|
37
|
+
onClick,
|
|
38
|
+
disabled,
|
|
39
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-4" })
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
const DictationStopButton = ({ onClick, disabled, className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
43
|
+
tooltipIconButton.TooltipIconButton,
|
|
44
|
+
{
|
|
45
|
+
tooltip: "Stop and review",
|
|
46
|
+
side: "bottom",
|
|
47
|
+
type: "button",
|
|
48
|
+
variant: "outline",
|
|
49
|
+
size: "icon",
|
|
50
|
+
className: cnfast.cn("aui-composer-dictate-stop size-8 rounded-full", className),
|
|
51
|
+
"aria-label": "Stop dictation",
|
|
52
|
+
onClick,
|
|
53
|
+
disabled,
|
|
54
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SquareIcon, { className: "size-3 fill-current" })
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
exports.DictationCancelButton = DictationCancelButton;
|
|
58
|
+
exports.DictationMicButton = DictationMicButton;
|
|
59
|
+
exports.DictationStopButton = DictationStopButton;
|
|
60
|
+
//# sourceMappingURL=dictation-controls.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-controls.cjs","sources":["../../src/dictation/dictation-controls.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { MicIcon, SquareIcon, XIcon } from 'lucide-react';\nimport { TooltipIconButton } from '../chat/tooltip-icon-button';\nimport { cn } from '../lib/utils';\n\ninterface DictationControlProps {\n onClick: () => void;\n disabled?: boolean;\n className?: string;\n}\n\n/** Toolbar mic: starts a dictation session. */\nexport const DictationMicButton: FC<DictationControlProps & { title?: string }> = ({\n onClick,\n disabled,\n className,\n title = 'Dictate a message',\n}) => (\n <TooltipIconButton\n tooltip={title}\n side=\"bottom\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn('aui-composer-dictate size-8 rounded-full text-muted-foreground hover:text-foreground', className)}\n aria-label={title}\n onClick={onClick}\n disabled={disabled}\n >\n <MicIcon className=\"aui-composer-dictate-icon size-4\" />\n </TooltipIconButton>\n);\n\n/** Recording mode, left side: discard the session. */\nexport const DictationCancelButton: FC<DictationControlProps> = ({ onClick, disabled, className }) => (\n <TooltipIconButton\n tooltip=\"Cancel dictation\"\n side=\"bottom\"\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className={cn('aui-composer-dictate-cancel size-8 rounded-full', className)}\n aria-label=\"Cancel dictation\"\n onClick={onClick}\n disabled={disabled}\n >\n <XIcon className=\"size-4\" />\n </TooltipIconButton>\n);\n\n/** Recording mode, right side: stop and put the words in the composer. */\nexport const DictationStopButton: FC<DictationControlProps> = ({ onClick, disabled, className }) => (\n <TooltipIconButton\n tooltip=\"Stop and review\"\n side=\"bottom\"\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className={cn('aui-composer-dictate-stop size-8 rounded-full', className)}\n aria-label=\"Stop dictation\"\n onClick={onClick}\n disabled={disabled}\n >\n <SquareIcon className=\"size-3 fill-current\" />\n </TooltipIconButton>\n);\n"],"names":["jsx","TooltipIconButton","cn","MicIcon","XIcon","SquareIcon"],"mappings":";;;;;;AAYO,MAAM,qBAAqE,CAAC;AAAA,EACjF;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,MACEA,2BAAAA;AAAAA,EAACC,kBAAAA;AAAAA,EAAA;AAAA,IACC,SAAS;AAAA,IACT,MAAK;AAAA,IACL,MAAK;AAAA,IACL,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,WAAWC,OAAAA,GAAG,wFAAwF,SAAS;AAAA,IAC/G,cAAY;AAAA,IACZ;AAAA,IACA;AAAA,IAEA,UAAAF,2BAAAA,IAACG,YAAAA,SAAA,EAAQ,WAAU,mCAAA,CAAmC;AAAA,EAAA;AACxD;AAIK,MAAM,wBAAmD,CAAC,EAAE,SAAS,UAAU,gBACpFH,2BAAAA;AAAAA,EAACC,kBAAAA;AAAAA,EAAA;AAAA,IACC,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,MAAK;AAAA,IACL,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,WAAWC,OAAAA,GAAG,mDAAmD,SAAS;AAAA,IAC1E,cAAW;AAAA,IACX;AAAA,IACA;AAAA,IAEA,UAAAF,2BAAAA,IAACI,YAAAA,OAAA,EAAM,WAAU,SAAA,CAAS;AAAA,EAAA;AAC5B;AAIK,MAAM,sBAAiD,CAAC,EAAE,SAAS,UAAU,gBAClFJ,2BAAAA;AAAAA,EAACC,kBAAAA;AAAAA,EAAA;AAAA,IACC,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,MAAK;AAAA,IACL,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,WAAWC,OAAAA,GAAG,iDAAiD,SAAS;AAAA,IACxE,cAAW;AAAA,IACX;AAAA,IACA;AAAA,IAEA,UAAAF,2BAAAA,IAACK,YAAAA,YAAA,EAAW,WAAU,sBAAA,CAAsB;AAAA,EAAA;AAC9C;;;;"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
interface DictationControlProps {
|
|
3
|
+
onClick: () => void;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Toolbar mic: starts a dictation session. */
|
|
8
|
+
export declare const DictationMicButton: FC<DictationControlProps & {
|
|
9
|
+
title?: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** Recording mode, left side: discard the session. */
|
|
12
|
+
export declare const DictationCancelButton: FC<DictationControlProps>;
|
|
13
|
+
/** Recording mode, right side: stop and put the words in the composer. */
|
|
14
|
+
export declare const DictationStopButton: FC<DictationControlProps>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { SquareIcon, MicIcon, XIcon } from "lucide-react";
|
|
3
|
+
import { TooltipIconButton } from "../chat/tooltip-icon-button.js";
|
|
4
|
+
import { cn } from "cnfast";
|
|
5
|
+
const DictationMicButton = ({
|
|
6
|
+
onClick,
|
|
7
|
+
disabled,
|
|
8
|
+
className,
|
|
9
|
+
title = "Dictate a message"
|
|
10
|
+
}) => /* @__PURE__ */ jsx(
|
|
11
|
+
TooltipIconButton,
|
|
12
|
+
{
|
|
13
|
+
tooltip: title,
|
|
14
|
+
side: "bottom",
|
|
15
|
+
type: "button",
|
|
16
|
+
variant: "ghost",
|
|
17
|
+
size: "icon",
|
|
18
|
+
className: cn("aui-composer-dictate size-8 rounded-full text-muted-foreground hover:text-foreground", className),
|
|
19
|
+
"aria-label": title,
|
|
20
|
+
onClick,
|
|
21
|
+
disabled,
|
|
22
|
+
children: /* @__PURE__ */ jsx(MicIcon, { className: "aui-composer-dictate-icon size-4" })
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
const DictationCancelButton = ({ onClick, disabled, className }) => /* @__PURE__ */ jsx(
|
|
26
|
+
TooltipIconButton,
|
|
27
|
+
{
|
|
28
|
+
tooltip: "Cancel dictation",
|
|
29
|
+
side: "bottom",
|
|
30
|
+
type: "button",
|
|
31
|
+
variant: "outline",
|
|
32
|
+
size: "icon",
|
|
33
|
+
className: cn("aui-composer-dictate-cancel size-8 rounded-full", className),
|
|
34
|
+
"aria-label": "Cancel dictation",
|
|
35
|
+
onClick,
|
|
36
|
+
disabled,
|
|
37
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
const DictationStopButton = ({ onClick, disabled, className }) => /* @__PURE__ */ jsx(
|
|
41
|
+
TooltipIconButton,
|
|
42
|
+
{
|
|
43
|
+
tooltip: "Stop and review",
|
|
44
|
+
side: "bottom",
|
|
45
|
+
type: "button",
|
|
46
|
+
variant: "outline",
|
|
47
|
+
size: "icon",
|
|
48
|
+
className: cn("aui-composer-dictate-stop size-8 rounded-full", className),
|
|
49
|
+
"aria-label": "Stop dictation",
|
|
50
|
+
onClick,
|
|
51
|
+
disabled,
|
|
52
|
+
children: /* @__PURE__ */ jsx(SquareIcon, { className: "size-3 fill-current" })
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
export {
|
|
56
|
+
DictationCancelButton,
|
|
57
|
+
DictationMicButton,
|
|
58
|
+
DictationStopButton
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=dictation-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-controls.js","sources":["../../src/dictation/dictation-controls.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { MicIcon, SquareIcon, XIcon } from 'lucide-react';\nimport { TooltipIconButton } from '../chat/tooltip-icon-button';\nimport { cn } from '../lib/utils';\n\ninterface DictationControlProps {\n onClick: () => void;\n disabled?: boolean;\n className?: string;\n}\n\n/** Toolbar mic: starts a dictation session. */\nexport const DictationMicButton: FC<DictationControlProps & { title?: string }> = ({\n onClick,\n disabled,\n className,\n title = 'Dictate a message',\n}) => (\n <TooltipIconButton\n tooltip={title}\n side=\"bottom\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn('aui-composer-dictate size-8 rounded-full text-muted-foreground hover:text-foreground', className)}\n aria-label={title}\n onClick={onClick}\n disabled={disabled}\n >\n <MicIcon className=\"aui-composer-dictate-icon size-4\" />\n </TooltipIconButton>\n);\n\n/** Recording mode, left side: discard the session. */\nexport const DictationCancelButton: FC<DictationControlProps> = ({ onClick, disabled, className }) => (\n <TooltipIconButton\n tooltip=\"Cancel dictation\"\n side=\"bottom\"\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className={cn('aui-composer-dictate-cancel size-8 rounded-full', className)}\n aria-label=\"Cancel dictation\"\n onClick={onClick}\n disabled={disabled}\n >\n <XIcon className=\"size-4\" />\n </TooltipIconButton>\n);\n\n/** Recording mode, right side: stop and put the words in the composer. */\nexport const DictationStopButton: FC<DictationControlProps> = ({ onClick, disabled, className }) => (\n <TooltipIconButton\n tooltip=\"Stop and review\"\n side=\"bottom\"\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className={cn('aui-composer-dictate-stop size-8 rounded-full', className)}\n aria-label=\"Stop dictation\"\n onClick={onClick}\n disabled={disabled}\n >\n <SquareIcon className=\"size-3 fill-current\" />\n </TooltipIconButton>\n);\n"],"names":[],"mappings":";;;;AAYO,MAAM,qBAAqE,CAAC;AAAA,EACjF;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,MACE;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,SAAS;AAAA,IACT,MAAK;AAAA,IACL,MAAK;AAAA,IACL,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,WAAW,GAAG,wFAAwF,SAAS;AAAA,IAC/G,cAAY;AAAA,IACZ;AAAA,IACA;AAAA,IAEA,UAAA,oBAAC,SAAA,EAAQ,WAAU,mCAAA,CAAmC;AAAA,EAAA;AACxD;AAIK,MAAM,wBAAmD,CAAC,EAAE,SAAS,UAAU,gBACpF;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,MAAK;AAAA,IACL,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,WAAW,GAAG,mDAAmD,SAAS;AAAA,IAC1E,cAAW;AAAA,IACX;AAAA,IACA;AAAA,IAEA,UAAA,oBAAC,OAAA,EAAM,WAAU,SAAA,CAAS;AAAA,EAAA;AAC5B;AAIK,MAAM,sBAAiD,CAAC,EAAE,SAAS,UAAU,gBAClF;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,MAAK;AAAA,IACL,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,WAAW,GAAG,iDAAiD,SAAS;AAAA,IACxE,cAAW;AAAA,IACX;AAAA,IACA;AAAA,IAEA,UAAA,oBAAC,YAAA,EAAW,WAAU,sBAAA,CAAsB;AAAA,EAAA;AAC9C;"}
|