beast-agent 0.16.0 → 0.17.0
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/.env.example +1 -0
- package/README.md +25 -9
- package/config.example.yaml +15 -2
- package/package.json +1 -1
- package/src/main.js +104 -21
- package/src/preload.js +3 -1
- package/src/renderer/index.html +2 -0
- package/src/renderer/renderer.js +16 -10
- package/src/renderer/style.css +27 -5
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -27,8 +27,6 @@ Beast Agent is a personal AI agent that runs on your machine and connects to **a
|
|
|
27
27
|
|
|
28
28
|
## 📦 Download & Install — 2 commands, ready
|
|
29
29
|
|
|
30
|
-
### Option 1 · npm (recommended)
|
|
31
|
-
|
|
32
30
|
```bash
|
|
33
31
|
npm install -g beast-agent
|
|
34
32
|
```
|
|
@@ -41,15 +39,33 @@ beast-agent
|
|
|
41
39
|
|
|
42
40
|
That's it — the app window opens. On first launch Beast also creates a **desktop shortcut** and registers itself to **start with Windows** (lives in the tray). Later updates: close the app and run `beast-agent update`.
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
## ⚙️ Configuration
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
### Recommended: OpenCode Zen (free tier, no credit card)
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
Get a key at [opencode.ai/auth](https://opencode.ai/auth), then copy `config.example.yaml` → `config.yaml`:
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
```yaml
|
|
49
|
+
defaultSelection: opencode::glm-5.2
|
|
50
|
+
providers:
|
|
51
|
+
- id: opencode
|
|
52
|
+
name: OpenCode Zen
|
|
53
|
+
baseUrl: https://opencode.ai/zen/v1
|
|
54
|
+
apiKey: <your-key>
|
|
55
|
+
models:
|
|
56
|
+
- glm-5.2
|
|
57
|
+
- kimi-k2.7-code
|
|
58
|
+
- deepseek-v4-flash-free
|
|
59
|
+
- big-pickle
|
|
60
|
+
- minimax-m3
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> Free models include `deepseek-v4-flash-free`, `big-pickle`, `mimo-v2.5-free`, `nemotron-3-ultra-free`. If you have an **OpenCode Go** subscription, use the same config with `baseUrl: https://opencode.ai/zen/go/v1`.
|
|
64
|
+
|
|
65
|
+
### Any other OpenAI-compatible provider
|
|
66
|
+
|
|
67
|
+
OpenRouter, Zhipu, Ollama, OpenAI — anything that speaks `/v1/chat/completions` works:
|
|
51
68
|
|
|
52
|
-
1. Copy `config.example.yaml` → `config.yaml` and add your providers:
|
|
53
69
|
```yaml
|
|
54
70
|
defaultSelection: openrouter::anthropic/claude-3.5-sonnet
|
|
55
71
|
providers:
|
|
@@ -61,8 +77,8 @@ providers:
|
|
|
61
77
|
- anthropic/claude-3.5-sonnet
|
|
62
78
|
- gpt-4o
|
|
63
79
|
```
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
|
|
81
|
+
API keys can also go into `.env` (copy `.env.example` → `.env`). On first launch, Settings → Provider pulls your models automatically.
|
|
66
82
|
|
|
67
83
|
All app data lives under `%APPDATA%\beast` (sessions, memory, WhatsApp pairing, encrypted settings).
|
|
68
84
|
|
package/config.example.yaml
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
# Kullanım: bu dosyayı %APPDATA%\beast\config.yaml olarak kopyala ve düzenle.
|
|
3
3
|
# (Taşınabilir kullanım için BEAST_DATA ortam değişkeni ile başka klasöre alabilirsin.)
|
|
4
4
|
|
|
5
|
+
# ÖNERİLEN: OpenCode Zen — ücretsiz modeller, kredi kartı gerekmez (opencode.ai/auth)
|
|
5
6
|
providers:
|
|
7
|
+
opencode:
|
|
8
|
+
name: OpenCode Zen
|
|
9
|
+
base_url: https://opencode.ai/zen/v1
|
|
10
|
+
key_env: OPENCODE_API_KEY
|
|
11
|
+
models:
|
|
12
|
+
glm-5.2: {}
|
|
13
|
+
kimi-k2.7-code: {}
|
|
14
|
+
deepseek-v4-flash-free: {}
|
|
15
|
+
big-pickle: {}
|
|
16
|
+
minimax-m3: {}
|
|
17
|
+
# OpenCode Go aboneliğin varsa base_url: https://opencode.ai/zen/go/v1 kullan.
|
|
18
|
+
|
|
6
19
|
zhipu:
|
|
7
20
|
name: Zhipu AI
|
|
8
21
|
base_url: https://api.z.ai/api/paas/v4
|
|
@@ -20,7 +33,7 @@ providers:
|
|
|
20
33
|
gpt-4o: {}
|
|
21
34
|
|
|
22
35
|
model:
|
|
23
|
-
provider:
|
|
24
|
-
default: glm-
|
|
36
|
+
provider: opencode # varsayılan provider id (yoksa ilk provider kullanılır)
|
|
37
|
+
default: glm-5.2 # varsayılan model
|
|
25
38
|
# base_url: ... # istersen aktif provider'ın URL'ini override et
|
|
26
39
|
# key_env: ... # istersen anahtarı farklı env'den oku
|
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -30,6 +30,33 @@ function isNpmMode() {
|
|
|
30
30
|
return !app.isPackaged && /node_modules[\\/]beast-agent/i.test(String(app.getAppPath()));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/* npm registry'den en son sürüm (10 dk cache) — Update sekmesi + otomatik kontrol */
|
|
34
|
+
let npmLatestCache = { version: null, at: 0 };
|
|
35
|
+
|
|
36
|
+
function isNewerVersion(a, b) {
|
|
37
|
+
const pa = String(a).split('.').map(Number);
|
|
38
|
+
const pb = String(b).split('.').map(Number);
|
|
39
|
+
for (let i = 0; i < 3; i++) {
|
|
40
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
41
|
+
if (d) return d > 0;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function getNpmLatest(force) {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
if (!force && npmLatestCache.version && now - npmLatestCache.at < 10 * 60 * 1000) return npmLatestCache.version;
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch('https://registry.npmjs.org/beast-agent/latest', { signal: AbortSignal.timeout(15000) });
|
|
51
|
+
if (res.ok) {
|
|
52
|
+
const j = await res.json();
|
|
53
|
+
const v = String(j.version || '').trim();
|
|
54
|
+
if (v) npmLatestCache = { version: v, at: now };
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
return npmLatestCache.version;
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
function emitUpdateEvent() {
|
|
34
61
|
try {
|
|
35
62
|
if (win && !win.isDestroyed()) {
|
|
@@ -99,6 +126,24 @@ function startAutoUpdater() {
|
|
|
99
126
|
}
|
|
100
127
|
} catch {}
|
|
101
128
|
}
|
|
129
|
+
|
|
130
|
+
/* npm kurulumu için otomatik sürüm kontrolü (registry üzerinden, 6 saatte bir) */
|
|
131
|
+
function startNpmUpdateWatch() {
|
|
132
|
+
if (!isNpmMode() || settings.autoCheckUpdate === false) return;
|
|
133
|
+
const check = async () => {
|
|
134
|
+
try {
|
|
135
|
+
const v = await getNpmLatest();
|
|
136
|
+
if (v && isNewerVersion(v, app.getVersion()) && !updateState.available) {
|
|
137
|
+
updateState.available = true;
|
|
138
|
+
updateState.version = v;
|
|
139
|
+
emitUpdateEvent();
|
|
140
|
+
log.info('main', `npm: yeni sürüm var ${v} (mevcut ${app.getVersion()})`);
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
};
|
|
144
|
+
check();
|
|
145
|
+
setInterval(check, 6 * 60 * 60 * 1000);
|
|
146
|
+
}
|
|
102
147
|
const { htmlToText, setExaKey, setTinyfishKey } = require('./agent/tools');
|
|
103
148
|
const { waToolLine } = require('./agent/watext');
|
|
104
149
|
|
|
@@ -460,12 +505,17 @@ function decodeAudioToPcm16k(buf) {
|
|
|
460
505
|
});
|
|
461
506
|
}
|
|
462
507
|
|
|
463
|
-
async function transcribeAudio(buf /*
|
|
508
|
+
async function transcribeAudio(buf, langOverride /* 'tr' | 'en' | 'auto' */) {
|
|
464
509
|
try {
|
|
465
510
|
const audio = await decodeAudioToPcm16k(buf);
|
|
466
511
|
if (!audio || !audio.length) return null;
|
|
467
512
|
const asr = await ensureStt();
|
|
468
|
-
|
|
513
|
+
/* dil: arayüz diline bağlı — UI Türkçe ise Türkçe, İngilizce ise İngilizce algılar */
|
|
514
|
+
const lang = langOverride || settings.sttLang || 'tr';
|
|
515
|
+
const opts = { task: 'transcribe', chunk_length_s: 30, stride_length_s: 5 };
|
|
516
|
+
if (lang === 'en') opts.language = 'english';
|
|
517
|
+
else if (lang === 'tr') opts.language = 'turkish';
|
|
518
|
+
const out = await asr(audio, opts);
|
|
469
519
|
return String((out && out.text) || '').trim() || null;
|
|
470
520
|
} catch (e) {
|
|
471
521
|
waLog('stt hata: ' + String((e && e.message) || e));
|
|
@@ -1740,6 +1790,7 @@ app.whenReady().then(() => {
|
|
|
1740
1790
|
maybeRunWhereWasI();
|
|
1741
1791
|
falloutResume();
|
|
1742
1792
|
startAutoUpdater(); // #3 sessiz güncelleme
|
|
1793
|
+
startNpmUpdateWatch(); // npm kurulumunda registry üzerinden otomatik sürüm kontrolü
|
|
1743
1794
|
|
|
1744
1795
|
// #12 STT prefetch: whisper modelini arka planda hazırla (ilk sesli mesajda bekleme olmasın)
|
|
1745
1796
|
if (settings.sttPrefetch !== false) {
|
|
@@ -3135,25 +3186,48 @@ ipcMain.handle('sec:set', (_e, cfg) => {
|
|
|
3135
3186
|
ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
|
|
3136
3187
|
|
|
3137
3188
|
/* ---------------- #Update IPC ---------------- */
|
|
3138
|
-
ipcMain.handle('update:status', () =>
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
ipcMain.handle('update:check', async (_e, viaCommand) => {
|
|
3148
|
-
if (isNpmMode()) return { ok: false, npm: true, error: 'npm kurulumu — güncelleme: beast-agent update' };
|
|
3149
|
-
if (!autoUpdater) return { ok: false, error: 'updater kullanılamıyor (taşınabilir/geliştirme modu)' };
|
|
3189
|
+
ipcMain.handle('update:status', async () => {
|
|
3190
|
+
const st = {
|
|
3191
|
+
current: app.getVersion(),
|
|
3192
|
+
packaged: app.isPackaged,
|
|
3193
|
+
npm: isNpmMode(),
|
|
3194
|
+
...updateState,
|
|
3195
|
+
autoCheck: settings.autoCheckUpdate !== false,
|
|
3196
|
+
autoDownload: settings.autoDownloadUpdate !== false,
|
|
3197
|
+
};
|
|
3150
3198
|
try {
|
|
3151
|
-
|
|
3152
|
-
const v =
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3199
|
+
/* npm registry'den güncel sürüm — updater ne yaparsa yapsın "En son sürüm" kartı dolu olsun */
|
|
3200
|
+
const v = await getNpmLatest();
|
|
3201
|
+
if (v) {
|
|
3202
|
+
st.npmLatest = v;
|
|
3203
|
+
if (!st.version) st.version = v;
|
|
3204
|
+
if (isNewerVersion(v, app.getVersion())) st.available = true;
|
|
3205
|
+
}
|
|
3206
|
+
} catch {}
|
|
3207
|
+
return st;
|
|
3208
|
+
});
|
|
3209
|
+
|
|
3210
|
+
ipcMain.handle('update:check', async () => {
|
|
3211
|
+
const current = app.getVersion();
|
|
3212
|
+
/* her modda npm registry'den taze kontrol (cache bypass) */
|
|
3213
|
+
const v = await getNpmLatest(true);
|
|
3214
|
+
const out = { ok: true, npm: isNpmMode(), version: v || undefined, available: v ? isNewerVersion(v, current) : false };
|
|
3215
|
+
if (v) {
|
|
3216
|
+
if (!updateState.version || isNewerVersion(v, updateState.version)) updateState.version = v;
|
|
3217
|
+
if (isNewerVersion(v, current)) updateState.available = true;
|
|
3156
3218
|
}
|
|
3219
|
+
/* packaged + updater: GitHub Releases kontrolü de çalışsın */
|
|
3220
|
+
if (!isNpmMode() && autoUpdater) {
|
|
3221
|
+
try {
|
|
3222
|
+
const r = await autoUpdater.checkForUpdates();
|
|
3223
|
+
const uv = r && r.update && r.update.version;
|
|
3224
|
+
if (uv) { out.version = uv; out.available = uv !== current; }
|
|
3225
|
+
} catch (e) {
|
|
3226
|
+
if (!v) return { ok: false, error: String((e && e.message) || e) };
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
emitUpdateEvent();
|
|
3230
|
+
return out;
|
|
3157
3231
|
});
|
|
3158
3232
|
|
|
3159
3233
|
ipcMain.handle('update:install', () => {
|
|
@@ -3191,17 +3265,26 @@ async function runUpdateCommand(reply /* fn(text) */) {
|
|
|
3191
3265
|
}
|
|
3192
3266
|
|
|
3193
3267
|
/* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
|
|
3194
|
-
|
|
3268
|
+
/* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
|
|
3269
|
+
ipcMain.handle('stt:transcribe', async (_e, b64, lang) => {
|
|
3195
3270
|
try {
|
|
3196
3271
|
const buf = Buffer.from(String(b64 || '').split(',').pop() || '', 'base64');
|
|
3197
3272
|
if (!buf.length) return { ok: false, error: 'boş ses kaydı' };
|
|
3198
|
-
const text = await transcribeAudio(buf);
|
|
3273
|
+
const text = await transcribeAudio(buf, lang === 'en' || lang === 'auto' ? lang : undefined);
|
|
3199
3274
|
return text ? { ok: true, text } : { ok: false, error: 'konuşma algılanamadı' };
|
|
3200
3275
|
} catch (e) {
|
|
3201
3276
|
return { ok: false, error: String((e && e.message) || e) };
|
|
3202
3277
|
}
|
|
3203
3278
|
});
|
|
3204
3279
|
|
|
3280
|
+
ipcMain.handle('stt:lang:get', () => settings.sttLang || 'tr');
|
|
3281
|
+
ipcMain.handle('stt:lang:set', (_e, lang) => {
|
|
3282
|
+
const v = ['auto', 'tr', 'en'].includes(String(lang)) ? String(lang) : 'tr';
|
|
3283
|
+
settings.sttLang = v;
|
|
3284
|
+
saveSettings();
|
|
3285
|
+
return v;
|
|
3286
|
+
});
|
|
3287
|
+
|
|
3205
3288
|
ipcMain.handle('limits:get', () => {
|
|
3206
3289
|
const l = settings.limits || {};
|
|
3207
3290
|
return {
|
package/src/preload.js
CHANGED
|
@@ -44,7 +44,9 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
44
44
|
secGet: () => ipcRenderer.invoke('sec:get'),
|
|
45
45
|
secSet: (cfg) => ipcRenderer.invoke('sec:set', cfg),
|
|
46
46
|
approvalRespond: (id, ok, always) => ipcRenderer.invoke('approval:respond', { id, ok, always }),
|
|
47
|
-
sttTranscribe: (b64) => ipcRenderer.invoke('stt:transcribe', b64),
|
|
47
|
+
sttTranscribe: (b64, lang) => ipcRenderer.invoke('stt:transcribe', b64, lang),
|
|
48
|
+
sttLangGet: () => ipcRenderer.invoke('stt:lang:get'),
|
|
49
|
+
sttLangSet: (lang) => ipcRenderer.invoke('stt:lang:set', lang),
|
|
48
50
|
updateStatus: () => ipcRenderer.invoke('update:status'),
|
|
49
51
|
updateCheck: () => ipcRenderer.invoke('update:check'),
|
|
50
52
|
updateInstall: () => ipcRenderer.invoke('update:install'),
|
package/src/renderer/index.html
CHANGED
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
<div id="settingsDialog">
|
|
94
94
|
<aside id="setTabs">
|
|
95
95
|
<div class="set-tabs-title" data-i18n="set_title">AYARLAR</div>
|
|
96
|
+
<nav class="set-tabs-list">
|
|
96
97
|
<button class="tab active" data-tab="provider" data-i18n="tab_provider">Provider</button>
|
|
97
98
|
<button class="tab" data-tab="fallout" data-i18n="tab_fallout">Fallout</button>
|
|
98
99
|
<button class="tab" data-tab="memory" data-i18n="tab_memory">Memory</button>
|
|
@@ -110,6 +111,7 @@
|
|
|
110
111
|
<button class="tab" data-tab="limits" data-i18n="tab_limits">Limit</button>
|
|
111
112
|
<button class="tab" data-tab="sec" data-i18n="tab_security">Güvenlik</button>
|
|
112
113
|
<button class="tab" data-tab="update" data-i18n="tab_update">Güncelleme</button>
|
|
114
|
+
</nav>
|
|
113
115
|
<div class="set-tabs-foot">
|
|
114
116
|
<div id="beastCodeBox" class="beast-code" data-i18n-title="bc_label" title="Beast Kodu">
|
|
115
117
|
<div class="bc-row">
|
package/src/renderer/renderer.js
CHANGED
|
@@ -1550,16 +1550,16 @@ let updatePaneTimer = null;
|
|
|
1550
1550
|
|
|
1551
1551
|
function renderUpdateStateHtml(st) {
|
|
1552
1552
|
let status;
|
|
1553
|
-
if (st.
|
|
1554
|
-
else if (st.error) status = '⚠ ' + st.error;
|
|
1553
|
+
if (st.error) status = '\u26A0\uFE0E ' + st.error;
|
|
1555
1554
|
else if (st.downloaded) status = _t('up_downloaded') + ' (v' + (st.version || '?') + ')';
|
|
1556
1555
|
else if (st.progress) status = _t('up_downloading') + ' %' + st.progress.percent;
|
|
1557
1556
|
else if (st.checking) status = _t('up_checking');
|
|
1558
1557
|
else if (st.available) status = _t('up_available') + ' (v' + (st.version || '?') + ')';
|
|
1558
|
+
else if (st.npm) status = _t('up_npm_mode');
|
|
1559
1559
|
else if (st.available === false) status = _t('up_uptodate');
|
|
1560
1560
|
else status = '—';
|
|
1561
1561
|
|
|
1562
|
-
return `<div class="usage-stat"
|
|
1562
|
+
return `<div class="usage-stat" id="upStatusBox"><div class="us-label">${_t('up_status')}</div><div class="us-value" style="font-size:14px">${escapeHtml(status)}</div></div>`;
|
|
1563
1563
|
}
|
|
1564
1564
|
|
|
1565
1565
|
async function renderUpdatePane() {
|
|
@@ -1599,7 +1599,9 @@ beast-agent update</pre></div>`
|
|
|
1599
1599
|
if (btnCheck) btnCheck.addEventListener('click', async () => {
|
|
1600
1600
|
btnCheck.disabled = true;
|
|
1601
1601
|
const r = await beast.updateCheck().catch(() => ({ ok: false, error: 'ipc' }));
|
|
1602
|
+
btnCheck.disabled = false;
|
|
1602
1603
|
if (!r.ok && r.error) toast(r.error);
|
|
1604
|
+
renderUpdatePane();
|
|
1603
1605
|
});
|
|
1604
1606
|
const btnInstall = pane.querySelector('#upInstall');
|
|
1605
1607
|
if (btnInstall) btnInstall.addEventListener('click', async () => {
|
|
@@ -1607,16 +1609,13 @@ beast-agent update</pre></div>`
|
|
|
1607
1609
|
if (!r.ok && r.error) toast(r.error);
|
|
1608
1610
|
});
|
|
1609
1611
|
|
|
1610
|
-
/* indirme ilerlemesi için sekme açıkken canlı tazele */
|
|
1612
|
+
/* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
|
|
1611
1613
|
updatePaneTimer = setInterval(() => {
|
|
1612
1614
|
if ($('#tab-update').hidden || els.settingsOverlay.hidden) { clearInterval(updatePaneTimer); return; }
|
|
1613
1615
|
beast.updateStatus().then((s) => {
|
|
1614
1616
|
if (!s) return;
|
|
1615
|
-
const box = pane.querySelector('
|
|
1616
|
-
if (box)
|
|
1617
|
-
const wrap = pane.querySelector('.usage-stat');
|
|
1618
|
-
if (wrap) wrap.outerHTML = renderUpdateStateHtml(s);
|
|
1619
|
-
}
|
|
1617
|
+
const box = pane.querySelector('#upStatusBox');
|
|
1618
|
+
if (box) box.outerHTML = renderUpdateStateHtml(s);
|
|
1620
1619
|
}).catch(() => {});
|
|
1621
1620
|
}, 1000);
|
|
1622
1621
|
}
|
|
@@ -2194,6 +2193,7 @@ function onEvent(ev) {
|
|
|
2194
2193
|
}
|
|
2195
2194
|
if (ev.type === 'update') {
|
|
2196
2195
|
if (ev.downloaded) toast(_t('up_downloaded') + ' (v' + (ev.version || '?') + ') — /update now');
|
|
2196
|
+
else if (ev.available && ev.version && ev.version !== ev.current) toast(_t('up_available') + ' (v' + ev.version + ')');
|
|
2197
2197
|
if (!els.settingsOverlay.hidden && setTab === 'update') renderUpdatePane();
|
|
2198
2198
|
return;
|
|
2199
2199
|
}
|
|
@@ -3051,6 +3051,10 @@ async function init() {
|
|
|
3051
3051
|
});
|
|
3052
3052
|
document.addEventListener('langchange', syncLangBtn);
|
|
3053
3053
|
document.addEventListener('langchange', renderActiveSettingsTab);
|
|
3054
|
+
/* Whisper STT dili arayüz dilini izler (WA sesli notları da) */
|
|
3055
|
+
const syncSttLang = () => { try { beast.sttLangSet((window.I18N && window.I18N.lang) || 'tr'); } catch {} };
|
|
3056
|
+
syncSttLang();
|
|
3057
|
+
document.addEventListener('langchange', syncSttLang);
|
|
3054
3058
|
}
|
|
3055
3059
|
|
|
3056
3060
|
els.attachBtn.addEventListener('click', () => els.fileInput.click());
|
|
@@ -3091,7 +3095,9 @@ async function init() {
|
|
|
3091
3095
|
if (!blob.size) return;
|
|
3092
3096
|
toast(_t('mic_transcribing'));
|
|
3093
3097
|
const b64 = await blobToDataUrl(blob);
|
|
3094
|
-
|
|
3098
|
+
/* arayüz dili = Whisper dili: TR ise Türkçe, EN ise İngilizce algılar */
|
|
3099
|
+
const uiLang = (window.I18N && window.I18N.lang) || 'tr';
|
|
3100
|
+
const r = await beast.sttTranscribe(b64, uiLang).catch(() => ({ ok: false, error: 'ipc' }));
|
|
3095
3101
|
if (r && r.ok && r.text) {
|
|
3096
3102
|
els.input.value = (els.input.value ? els.input.value.replace(/\s+$/, '') + ' ' : '') + r.text;
|
|
3097
3103
|
els.input.focus();
|
package/src/renderer/style.css
CHANGED
|
@@ -1096,11 +1096,33 @@ body.browser-open #toast { left: calc((100vw - var(--bw, 480px)) / 2); }
|
|
|
1096
1096
|
flex-direction: column;
|
|
1097
1097
|
padding: 16px 10px 12px;
|
|
1098
1098
|
}
|
|
1099
|
-
.set-tabs-title {
|
|
1100
|
-
font-size: 11px;
|
|
1101
|
-
letter-spacing: 2px;
|
|
1102
|
-
color: var(--muted);
|
|
1103
|
-
padding: 0 12px 10px;
|
|
1099
|
+
.set-tabs-title {
|
|
1100
|
+
font-size: 11px;
|
|
1101
|
+
letter-spacing: 2px;
|
|
1102
|
+
color: var(--muted);
|
|
1103
|
+
padding: 0 12px 10px;
|
|
1104
|
+
}
|
|
1105
|
+
/* sekme listesi: küçük ekranda taşarsa içeride kayar — alt kısım (Beast Kodu + Kapat) sabit kalır */
|
|
1106
|
+
.set-tabs-list {
|
|
1107
|
+
display: flex;
|
|
1108
|
+
flex-direction: column;
|
|
1109
|
+
align-items: stretch;
|
|
1110
|
+
flex: 1 1 auto;
|
|
1111
|
+
min-height: 0;
|
|
1112
|
+
overflow-y: auto;
|
|
1113
|
+
padding-right: 2px;
|
|
1114
|
+
margin: 0 -2px;
|
|
1115
|
+
scrollbar-width: thin;
|
|
1116
|
+
scrollbar-color: var(--border) transparent;
|
|
1117
|
+
}
|
|
1118
|
+
.set-tabs-list::-webkit-scrollbar { width: 8px; }
|
|
1119
|
+
.set-tabs-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }
|
|
1120
|
+
.set-tabs-list::-webkit-scrollbar-thumb:hover { background: var(--muted); }
|
|
1121
|
+
.set-tabs-list::-webkit-scrollbar-track { background: transparent; }
|
|
1122
|
+
@media (max-height: 860px) {
|
|
1123
|
+
#setTabs { padding: 10px 8px; }
|
|
1124
|
+
.set-tabs-title { padding: 0 12px 6px; }
|
|
1125
|
+
#setTabs .tab { padding: 6px 12px; font-size: 12.5px; margin-bottom: 1px; }
|
|
1104
1126
|
}
|
|
1105
1127
|
#setTabs .tab {
|
|
1106
1128
|
text-align: left;
|