Skip to content

Commit 5a8264d

Browse files
Add SpeechRecognition service source and chat/social voice transcription (#294)
Co-Authored-By: Taromati2 <taromati2@outlook.com>
1 parent 843131f commit 5a8264d

105 files changed

Lines changed: 14004 additions & 116 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- **APIs/Types**: `@src/decl/` (`CharAPI_t``charAPI.ts`). Consult for required methods.
1616
- **Key structs**: `prompt_struct_t` (`@src/decl/prompt_struct.ts`), `chatMetadata_t` (`@src/public/parts/shells/chat/src/chat/session/models.mjs`).
1717
- **Registries**: `fount.json``registries: [{ id, level, path }]`; `GET /api/registries/:name`; helpers: `@src/server/registries.mjs` (backend), `@src/public/pages/scripts/registries.mjs` (frontend).
18+
- **Service sources**: types under `serviceSources/{AI,search,translate,SpeechRecognition}/` + generators under `serviceGenerators/...`. `SpeechRecognitionSource_t.Recognize({ audio | feed, onResult })` — stream-first feed, buffer convenience; endpoints only on chat (`/speechRecognition/recognize` + `/speechRecognition/session`), social reuses them; config probe via part list / defaults.
1819

1920
## Dev Guidelines
2021

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { info_t, locale_t } from './basedefs.ts'
2+
3+
/**
4+
* 语音识别服务源类型定义。
5+
*/
6+
7+
/** 增量识别结果。 */
8+
export class SpeechRecognitionPartial_t {
9+
/** 当前累计全文(每次回调都是从头开始的完整文本,非增量片段) */
10+
text!: string
11+
/** 是否为最终结果片段 */
12+
isFinal?: boolean
13+
}
14+
15+
/** 一次识别会话的最终结果。 */
16+
export class SpeechRecognitionResult_t {
17+
/** 最终文本 */
18+
text!: string
19+
/** 检测到的语言(若有) */
20+
language?: string
21+
}
22+
23+
/** 实时喂音频控制面。 */
24+
export class SpeechRecognitionFeedControl_t {
25+
/**
26+
* 推送一帧音频(PCM 或源约定格式)。
27+
* @param chunk 音频字节
28+
*/
29+
send!: (chunk: Uint8Array) => Promise<void>
30+
/**
31+
* 声明输入结束并等待收尾。
32+
*/
33+
end!: () => Promise<void>
34+
}
35+
36+
/** Recognize 选项。 */
37+
export class SpeechRecognitionOptions_t {
38+
language?: string
39+
hotwords?: string[]
40+
signal?: AbortSignal
41+
/** 实时 / 增量结果回调 */
42+
onResult?: (partial: SpeechRecognitionPartial_t) => void
43+
/**
44+
* 便捷:一次传入完整音频。与 `feed` 二选一。
45+
*/
46+
audio?: { buffer: Uint8Array, mime_type: string, name?: string }
47+
/**
48+
* 实时输入:在回调里 `await send(chunk)`,结束时 `await end()`。与 `audio` 二选一。
49+
*/
50+
feed?: (ctl: SpeechRecognitionFeedControl_t) => Promise<void>
51+
}
52+
53+
/**
54+
* 语音识别数据源接口。
55+
*/
56+
export class SpeechRecognitionSource_t {
57+
filename!: string
58+
type!: 'speech-recognition' | string
59+
info!: info_t<{
60+
provider: string;
61+
}>
62+
is_paid!: boolean
63+
extension!: object
64+
65+
Unload?: () => Promise<void>
66+
/**
67+
* 执行识别。`audio` 与 `feed` 二选一;流式源在 `feed`/`send` 时立即上行并经 `onResult` 出字。
68+
*/
69+
Recognize!: (options: SpeechRecognitionOptions_t) => Promise<SpeechRecognitionResult_t>
70+
interfaces!: {
71+
info?: {
72+
UpdateInfo: (locales: locale_t[]) => Promise<info_t>,
73+
},
74+
}
75+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { locale_t, info_t } from './basedefs.ts'
2+
import { SpeechRecognitionSource_t } from './SpeechRecognitionSource.ts'
3+
4+
/**
5+
* 语音识别数据源生成器接口。
6+
*/
7+
export class SpeechRecognitionSourceGenerator_t {
8+
info!: info_t
9+
10+
Init?: () => Promise<void>
11+
Load?: () => Promise<void>
12+
Unload?: () => Promise<void>
13+
Uninstall?: () => Promise<void>
14+
15+
interfaces!: {
16+
info?: {
17+
UpdateInfo: (locales: locale_t[]) => Promise<info_t>,
18+
},
19+
config?: {
20+
GetData: () => Promise<any>
21+
SetData: (data: any) => Promise<void>
22+
},
23+
serviceGenerator: {
24+
GetConfigDisplayContent: () => Promise<{ html?: string, js?: string }>
25+
GetConfigTemplate: () => Promise<any>
26+
GetSource: (config: any, args: { username: string, SaveConfig: () => Promise<void> }) => Promise<SpeechRecognitionSource_t>
27+
}
28+
}
29+
}

src/decl/locale_data.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,24 @@ export type LocaleData = {
929929
}
930930
}
931931
}
932+
'generators/SpeechRecognition': {
933+
title: string
934+
subtitle: string
935+
card: {
936+
defaultCheckbox: {
937+
title: string
938+
}
939+
}
940+
}
941+
'sources/SpeechRecognition': {
942+
title: string
943+
subtitle: string
944+
card: {
945+
defaultCheckbox: {
946+
title: string
947+
}
948+
}
949+
}
932950
}
933951
}
934952
escapeConfirm: string
@@ -2818,6 +2836,8 @@ export type LocaleData = {
28182836
}
28192837
voiceRecording: {
28202838
errorAccessingMicrophone: string
2839+
confirmSpeechRecognition: string
2840+
speechRecognitionFailed: string
28212841
}
28222842
attachment: {
28232843
buttons: {
@@ -2833,6 +2853,12 @@ export type LocaleData = {
28332853
deleteIcon: {
28342854
alt: string
28352855
}
2856+
more: {
2857+
title: string
2858+
}
2859+
recognize: {
2860+
title: string
2861+
}
28362862
}
28372863
}
28382864
charCard: {
@@ -4248,6 +4274,16 @@ export type LocaleData = {
42484274
label: string
42494275
}
42504276
}
4277+
voiceButton: {
4278+
title: string
4279+
'aria-label': string
4280+
}
4281+
voiceFailed: string
4282+
audioTranscript: {
4283+
placeholder: string
4284+
}
4285+
confirmSpeechRecognition: string
4286+
speechRecognitionFailed: string
42514287
}
42524288
feed: {
42534289
refresh: {
@@ -6029,6 +6065,7 @@ export type LocaleKeyParams = {
60296065
'chat.stickers.authorLabel': { author: string | number }
60306066
'chat.stickers.sticker.count': { count: string | number }
60316067
'chat.typingIndicator.isTyping': { names: string | number }
6068+
'chat.voiceRecording.speechRecognitionFailed': { error: string | number }
60326069
'deskpet.toasts.start_failed': { charname: string | number; message: string | number }
60336070
'deskpet.toasts.started': { charname: string | number }
60346071
'deskpet.toasts.stop_failed': { charname: string | number; message: string | number }
@@ -6231,6 +6268,8 @@ export type LocaleKeyParams = {
62316268
'social.actions.repostFailed': { error: string | number }
62326269
'social.actions.saveFailed': { error: string | number }
62336270
'social.bootstrapFailed': { error: string | number }
6271+
'social.composer.speechRecognitionFailed': { error: string | number }
6272+
'social.composer.voiceFailed': { error: string | number }
62346273
'social.connectNodeFailed': { error: string | number }
62356274
'social.drafts.deleteFailed': { error: string | number }
62366275
'social.drafts.loadFailed': { error: string | number }

src/public/locales/ar-SA.json

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,24 @@
960960
"title": "تعيين كمصدر افتراضي لخدمة الترجمة"
961961
}
962962
}
963+
},
964+
"generators/SpeechRecognition": {
965+
"title": "مولّد خدمة التعرف على الكلام",
966+
"subtitle": "حوّل الموجات الصوتية إلى كلمات — ما تلفظه يُكتب أمامك.",
967+
"card": {
968+
"defaultCheckbox": {
969+
"title": "تعيين كمولّد خدمة التعرف على الكلام الافتراضي"
970+
}
971+
}
972+
},
973+
"sources/SpeechRecognition": {
974+
"title": "مصدر خدمة التعرف على الكلام",
975+
"subtitle": "نافذة تسمع الأصوات، حيث يبدأ المعنى.",
976+
"card": {
977+
"defaultCheckbox": {
978+
"title": "تعيين كمصدر خدمة التعرف على الكلام الافتراضي"
979+
}
980+
}
963981
}
964982
}
965983
},
@@ -2849,7 +2867,9 @@
28492867
}
28502868
},
28512869
"voiceRecording": {
2852-
"errorAccessingMicrophone": "لا يمكن الوصول إلى الميكروفون"
2870+
"errorAccessingMicrophone": "لا يمكن الوصول إلى الميكروفون",
2871+
"confirmSpeechRecognition": "هل تريد ملء النص تلقائياً بالتعرف على الكلام؟ سيُحفظ مرفق التسجيل.",
2872+
"speechRecognitionFailed": "فشل التعرف على الكلام: ${error}"
28532873
},
28542874
"attachment": {
28552875
"buttons": {
@@ -2864,6 +2884,12 @@
28642884
},
28652885
"deleteIcon": {
28662886
"alt": "أيقونة الحذف"
2887+
},
2888+
"more": {
2889+
"title": "أكثر"
2890+
},
2891+
"recognize": {
2892+
"title": "تحويل الصوت إلى نص"
28672893
}
28682894
}
28692895
},
@@ -4279,7 +4305,17 @@
42794305
"followers7d": "متابعون لأكثر من 7 أيام",
42804306
"label": "من يستطيع الرد"
42814307
}
4282-
}
4308+
},
4309+
"voiceButton": {
4310+
"title": "تسجيل",
4311+
"aria-label": "تسجيل"
4312+
},
4313+
"voiceFailed": "فشل التسجيل: ${error}",
4314+
"audioTranscript": {
4315+
"placeholder": "نص التفريغ أو ملاحظات"
4316+
},
4317+
"confirmSpeechRecognition": "هل تريد ملء المحتوى تلقائياً بالتعرف على الكلام؟ سيُحفظ مرفق التسجيل.",
4318+
"speechRecognitionFailed": "فشل التعرف على الكلام: ${error}"
42834319
},
42844320
"feed": {
42854321
"refresh": {

src/public/locales/de-DE.json

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,24 @@
960960
"title": "Als Standard-Übersetzungsdienstquelle festlegen"
961961
}
962962
}
963+
},
964+
"generators/SpeechRecognition": {
965+
"title": "Generator für Spracherkennungsdienste",
966+
"subtitle": "Verwandle Schallwellen in Schrift — gesprochenes Wort, geschriebene Zeile.",
967+
"card": {
968+
"defaultCheckbox": {
969+
"title": "Als Standardgenerator für Spracherkennungsdienste festlegen"
970+
}
971+
}
972+
},
973+
"sources/SpeechRecognition": {
974+
"title": "Quelle des Spracherkennungsdienstes",
975+
"subtitle": "Ein Fenster zum Hören von Stimme und Klang — wo Bedeutung beginnt.",
976+
"card": {
977+
"defaultCheckbox": {
978+
"title": "Als Standardquelle für Spracherkennungsdienste festlegen"
979+
}
980+
}
963981
}
964982
}
965983
},
@@ -2849,7 +2867,9 @@
28492867
}
28502868
},
28512869
"voiceRecording": {
2852-
"errorAccessingMicrophone": "Fehler beim Zugriff auf das Mikrofon."
2870+
"errorAccessingMicrophone": "Fehler beim Zugriff auf das Mikrofon.",
2871+
"confirmSpeechRecognition": "Text per Spracherkennung automatisch ausfüllen? Die Audioaufnahme bleibt als Anhang erhalten.",
2872+
"speechRecognitionFailed": "Spracherkennung fehlgeschlagen: ${error}"
28532873
},
28542874
"attachment": {
28552875
"buttons": {
@@ -2864,6 +2884,12 @@
28642884
},
28652885
"deleteIcon": {
28662886
"alt": "Löschen-Symbol"
2887+
},
2888+
"more": {
2889+
"title": "Mehr"
2890+
},
2891+
"recognize": {
2892+
"title": "Sprache in Text"
28672893
}
28682894
}
28692895
},
@@ -4279,7 +4305,17 @@
42794305
"followers7d": "Follower seit mehr als 7 Tagen",
42804306
"label": "Wer kann antworten?"
42814307
}
4282-
}
4308+
},
4309+
"voiceButton": {
4310+
"title": "Aufnehmen",
4311+
"aria-label": "Aufnehmen"
4312+
},
4313+
"voiceFailed": "Aufnahme fehlgeschlagen: ${error}",
4314+
"audioTranscript": {
4315+
"placeholder": "Transkript oder Notizen"
4316+
},
4317+
"confirmSpeechRecognition": "Beitrag per Spracherkennung automatisch ausfüllen? Die Audioaufnahme bleibt als Anhang erhalten.",
4318+
"speechRecognitionFailed": "Spracherkennung fehlgeschlagen: ${error}"
42834319
},
42844320
"feed": {
42854321
"refresh": {

src/public/locales/emoji.json

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,24 @@
960960
"title": "📌🌐🚰"
961961
}
962962
}
963+
},
964+
"generators/SpeechRecognition": {
965+
"title": "🎙️⚙️✨",
966+
"subtitle": "🎙️➡️📝",
967+
"card": {
968+
"defaultCheckbox": {
969+
"title": "📌🎙️⚙️✨"
970+
}
971+
}
972+
},
973+
"sources/SpeechRecognition": {
974+
"title": "🎙️🚰",
975+
"subtitle": "👂🎙️➡️💬",
976+
"card": {
977+
"defaultCheckbox": {
978+
"title": "📌🎙️🚰"
979+
}
980+
}
963981
}
964982
}
965983
},
@@ -2849,7 +2867,9 @@
28492867
}
28502868
},
28512869
"voiceRecording": {
2852-
"errorAccessingMicrophone": "🚫🎤"
2870+
"errorAccessingMicrophone": "🚫🎤",
2871+
"confirmSpeechRecognition": "🎙️➡️📝❓🔊📎✅",
2872+
"speechRecognitionFailed": "🎙️❌:${error}"
28532873
},
28542874
"attachment": {
28552875
"buttons": {
@@ -2864,6 +2884,12 @@
28642884
},
28652885
"deleteIcon": {
28662886
"alt": "🗑️"
2887+
},
2888+
"more": {
2889+
"title": ""
2890+
},
2891+
"recognize": {
2892+
"title": "🎙️➡️📝"
28672893
}
28682894
}
28692895
},
@@ -4279,7 +4305,17 @@
42794305
"followers7d": "7️⃣📅➕👀",
42804306
"label": "💬🔐"
42814307
}
4282-
}
4308+
},
4309+
"voiceButton": {
4310+
"title": "🎤",
4311+
"aria-label": "🎤"
4312+
},
4313+
"voiceFailed": "🎤❌:${error}",
4314+
"audioTranscript": {
4315+
"placeholder": "📝💬"
4316+
},
4317+
"confirmSpeechRecognition": "🎙️➡️📝❓🔊📎✅",
4318+
"speechRecognitionFailed": "🎙️❌:${error}"
42834319
},
42844320
"feed": {
42854321
"refresh": {

0 commit comments

Comments
 (0)