@desktalk/miniapp-preference 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/dist/backend.d.ts +12 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +622 -0
- package/dist/backend.js.map +7 -0
- package/dist/components/AiProviderList.d.ts +8 -0
- package/dist/components/AiProviderList.d.ts.map +1 -0
- package/dist/components/PreferenceActions.d.ts +8 -0
- package/dist/components/PreferenceActions.d.ts.map +1 -0
- package/dist/components/PreferenceCategoryList.d.ts +9 -0
- package/dist/components/PreferenceCategoryList.d.ts.map +1 -0
- package/dist/components/PreferenceRow.d.ts +9 -0
- package/dist/components/PreferenceRow.d.ts.map +1 -0
- package/dist/components/PreferenceSection.d.ts +8 -0
- package/dist/components/PreferenceSection.d.ts.map +1 -0
- package/dist/components/VoiceProviderList.d.ts +8 -0
- package/dist/components/VoiceProviderList.d.ts.map +1 -0
- package/dist/frontend.d.ts +3 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +4652 -0
- package/dist/frontend.js.map +7 -0
- package/dist/i18n/manifest.json +19 -0
- package/dist/i18n/zh-CN.json +4 -0
- package/dist/meta.json +3 -0
- package/dist/schema.d.ts +55 -0
- package/dist/schema.d.ts.map +1 -0
- package/icons/miniapp-preference-icon.png +0 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeskTalk contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MiniAppManifest, MiniAppContext, MiniAppBackendActivation } from '@desktalk/sdk';
|
|
2
|
+
export declare const manifest: MiniAppManifest;
|
|
3
|
+
/**
|
|
4
|
+
* The Preference MiniApp uses ctx.storage as the persistence layer for settings.
|
|
5
|
+
* In production, the core would provide a privileged ctx.config hook that writes
|
|
6
|
+
* to <config>/config.toml. For now, ctx.storage serves as the backing store.
|
|
7
|
+
*
|
|
8
|
+
* All settings are stored under the key "config" as a flat Config object.
|
|
9
|
+
*/
|
|
10
|
+
export declare function activate(ctx: MiniAppContext): MiniAppBackendActivation;
|
|
11
|
+
export declare function deactivate(): void;
|
|
12
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAmB/F,eAAO,MAAM,QAAQ,EAAE,eAMtB,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,wBAAwB,CAiTtE;AAID,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
var DEFAULT_AI_PROVIDER_ID = "openai";
|
|
3
|
+
var AI_PROVIDER_DEFINITIONS = [
|
|
4
|
+
{ id: "anthropic", label: "Anthropic", supportsApiKey: true, supportsBaseUrl: false },
|
|
5
|
+
{
|
|
6
|
+
id: "azure-openai-responses",
|
|
7
|
+
label: "Azure OpenAI",
|
|
8
|
+
supportsApiKey: true,
|
|
9
|
+
supportsBaseUrl: true
|
|
10
|
+
},
|
|
11
|
+
{ id: DEFAULT_AI_PROVIDER_ID, label: "OpenAI", supportsApiKey: true, supportsBaseUrl: true },
|
|
12
|
+
{ id: "google", label: "Google Gemini", supportsApiKey: true, supportsBaseUrl: false },
|
|
13
|
+
{
|
|
14
|
+
id: "mistral",
|
|
15
|
+
label: "Mistral",
|
|
16
|
+
supportsApiKey: true,
|
|
17
|
+
supportsBaseUrl: true
|
|
18
|
+
},
|
|
19
|
+
{ id: "groq", label: "Groq", supportsApiKey: true, supportsBaseUrl: true },
|
|
20
|
+
{
|
|
21
|
+
id: "cerebras",
|
|
22
|
+
label: "Cerebras",
|
|
23
|
+
supportsApiKey: true,
|
|
24
|
+
supportsBaseUrl: true
|
|
25
|
+
},
|
|
26
|
+
{ id: "xai", label: "xAI", supportsApiKey: true, supportsBaseUrl: true },
|
|
27
|
+
{
|
|
28
|
+
id: "openrouter",
|
|
29
|
+
label: "OpenRouter",
|
|
30
|
+
supportsApiKey: true,
|
|
31
|
+
supportsBaseUrl: true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "vercel-ai-gateway",
|
|
35
|
+
label: "Vercel AI Gateway",
|
|
36
|
+
supportsApiKey: true,
|
|
37
|
+
supportsBaseUrl: true
|
|
38
|
+
},
|
|
39
|
+
{ id: "zai", label: "ZAI", supportsApiKey: true, supportsBaseUrl: false },
|
|
40
|
+
{ id: "opencode", label: "OpenCode Zen", supportsApiKey: true, supportsBaseUrl: false },
|
|
41
|
+
{ id: "opencode-go", label: "OpenCode Go", supportsApiKey: true, supportsBaseUrl: false },
|
|
42
|
+
{
|
|
43
|
+
id: "huggingface",
|
|
44
|
+
label: "Hugging Face",
|
|
45
|
+
supportsApiKey: true,
|
|
46
|
+
supportsBaseUrl: true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "kimi-coding",
|
|
50
|
+
label: "Kimi For Coding",
|
|
51
|
+
supportsApiKey: true,
|
|
52
|
+
supportsBaseUrl: false
|
|
53
|
+
},
|
|
54
|
+
{ id: "minimax", label: "MiniMax", supportsApiKey: true, supportsBaseUrl: false },
|
|
55
|
+
{
|
|
56
|
+
id: "minimax-cn",
|
|
57
|
+
label: "MiniMax China",
|
|
58
|
+
supportsApiKey: true,
|
|
59
|
+
supportsBaseUrl: false
|
|
60
|
+
},
|
|
61
|
+
{ id: "ollama", label: "Ollama", supportsApiKey: false, supportsBaseUrl: true }
|
|
62
|
+
];
|
|
63
|
+
var DEFAULT_VOICE_PROVIDER_ID = "openai-whisper";
|
|
64
|
+
var VOICE_PROVIDER_DEFINITIONS = [
|
|
65
|
+
{
|
|
66
|
+
id: DEFAULT_VOICE_PROVIDER_ID,
|
|
67
|
+
label: "OpenAI Whisper",
|
|
68
|
+
supportsApiKey: true,
|
|
69
|
+
supportsBaseUrl: true,
|
|
70
|
+
supportsModel: true,
|
|
71
|
+
supportsAzureDeployment: false,
|
|
72
|
+
supportsAzureApiVersion: false
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "azure-openai-whisper",
|
|
76
|
+
label: "Azure OpenAI Whisper",
|
|
77
|
+
supportsApiKey: true,
|
|
78
|
+
supportsBaseUrl: true,
|
|
79
|
+
supportsModel: false,
|
|
80
|
+
supportsAzureDeployment: true,
|
|
81
|
+
supportsAzureApiVersion: true
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
var AI_PROVIDER_IDS = new Set(AI_PROVIDER_DEFINITIONS.map((provider) => provider.id));
|
|
85
|
+
var VOICE_PROVIDER_IDS = new Set(VOICE_PROVIDER_DEFINITIONS.map((provider) => provider.id));
|
|
86
|
+
function getAiProviderDefinition(providerId) {
|
|
87
|
+
return AI_PROVIDER_DEFINITIONS.find((provider) => provider.id === providerId);
|
|
88
|
+
}
|
|
89
|
+
function getAiProviderConfigKeys(providerId) {
|
|
90
|
+
const definition = getAiProviderDefinition(providerId);
|
|
91
|
+
if (!definition) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
const keys = [`ai.providers.${providerId}.model`];
|
|
95
|
+
if (definition.supportsApiKey) {
|
|
96
|
+
keys.push(`ai.providers.${providerId}.apiKey`);
|
|
97
|
+
}
|
|
98
|
+
if (definition.supportsBaseUrl) {
|
|
99
|
+
keys.push(`ai.providers.${providerId}.baseUrl`);
|
|
100
|
+
}
|
|
101
|
+
return keys;
|
|
102
|
+
}
|
|
103
|
+
function parseAiEnabledProviders(value) {
|
|
104
|
+
if (typeof value !== "string") {
|
|
105
|
+
return [DEFAULT_AI_PROVIDER_ID];
|
|
106
|
+
}
|
|
107
|
+
const providers = value.split(",").map((item) => item.trim()).filter(
|
|
108
|
+
(item, index, items) => item && items.indexOf(item) === index && AI_PROVIDER_IDS.has(item)
|
|
109
|
+
);
|
|
110
|
+
return providers.length > 0 ? providers : [DEFAULT_AI_PROVIDER_ID];
|
|
111
|
+
}
|
|
112
|
+
function serializeAiEnabledProviders(providerIds) {
|
|
113
|
+
return parseAiEnabledProviders(providerIds.join(",")).join(",");
|
|
114
|
+
}
|
|
115
|
+
function hasAiProviderConfig(config, providerId) {
|
|
116
|
+
return getAiProviderConfigKeys(providerId).some((key) => {
|
|
117
|
+
const value = config[key];
|
|
118
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function getVoiceProviderDefinition(providerId) {
|
|
122
|
+
return VOICE_PROVIDER_DEFINITIONS.find((provider) => provider.id === providerId);
|
|
123
|
+
}
|
|
124
|
+
function getVoiceProviderConfigKeys(providerId) {
|
|
125
|
+
const definition = getVoiceProviderDefinition(providerId);
|
|
126
|
+
if (!definition) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const keys = [];
|
|
130
|
+
if (definition.supportsApiKey) {
|
|
131
|
+
keys.push(`voice.providers.${providerId}.apiKey`);
|
|
132
|
+
}
|
|
133
|
+
if (definition.supportsModel) {
|
|
134
|
+
keys.push(`voice.providers.${providerId}.model`);
|
|
135
|
+
}
|
|
136
|
+
if (definition.supportsBaseUrl) {
|
|
137
|
+
keys.push(`voice.providers.${providerId}.baseUrl`);
|
|
138
|
+
}
|
|
139
|
+
if (definition.supportsAzureDeployment) {
|
|
140
|
+
keys.push(`voice.providers.${providerId}.azureDeployment`);
|
|
141
|
+
}
|
|
142
|
+
if (definition.supportsAzureApiVersion) {
|
|
143
|
+
keys.push(`voice.providers.${providerId}.azureApiVersion`);
|
|
144
|
+
}
|
|
145
|
+
return keys;
|
|
146
|
+
}
|
|
147
|
+
function parseVoiceEnabledProviders(value) {
|
|
148
|
+
if (typeof value !== "string") {
|
|
149
|
+
return [DEFAULT_VOICE_PROVIDER_ID];
|
|
150
|
+
}
|
|
151
|
+
const providers = value.split(",").map((item) => item.trim()).filter(
|
|
152
|
+
(item, index, items) => item && items.indexOf(item) === index && VOICE_PROVIDER_IDS.has(item)
|
|
153
|
+
);
|
|
154
|
+
return providers.length > 0 ? providers : [DEFAULT_VOICE_PROVIDER_ID];
|
|
155
|
+
}
|
|
156
|
+
function serializeVoiceEnabledProviders(providerIds) {
|
|
157
|
+
return parseVoiceEnabledProviders(providerIds.join(",")).join(",");
|
|
158
|
+
}
|
|
159
|
+
function hasVoiceProviderConfig(config, providerId) {
|
|
160
|
+
return getVoiceProviderConfigKeys(providerId).some((key) => {
|
|
161
|
+
const value = config[key];
|
|
162
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function getAiProviderPreferenceSchemas() {
|
|
166
|
+
const schemas = [
|
|
167
|
+
{
|
|
168
|
+
key: "ai.enabledProviders",
|
|
169
|
+
label: "Enabled Providers",
|
|
170
|
+
description: "Ordered list of configured AI providers.",
|
|
171
|
+
type: "string",
|
|
172
|
+
default: DEFAULT_AI_PROVIDER_ID,
|
|
173
|
+
category: "AI"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: "ai.defaultProvider",
|
|
177
|
+
label: "Default Provider",
|
|
178
|
+
description: "Provider selected by default for chat and tool execution.",
|
|
179
|
+
type: "string",
|
|
180
|
+
default: DEFAULT_AI_PROVIDER_ID,
|
|
181
|
+
options: AI_PROVIDER_DEFINITIONS.map((provider) => provider.id),
|
|
182
|
+
category: "AI"
|
|
183
|
+
}
|
|
184
|
+
];
|
|
185
|
+
for (const provider of AI_PROVIDER_DEFINITIONS) {
|
|
186
|
+
schemas.push({
|
|
187
|
+
key: `ai.providers.${provider.id}.model`,
|
|
188
|
+
label: `${provider.label} Model`,
|
|
189
|
+
description: `Model identifier to use when ${provider.label} is selected.`,
|
|
190
|
+
type: "string",
|
|
191
|
+
default: "",
|
|
192
|
+
category: "AI"
|
|
193
|
+
});
|
|
194
|
+
if (provider.supportsApiKey) {
|
|
195
|
+
schemas.push({
|
|
196
|
+
key: `ai.providers.${provider.id}.apiKey`,
|
|
197
|
+
label: `${provider.label} API Key`,
|
|
198
|
+
description: `API key for ${provider.label}.`,
|
|
199
|
+
type: "string",
|
|
200
|
+
default: "",
|
|
201
|
+
category: "AI",
|
|
202
|
+
sensitive: true
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (provider.supportsBaseUrl) {
|
|
206
|
+
schemas.push({
|
|
207
|
+
key: `ai.providers.${provider.id}.baseUrl`,
|
|
208
|
+
label: `${provider.label} Base URL`,
|
|
209
|
+
description: `Optional custom API base URL for ${provider.label}.`,
|
|
210
|
+
type: "string",
|
|
211
|
+
default: "",
|
|
212
|
+
category: "AI"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
schemas.push({
|
|
217
|
+
key: "ai.maxTokens",
|
|
218
|
+
label: "Max Tokens",
|
|
219
|
+
description: "Maximum tokens per AI response.",
|
|
220
|
+
type: "number",
|
|
221
|
+
default: 4096,
|
|
222
|
+
min: 256,
|
|
223
|
+
max: 128e3,
|
|
224
|
+
category: "AI"
|
|
225
|
+
});
|
|
226
|
+
return schemas;
|
|
227
|
+
}
|
|
228
|
+
var AI_PREFERENCE_SCHEMAS = getAiProviderPreferenceSchemas();
|
|
229
|
+
function getVoiceProviderPreferenceSchemas() {
|
|
230
|
+
const schemas = [
|
|
231
|
+
{
|
|
232
|
+
key: "voice.enabledProviders",
|
|
233
|
+
label: "Enabled Providers",
|
|
234
|
+
description: "Ordered list of configured STT providers.",
|
|
235
|
+
type: "string",
|
|
236
|
+
default: DEFAULT_VOICE_PROVIDER_ID,
|
|
237
|
+
category: "Voice"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
key: "voice.defaultProvider",
|
|
241
|
+
label: "Default Provider",
|
|
242
|
+
description: "Provider selected by default for voice transcription.",
|
|
243
|
+
type: "string",
|
|
244
|
+
default: DEFAULT_VOICE_PROVIDER_ID,
|
|
245
|
+
options: VOICE_PROVIDER_DEFINITIONS.map((provider) => provider.id),
|
|
246
|
+
category: "Voice"
|
|
247
|
+
}
|
|
248
|
+
];
|
|
249
|
+
for (const provider of VOICE_PROVIDER_DEFINITIONS) {
|
|
250
|
+
if (provider.supportsApiKey) {
|
|
251
|
+
schemas.push({
|
|
252
|
+
key: `voice.providers.${provider.id}.apiKey`,
|
|
253
|
+
label: `${provider.label} API Key`,
|
|
254
|
+
description: `API key for ${provider.label}.`,
|
|
255
|
+
type: "string",
|
|
256
|
+
default: "",
|
|
257
|
+
category: "Voice",
|
|
258
|
+
sensitive: true
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (provider.supportsModel) {
|
|
262
|
+
schemas.push({
|
|
263
|
+
key: `voice.providers.${provider.id}.model`,
|
|
264
|
+
label: `${provider.label} Model`,
|
|
265
|
+
description: `Model identifier to use when ${provider.label} is selected.`,
|
|
266
|
+
type: "string",
|
|
267
|
+
default: "whisper-1",
|
|
268
|
+
category: "Voice"
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (provider.supportsBaseUrl) {
|
|
272
|
+
schemas.push({
|
|
273
|
+
key: `voice.providers.${provider.id}.baseUrl`,
|
|
274
|
+
label: `${provider.label} Base URL`,
|
|
275
|
+
description: provider.id === "azure-openai-whisper" ? "Base URL for Azure OpenAI Whisper requests." : `Optional custom API base URL for ${provider.label}.`,
|
|
276
|
+
type: "string",
|
|
277
|
+
default: provider.id === DEFAULT_VOICE_PROVIDER_ID ? "https://api.openai.com/v1" : "",
|
|
278
|
+
category: "Voice"
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (provider.supportsAzureDeployment) {
|
|
282
|
+
schemas.push({
|
|
283
|
+
key: `voice.providers.${provider.id}.azureDeployment`,
|
|
284
|
+
label: `${provider.label} Deployment`,
|
|
285
|
+
description: "Azure OpenAI deployment name for Whisper transcription.",
|
|
286
|
+
type: "string",
|
|
287
|
+
default: "",
|
|
288
|
+
category: "Voice"
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (provider.supportsAzureApiVersion) {
|
|
292
|
+
schemas.push({
|
|
293
|
+
key: `voice.providers.${provider.id}.azureApiVersion`,
|
|
294
|
+
label: `${provider.label} API Version`,
|
|
295
|
+
description: "Azure OpenAI API version used for transcription requests.",
|
|
296
|
+
type: "string",
|
|
297
|
+
default: "2024-06-01",
|
|
298
|
+
category: "Voice"
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return schemas;
|
|
303
|
+
}
|
|
304
|
+
var VOICE_PREFERENCE_SCHEMAS = getVoiceProviderPreferenceSchemas();
|
|
305
|
+
var PREFERENCE_SCHEMAS = [
|
|
306
|
+
// ─── General ─────────────────────────────────────────────────────────────
|
|
307
|
+
{
|
|
308
|
+
key: "general.theme",
|
|
309
|
+
label: "Theme",
|
|
310
|
+
description: "UI theme: light or dark.",
|
|
311
|
+
type: "string",
|
|
312
|
+
default: "light",
|
|
313
|
+
options: ["light", "dark"],
|
|
314
|
+
category: "General"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
key: "general.accentColor",
|
|
318
|
+
label: "Accent Color",
|
|
319
|
+
description: "Primary theme color. Accepts hex values or any CSS color string.",
|
|
320
|
+
type: "string",
|
|
321
|
+
default: "#7c6ff7",
|
|
322
|
+
category: "General"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
key: "general.language",
|
|
326
|
+
label: "Language",
|
|
327
|
+
description: "UI language/locale.",
|
|
328
|
+
type: "string",
|
|
329
|
+
default: "en",
|
|
330
|
+
options: ["en", "zh", "ja", "ko", "es", "fr", "de"],
|
|
331
|
+
category: "General"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
key: "general.dataDirectory",
|
|
335
|
+
label: "Data Directory",
|
|
336
|
+
description: "Override the base data directory. Leave empty for platform default (resolved via env-paths).",
|
|
337
|
+
type: "string",
|
|
338
|
+
default: "",
|
|
339
|
+
category: "General",
|
|
340
|
+
requiresRestart: true
|
|
341
|
+
},
|
|
342
|
+
// ─── Server ──────────────────────────────────────────────────────────────
|
|
343
|
+
{
|
|
344
|
+
key: "server.host",
|
|
345
|
+
label: "Host",
|
|
346
|
+
description: "Server bind address.",
|
|
347
|
+
type: "string",
|
|
348
|
+
default: "localhost",
|
|
349
|
+
category: "Server",
|
|
350
|
+
requiresRestart: true
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
key: "server.port",
|
|
354
|
+
label: "Port",
|
|
355
|
+
description: "Server listen port.",
|
|
356
|
+
type: "number",
|
|
357
|
+
default: 3e3,
|
|
358
|
+
min: 1,
|
|
359
|
+
max: 65535,
|
|
360
|
+
category: "Server",
|
|
361
|
+
requiresRestart: true
|
|
362
|
+
},
|
|
363
|
+
// ─── AI ──────────────────────────────────────────────────────────────────
|
|
364
|
+
...AI_PREFERENCE_SCHEMAS,
|
|
365
|
+
// ─── Voice ────────────────────────────────────────────────────────────
|
|
366
|
+
...VOICE_PREFERENCE_SCHEMAS,
|
|
367
|
+
{
|
|
368
|
+
key: "voice.silenceTimeoutMs",
|
|
369
|
+
label: "Silence Timeout",
|
|
370
|
+
description: "Silence duration (ms) before finalizing an utterance.",
|
|
371
|
+
type: "number",
|
|
372
|
+
default: 800,
|
|
373
|
+
min: 200,
|
|
374
|
+
max: 5e3,
|
|
375
|
+
category: "Voice"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
key: "voice.energyThreshold",
|
|
379
|
+
label: "Energy Threshold",
|
|
380
|
+
description: "RMS energy threshold for voice activity detection (0\u201332767).",
|
|
381
|
+
type: "number",
|
|
382
|
+
default: 500,
|
|
383
|
+
min: 50,
|
|
384
|
+
max: 1e4,
|
|
385
|
+
category: "Voice"
|
|
386
|
+
}
|
|
387
|
+
];
|
|
388
|
+
function getDefaultConfig() {
|
|
389
|
+
const config = {};
|
|
390
|
+
for (const schema of PREFERENCE_SCHEMAS) {
|
|
391
|
+
config[schema.key] = schema.default;
|
|
392
|
+
}
|
|
393
|
+
return config;
|
|
394
|
+
}
|
|
395
|
+
function getSchema(key) {
|
|
396
|
+
return PREFERENCE_SCHEMAS.find((s) => s.key === key);
|
|
397
|
+
}
|
|
398
|
+
function maskSensitive(value) {
|
|
399
|
+
if (value.length <= 4) return "****";
|
|
400
|
+
return "*".repeat(value.length - 4) + value.slice(-4);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/backend.ts
|
|
404
|
+
var manifest = {
|
|
405
|
+
id: "preference",
|
|
406
|
+
name: "Preferences",
|
|
407
|
+
icon: "\u2699\uFE0F",
|
|
408
|
+
version: "0.1.0",
|
|
409
|
+
description: "Application settings and configuration"
|
|
410
|
+
};
|
|
411
|
+
function activate(ctx) {
|
|
412
|
+
ctx.logger.info("Preference MiniApp activated");
|
|
413
|
+
const defaults = getDefaultConfig();
|
|
414
|
+
async function loadConfig() {
|
|
415
|
+
const stored = await ctx.storage.get("config");
|
|
416
|
+
const config = { ...defaults, ...stored ?? {} };
|
|
417
|
+
const legacyProvider = typeof stored?.["ai.provider"] === "string" ? stored["ai.provider"] : void 0;
|
|
418
|
+
if (legacyProvider && typeof stored?.["ai.defaultProvider"] !== "string") {
|
|
419
|
+
config["ai.defaultProvider"] = legacyProvider;
|
|
420
|
+
}
|
|
421
|
+
if (legacyProvider) {
|
|
422
|
+
const legacyModel = typeof stored?.["ai.model"] === "string" ? stored["ai.model"] : void 0;
|
|
423
|
+
const legacyApiKey = typeof stored?.["ai.apiKey"] === "string" ? stored["ai.apiKey"] : void 0;
|
|
424
|
+
const legacyBaseUrl = typeof stored?.["ai.baseUrl"] === "string" ? stored["ai.baseUrl"] : void 0;
|
|
425
|
+
const providerModelKey = `ai.providers.${legacyProvider}.model`;
|
|
426
|
+
const providerApiKeyKey = `ai.providers.${legacyProvider}.apiKey`;
|
|
427
|
+
const providerBaseUrlKey = `ai.providers.${legacyProvider}.baseUrl`;
|
|
428
|
+
if (legacyModel && !config[providerModelKey]) {
|
|
429
|
+
config[providerModelKey] = legacyModel;
|
|
430
|
+
}
|
|
431
|
+
if (legacyApiKey && !config[providerApiKeyKey]) {
|
|
432
|
+
config[providerApiKeyKey] = legacyApiKey;
|
|
433
|
+
}
|
|
434
|
+
if (legacyBaseUrl && !config[providerBaseUrlKey]) {
|
|
435
|
+
config[providerBaseUrlKey] = legacyBaseUrl;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const enabledProviders = parseAiEnabledProviders(stored?.["ai.enabledProviders"]);
|
|
439
|
+
const derivedProviders = enabledProviders.filter(
|
|
440
|
+
(providerId) => hasAiProviderConfig(config, providerId)
|
|
441
|
+
);
|
|
442
|
+
for (const schema of PREFERENCE_SCHEMAS) {
|
|
443
|
+
if (!schema.key.startsWith("ai.providers.")) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const match = /^ai\.providers\.([^.]+)\./.exec(schema.key);
|
|
447
|
+
const providerId = match?.[1];
|
|
448
|
+
if (providerId && hasAiProviderConfig(config, providerId) && !derivedProviders.includes(providerId)) {
|
|
449
|
+
derivedProviders.push(providerId);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const defaultProvider = typeof config["ai.defaultProvider"] === "string" && config["ai.defaultProvider"] ? String(config["ai.defaultProvider"]) : DEFAULT_AI_PROVIDER_ID;
|
|
453
|
+
if (!derivedProviders.includes(defaultProvider)) {
|
|
454
|
+
derivedProviders.unshift(defaultProvider);
|
|
455
|
+
}
|
|
456
|
+
for (const providerId of enabledProviders) {
|
|
457
|
+
if (!derivedProviders.includes(providerId)) {
|
|
458
|
+
derivedProviders.push(providerId);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
config["ai.enabledProviders"] = serializeAiEnabledProviders(derivedProviders);
|
|
462
|
+
config["ai.defaultProvider"] = derivedProviders[0] ?? DEFAULT_AI_PROVIDER_ID;
|
|
463
|
+
const legacyVoiceProvider = typeof stored?.["voice.provider"] === "string" ? stored["voice.provider"] : void 0;
|
|
464
|
+
if (legacyVoiceProvider && typeof stored?.["voice.defaultProvider"] !== "string") {
|
|
465
|
+
config["voice.defaultProvider"] = legacyVoiceProvider;
|
|
466
|
+
}
|
|
467
|
+
if (legacyVoiceProvider) {
|
|
468
|
+
const legacyVoiceApiKey = typeof stored?.["voice.apiKey"] === "string" ? stored["voice.apiKey"] : void 0;
|
|
469
|
+
const legacyVoiceModel = typeof stored?.["voice.model"] === "string" ? stored["voice.model"] : void 0;
|
|
470
|
+
const legacyVoiceBaseUrl = typeof stored?.["voice.baseUrl"] === "string" ? stored["voice.baseUrl"] : void 0;
|
|
471
|
+
const legacyVoiceDeployment = typeof stored?.["voice.azureDeployment"] === "string" ? stored["voice.azureDeployment"] : void 0;
|
|
472
|
+
const legacyVoiceApiVersion = typeof stored?.["voice.azureApiVersion"] === "string" ? stored["voice.azureApiVersion"] : void 0;
|
|
473
|
+
if (legacyVoiceApiKey && !config[`voice.providers.${legacyVoiceProvider}.apiKey`]) {
|
|
474
|
+
config[`voice.providers.${legacyVoiceProvider}.apiKey`] = legacyVoiceApiKey;
|
|
475
|
+
}
|
|
476
|
+
if (legacyVoiceModel && !config[`voice.providers.${legacyVoiceProvider}.model`]) {
|
|
477
|
+
config[`voice.providers.${legacyVoiceProvider}.model`] = legacyVoiceModel;
|
|
478
|
+
}
|
|
479
|
+
if (legacyVoiceBaseUrl && !config[`voice.providers.${legacyVoiceProvider}.baseUrl`]) {
|
|
480
|
+
config[`voice.providers.${legacyVoiceProvider}.baseUrl`] = legacyVoiceBaseUrl;
|
|
481
|
+
}
|
|
482
|
+
if (legacyVoiceDeployment && !config[`voice.providers.${legacyVoiceProvider}.azureDeployment`]) {
|
|
483
|
+
config[`voice.providers.${legacyVoiceProvider}.azureDeployment`] = legacyVoiceDeployment;
|
|
484
|
+
}
|
|
485
|
+
if (legacyVoiceApiVersion && !config[`voice.providers.${legacyVoiceProvider}.azureApiVersion`]) {
|
|
486
|
+
config[`voice.providers.${legacyVoiceProvider}.azureApiVersion`] = legacyVoiceApiVersion;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const enabledVoiceProviders = parseVoiceEnabledProviders(stored?.["voice.enabledProviders"]);
|
|
490
|
+
const derivedVoiceProviders = enabledVoiceProviders.filter(
|
|
491
|
+
(providerId) => hasVoiceProviderConfig(config, providerId)
|
|
492
|
+
);
|
|
493
|
+
for (const schema of PREFERENCE_SCHEMAS) {
|
|
494
|
+
if (!schema.key.startsWith("voice.providers.")) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
const match = /^voice\.providers\.([^.]+)\./.exec(schema.key);
|
|
498
|
+
const providerId = match?.[1];
|
|
499
|
+
if (providerId && hasVoiceProviderConfig(config, providerId) && !derivedVoiceProviders.includes(providerId)) {
|
|
500
|
+
derivedVoiceProviders.push(providerId);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const defaultVoiceProvider = typeof config["voice.defaultProvider"] === "string" && config["voice.defaultProvider"] ? String(config["voice.defaultProvider"]) : DEFAULT_VOICE_PROVIDER_ID;
|
|
504
|
+
if (!derivedVoiceProviders.includes(defaultVoiceProvider)) {
|
|
505
|
+
derivedVoiceProviders.unshift(defaultVoiceProvider);
|
|
506
|
+
}
|
|
507
|
+
for (const providerId of enabledVoiceProviders) {
|
|
508
|
+
if (!derivedVoiceProviders.includes(providerId)) {
|
|
509
|
+
derivedVoiceProviders.push(providerId);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
config["voice.enabledProviders"] = serializeVoiceEnabledProviders(derivedVoiceProviders);
|
|
513
|
+
config["voice.defaultProvider"] = derivedVoiceProviders[0] ?? DEFAULT_VOICE_PROVIDER_ID;
|
|
514
|
+
return config;
|
|
515
|
+
}
|
|
516
|
+
async function saveConfig(config) {
|
|
517
|
+
await ctx.storage.set("config", config);
|
|
518
|
+
}
|
|
519
|
+
function maskConfig(config) {
|
|
520
|
+
const masked = { ...config };
|
|
521
|
+
for (const schema of PREFERENCE_SCHEMAS) {
|
|
522
|
+
if (schema.sensitive && typeof masked[schema.key] === "string") {
|
|
523
|
+
const raw = masked[schema.key];
|
|
524
|
+
masked[schema.key] = raw ? maskSensitive(raw) : "";
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return masked;
|
|
528
|
+
}
|
|
529
|
+
ctx.messaging.onCommand("preferences.getAll", async () => {
|
|
530
|
+
const config = await loadConfig();
|
|
531
|
+
return maskConfig(config);
|
|
532
|
+
});
|
|
533
|
+
ctx.messaging.onCommand(
|
|
534
|
+
"preferences.get",
|
|
535
|
+
async (req) => {
|
|
536
|
+
const config = await loadConfig();
|
|
537
|
+
const schema = getSchema(req.key);
|
|
538
|
+
let value = config[req.key] ?? defaults[req.key];
|
|
539
|
+
if (value === void 0) {
|
|
540
|
+
throw new Error(`Unknown preference key: ${req.key}`);
|
|
541
|
+
}
|
|
542
|
+
if (schema?.sensitive && typeof value === "string" && value) {
|
|
543
|
+
value = maskSensitive(value);
|
|
544
|
+
}
|
|
545
|
+
return { value };
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
ctx.messaging.onCommand(
|
|
549
|
+
"preferences.getRaw",
|
|
550
|
+
async (req) => {
|
|
551
|
+
const config = await loadConfig();
|
|
552
|
+
const value = config[req.key] ?? defaults[req.key];
|
|
553
|
+
if (value === void 0) {
|
|
554
|
+
throw new Error(`Unknown preference key: ${req.key}`);
|
|
555
|
+
}
|
|
556
|
+
return { value };
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
ctx.messaging.onCommand(
|
|
560
|
+
"preferences.set",
|
|
561
|
+
async (req) => {
|
|
562
|
+
const schema = getSchema(req.key);
|
|
563
|
+
if (!schema) {
|
|
564
|
+
throw new Error(`Unknown preference key: ${req.key}`);
|
|
565
|
+
}
|
|
566
|
+
if (typeof req.value !== schema.type) {
|
|
567
|
+
throw new Error(`Expected ${schema.type} for ${req.key}, got ${typeof req.value}`);
|
|
568
|
+
}
|
|
569
|
+
if (schema.type === "number" && typeof req.value === "number") {
|
|
570
|
+
if (schema.min !== void 0 && req.value < schema.min) {
|
|
571
|
+
throw new Error(`${req.key} must be >= ${schema.min}`);
|
|
572
|
+
}
|
|
573
|
+
if (schema.max !== void 0 && req.value > schema.max) {
|
|
574
|
+
throw new Error(`${req.key} must be <= ${schema.max}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (schema.options && typeof req.value === "string") {
|
|
578
|
+
if (!schema.options.includes(req.value)) {
|
|
579
|
+
throw new Error(`${req.key} must be one of: ${schema.options.join(", ")}`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const config = await loadConfig();
|
|
583
|
+
config[req.key] = req.value;
|
|
584
|
+
await saveConfig(config);
|
|
585
|
+
ctx.logger.info(`Setting updated: ${req.key}`);
|
|
586
|
+
ctx.messaging.emit("preferences:changed", {
|
|
587
|
+
key: req.key,
|
|
588
|
+
value: schema.sensitive ? maskSensitive(String(req.value)) : req.value,
|
|
589
|
+
requiresRestart: schema.requiresRestart ?? false
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
ctx.messaging.onCommand("preferences.reset", async (req) => {
|
|
594
|
+
const schema = getSchema(req.key);
|
|
595
|
+
if (!schema) {
|
|
596
|
+
throw new Error(`Unknown preference key: ${req.key}`);
|
|
597
|
+
}
|
|
598
|
+
const config = await loadConfig();
|
|
599
|
+
config[req.key] = schema.default;
|
|
600
|
+
await saveConfig(config);
|
|
601
|
+
ctx.logger.info(`Setting reset: ${req.key}`);
|
|
602
|
+
ctx.messaging.emit("preferences:changed", {
|
|
603
|
+
key: req.key,
|
|
604
|
+
value: schema.default,
|
|
605
|
+
requiresRestart: schema.requiresRestart ?? false
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
ctx.messaging.onCommand("preferences.resetAll", async () => {
|
|
609
|
+
await saveConfig({ ...defaults });
|
|
610
|
+
ctx.logger.info("All settings reset to defaults");
|
|
611
|
+
ctx.messaging.emit("preferences:resetAll", {});
|
|
612
|
+
});
|
|
613
|
+
return {};
|
|
614
|
+
}
|
|
615
|
+
function deactivate() {
|
|
616
|
+
}
|
|
617
|
+
export {
|
|
618
|
+
activate,
|
|
619
|
+
deactivate,
|
|
620
|
+
manifest
|
|
621
|
+
};
|
|
622
|
+
//# sourceMappingURL=backend.js.map
|