-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMainWindow.axaml
More file actions
308 lines (286 loc) · 15.6 KB
/
Copy pathMainWindow.axaml
File metadata and controls
308 lines (286 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Babelive.MainWindow"
Title="Babelive — Realtime Audio Translator"
Width="900" Height="640" MinWidth="700" MinHeight="460"
Background="Transparent"
TransparencyLevelHint="Transparent"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="36"
FontFamily="Microsoft YaHei UI, Segoe UI, PingFang SC"
FontSize="13">
<!-- Outer rounded shell. CornerRadius is flipped to 0 when the window
maximizes (see OnPropertyChanged in code-behind) so it doesn't show
rounded clipping against the screen edges. -->
<Border x:Name="OuterShell"
Background="{StaticResource WindowBg}"
BorderBrush="{StaticResource WindowBorder}"
BorderThickness="1"
CornerRadius="12">
<Grid RowDefinitions="36,*">
<!-- ===== Title bar (drag region) ===== -->
<Grid Grid.Row="0" Background="Transparent"
x:Name="TitleBar"
PointerPressed="TitleBar_PointerPressed"
ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal"
VerticalAlignment="Center"
Margin="14,0,0,0">
<Image x:Name="TitleIcon"
Width="18" Height="18"
Margin="0,0,8,0"
VerticalAlignment="Center"/>
<TextBlock Text="Babelive.org"
FontSize="13" FontWeight="SemiBold"
VerticalAlignment="Center"
Foreground="{StaticResource TextSecondary}"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Classes="caption" Content="—"
Click="MinBtn_Click"
ToolTip.Tip="Minimize"/>
<Button x:Name="MaxBtn" Classes="caption" Content="□"
Click="MaxBtn_Click"
ToolTip.Tip="Maximize / Restore"/>
<Button Classes="caption close" Content="✕"
Click="CloseBtn_Click"
ToolTip.Tip="Close (overlay stays in tray)"/>
</StackPanel>
</Grid>
<!-- ===== Main content =====
Rows:
0 — top control panel (dropdowns, options, sliders, right buttons)
1 — recordings folder row (path TextBox + Record button)
2 — status row (Status / state text)
3 — transcripts (Source ↕ Translation, with GridSplitter)
-->
<Grid Grid.Row="1" Margin="24,4,24,20"
RowDefinitions="Auto,Auto,Auto,*">
<!-- Top control panel: 2-col wrapper.
Col 0 = labels / combos / shared refresh (the dense left grid).
Col 1 = Start / API / About vertical button stack.
Keeping the right stack in its OWN column (rather than spanning
the left grid's 4 Auto rows via RowSpan="4") means the 130×130
square Start button can't inflate the left rows' heights —
those rows stay compact, sized only to their own content. -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
<!-- Left grid: labels / combos / shared refresh. -->
<Grid Grid.Column="0"
ColumnDefinitions="120,*,Auto"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Text="Translate to" Grid.Row="0" Grid.Column="0"
Classes="label" Margin="0,8"/>
<!-- Spans Col 1 + Col 2 (refresh column) since there's no refresh
button on this row — fills the empty gap. -->
<ComboBox x:Name="LangCombo" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
HorizontalAlignment="Stretch" Margin="6,4"/>
<TextBlock Text="Capture source" Grid.Row="1" Grid.Column="0"
Classes="label" Margin="0,8"/>
<ComboBox x:Name="CaptureCombo" Grid.Row="1" Grid.Column="1"
HorizontalAlignment="Stretch" Margin="6,4"
ToolTip.Tip="'All system audio (no echo)' uses the Win10 20348+ Process Loopback API to exclude this app from capture — recommended; lets you use the same physical device for capture and playback. The other 'Loopback: …' entries use legacy device loopback (will echo if same device as playback)."/>
<TextBlock Text="Playback device" Grid.Row="2" Grid.Column="0"
Classes="label" Margin="0,8"/>
<ComboBox x:Name="PlayCombo" Grid.Row="2" Grid.Column="1"
HorizontalAlignment="Stretch" Margin="6,4"/>
<!-- Shared refresh button: tall, spans the Capture + Playback rows.
One click re-enumerates BOTH the process / audio-session list
(Capture source dropdown) and the render endpoints (Playback
dropdown). Replaces the two separate ↻ buttons that used to
sit beside each combo. -->
<Button x:Name="RefreshAllBtn" Classes="icon"
Content="↻"
Grid.Row="1" Grid.Column="2" Grid.RowSpan="2"
Width="32" Height="68"
Margin="6,4"
ToolTip.Tip="Refresh both lists: re-detect running processes / audio sessions, and re-enumerate render devices. Useful after Bluetooth (re)connects, USB headset plugs in, or you launch/close a source app."
Click="RefreshAllBtn_Click"/>
<StackPanel Grid.Row="3" Grid.Column="1" Margin="6,10,6,4">
<!-- Top row: option toggles -->
<WrapPanel>
<CheckBox x:Name="MuteCheck"
Content="Transcript only" Margin="0,4,20,4"/>
<CheckBox x:Name="AltUrlCheck"
Content="Alt endpoint" Margin="0,4,20,4"/>
<CheckBox x:Name="EchoSuppressCheck"
Content="Echo suppress" Margin="0,4,20,4"
ToolTip.Tip="Silences input to API while translation is playing. Prevents echo loops but the model may stall after a few seconds of silence."/>
<CheckBox x:Name="MuteOtherDevicesCheck"
Content="Mute source" Margin="0,4,20,4"
ToolTip.Tip="Silence the original audio so you only hear the translation. Babelive's API still receives the source signal (the loopback tap is upstream of the driver mute) — only your physical speakers go quiet. Both the system default device and per-endpoint mutes are restored on Stop."/>
</WrapPanel>
<!-- Volume sliders — both on a single horizontal row to free
vertical space for the transcript panes below. Two
[label / value / slider] groups separated by a 20 px gap.
The two sliders share the row's remaining width 50/50 via
the two `*` columns. -->
<Grid Margin="0,2,0,0"
ColumnDefinitions="Auto,45,*,20,Auto,45,*">
<!-- ===== Source volume (= duck ratio while translating) ===== -->
<TextBlock Grid.Column="0"
Text="Source volume"
VerticalAlignment="Center"
Foreground="{StaticResource TextPrimary}"
Margin="0,0,8,0"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Foreground="{StaticResource Accent}"
FontWeight="SemiBold"
Text="{Binding #DuckSlider.Value, StringFormat={}{0:F0}%}"/>
<Slider Grid.Column="2"
x:Name="DuckSlider"
Minimum="5" Maximum="100" Value="10"
SmallChange="5"
TickFrequency="5" IsSnapToTickEnabled="True"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ValueChanged="DuckSlider_ValueChanged"
PointerWheelChanged="Slider_PointerWheelChanged"
ToolTip.Tip="Volume source apps drop to while translation plays. 100% = no ducking. Minimum 5% — at 0% the API would lose source signal and translation would stall."/>
<!-- Column 3 is a 20 px gap between the two groups. -->
<!-- ===== Translation volume (PCM-level gain) ===== -->
<TextBlock Grid.Column="4"
Text="Translation volume"
VerticalAlignment="Center"
Foreground="{StaticResource TextPrimary}"
Margin="0,0,8,0"/>
<TextBlock Grid.Column="5"
VerticalAlignment="Center"
Foreground="{StaticResource Accent}"
FontWeight="SemiBold"
Text="{Binding #TranslationVolumeSlider.Value, StringFormat={}{0:F0}%}"/>
<Slider Grid.Column="6"
x:Name="TranslationVolumeSlider"
Minimum="0" Maximum="200" Value="100"
SmallChange="10"
TickFrequency="25" IsSnapToTickEnabled="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ValueChanged="TranslationVolumeSlider_ValueChanged"
PointerWheelChanged="Slider_PointerWheelChanged"
ToolTip.Tip="Playback volume of the translated audio. 100% = unchanged. The OpenAI realtime model's TTS is quieter than typical system audio, so push above 100% to match (samples clip at the PCM16 ceiling, generally inaudible until ~180%)."/>
</Grid>
</StackPanel>
</Grid>
<!-- Right column: square Start primary CTA on top, secondary
API… and About buttons beneath. Sibling of the left grid
inside the wrapper (Col 1) — its 130 + 8 + 32 + 8 + 32 = 210
px height does NOT push the left rows taller. -->
<StackPanel Grid.Column="1"
Orientation="Vertical" Margin="14,4,0,4"
VerticalAlignment="Top">
<Button x:Name="StartBtn" Classes="primary"
Content="Start" Width="130" Height="130"
FontSize="28"
Margin="0,0,0,8"
Click="StartBtn_Click"/>
<Button x:Name="ApiBtn" Classes="flat"
Content="API…" Width="130" Height="32"
Click="ApiBtn_Click"
ToolTip.Tip="Configure the API endpoint and key (supports reverse-proxy hosts)"/>
<Button x:Name="ProxyBtn" Classes="flat"
Content="Proxy" Width="130" Height="32"
Margin="0,8,0,0"
Click="ProxyBtn_Click"
ToolTip.Tip="Route the API connection through an HTTP / HTTPS or SOCKS5 proxy"/>
<Button x:Name="AboutBtn" Classes="flat"
Content="About" Width="130" Height="32"
Margin="0,8,0,0"
Click="AboutBtn_Click"
ToolTip.Tip="Open babelive.org in your browser"/>
</StackPanel>
</Grid>
<!-- ===== Row 1: Recordings folder + Record button =====
Path TextBox stretches to fill, Record button pinned right. -->
<Grid Grid.Row="1" Margin="2,16,2,4"
ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="Recordings folder"
Foreground="{StaticResource TextMuted}"
FontSize="11" FontWeight="SemiBold"
VerticalAlignment="Center"
Margin="0,0,10,0"/>
<TextBox Grid.Column="1" x:Name="RecordingsFolderBox"
Classes="input"
IsReadOnly="True"
HorizontalAlignment="Stretch"
FontSize="11"
Padding="8,4"
VerticalAlignment="Center"
ToolTip.Tip="Click to open in Explorer"
Cursor="Hand"
PointerPressed="RecordingsFolderBox_PointerPressed"/>
<Button Grid.Column="2" x:Name="RecordMainBtn" Classes="flat"
Content="● Record" Width="130" Height="32"
Margin="12,0,0,0"
Foreground="#FFE03E3E"
Click="RecordMainBtn_Click"
ToolTip.Tip="Record this segment (audio + transcripts) — same as the red dot on the lyric overlay."/>
</Grid>
<!-- ===== Row 2: Status row =====
[Status] [idle/running …] ← left [Clear text] → right
Clear text sits here (rather than the right button stack) so it
lives next to the transcript panes it actually clears. -->
<DockPanel Grid.Row="2" Margin="2,10,2,10">
<Button x:Name="ClearBtn" Classes="flat"
DockPanel.Dock="Right"
Content="Clear text" Width="130" Height="32"
VerticalAlignment="Center"
Click="ClearBtn_Click"/>
<TextBlock Text="Status" Classes="label"
Foreground="{StaticResource TextMuted}"
FontSize="11" FontWeight="SemiBold"/>
<TextBlock x:Name="StatusText" Text="idle"
Foreground="{StaticResource Accent}"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontSize="11" FontWeight="SemiBold"/>
</DockPanel>
<!-- ===== Row 3: Transcripts =====
50/50 split between SOURCE and TRANSLATION; user can drag the
GridSplitter to rebalance. -->
<Grid Grid.Row="3" RowDefinitions="*,8,*">
<!-- Source (heard) -->
<Grid Grid.Row="0" RowDefinitions="Auto,*">
<TextBlock Grid.Row="0" Text="SOURCE (HEARD)" Classes="section"/>
<Border Grid.Row="1"
Background="{StaticResource ContentBg}"
BorderBrush="{StaticResource ControlBorder}"
BorderThickness="1"
CornerRadius="8">
<TextBox x:Name="SourceText"
Classes="transcript"
TextWrapping="Wrap"
IsReadOnly="True"
AcceptsReturn="True"
FontSize="12"
Foreground="{StaticResource TextSecondary}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</Border>
</Grid>
<GridSplitter Grid.Row="1" ResizeBehavior="PreviousAndNext"
ResizeDirection="Rows"/>
<!-- Translation -->
<Grid Grid.Row="2" RowDefinitions="Auto,*">
<TextBlock Grid.Row="0" Text="TRANSLATION" Classes="section"/>
<Border Grid.Row="1"
Background="{StaticResource ContentBg}"
BorderBrush="{StaticResource ControlBorder}"
BorderThickness="1"
CornerRadius="8">
<TextBox x:Name="TargetText"
Classes="transcript"
TextWrapping="Wrap"
IsReadOnly="True"
AcceptsReturn="True"
FontSize="14"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</Border>
</Grid>
</Grid>
</Grid>
</Grid>
</Border>
</Window>