@magmamath/students-features 1.6.4 → 1.7.0-rc.1

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.
Files changed (81) hide show
  1. package/dist/commonjs/features/chatbot/index.js +0 -7
  2. package/dist/commonjs/features/chatbot/index.js.map +1 -1
  3. package/dist/commonjs/features/keyboard/constants/groups/currency.constants.js +3 -0
  4. package/dist/commonjs/features/keyboard/constants/groups/currency.constants.js.map +1 -1
  5. package/dist/commonjs/features/keyboard/constants/keyboard.helpers.js +2 -1
  6. package/dist/commonjs/features/keyboard/constants/keyboard.helpers.js.map +1 -1
  7. package/dist/commonjs/features/keyboard/constants/keys/units.constants.js +12 -4
  8. package/dist/commonjs/features/keyboard/constants/keys/units.constants.js.map +1 -1
  9. package/dist/commonjs/features/voice/recording/components/VoiceRecordButton.js +1 -2
  10. package/dist/commonjs/features/voice/recording/components/VoiceRecordButton.js.map +1 -1
  11. package/dist/commonjs/lib/effector/createControllerEffect.js +6 -7
  12. package/dist/commonjs/lib/effector/createControllerEffect.js.map +1 -1
  13. package/dist/commonjs/shared/icons/keyboard/DinarIcon.js +34 -0
  14. package/dist/commonjs/shared/icons/keyboard/DinarIcon.js.map +1 -0
  15. package/dist/commonjs/shared/icons/keyboard/FrancIcon.js +1 -1
  16. package/dist/commonjs/shared/icons/keyboard/RappenIcon.js +2 -2
  17. package/dist/commonjs/shared/icons/keyboard/RappenIcon.js.map +1 -1
  18. package/dist/module/features/chatbot/index.js +0 -1
  19. package/dist/module/features/chatbot/index.js.map +1 -1
  20. package/dist/module/features/keyboard/constants/groups/currency.constants.js +3 -0
  21. package/dist/module/features/keyboard/constants/groups/currency.constants.js.map +1 -1
  22. package/dist/module/features/keyboard/constants/keyboard.helpers.js +2 -1
  23. package/dist/module/features/keyboard/constants/keyboard.helpers.js.map +1 -1
  24. package/dist/module/features/keyboard/constants/keys/units.constants.js +12 -4
  25. package/dist/module/features/keyboard/constants/keys/units.constants.js.map +1 -1
  26. package/dist/module/features/voice/recording/components/VoiceRecordButton.js +1 -2
  27. package/dist/module/features/voice/recording/components/VoiceRecordButton.js.map +1 -1
  28. package/dist/module/lib/effector/createControllerEffect.js +6 -7
  29. package/dist/module/lib/effector/createControllerEffect.js.map +1 -1
  30. package/dist/module/shared/icons/keyboard/DinarIcon.js +26 -0
  31. package/dist/module/shared/icons/keyboard/DinarIcon.js.map +1 -0
  32. package/dist/module/shared/icons/keyboard/FrancIcon.js +1 -1
  33. package/dist/module/shared/icons/keyboard/RappenIcon.js +2 -2
  34. package/dist/module/shared/icons/keyboard/RappenIcon.js.map +1 -1
  35. package/dist/typescript/commonjs/features/chatbot/index.d.ts +1 -2
  36. package/dist/typescript/commonjs/features/chatbot/index.d.ts.map +1 -1
  37. package/dist/typescript/commonjs/features/chatbot/types/api.types.d.ts +0 -1
  38. package/dist/typescript/commonjs/features/chatbot/types/api.types.d.ts.map +1 -1
  39. package/dist/typescript/commonjs/features/keyboard/constants/groups/currency.constants.d.ts.map +1 -1
  40. package/dist/typescript/commonjs/features/keyboard/constants/keyboard.helpers.d.ts.map +1 -1
  41. package/dist/typescript/commonjs/features/keyboard/constants/keys/units.constants.d.ts.map +1 -1
  42. package/dist/typescript/commonjs/features/pmProgress/shared/pmProgress.constants.d.ts +1 -1
  43. package/dist/typescript/commonjs/features/voice/recording/components/VoiceRecordButton.d.ts.map +1 -1
  44. package/dist/typescript/commonjs/lib/effector/createControllerEffect.d.ts +0 -1
  45. package/dist/typescript/commonjs/lib/effector/createControllerEffect.d.ts.map +1 -1
  46. package/dist/typescript/commonjs/shared/icons/keyboard/DinarIcon.d.ts +4 -0
  47. package/dist/typescript/commonjs/shared/icons/keyboard/DinarIcon.d.ts.map +1 -0
  48. package/dist/typescript/module/features/chatbot/index.d.ts +1 -2
  49. package/dist/typescript/module/features/chatbot/index.d.ts.map +1 -1
  50. package/dist/typescript/module/features/chatbot/types/api.types.d.ts +0 -1
  51. package/dist/typescript/module/features/chatbot/types/api.types.d.ts.map +1 -1
  52. package/dist/typescript/module/features/keyboard/constants/groups/currency.constants.d.ts.map +1 -1
  53. package/dist/typescript/module/features/keyboard/constants/keyboard.helpers.d.ts.map +1 -1
  54. package/dist/typescript/module/features/keyboard/constants/keys/units.constants.d.ts.map +1 -1
  55. package/dist/typescript/module/features/pmProgress/shared/pmProgress.constants.d.ts +1 -1
  56. package/dist/typescript/module/features/voice/recording/components/VoiceRecordButton.d.ts.map +1 -1
  57. package/dist/typescript/module/lib/effector/createControllerEffect.d.ts +0 -1
  58. package/dist/typescript/module/lib/effector/createControllerEffect.d.ts.map +1 -1
  59. package/dist/typescript/module/shared/icons/keyboard/DinarIcon.d.ts +4 -0
  60. package/dist/typescript/module/shared/icons/keyboard/DinarIcon.d.ts.map +1 -0
  61. package/package.json +3 -3
  62. package/src/features/chatbot/index.ts +0 -2
  63. package/src/features/chatbot/types/api.types.ts +0 -1
  64. package/src/features/keyboard/constants/groups/currency.constants.ts +1 -0
  65. package/src/features/keyboard/constants/keyboard.helpers.ts +1 -0
  66. package/src/features/keyboard/constants/keys/units.constants.tsx +10 -4
  67. package/src/features/voice/recording/components/VoiceRecordButton.tsx +3 -2
  68. package/src/i18n/.generated/schema.json +6 -7
  69. package/src/lib/effector/createControllerEffect.ts +6 -13
  70. package/src/shared/icons/keyboard/DinarIcon.tsx +20 -0
  71. package/src/shared/icons/keyboard/FrancIcon.tsx +1 -1
  72. package/src/shared/icons/keyboard/RappenIcon.tsx +2 -2
  73. package/dist/commonjs/features/chatbot/model/VoiceTranscriptSnapshotModel.js +0 -126
  74. package/dist/commonjs/features/chatbot/model/VoiceTranscriptSnapshotModel.js.map +0 -1
  75. package/dist/module/features/chatbot/model/VoiceTranscriptSnapshotModel.js +0 -121
  76. package/dist/module/features/chatbot/model/VoiceTranscriptSnapshotModel.js.map +0 -1
  77. package/dist/typescript/commonjs/features/chatbot/model/VoiceTranscriptSnapshotModel.d.ts +0 -32
  78. package/dist/typescript/commonjs/features/chatbot/model/VoiceTranscriptSnapshotModel.d.ts.map +0 -1
  79. package/dist/typescript/module/features/chatbot/model/VoiceTranscriptSnapshotModel.d.ts +0 -32
  80. package/dist/typescript/module/features/chatbot/model/VoiceTranscriptSnapshotModel.d.ts.map +0 -1
  81. package/src/features/chatbot/model/VoiceTranscriptSnapshotModel.ts +0 -159
@@ -1,126 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.VoiceTranscriptSnapshotModel = void 0;
7
- var _effector = require("effector");
8
- var _types = require("../../voice/types.js");
9
- var _i18n = require("../../../i18n/i18n.js");
10
- const TRANSCRIPT_RETRY_INTERVAL_MS = 2000;
11
- const TRANSCRIPT_MAX_RETRIES = 20;
12
- const CHAT_WITH_VOICE_TIMEOUT_MS = 60_000;
13
- // Voice key: `${assignmentId}-${problemId}-${attemptsCount}`
14
- // Chat key: `${assignmentId}-${problemId}` — survives attempt increments and voice deletion.
15
- const toChatKey = voiceKey => {
16
- const lastDashIndex = voiceKey.lastIndexOf('-');
17
- if (lastDashIndex <= 0) return null;
18
- return voiceKey.slice(0, lastDashIndex);
19
- };
20
- class VoiceTranscriptSnapshotModel {
21
- fetchTranscript = audioFileId => {
22
- const cached = this.transcriptByAudioFileId.get(audioFileId);
23
- if (cached) return cached;
24
- const promise = this.pollTranscript(audioFileId).catch(() => undefined);
25
- this.transcriptByAudioFileId.set(audioFileId, promise);
26
- return promise;
27
- };
28
- getTranscriptForCurrent = async () => {
29
- const voiceKey = this.voiceRecord.$currentKey.getState();
30
- if (!voiceKey) return undefined;
31
- const chatKey = toChatKey(voiceKey);
32
- if (chatKey) {
33
- const snapshot = this.snapshotByChatKey.get(chatKey);
34
- if (snapshot) return snapshot;
35
- }
36
- const record = this.voiceRecord.collection.get(voiceKey);
37
- if (!record) return undefined;
38
- const audioFileId = await this.resolveAudioFileId(record);
39
- if (!audioFileId) return undefined;
40
- return this.fetchTranscript(audioFileId);
41
- };
42
- wrapRequestHint = apiFn => {
43
- return async (params, requestConfig) => {
44
- if (this.hasPendingVoice()) {
45
- requestConfig?.extendTimeoutMs?.(CHAT_WITH_VOICE_TIMEOUT_MS);
46
- }
47
- const voiceTranscript = await this.getTranscriptForCurrent();
48
- return apiFn(voiceTranscript ? {
49
- ...params,
50
- voiceTranscript
51
- } : params, requestConfig);
52
- };
53
- };
54
- hasPendingVoice() {
55
- const voiceKey = this.voiceRecord.$currentKey.getState();
56
- if (!voiceKey) return false;
57
- const chatKey = toChatKey(voiceKey);
58
- if (chatKey && this.snapshotByChatKey.has(chatKey)) return true;
59
- return !!this.voiceRecord.collection.get(voiceKey);
60
- }
61
- constructor({
62
- voiceRecord,
63
- api,
64
- errorHandler
65
- }) {
66
- this.voiceRecord = voiceRecord;
67
- this.api = api;
68
- this.errorHandler = errorHandler;
69
- (0, _effector.sample)({
70
- clock: voiceRecord.setCurrentRecord,
71
- source: voiceRecord.$currentKey,
72
- filter: (_, record) => !!record,
73
- fn: (voiceKey, record) => ({
74
- voiceKey,
75
- record
76
- }),
77
- target: this.captureSnapshotFx
78
- });
79
- (0, _effector.sample)({
80
- clock: voiceRecord.deleteCurrentRecording,
81
- source: voiceRecord.$currentKey,
82
- target: this.clearSnapshotFx
83
- });
84
- }
85
- transcriptByAudioFileId = new Map();
86
- snapshotByChatKey = new Map();
87
- captureSnapshotFx = (0, _effector.createEffect)(({
88
- voiceKey,
89
- record
90
- }) => {
91
- const chatKey = toChatKey(voiceKey);
92
- if (!chatKey) return;
93
- const transcriptPromise = this.resolveAudioFileId(record).then(audioFileId => audioFileId ? this.fetchTranscript(audioFileId) : undefined);
94
- this.snapshotByChatKey.set(chatKey, transcriptPromise);
95
- });
96
- clearSnapshotFx = (0, _effector.createEffect)(voiceKey => {
97
- const chatKey = toChatKey(voiceKey);
98
- if (chatKey) this.snapshotByChatKey.delete(chatKey);
99
- });
100
- async pollTranscript(audioFileId) {
101
- for (let attempt = 0; attempt < TRANSCRIPT_MAX_RETRIES; attempt++) {
102
- await new Promise(resolve => setTimeout(resolve, TRANSCRIPT_RETRY_INTERVAL_MS));
103
- try {
104
- const response = await this.api.getAudioFileTranscript(audioFileId);
105
- if (response.status === _types.TranscriptionStatus.COMPLETED) return response.text;
106
- if (response.status === _types.TranscriptionStatus.FAILED) break;
107
- } catch {
108
- // network/request failure — retry on next iteration
109
- }
110
- }
111
- this.errorHandler?.((0, _i18n.getText)('voice.transcriptionError'));
112
- return undefined;
113
- }
114
- async resolveAudioFileId(record) {
115
- if (record.id) return record.id;
116
- if (!record.audioUploadPromise) return undefined;
117
- try {
118
- const result = await record.audioUploadPromise;
119
- return result?.id;
120
- } catch {
121
- return undefined;
122
- }
123
- }
124
- }
125
- exports.VoiceTranscriptSnapshotModel = VoiceTranscriptSnapshotModel;
126
- //# sourceMappingURL=VoiceTranscriptSnapshotModel.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_effector","require","_types","_i18n","TRANSCRIPT_RETRY_INTERVAL_MS","TRANSCRIPT_MAX_RETRIES","CHAT_WITH_VOICE_TIMEOUT_MS","toChatKey","voiceKey","lastDashIndex","lastIndexOf","slice","VoiceTranscriptSnapshotModel","fetchTranscript","audioFileId","cached","transcriptByAudioFileId","get","promise","pollTranscript","catch","undefined","set","getTranscriptForCurrent","voiceRecord","$currentKey","getState","chatKey","snapshot","snapshotByChatKey","record","collection","resolveAudioFileId","wrapRequestHint","apiFn","params","requestConfig","hasPendingVoice","extendTimeoutMs","voiceTranscript","has","constructor","api","errorHandler","sample","clock","setCurrentRecord","source","filter","_","fn","target","captureSnapshotFx","deleteCurrentRecording","clearSnapshotFx","Map","createEffect","transcriptPromise","then","delete","attempt","Promise","resolve","setTimeout","response","getAudioFileTranscript","status","TranscriptionStatus","COMPLETED","text","FAILED","getText","id","audioUploadPromise","result","exports"],"sourceRoot":"../../../../../src","sources":["features/chatbot/model/VoiceTranscriptSnapshotModel.ts"],"mappings":"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","ignoreList":[]}
@@ -1,121 +0,0 @@
1
- "use strict";
2
-
3
- import { createEffect, sample } from 'effector';
4
- import { TranscriptionStatus } from "../../voice/types.js";
5
- import { getText } from "../../../i18n/i18n.js";
6
- const TRANSCRIPT_RETRY_INTERVAL_MS = 2000;
7
- const TRANSCRIPT_MAX_RETRIES = 20;
8
- const CHAT_WITH_VOICE_TIMEOUT_MS = 60_000;
9
- // Voice key: `${assignmentId}-${problemId}-${attemptsCount}`
10
- // Chat key: `${assignmentId}-${problemId}` — survives attempt increments and voice deletion.
11
- const toChatKey = voiceKey => {
12
- const lastDashIndex = voiceKey.lastIndexOf('-');
13
- if (lastDashIndex <= 0) return null;
14
- return voiceKey.slice(0, lastDashIndex);
15
- };
16
- export class VoiceTranscriptSnapshotModel {
17
- fetchTranscript = audioFileId => {
18
- const cached = this.transcriptByAudioFileId.get(audioFileId);
19
- if (cached) return cached;
20
- const promise = this.pollTranscript(audioFileId).catch(() => undefined);
21
- this.transcriptByAudioFileId.set(audioFileId, promise);
22
- return promise;
23
- };
24
- getTranscriptForCurrent = async () => {
25
- const voiceKey = this.voiceRecord.$currentKey.getState();
26
- if (!voiceKey) return undefined;
27
- const chatKey = toChatKey(voiceKey);
28
- if (chatKey) {
29
- const snapshot = this.snapshotByChatKey.get(chatKey);
30
- if (snapshot) return snapshot;
31
- }
32
- const record = this.voiceRecord.collection.get(voiceKey);
33
- if (!record) return undefined;
34
- const audioFileId = await this.resolveAudioFileId(record);
35
- if (!audioFileId) return undefined;
36
- return this.fetchTranscript(audioFileId);
37
- };
38
- wrapRequestHint = apiFn => {
39
- return async (params, requestConfig) => {
40
- if (this.hasPendingVoice()) {
41
- requestConfig?.extendTimeoutMs?.(CHAT_WITH_VOICE_TIMEOUT_MS);
42
- }
43
- const voiceTranscript = await this.getTranscriptForCurrent();
44
- return apiFn(voiceTranscript ? {
45
- ...params,
46
- voiceTranscript
47
- } : params, requestConfig);
48
- };
49
- };
50
- hasPendingVoice() {
51
- const voiceKey = this.voiceRecord.$currentKey.getState();
52
- if (!voiceKey) return false;
53
- const chatKey = toChatKey(voiceKey);
54
- if (chatKey && this.snapshotByChatKey.has(chatKey)) return true;
55
- return !!this.voiceRecord.collection.get(voiceKey);
56
- }
57
- constructor({
58
- voiceRecord,
59
- api,
60
- errorHandler
61
- }) {
62
- this.voiceRecord = voiceRecord;
63
- this.api = api;
64
- this.errorHandler = errorHandler;
65
- sample({
66
- clock: voiceRecord.setCurrentRecord,
67
- source: voiceRecord.$currentKey,
68
- filter: (_, record) => !!record,
69
- fn: (voiceKey, record) => ({
70
- voiceKey,
71
- record
72
- }),
73
- target: this.captureSnapshotFx
74
- });
75
- sample({
76
- clock: voiceRecord.deleteCurrentRecording,
77
- source: voiceRecord.$currentKey,
78
- target: this.clearSnapshotFx
79
- });
80
- }
81
- transcriptByAudioFileId = new Map();
82
- snapshotByChatKey = new Map();
83
- captureSnapshotFx = createEffect(({
84
- voiceKey,
85
- record
86
- }) => {
87
- const chatKey = toChatKey(voiceKey);
88
- if (!chatKey) return;
89
- const transcriptPromise = this.resolveAudioFileId(record).then(audioFileId => audioFileId ? this.fetchTranscript(audioFileId) : undefined);
90
- this.snapshotByChatKey.set(chatKey, transcriptPromise);
91
- });
92
- clearSnapshotFx = createEffect(voiceKey => {
93
- const chatKey = toChatKey(voiceKey);
94
- if (chatKey) this.snapshotByChatKey.delete(chatKey);
95
- });
96
- async pollTranscript(audioFileId) {
97
- for (let attempt = 0; attempt < TRANSCRIPT_MAX_RETRIES; attempt++) {
98
- await new Promise(resolve => setTimeout(resolve, TRANSCRIPT_RETRY_INTERVAL_MS));
99
- try {
100
- const response = await this.api.getAudioFileTranscript(audioFileId);
101
- if (response.status === TranscriptionStatus.COMPLETED) return response.text;
102
- if (response.status === TranscriptionStatus.FAILED) break;
103
- } catch {
104
- // network/request failure — retry on next iteration
105
- }
106
- }
107
- this.errorHandler?.(getText('voice.transcriptionError'));
108
- return undefined;
109
- }
110
- async resolveAudioFileId(record) {
111
- if (record.id) return record.id;
112
- if (!record.audioUploadPromise) return undefined;
113
- try {
114
- const result = await record.audioUploadPromise;
115
- return result?.id;
116
- } catch {
117
- return undefined;
118
- }
119
- }
120
- }
121
- //# sourceMappingURL=VoiceTranscriptSnapshotModel.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["createEffect","sample","TranscriptionStatus","getText","TRANSCRIPT_RETRY_INTERVAL_MS","TRANSCRIPT_MAX_RETRIES","CHAT_WITH_VOICE_TIMEOUT_MS","toChatKey","voiceKey","lastDashIndex","lastIndexOf","slice","VoiceTranscriptSnapshotModel","fetchTranscript","audioFileId","cached","transcriptByAudioFileId","get","promise","pollTranscript","catch","undefined","set","getTranscriptForCurrent","voiceRecord","$currentKey","getState","chatKey","snapshot","snapshotByChatKey","record","collection","resolveAudioFileId","wrapRequestHint","apiFn","params","requestConfig","hasPendingVoice","extendTimeoutMs","voiceTranscript","has","constructor","api","errorHandler","clock","setCurrentRecord","source","filter","_","fn","target","captureSnapshotFx","deleteCurrentRecording","clearSnapshotFx","Map","transcriptPromise","then","delete","attempt","Promise","resolve","setTimeout","response","getAudioFileTranscript","status","COMPLETED","text","FAILED","id","audioUploadPromise","result"],"sourceRoot":"../../../../../src","sources":["features/chatbot/model/VoiceTranscriptSnapshotModel.ts"],"mappings":"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","ignoreList":[]}
@@ -1,32 +0,0 @@
1
- import { AudioTranscriptResponse } from '../../voice/types';
2
- import { VoiceRecordModel } from '../../voice/recording/model/VoiceRecord.model';
3
- import { RequestOptionalConfig } from '../../../lib/effector/createControllerEffect';
4
- type GetTranscriptApi = {
5
- getAudioFileTranscript: (audioFileId: string) => Promise<AudioTranscriptResponse>;
6
- };
7
- export type VoiceTranscriptErrorHandler = (errorMessage: string) => void;
8
- type VoiceTranscriptSnapshotModelProps = {
9
- voiceRecord: VoiceRecordModel;
10
- api: GetTranscriptApi;
11
- errorHandler?: VoiceTranscriptErrorHandler;
12
- };
13
- export declare class VoiceTranscriptSnapshotModel {
14
- readonly fetchTranscript: (audioFileId: string) => Promise<string | undefined>;
15
- readonly getTranscriptForCurrent: () => Promise<string | undefined>;
16
- readonly wrapRequestHint: <P extends object, R>(apiFn: (params: P & {
17
- voiceTranscript?: string;
18
- }, requestConfig?: RequestOptionalConfig) => Promise<R>) => (params: P, requestConfig?: RequestOptionalConfig) => Promise<R>;
19
- private hasPendingVoice;
20
- constructor({ voiceRecord, api, errorHandler }: VoiceTranscriptSnapshotModelProps);
21
- private readonly voiceRecord;
22
- private readonly api;
23
- private readonly errorHandler?;
24
- private readonly transcriptByAudioFileId;
25
- private readonly snapshotByChatKey;
26
- private readonly captureSnapshotFx;
27
- private readonly clearSnapshotFx;
28
- private pollTranscript;
29
- private resolveAudioFileId;
30
- }
31
- export {};
32
- //# sourceMappingURL=VoiceTranscriptSnapshotModel.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VoiceTranscriptSnapshotModel.d.ts","sourceRoot":"","sources":["../../../../../../src/features/chatbot/model/VoiceTranscriptSnapshotModel.ts"],"names":[],"mappings":"AACA,OAAO,EACL,uBAAuB,EAGxB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAA;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAA;AAOpF,KAAK,gBAAgB,GAAG;IACtB,sBAAsB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAA;CAClF,CAAA;AAOD,MAAM,MAAM,2BAA2B,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;AAExE,KAAK,iCAAiC,GAAG;IACvC,WAAW,EAAE,gBAAgB,CAAA;IAC7B,GAAG,EAAE,gBAAgB,CAAA;IACrB,YAAY,CAAC,EAAE,2BAA2B,CAAA;CAC3C,CAAA;AAUD,qBAAa,4BAA4B;IACvC,SAAgB,eAAe,gBAAiB,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAOnF;IAED,SAAgB,uBAAuB,QAAa,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB9E;IAED,SAAgB,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAC7C,CACL,MAAM,EAAE,CAAC,GAAG;QAAE,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,EACxC,aAAa,CAAC,EAAE,qBAAqB,KAClC,OAAO,CAAC,CAAC,CAAC,cAEO,CAAC,kBAAkB,qBAAqB,KAAG,OAAO,CAAC,CAAC,CAAC,CAO5E;IAED,OAAO,CAAC,eAAe;gBAQX,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,iCAAiC;IAoBjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAiD;IACzF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiD;IAEnF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAQhC;IAEF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;YAEY,cAAc;YAgBd,kBAAkB;CAUjC"}
@@ -1,32 +0,0 @@
1
- import { AudioTranscriptResponse } from '../../voice/types';
2
- import { VoiceRecordModel } from '../../voice/recording/model/VoiceRecord.model';
3
- import { RequestOptionalConfig } from '../../../lib/effector/createControllerEffect';
4
- type GetTranscriptApi = {
5
- getAudioFileTranscript: (audioFileId: string) => Promise<AudioTranscriptResponse>;
6
- };
7
- export type VoiceTranscriptErrorHandler = (errorMessage: string) => void;
8
- type VoiceTranscriptSnapshotModelProps = {
9
- voiceRecord: VoiceRecordModel;
10
- api: GetTranscriptApi;
11
- errorHandler?: VoiceTranscriptErrorHandler;
12
- };
13
- export declare class VoiceTranscriptSnapshotModel {
14
- readonly fetchTranscript: (audioFileId: string) => Promise<string | undefined>;
15
- readonly getTranscriptForCurrent: () => Promise<string | undefined>;
16
- readonly wrapRequestHint: <P extends object, R>(apiFn: (params: P & {
17
- voiceTranscript?: string;
18
- }, requestConfig?: RequestOptionalConfig) => Promise<R>) => (params: P, requestConfig?: RequestOptionalConfig) => Promise<R>;
19
- private hasPendingVoice;
20
- constructor({ voiceRecord, api, errorHandler }: VoiceTranscriptSnapshotModelProps);
21
- private readonly voiceRecord;
22
- private readonly api;
23
- private readonly errorHandler?;
24
- private readonly transcriptByAudioFileId;
25
- private readonly snapshotByChatKey;
26
- private readonly captureSnapshotFx;
27
- private readonly clearSnapshotFx;
28
- private pollTranscript;
29
- private resolveAudioFileId;
30
- }
31
- export {};
32
- //# sourceMappingURL=VoiceTranscriptSnapshotModel.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VoiceTranscriptSnapshotModel.d.ts","sourceRoot":"","sources":["../../../../../../src/features/chatbot/model/VoiceTranscriptSnapshotModel.ts"],"names":[],"mappings":"AACA,OAAO,EACL,uBAAuB,EAGxB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAA;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAA;AAOpF,KAAK,gBAAgB,GAAG;IACtB,sBAAsB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAA;CAClF,CAAA;AAOD,MAAM,MAAM,2BAA2B,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;AAExE,KAAK,iCAAiC,GAAG;IACvC,WAAW,EAAE,gBAAgB,CAAA;IAC7B,GAAG,EAAE,gBAAgB,CAAA;IACrB,YAAY,CAAC,EAAE,2BAA2B,CAAA;CAC3C,CAAA;AAUD,qBAAa,4BAA4B;IACvC,SAAgB,eAAe,gBAAiB,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAOnF;IAED,SAAgB,uBAAuB,QAAa,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB9E;IAED,SAAgB,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAC7C,CACL,MAAM,EAAE,CAAC,GAAG;QAAE,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,EACxC,aAAa,CAAC,EAAE,qBAAqB,KAClC,OAAO,CAAC,CAAC,CAAC,cAEO,CAAC,kBAAkB,qBAAqB,KAAG,OAAO,CAAC,CAAC,CAAC,CAO5E;IAED,OAAO,CAAC,eAAe;gBAQX,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,iCAAiC;IAoBjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAiD;IACzF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiD;IAEnF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAQhC;IAEF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;YAEY,cAAc;YAgBd,kBAAkB;CAUjC"}
@@ -1,159 +0,0 @@
1
- import { createEffect, sample } from 'effector'
2
- import {
3
- AudioTranscriptResponse,
4
- TranscriptionStatus,
5
- VoiceRecordCollectionItem,
6
- } from '../../voice/types'
7
- import { VoiceRecordModel } from '../../voice/recording/model/VoiceRecord.model'
8
- import { RequestOptionalConfig } from '../../../lib/effector/createControllerEffect'
9
- import { getText } from '../../../i18n/i18n'
10
-
11
- const TRANSCRIPT_RETRY_INTERVAL_MS = 2000
12
- const TRANSCRIPT_MAX_RETRIES = 20
13
- const CHAT_WITH_VOICE_TIMEOUT_MS = 60_000
14
-
15
- type GetTranscriptApi = {
16
- getAudioFileTranscript: (audioFileId: string) => Promise<AudioTranscriptResponse>
17
- }
18
-
19
- type CaptureParams = {
20
- voiceKey: string
21
- record: VoiceRecordCollectionItem
22
- }
23
-
24
- export type VoiceTranscriptErrorHandler = (errorMessage: string) => void
25
-
26
- type VoiceTranscriptSnapshotModelProps = {
27
- voiceRecord: VoiceRecordModel
28
- api: GetTranscriptApi
29
- errorHandler?: VoiceTranscriptErrorHandler
30
- }
31
-
32
- // Voice key: `${assignmentId}-${problemId}-${attemptsCount}`
33
- // Chat key: `${assignmentId}-${problemId}` — survives attempt increments and voice deletion.
34
- const toChatKey = (voiceKey: string): string | null => {
35
- const lastDashIndex = voiceKey.lastIndexOf('-')
36
- if (lastDashIndex <= 0) return null
37
- return voiceKey.slice(0, lastDashIndex)
38
- }
39
-
40
- export class VoiceTranscriptSnapshotModel {
41
- public readonly fetchTranscript = (audioFileId: string): Promise<string | undefined> => {
42
- const cached = this.transcriptByAudioFileId.get(audioFileId)
43
- if (cached) return cached
44
-
45
- const promise = this.pollTranscript(audioFileId).catch(() => undefined)
46
- this.transcriptByAudioFileId.set(audioFileId, promise)
47
- return promise
48
- }
49
-
50
- public readonly getTranscriptForCurrent = async (): Promise<string | undefined> => {
51
- const voiceKey = this.voiceRecord.$currentKey.getState()
52
- if (!voiceKey) return undefined
53
-
54
- const chatKey = toChatKey(voiceKey)
55
- if (chatKey) {
56
- const snapshot = this.snapshotByChatKey.get(chatKey)
57
- if (snapshot) return snapshot
58
- }
59
-
60
- const record = this.voiceRecord.collection.get(voiceKey)
61
- if (!record) return undefined
62
-
63
- const audioFileId = await this.resolveAudioFileId(record)
64
- if (!audioFileId) return undefined
65
-
66
- return this.fetchTranscript(audioFileId)
67
- }
68
-
69
- public readonly wrapRequestHint = <P extends object, R>(
70
- apiFn: (
71
- params: P & { voiceTranscript?: string },
72
- requestConfig?: RequestOptionalConfig,
73
- ) => Promise<R>,
74
- ) => {
75
- return async (params: P, requestConfig?: RequestOptionalConfig): Promise<R> => {
76
- if (this.hasPendingVoice()) {
77
- requestConfig?.extendTimeoutMs?.(CHAT_WITH_VOICE_TIMEOUT_MS)
78
- }
79
- const voiceTranscript = await this.getTranscriptForCurrent()
80
- return apiFn(voiceTranscript ? { ...params, voiceTranscript } : params, requestConfig)
81
- }
82
- }
83
-
84
- private hasPendingVoice(): boolean {
85
- const voiceKey = this.voiceRecord.$currentKey.getState()
86
- if (!voiceKey) return false
87
- const chatKey = toChatKey(voiceKey)
88
- if (chatKey && this.snapshotByChatKey.has(chatKey)) return true
89
- return !!this.voiceRecord.collection.get(voiceKey)
90
- }
91
-
92
- constructor({ voiceRecord, api, errorHandler }: VoiceTranscriptSnapshotModelProps) {
93
- this.voiceRecord = voiceRecord
94
- this.api = api
95
- this.errorHandler = errorHandler
96
-
97
- sample({
98
- clock: voiceRecord.setCurrentRecord,
99
- source: voiceRecord.$currentKey,
100
- filter: (_, record): record is VoiceRecordCollectionItem => !!record,
101
- fn: (voiceKey, record) => ({ voiceKey, record }),
102
- target: this.captureSnapshotFx,
103
- })
104
-
105
- sample({
106
- clock: voiceRecord.deleteCurrentRecording,
107
- source: voiceRecord.$currentKey,
108
- target: this.clearSnapshotFx,
109
- })
110
- }
111
-
112
- private readonly voiceRecord: VoiceRecordModel
113
- private readonly api: GetTranscriptApi
114
- private readonly errorHandler?: VoiceTranscriptErrorHandler
115
- private readonly transcriptByAudioFileId = new Map<string, Promise<string | undefined>>()
116
- private readonly snapshotByChatKey = new Map<string, Promise<string | undefined>>()
117
-
118
- private readonly captureSnapshotFx = createEffect(({ voiceKey, record }: CaptureParams) => {
119
- const chatKey = toChatKey(voiceKey)
120
- if (!chatKey) return
121
-
122
- const transcriptPromise = this.resolveAudioFileId(record).then((audioFileId) =>
123
- audioFileId ? this.fetchTranscript(audioFileId) : undefined,
124
- )
125
- this.snapshotByChatKey.set(chatKey, transcriptPromise)
126
- })
127
-
128
- private readonly clearSnapshotFx = createEffect((voiceKey: string) => {
129
- const chatKey = toChatKey(voiceKey)
130
- if (chatKey) this.snapshotByChatKey.delete(chatKey)
131
- })
132
-
133
- private async pollTranscript(audioFileId: string): Promise<string | undefined> {
134
- for (let attempt = 0; attempt < TRANSCRIPT_MAX_RETRIES; attempt++) {
135
- await new Promise((resolve) => setTimeout(resolve, TRANSCRIPT_RETRY_INTERVAL_MS))
136
-
137
- try {
138
- const response = await this.api.getAudioFileTranscript(audioFileId)
139
- if (response.status === TranscriptionStatus.COMPLETED) return response.text
140
- if (response.status === TranscriptionStatus.FAILED) break
141
- } catch {
142
- // network/request failure — retry on next iteration
143
- }
144
- }
145
- this.errorHandler?.(getText('voice.transcriptionError'))
146
- return undefined
147
- }
148
-
149
- private async resolveAudioFileId(record: VoiceRecordCollectionItem): Promise<string | undefined> {
150
- if (record.id) return record.id
151
- if (!record.audioUploadPromise) return undefined
152
- try {
153
- const result = await record.audioUploadPromise
154
- return result?.id
155
- } catch {
156
- return undefined
157
- }
158
- }
159
- }