@tx5dr/contracts 1.7.3 → 1.7.4

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 (108) hide show
  1. package/dist/index.d.ts +4 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/schema/__tests__/ability.test.d.ts +2 -0
  6. package/dist/schema/__tests__/ability.test.d.ts.map +1 -0
  7. package/dist/schema/__tests__/ability.test.js +32 -0
  8. package/dist/schema/__tests__/ability.test.js.map +1 -0
  9. package/dist/schema/__tests__/cw-decoder.schema.test.d.ts +2 -0
  10. package/dist/schema/__tests__/cw-decoder.schema.test.d.ts.map +1 -0
  11. package/dist/schema/__tests__/cw-decoder.schema.test.js +114 -0
  12. package/dist/schema/__tests__/cw-decoder.schema.test.js.map +1 -0
  13. package/dist/schema/__tests__/device-ui.schema.test.d.ts +2 -0
  14. package/dist/schema/__tests__/device-ui.schema.test.d.ts.map +1 -0
  15. package/dist/schema/__tests__/device-ui.schema.test.js +111 -0
  16. package/dist/schema/__tests__/device-ui.schema.test.js.map +1 -0
  17. package/dist/schema/__tests__/process-monitor.schema.test.js +96 -0
  18. package/dist/schema/__tests__/process-monitor.schema.test.js.map +1 -1
  19. package/dist/schema/__tests__/radio.schema.test.d.ts +2 -0
  20. package/dist/schema/__tests__/radio.schema.test.d.ts.map +1 -0
  21. package/dist/schema/__tests__/radio.schema.test.js +143 -0
  22. package/dist/schema/__tests__/radio.schema.test.js.map +1 -0
  23. package/dist/schema/ability.d.ts +3 -1
  24. package/dist/schema/ability.d.ts.map +1 -1
  25. package/dist/schema/ability.js +11 -0
  26. package/dist/schema/ability.js.map +1 -1
  27. package/dist/schema/api-response.schema.d.ts +168 -1
  28. package/dist/schema/api-response.schema.d.ts.map +1 -1
  29. package/dist/schema/api-response.schema.js +22 -0
  30. package/dist/schema/api-response.schema.js.map +1 -1
  31. package/dist/schema/cpu-profile.schema.d.ts +6 -6
  32. package/dist/schema/cw-decoder.schema.d.ts +2501 -0
  33. package/dist/schema/cw-decoder.schema.d.ts.map +1 -0
  34. package/dist/schema/cw-decoder.schema.js +196 -0
  35. package/dist/schema/cw-decoder.schema.js.map +1 -0
  36. package/dist/schema/cw-keyer.schema.d.ts +368 -0
  37. package/dist/schema/cw-keyer.schema.d.ts.map +1 -0
  38. package/dist/schema/cw-keyer.schema.js +143 -0
  39. package/dist/schema/cw-keyer.schema.js.map +1 -0
  40. package/dist/schema/desktop-https.schema.js +1 -1
  41. package/dist/schema/desktop-https.schema.js.map +1 -1
  42. package/dist/schema/device-ui.schema.d.ts +1782 -0
  43. package/dist/schema/device-ui.schema.d.ts.map +1 -0
  44. package/dist/schema/device-ui.schema.js +156 -0
  45. package/dist/schema/device-ui.schema.js.map +1 -0
  46. package/dist/schema/mode.schema.d.ts +1 -0
  47. package/dist/schema/mode.schema.d.ts.map +1 -1
  48. package/dist/schema/mode.schema.js +8 -0
  49. package/dist/schema/mode.schema.js.map +1 -1
  50. package/dist/schema/plugin.schema.d.ts +637 -54
  51. package/dist/schema/plugin.schema.d.ts.map +1 -1
  52. package/dist/schema/plugin.schema.js +22 -2
  53. package/dist/schema/plugin.schema.js.map +1 -1
  54. package/dist/schema/process-monitor.schema.d.ts +1948 -261
  55. package/dist/schema/process-monitor.schema.d.ts.map +1 -1
  56. package/dist/schema/process-monitor.schema.js +11 -0
  57. package/dist/schema/process-monitor.schema.js.map +1 -1
  58. package/dist/schema/radio-capability.schema.d.ts +1 -1
  59. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  60. package/dist/schema/radio-capability.schema.js +41 -0
  61. package/dist/schema/radio-capability.schema.js.map +1 -1
  62. package/dist/schema/radio-power.schema.d.ts +6 -0
  63. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  64. package/dist/schema/radio-profile.schema.d.ts +86 -0
  65. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  66. package/dist/schema/radio.schema.d.ts +160 -3
  67. package/dist/schema/radio.schema.d.ts.map +1 -1
  68. package/dist/schema/radio.schema.js +82 -0
  69. package/dist/schema/radio.schema.js.map +1 -1
  70. package/dist/schema/realtime.schema.d.ts +6 -6
  71. package/dist/schema/voice.schema.d.ts +1 -1
  72. package/dist/schema/voice.schema.d.ts.map +1 -1
  73. package/dist/schema/voice.schema.js +1 -1
  74. package/dist/schema/voice.schema.js.map +1 -1
  75. package/dist/schema/websocket.schema.d.ts +5615 -412
  76. package/dist/schema/websocket.schema.d.ts.map +1 -1
  77. package/dist/schema/websocket.schema.js +102 -2
  78. package/dist/schema/websocket.schema.js.map +1 -1
  79. package/dist/utils/__tests__/cwTiming.test.d.ts +2 -0
  80. package/dist/utils/__tests__/cwTiming.test.d.ts.map +1 -0
  81. package/dist/utils/__tests__/cwTiming.test.js +74 -0
  82. package/dist/utils/__tests__/cwTiming.test.js.map +1 -0
  83. package/dist/utils/cwTiming.d.ts +36 -0
  84. package/dist/utils/cwTiming.d.ts.map +1 -0
  85. package/dist/utils/cwTiming.js +155 -0
  86. package/dist/utils/cwTiming.js.map +1 -0
  87. package/package.json +1 -1
  88. package/src/index.ts +8 -0
  89. package/src/schema/__tests__/ability.test.ts +40 -0
  90. package/src/schema/__tests__/cw-decoder.schema.test.ts +127 -0
  91. package/src/schema/__tests__/device-ui.schema.test.ts +122 -0
  92. package/src/schema/__tests__/process-monitor.schema.test.ts +99 -0
  93. package/src/schema/__tests__/radio.schema.test.ts +160 -0
  94. package/src/schema/ability.ts +13 -0
  95. package/src/schema/api-response.schema.ts +22 -0
  96. package/src/schema/cw-decoder.schema.ts +234 -0
  97. package/src/schema/cw-keyer.schema.ts +177 -0
  98. package/src/schema/desktop-https.schema.ts +1 -1
  99. package/src/schema/device-ui.schema.ts +182 -0
  100. package/src/schema/mode.schema.ts +8 -0
  101. package/src/schema/plugin.schema.ts +28 -2
  102. package/src/schema/process-monitor.schema.ts +18 -0
  103. package/src/schema/radio-capability.schema.ts +41 -0
  104. package/src/schema/radio.schema.ts +97 -0
  105. package/src/schema/voice.schema.ts +1 -1
  106. package/src/schema/websocket.schema.ts +131 -2
  107. package/src/utils/__tests__/cwTiming.test.ts +91 -0
  108. package/src/utils/cwTiming.ts +203 -0
@@ -0,0 +1,160 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { PresetFrequencySchema } from '../radio.schema.js';
3
+
4
+ describe('PresetFrequencySchema repeater DUP fields', () => {
5
+ it('accepts existing presets without repeater DUP fields', () => {
6
+ const parsed = PresetFrequencySchema.parse({
7
+ band: '2m',
8
+ mode: 'VOICE',
9
+ radioMode: 'FM',
10
+ frequency: 145000000,
11
+ description: '145.000 MHz 2m FM',
12
+ });
13
+
14
+ expect(parsed.repeaterShift).toBeUndefined();
15
+ expect(parsed.repeaterOffsetHz).toBeUndefined();
16
+ });
17
+
18
+ it('accepts plus or minus DUP presets with positive Hz offsets', () => {
19
+ const parsed = PresetFrequencySchema.parse({
20
+ band: '2m',
21
+ mode: 'VOICE',
22
+ radioMode: 'FM',
23
+ frequency: 145000000,
24
+ repeaterShift: 'plus',
25
+ repeaterOffsetHz: 600000,
26
+ });
27
+
28
+ expect(parsed.repeaterShift).toBe('plus');
29
+ expect(parsed.repeaterOffsetHz).toBe(600000);
30
+ });
31
+
32
+ it('rejects DUP direction without a positive offset', () => {
33
+ expect(() => PresetFrequencySchema.parse({
34
+ band: '2m',
35
+ mode: 'VOICE',
36
+ radioMode: 'FM',
37
+ frequency: 145000000,
38
+ repeaterShift: 'minus',
39
+ })).toThrow();
40
+
41
+ expect(() => PresetFrequencySchema.parse({
42
+ band: '2m',
43
+ mode: 'VOICE',
44
+ radioMode: 'FM',
45
+ frequency: 145000000,
46
+ repeaterShift: 'minus',
47
+ repeaterOffsetHz: 0,
48
+ })).toThrow();
49
+ });
50
+
51
+ it('rejects invalid DUP directions', () => {
52
+ expect(() => PresetFrequencySchema.parse({
53
+ band: '2m',
54
+ mode: 'VOICE',
55
+ radioMode: 'FM',
56
+ frequency: 145000000,
57
+ repeaterShift: 'up',
58
+ repeaterOffsetHz: 600000,
59
+ })).toThrow();
60
+ });
61
+
62
+ it('rejects DUP presets outside VOICE FM', () => {
63
+ expect(() => PresetFrequencySchema.parse({
64
+ band: '20m',
65
+ mode: 'VOICE',
66
+ radioMode: 'USB',
67
+ frequency: 14270000,
68
+ repeaterShift: 'plus',
69
+ repeaterOffsetHz: 600000,
70
+ })).toThrow();
71
+ });
72
+ });
73
+
74
+ describe('PresetFrequencySchema tone squelch fields', () => {
75
+ it('accepts CTCSS tone presets with positive 0.1 Hz values', () => {
76
+ const parsed = PresetFrequencySchema.parse({
77
+ band: '2m',
78
+ mode: 'VOICE',
79
+ radioMode: 'FM',
80
+ frequency: 145000000,
81
+ toneMode: 'ctcss',
82
+ ctcssToneTenthsHz: 885,
83
+ });
84
+
85
+ expect(parsed.toneMode).toBe('ctcss');
86
+ expect(parsed.ctcssToneTenthsHz).toBe(885);
87
+ });
88
+
89
+ it('accepts DCS presets with positive codes', () => {
90
+ const parsed = PresetFrequencySchema.parse({
91
+ band: '2m',
92
+ mode: 'VOICE',
93
+ radioMode: 'FM',
94
+ frequency: 145000000,
95
+ toneMode: 'dcs',
96
+ dcsCode: 23,
97
+ });
98
+
99
+ expect(parsed.toneMode).toBe('dcs');
100
+ expect(parsed.dcsCode).toBe(23);
101
+ });
102
+
103
+ it('rejects tone squelch modes without required values', () => {
104
+ expect(() => PresetFrequencySchema.parse({
105
+ band: '2m',
106
+ mode: 'VOICE',
107
+ radioMode: 'FM',
108
+ frequency: 145000000,
109
+ toneMode: 'ctcss',
110
+ })).toThrow();
111
+
112
+ expect(() => PresetFrequencySchema.parse({
113
+ band: '2m',
114
+ mode: 'VOICE',
115
+ radioMode: 'FM',
116
+ frequency: 145000000,
117
+ toneMode: 'dcs',
118
+ })).toThrow();
119
+ });
120
+
121
+ it('rejects invalid tone squelch values and modes', () => {
122
+ expect(() => PresetFrequencySchema.parse({
123
+ band: '2m',
124
+ mode: 'VOICE',
125
+ radioMode: 'FM',
126
+ frequency: 145000000,
127
+ toneMode: 'ctcss',
128
+ ctcssToneTenthsHz: 0,
129
+ })).toThrow();
130
+
131
+ expect(() => PresetFrequencySchema.parse({
132
+ band: '2m',
133
+ mode: 'VOICE',
134
+ radioMode: 'FM',
135
+ frequency: 145000000,
136
+ toneMode: 'dcs',
137
+ dcsCode: -1,
138
+ })).toThrow();
139
+
140
+ expect(() => PresetFrequencySchema.parse({
141
+ band: '2m',
142
+ mode: 'VOICE',
143
+ radioMode: 'FM',
144
+ frequency: 145000000,
145
+ toneMode: 'tone',
146
+ ctcssToneTenthsHz: 885,
147
+ })).toThrow();
148
+ });
149
+
150
+ it('rejects tone squelch presets outside VOICE FM', () => {
151
+ expect(() => PresetFrequencySchema.parse({
152
+ band: '20m',
153
+ mode: 'VOICE',
154
+ radioMode: 'USB',
155
+ frequency: 14270000,
156
+ toneMode: 'ctcss',
157
+ ctcssToneTenthsHz: 885,
158
+ })).toThrow();
159
+ });
160
+ });
@@ -34,6 +34,8 @@ export type CapabilitySubject =
34
34
  | 'RigctldBridge'
35
35
  | 'Engine'
36
36
  | 'ModeSwitch'
37
+ | 'CWDecoder'
38
+ | 'CWDecoderConfig'
37
39
  | 'SettingsDecodeWindows'
38
40
  | 'SettingsFrequencyPresets'
39
41
  | 'StationInfo';
@@ -59,6 +61,8 @@ export enum Permission {
59
61
  RIGCTLD_BRIDGE = 'rigctld:bridge',
60
62
  ENGINE_START_STOP = 'engine:start_stop',
61
63
  MODE_SWITCH = 'mode:switch',
64
+ CW_DECODER_CONTROL = 'cw:decoder_control',
65
+ CW_DECODER_CONFIG = 'cw:decoder_config',
62
66
  SETTINGS_DECODE_WINDOWS = 'settings:decode_windows',
63
67
  SETTINGS_FREQUENCY_PRESETS = 'settings:frequency_presets',
64
68
  STATION_UPDATE = 'station:update',
@@ -79,6 +83,8 @@ export const PERMISSION_RULE_MAP: Record<Permission, { action: AppAction; subjec
79
83
  [Permission.RIGCTLD_BRIDGE]: { action: 'execute', subject: 'RigctldBridge' },
80
84
  [Permission.ENGINE_START_STOP]: { action: 'execute', subject: 'Engine' },
81
85
  [Permission.MODE_SWITCH]: { action: 'execute', subject: 'ModeSwitch' },
86
+ [Permission.CW_DECODER_CONTROL]: { action: 'execute', subject: 'CWDecoder' },
87
+ [Permission.CW_DECODER_CONFIG]: { action: 'update', subject: 'CWDecoderConfig' },
82
88
  [Permission.SETTINGS_DECODE_WINDOWS]: { action: 'update', subject: 'SettingsDecodeWindows' },
83
89
  [Permission.SETTINGS_FREQUENCY_PRESETS]: { action: 'update', subject: 'SettingsFrequencyPresets' },
84
90
  [Permission.STATION_UPDATE]: { action: 'update', subject: 'StationInfo' },
@@ -181,6 +187,13 @@ export const PERMISSION_GROUPS: PermissionGroupDef[] = [
181
187
  },
182
188
  { key: 'engine', permissions: [Permission.ENGINE_START_STOP] },
183
189
  { key: 'mode', permissions: [Permission.MODE_SWITCH] },
190
+ {
191
+ key: 'cw',
192
+ permissions: [
193
+ Permission.CW_DECODER_CONTROL,
194
+ Permission.CW_DECODER_CONFIG,
195
+ ],
196
+ },
184
197
  {
185
198
  key: 'settings',
186
199
  permissions: [
@@ -115,6 +115,23 @@ export const DisconnectRadioResponseSchema = z.object({
115
115
  export const LastFrequencyResponseSchema = z.object({
116
116
  success: z.boolean(),
117
117
  lastFrequency: PresetFrequencySchema.nullable(),
118
+ lastVoiceFrequency: z.object({
119
+ frequency: z.number(),
120
+ radioMode: z.string().optional(),
121
+ band: z.string(),
122
+ description: z.string().optional(),
123
+ repeaterShift: z.enum(['none', 'minus', 'plus']).optional(),
124
+ repeaterOffsetHz: z.number().optional(),
125
+ toneMode: z.enum(['none', 'ctcss', 'dcs']).optional(),
126
+ ctcssToneTenthsHz: z.number().optional(),
127
+ dcsCode: z.number().optional(),
128
+ }).nullable().optional(),
129
+ lastCWFrequency: z.object({
130
+ frequency: z.number(),
131
+ radioMode: z.string().optional(),
132
+ band: z.string(),
133
+ description: z.string().optional(),
134
+ }).nullable().optional(),
118
135
  });
119
136
 
120
137
  /**
@@ -127,6 +144,11 @@ export const SetFrequencyResponseSchema = z.object({
127
144
  mode: z.string().optional(),
128
145
  band: z.string().optional(),
129
146
  radioMode: z.string().optional(),
147
+ repeaterShift: z.enum(['none', 'minus', 'plus']).optional(),
148
+ repeaterOffsetHz: z.number().optional(),
149
+ toneMode: z.enum(['none', 'ctcss', 'dcs']).optional(),
150
+ ctcssToneTenthsHz: z.number().optional(),
151
+ dcsCode: z.number().optional(),
130
152
  radioConnected: z.boolean().optional(),
131
153
  });
132
154
 
@@ -0,0 +1,234 @@
1
+ import { z } from 'zod';
2
+
3
+ export const CWDecoderBackendSchema = z.enum(['deepcw-onnx']);
4
+ export type CWDecoderBackend = z.infer<typeof CWDecoderBackendSchema>;
5
+
6
+ export const CWDecoderRuntimeBackendSchema = z.enum([
7
+ 'cpu',
8
+ 'cuda',
9
+ 'coreml',
10
+ 'directml',
11
+ 'wasm',
12
+ 'webgpu',
13
+ ]);
14
+ export type CWDecoderRuntimeBackend = z.infer<typeof CWDecoderRuntimeBackendSchema>;
15
+
16
+ export const CWDecoderModelSizeSchema = z.enum(['tiny', 'small']);
17
+ export type CWDecoderModelSize = z.infer<typeof CWDecoderModelSizeSchema>;
18
+
19
+ export const CWDecoderModeSchema = z.enum(['streaming']);
20
+ export type CWDecoderMode = z.infer<typeof CWDecoderModeSchema>;
21
+
22
+ export const CWDecoderConfigSchema = z.object({
23
+ enabled: z.boolean().default(false),
24
+ backend: CWDecoderBackendSchema.default('deepcw-onnx'),
25
+ runtimeBackend: CWDecoderRuntimeBackendSchema.default('cpu'),
26
+ modelSize: CWDecoderModelSizeSchema.default('tiny'),
27
+ language: z.string().min(1).default('en'),
28
+ mode: CWDecoderModeSchema.default('streaming'),
29
+ targetFreqHz: z.number().int().positive().default(800),
30
+ filterWidthHz: z.number().int().positive().default(800),
31
+ windowSeconds: z.number().positive().default(12),
32
+ decodeIntervalMs: z.number().int().positive().default(1000),
33
+ muteWhileTransmitting: z.boolean().default(true),
34
+ workerCount: z.number().int().positive().max(4).default(1),
35
+ minCommitChars: z.number().int().positive().default(1),
36
+ commitStability: z.number().int().positive().default(2),
37
+ maxPendingAgeMs: z.number().int().positive().default(4000),
38
+ });
39
+ export type CWDecoderConfig = z.infer<typeof CWDecoderConfigSchema>;
40
+
41
+ export const CWDecoderTuningUpdateSchema = z.object({
42
+ targetFreqHz: z.number().int().min(100).max(1500).optional(),
43
+ filterWidthHz: z.number().int().min(100).max(800).optional(),
44
+ }).refine(value => value.targetFreqHz !== undefined || value.filterWidthHz !== undefined, {
45
+ message: 'At least one CW decoder tuning field is required',
46
+ });
47
+ export type CWDecoderTuningUpdate = z.infer<typeof CWDecoderTuningUpdateSchema>;
48
+
49
+ export const CWDecoderBackendDescriptorSchema = z.object({
50
+ id: CWDecoderBackendSchema,
51
+ name: z.string(),
52
+ available: z.boolean(),
53
+ runtimeBackends: z.array(CWDecoderRuntimeBackendSchema).default(['cpu']),
54
+ modelSizes: z.array(CWDecoderModelSizeSchema).default(['tiny']),
55
+ languages: z.array(z.string()).default(['en']),
56
+ modes: z.array(CWDecoderModeSchema).default(['streaming']),
57
+ version: z.string().optional(),
58
+ label: z.string().optional(),
59
+ model: z.string().optional(),
60
+ runtime: z.string().optional(),
61
+ attributionName: z.string().optional(),
62
+ sourceUrl: z.string().url().optional(),
63
+ license: z.string().optional(),
64
+ error: z.string().nullable().optional(),
65
+ reason: z.string().optional(),
66
+ });
67
+ export type CWDecoderBackendDescriptor = z.infer<typeof CWDecoderBackendDescriptorSchema>;
68
+
69
+ export const CWDecoderStatusStateSchema = z.enum([
70
+ 'disabled',
71
+ 'starting',
72
+ 'listening',
73
+ 'decoding',
74
+ 'muted',
75
+ 'error',
76
+ ]);
77
+ export type CWDecoderStatusState = z.infer<typeof CWDecoderStatusStateSchema>;
78
+
79
+ export const CWDecoderStatusSchema = z.object({
80
+ enabled: z.boolean(),
81
+ state: CWDecoderStatusStateSchema,
82
+ config: CWDecoderConfigSchema,
83
+ backend: CWDecoderBackendDescriptorSchema.optional(),
84
+ muted: z.boolean().default(false),
85
+ active: z.boolean().default(false),
86
+ lastDecodeAt: z.number().optional(),
87
+ lastError: z.string().nullable().optional(),
88
+ running: z.boolean().optional(),
89
+ backendId: CWDecoderBackendSchema.optional(),
90
+ pendingText: z.string().optional(),
91
+ committedText: z.string().optional(),
92
+ queuedSamples: z.number().int().nonnegative().optional(),
93
+ updatedAt: z.number(),
94
+ });
95
+ export type CWDecoderStatus = z.infer<typeof CWDecoderStatusSchema>;
96
+
97
+ export const CWDecoderWordSpaceSpanSchema = z.object({
98
+ startFrame: z.number().int().nonnegative(),
99
+ endFrame: z.number().int().nonnegative(),
100
+ });
101
+ export type CWDecoderWordSpaceSpan = z.infer<typeof CWDecoderWordSpaceSpanSchema>;
102
+
103
+ export const CWDecoderCharacterSpanSchema = z.object({
104
+ char: z.string(),
105
+ startFrame: z.number().int().nonnegative(),
106
+ endFrame: z.number().int().nonnegative(),
107
+ });
108
+ export type CWDecoderCharacterSpan = z.infer<typeof CWDecoderCharacterSpanSchema>;
109
+
110
+ export const CWDecoderTranscriptSegmentSchema = z.object({
111
+ id: z.string(),
112
+ sessionId: z.string(),
113
+ sequence: z.number().int().nonnegative(),
114
+ text: z.string(),
115
+ plainText: z.string().optional(),
116
+ finalized: z.literal(true).default(true),
117
+ prependSpace: z.boolean().default(true),
118
+ confidence: z.number().min(0).max(1).optional(),
119
+ targetFreqHz: z.number().positive().optional(),
120
+ filterWidthHz: z.number().positive().optional(),
121
+ characterSpans: z.array(CWDecoderCharacterSpanSchema).optional(),
122
+ wordSpaceSpans: z.array(CWDecoderWordSpaceSpanSchema).optional(),
123
+ startedAt: z.number().optional(),
124
+ endedAt: z.number().nullable().optional(),
125
+ updatedAt: z.number(),
126
+ wpm: z.number().positive().optional(),
127
+ });
128
+ export type CWDecoderTranscriptSegment = z.infer<typeof CWDecoderTranscriptSegmentSchema>;
129
+
130
+ export const CWDecoderPendingSegmentSchema = z.object({
131
+ sessionId: z.string(),
132
+ version: z.number().int().nonnegative(),
133
+ text: z.string(),
134
+ plainText: z.string().optional(),
135
+ finalized: z.literal(false).default(false),
136
+ confidence: z.number().min(0).max(1).optional(),
137
+ targetFreqHz: z.number().positive().optional(),
138
+ filterWidthHz: z.number().positive().optional(),
139
+ characterSpans: z.array(CWDecoderCharacterSpanSchema).optional(),
140
+ wordSpaceSpans: z.array(CWDecoderWordSpaceSpanSchema).optional(),
141
+ updatedAt: z.number(),
142
+ });
143
+ export type CWDecoderPendingSegment = z.infer<typeof CWDecoderPendingSegmentSchema>;
144
+
145
+ export const CWDecoderStatusEventSchema = z.object({
146
+ kind: z.literal('status'),
147
+ status: CWDecoderStatusSchema,
148
+ });
149
+
150
+ export const CWDecoderTranscriptEventSchema = z.object({
151
+ kind: z.literal('transcript'),
152
+ segment: CWDecoderTranscriptSegmentSchema,
153
+ });
154
+
155
+ export const CWDecoderTranscriptResetEventSchema = z.object({
156
+ kind: z.literal('transcript_reset'),
157
+ sessionId: z.string(),
158
+ timestamp: z.number(),
159
+ });
160
+
161
+ export const CWDecoderTranscriptPendingEventSchema = z.object({
162
+ kind: z.literal('transcript_pending'),
163
+ pending: CWDecoderPendingSegmentSchema.nullable(),
164
+ timestamp: z.number(),
165
+ });
166
+
167
+ export const CWDecoderTranscriptCommitEventSchema = z.object({
168
+ kind: z.literal('transcript_commit'),
169
+ segment: CWDecoderTranscriptSegmentSchema,
170
+ timestamp: z.number(),
171
+ });
172
+
173
+ export const CWDecoderPendingEventSchema = z.object({
174
+ kind: z.literal('pending'),
175
+ text: z.string(),
176
+ confidence: z.number().min(0).max(1).optional(),
177
+ timestamp: z.number(),
178
+ });
179
+
180
+ export const CWDecoderCommitEventSchema = z.object({
181
+ kind: z.literal('commit'),
182
+ segment: CWDecoderTranscriptSegmentSchema,
183
+ text: z.string().optional(),
184
+ confidence: z.number().min(0).max(1).optional(),
185
+ timestamp: z.number().optional(),
186
+ });
187
+
188
+ export const CWDecoderPartialEventSchema = z.object({
189
+ kind: z.literal('partial'),
190
+ text: z.string(),
191
+ confidence: z.number().min(0).max(1).optional(),
192
+ timestamp: z.number(),
193
+ });
194
+
195
+ export const CWDecoderErrorEventSchema = z.object({
196
+ kind: z.literal('error'),
197
+ message: z.string(),
198
+ code: z.string().optional(),
199
+ recoverable: z.boolean().optional(),
200
+ timestamp: z.number(),
201
+ });
202
+
203
+ export const CWDecoderModelLoadEventSchema = z.object({
204
+ kind: z.literal('model_load'),
205
+ backend: CWDecoderBackendSchema,
206
+ modelSize: CWDecoderModelSizeSchema.optional(),
207
+ language: z.string().optional(),
208
+ progress: z.number().min(0).max(1).optional(),
209
+ message: z.string().optional(),
210
+ timestamp: z.number(),
211
+ });
212
+
213
+ export const CWDecoderAudioBufferEventSchema = z.object({
214
+ kind: z.literal('audio_buffer'),
215
+ queuedSamples: z.number().int().nonnegative(),
216
+ sampleRate: z.number().int().positive().optional(),
217
+ durationMs: z.number().nonnegative().optional(),
218
+ timestamp: z.number(),
219
+ });
220
+
221
+ export const CWDecoderEventSchema = z.discriminatedUnion('kind', [
222
+ CWDecoderStatusEventSchema,
223
+ CWDecoderTranscriptEventSchema,
224
+ CWDecoderTranscriptResetEventSchema,
225
+ CWDecoderTranscriptPendingEventSchema,
226
+ CWDecoderTranscriptCommitEventSchema,
227
+ CWDecoderPendingEventSchema,
228
+ CWDecoderCommitEventSchema,
229
+ CWDecoderPartialEventSchema,
230
+ CWDecoderErrorEventSchema,
231
+ CWDecoderModelLoadEventSchema,
232
+ CWDecoderAudioBufferEventSchema,
233
+ ]);
234
+ export type CWDecoderEvent = z.infer<typeof CWDecoderEventSchema>;
@@ -0,0 +1,177 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * CW 键控器配置
5
+ */
6
+ export const CWKeyerBackendSchema = z.enum(['cat', 'serial']);
7
+ export type CWKeyerBackend = z.infer<typeof CWKeyerBackendSchema>;
8
+
9
+ export const CWKeyerConfigSchema = z.object({
10
+ /** CW 发报后端:cat=Hamlib 整报文,serial=DTR/RTS 时序键控 */
11
+ backend: CWKeyerBackendSchema.default('cat'),
12
+ /** CW 键控串口路径(用于 DTR/RTS 引脚控制) */
13
+ keyPort: z.string(),
14
+ /** CW 键控引脚类型 */
15
+ keyMethod: z.enum(['dtr', 'rts']),
16
+ /** 莫尔斯码速度 (WPM, 5-60) */
17
+ wpm: z.number().int().min(5).max(60).default(20),
18
+ });
19
+
20
+ export type CWKeyerConfig = z.infer<typeof CWKeyerConfigSchema>;
21
+
22
+ /**
23
+ * CW 键控器状态
24
+ */
25
+ export const CWKeyerStatusSchema = z.object({
26
+ /** 是否有活动键控 */
27
+ active: z.boolean(),
28
+ /** 当前状态模式 */
29
+ mode: z.enum(['idle', 'keying', 'playing', 'repeat-waiting', 'error']),
30
+ /** 发起键控的客户端 ID */
31
+ startedBy: z.string().nullable(),
32
+ /** 发起键控的客户端显示名 */
33
+ startedByLabel: z.string().nullable(),
34
+ /** 当前播放的报文 ID */
35
+ messageId: z.string().nullable(),
36
+ /** 下次执行时间戳 (repeat-waiting 时有效) */
37
+ nextRunAt: z.number().nullable(),
38
+ /** 错误信息 */
39
+ error: z.string().nullable(),
40
+ /** 当前 CW 发报后端 */
41
+ backend: CWKeyerBackendSchema.optional(),
42
+ /** 当前后端是否具备发报条件 */
43
+ backendAvailable: z.boolean().optional(),
44
+ /** 当前后端不可用原因 */
45
+ backendError: z.string().nullable().optional(),
46
+ /** 当前正在发送的文本(只读状态字段) */
47
+ currentText: z.string().nullable().optional(),
48
+ /** 最近一次发送的文本(只读状态字段) */
49
+ lastText: z.string().nullable().optional(),
50
+ });
51
+
52
+ export type CWKeyerStatus = z.infer<typeof CWKeyerStatusSchema>;
53
+
54
+ /**
55
+ * CW 报文槽位
56
+ */
57
+ export const CWMessageSlotSchema = z.object({
58
+ /** 槽位唯一 ID */
59
+ id: z.string(),
60
+ /** 显示序号 (1-based) */
61
+ index: z.number().int().min(1),
62
+ /** 报文标签 */
63
+ label: z.string(),
64
+ /** 报文文本内容 */
65
+ text: z.string(),
66
+ /** 是否启用循环播放 */
67
+ repeatEnabled: z.boolean(),
68
+ /** 循环播放间隔(秒) */
69
+ repeatIntervalSec: z.number().int().min(1).max(300),
70
+ });
71
+
72
+ export type CWMessageSlot = z.infer<typeof CWMessageSlotSchema>;
73
+
74
+ /**
75
+ * CW 报文面板
76
+ */
77
+ export const CWMessagePanelSchema = z.object({
78
+ /** 呼号 */
79
+ callsign: z.string(),
80
+ /** 当前槽位数 */
81
+ slotCount: z.number().int().min(3).max(12),
82
+ /** 最大槽位数 */
83
+ maxSlotCount: z.number().int().min(12),
84
+ /** 槽位列表 */
85
+ slots: z.array(CWMessageSlotSchema),
86
+ });
87
+
88
+ export type CWMessagePanel = z.infer<typeof CWMessagePanelSchema>;
89
+
90
+ /**
91
+ * CW 手键动作
92
+ */
93
+ export const CWKeyActionSchema = z.object({
94
+ /** 键控动作 */
95
+ action: z.enum(['key-down', 'key-up']),
96
+ });
97
+
98
+ export type CWKeyAction = z.infer<typeof CWKeyActionSchema>;
99
+
100
+ /**
101
+ * CW 报文占位符值(由前端根据当前 UI 上下文提供)
102
+ */
103
+ export const CWPlaceholderValuesSchema = z.object({
104
+ /** 当前操作员呼号,用于 {MYCALL} */
105
+ myCall: z.string().optional(),
106
+ /** 当前通联对象呼号,用于 {HISCALL} */
107
+ hisCall: z.string().optional(),
108
+ /** 发送的 RST 信号报告,用于 {TRST} */
109
+ trst: z.string().optional(),
110
+ /** 接收的 RST 信号报告,用于 {RRST} */
111
+ rrst: z.string().optional(),
112
+ });
113
+
114
+ export type CWPlaceholderValues = z.infer<typeof CWPlaceholderValuesSchema>;
115
+
116
+ /**
117
+ * CW 文字输入
118
+ */
119
+ export const CWTextInputSchema = z.object({
120
+ /** 要发送的文字 */
121
+ text: z.string().min(1).max(500),
122
+ /** 操作员呼号,用于占位符替换(如 {MYCALL}) */
123
+ callsign: z.string().optional(),
124
+ /** 前端解析出的占位符值 */
125
+ placeholderValues: CWPlaceholderValuesSchema.optional(),
126
+ });
127
+
128
+ export type CWTextInput = z.infer<typeof CWTextInputSchema>;
129
+
130
+ /**
131
+ * CW 播放预设报文请求
132
+ */
133
+ export const CWPlayMessageSchema = z.object({
134
+ /** 呼号 */
135
+ callsign: z.string(),
136
+ /** 报文槽位 ID */
137
+ slotId: z.string(),
138
+ /** 是否循环播放 */
139
+ repeat: z.boolean().optional().default(false),
140
+ /** 是否立即发送;false 时仅进入循环等待 */
141
+ startImmediately: z.boolean().optional(),
142
+ /** 前端解析出的占位符值 */
143
+ placeholderValues: CWPlaceholderValuesSchema.optional(),
144
+ });
145
+
146
+ export type CWPlayMessage = z.infer<typeof CWPlayMessageSchema>;
147
+
148
+ /**
149
+ * CW 报文槽位更新
150
+ */
151
+ export const CWMessageSlotUpdateSchema = z.object({
152
+ label: z.string().max(32).optional(),
153
+ text: z.string().max(500).optional(),
154
+ repeatEnabled: z.boolean().optional(),
155
+ repeatIntervalSec: z.number().int().min(1).max(300).optional(),
156
+ });
157
+
158
+ export type CWMessageSlotUpdate = z.infer<typeof CWMessageSlotUpdateSchema>;
159
+
160
+ /**
161
+ * CW 报文面板更新
162
+ */
163
+ export const CWMessagePanelUpdateSchema = z.object({
164
+ slotCount: z.number().int().min(3).max(12),
165
+ });
166
+
167
+ export type CWMessagePanelUpdate = z.infer<typeof CWMessagePanelUpdateSchema>;
168
+
169
+ /**
170
+ * CW 报文槽位交换请求
171
+ */
172
+ export const CWMessageSlotSwapSchema = z.object({
173
+ slotIdA: z.string(),
174
+ slotIdB: z.string(),
175
+ });
176
+
177
+ export type CWMessageSlotSwap = z.infer<typeof CWMessageSlotSwapSchema>;
@@ -23,7 +23,7 @@ export const DesktopHttpsCertificateMetaSchema = z.object({
23
23
  export type DesktopHttpsCertificateMeta = z.infer<typeof DesktopHttpsCertificateMetaSchema>;
24
24
 
25
25
  export const DesktopHttpsSettingsSchema = z.object({
26
- enabled: z.boolean().default(false),
26
+ enabled: z.boolean().default(true),
27
27
  mode: DesktopHttpsModeSchema.default('self-signed'),
28
28
  httpsPort: z.number().int().min(1).max(65535).default(8443),
29
29
  redirectExternalHttp: z.boolean().default(true),