Implement Push-to-Talk feature and enhance audio input settings in AssistantView and CustomizeView. Update README for API key instructions and improve audio processing logic in OpenAI SDK. Adjust pnpm-lock.yaml for dependency updates.

This commit is contained in:
Илья Глазунов
2026-01-16 00:41:58 +03:00
parent 669c019fd8
commit 656e8f0932
10 changed files with 5190 additions and 4095 deletions
+112
View File
@@ -366,6 +366,57 @@ export class AssistantView extends LitElement {
.region-select-btn span {
margin-left: 4px;
}
.ptt-toggle-btn {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
transition: all 0.15s ease;
}
.ptt-toggle-btn:hover {
background: var(--hover-background);
color: var(--text-color);
border-color: var(--text-color);
}
.ptt-toggle-btn.active {
color: var(--error-color);
border-color: var(--error-color);
}
.ptt-indicator {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--text-secondary);
margin-bottom: 6px;
}
.ptt-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--border-color);
box-shadow: 0 0 0 1px var(--border-color);
}
.ptt-dot.active {
background: var(--error-color);
box-shadow: 0 0 0 1px var(--error-color);
}
.ptt-label {
font-family: 'SF Mono', Monaco, monospace;
}
`;
static properties = {
@@ -377,6 +428,9 @@ export class AssistantView extends LitElement {
flashCount: { type: Number },
flashLiteCount: { type: Number },
aiProvider: { type: String },
pushToTalkActive: { type: Boolean },
audioInputMode: { type: String },
pushToTalkKeybind: { type: String },
};
constructor() {
@@ -388,6 +442,9 @@ export class AssistantView extends LitElement {
this.flashCount = 0;
this.flashLiteCount = 0;
this.aiProvider = 'gemini';
this.pushToTalkActive = false;
this.audioInputMode = 'auto';
this.pushToTalkKeybind = '';
}
getProfileNames() {
@@ -507,6 +564,7 @@ export class AssistantView extends LitElement {
// Load limits on mount
this.loadLimits();
this.loadPushToTalkKeybind();
// Set up IPC listeners for keyboard shortcuts
if (window.require) {
@@ -532,10 +590,17 @@ export class AssistantView extends LitElement {
this.scrollResponseDown();
};
this.handlePushToTalkState = (event, state) => {
this.pushToTalkActive = state?.active ?? false;
this.audioInputMode = state?.inputMode ?? 'auto';
this.requestUpdate();
};
ipcRenderer.on('navigate-previous-response', this.handlePreviousResponse);
ipcRenderer.on('navigate-next-response', this.handleNextResponse);
ipcRenderer.on('scroll-response-up', this.handleScrollUp);
ipcRenderer.on('scroll-response-down', this.handleScrollDown);
ipcRenderer.on('push-to-talk-state', this.handlePushToTalkState);
}
}
@@ -557,6 +622,9 @@ export class AssistantView extends LitElement {
if (this.handleScrollDown) {
ipcRenderer.removeListener('scroll-response-down', this.handleScrollDown);
}
if (this.handlePushToTalkState) {
ipcRenderer.removeListener('push-to-talk-state', this.handlePushToTalkState);
}
}
}
@@ -584,6 +652,15 @@ export class AssistantView extends LitElement {
}
}
async loadPushToTalkKeybind() {
if (window.cheatingDaddy?.storage?.getKeybinds) {
const isMac = window.cheatingDaddy?.isMacOS || navigator.platform.includes('Mac');
const defaultKeybind = isMac ? 'Ctrl+Space' : 'Ctrl+Space';
const keybinds = await window.cheatingDaddy.storage.getKeybinds();
this.pushToTalkKeybind = keybinds?.pushToTalk || defaultKeybind;
}
}
getTotalUsed() {
return this.flashCount + this.flashLiteCount;
}
@@ -608,6 +685,14 @@ export class AssistantView extends LitElement {
}
}
handlePushToTalkToggle() {
if (!window.require) {
return;
}
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('push-to-talk-toggle');
}
scrollToBottom() {
setTimeout(() => {
const container = this.shadowRoot.querySelector('.response-container');
@@ -649,10 +734,26 @@ export class AssistantView extends LitElement {
render() {
const responseCounter = this.getResponseCounter();
const showPushToTalk = this.aiProvider === 'openai-sdk' && this.audioInputMode === 'push-to-talk';
const keybindLabel = this.pushToTalkKeybind || 'Hotkey';
const pushToTalkLabel = this.pushToTalkActive
? 'Recording...'
: `Press ${keybindLabel} to start/stop`;
const pushToTalkButtonLabel = this.pushToTalkActive ? 'Stop' : 'Record';
return html`
<div class="response-container" id="responseContainer"></div>
${showPushToTalk
? html`
<div class="ptt-indicator">
<span class="ptt-dot ${this.pushToTalkActive ? 'active' : ''}"></span>
<span>Push-to-Talk:</span>
<span class="ptt-label">${pushToTalkLabel}</span>
</div>
`
: ''}
<div class="text-input-container">
<button class="nav-button" @click=${this.navigateToPreviousResponse} ?disabled=${this.currentResponseIndex <= 0}>
<svg width="24px" height="24px" stroke-width="1.7" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -671,6 +772,17 @@ export class AssistantView extends LitElement {
<input type="text" id="textInput" placeholder="Type a message to the AI..." @keydown=${this.handleTextKeydown} />
<div class="capture-buttons">
${showPushToTalk
? html`
<button
class="ptt-toggle-btn ${this.pushToTalkActive ? 'active' : ''}"
@click=${this.handlePushToTalkToggle}
title="Toggle Push-to-Talk recording"
>
${pushToTalkButtonLabel}
</button>
`
: ''}
<button class="region-select-btn" @click=${this.handleRegionSelect} title="Select region to analyze (like Win+Shift+S)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path
+62
View File
@@ -537,6 +537,7 @@ export class CustomizeView extends LitElement {
color: var(--error-color);
border-left: 2px solid var(--error-color);
}
`;
static properties = {
@@ -549,6 +550,7 @@ export class CustomizeView extends LitElement {
backgroundTransparency: { type: Number },
fontSize: { type: Number },
theme: { type: String },
audioInputMode: { type: String },
onProfileChange: { type: Function },
onLanguageChange: { type: Function },
onImageQualityChange: { type: Function },
@@ -587,6 +589,7 @@ export class CustomizeView extends LitElement {
// Audio mode default
this.audioMode = 'speaker_only';
this.audioInputMode = 'auto';
// Custom prompt
this.customPrompt = '';
@@ -795,6 +798,7 @@ export class CustomizeView extends LitElement {
this.backgroundTransparency = prefs.backgroundTransparency ?? 0.8;
this.fontSize = prefs.fontSize ?? 20;
this.audioMode = prefs.audioMode ?? 'speaker_only';
this.audioInputMode = prefs.audioInputMode ?? 'auto';
this.customPrompt = prefs.customPrompt ?? '';
this.theme = prefs.theme ?? 'dark';
this.aiProvider = prefs.aiProvider ?? 'gemini';
@@ -820,6 +824,7 @@ export class CustomizeView extends LitElement {
this.updateBackgroundTransparency();
this.updateFontSize();
this.notifyPushToTalkSettings();
this.requestUpdate();
} catch (error) {
console.error('Error loading settings:', error);
@@ -832,6 +837,10 @@ export class CustomizeView extends LitElement {
resizeLayout();
}
disconnectedCallback() {
super.disconnectedCallback();
}
getProfiles() {
return [
{
@@ -944,6 +953,28 @@ export class CustomizeView extends LitElement {
this.requestUpdate();
}
async handleAudioInputModeChange(e) {
this.audioInputMode = e.target.value;
await cheatingDaddy.storage.updatePreference('audioInputMode', e.target.value);
this.notifyPushToTalkSettings();
this.requestUpdate();
}
notifyPushToTalkSettings() {
if (!window.require) {
return;
}
try {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('update-push-to-talk-settings', {
inputMode: this.audioInputMode,
});
ipcRenderer.send('update-keybinds', this.keybinds);
} catch (error) {
console.error('Failed to notify push-to-talk settings:', error);
}
}
async handleThemeChange(e) {
this.theme = e.target.value;
await cheatingDaddy.theme.save(this.theme);
@@ -965,6 +996,7 @@ export class CustomizeView extends LitElement {
nextResponse: isMac ? 'Cmd+]' : 'Ctrl+]',
scrollUp: isMac ? 'Cmd+Shift+Up' : 'Ctrl+Shift+Up',
scrollDown: isMac ? 'Cmd+Shift+Down' : 'Ctrl+Shift+Down',
pushToTalk: isMac ? 'Ctrl+Space' : 'Ctrl+Space',
};
}
@@ -1050,6 +1082,11 @@ export class CustomizeView extends LitElement {
name: 'Scroll Response Down',
description: 'Scroll the AI response content down',
},
{
key: 'pushToTalk',
name: 'Push-to-Talk',
description: 'Activate audio recording (OpenAI SDK only)',
},
];
}
@@ -1319,6 +1356,9 @@ export class CustomizeView extends LitElement {
}
renderAudioSection() {
const isPushToTalkAvailable = this.aiProvider === 'openai-sdk';
const pushToTalkDisabled = !isPushToTalkAvailable;
return html`
<div class="content-header">Audio Settings</div>
<div class="form-grid">
@@ -1331,6 +1371,28 @@ export class CustomizeView extends LitElement {
</select>
<div class="form-description">Choose which audio sources to capture for the AI.</div>
</div>
<div class="form-group">
<label class="form-label">Audio Input Mode</label>
<select
class="form-control"
.value=${this.audioInputMode}
@change=${this.handleAudioInputModeChange}
?disabled=${pushToTalkDisabled}
>
<option value="auto">Automatic (Always Listening)</option>
<option value="push-to-talk">Push-to-Talk (Hotkey Activated)</option>
</select>
<div class="form-description">
${pushToTalkDisabled
? 'Push-to-Talk is available only with the OpenAI SDK provider.'
: this.audioInputMode === 'auto'
? 'Audio is continuously recorded and transcribed when silence is detected.'
: 'Audio recording starts when you press and hold/toggle the hotkey.'}
</div>
</div>
${this.audioInputMode === 'push-to-talk'
? html`<div class="form-description">Use the Push-to-Talk hotkey (toggle) to start/stop recording.</div>`
: ''}
</div>
`;
}
+5 -5
View File
@@ -295,7 +295,7 @@ export class HelpView extends LitElement {
<span>Community & Support</span>
</div>
<div class="community-links">
<div class="community-link" @click=${() => this.handleExternalLinkClick('https://cheatingdaddy.com')}>
<!-- <div class="community-link" @click=${() => this.handleExternalLinkClick('https://cheatingdaddy.com')}>
<svg
viewBox="0 0 24 24"
fill="none"
@@ -312,8 +312,8 @@ export class HelpView extends LitElement {
></path>
</svg>
Website
</div>
<div class="community-link" @click=${() => this.handleExternalLinkClick('https://github.com/sohzm/cheating-daddy')}>
</div> -->
<div class="community-link" @click=${() => this.handleExternalLinkClick('https://github.com/ShiftyX1/Mastermind')}>
<svg
viewBox="0 0 24 24"
fill="none"
@@ -329,7 +329,7 @@ export class HelpView extends LitElement {
</svg>
GitHub
</div>
<div class="community-link" @click=${() => this.handleExternalLinkClick('https://discord.gg/GCBdubnXfJ')}>
<!-- <div class="community-link" @click=${() => this.handleExternalLinkClick('https://discord.gg/GCBdubnXfJ')}>
<svg
viewBox="0 0 24 24"
fill="none"
@@ -353,7 +353,7 @@ export class HelpView extends LitElement {
></path>
</svg>
Discord
</div>
</div> -->
</div>
</div>