feat(coding-agent): 拆分两功能 — 选中润色(快取用键)+ Cloud Agent(语音键,占位);热键移到通用设置
- 快取用键 = 选中文本润色:选中→Claude 润色→回插替换;全程胶囊反馈 (没选中提示"请先选中文本";润色中/完成/出错都显示),纯文本 plan 只读、无 guard、便宜可靠。 - 面板键 = Cloud Agent(语音→ASR→Claude→结果弹窗),完整流程开发中,先给可见提示。 - 两个热键从「高级」移到「通用→快捷键」,停用按钮置于录制器左边、常驻行。 - CodingAgentSection 精简为开关/后端/模型/权限/工作目录 + 热键位置提示;5 语言 i18n。 cargo check + tsc + vite 均绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0463c30282
commit
8211dea3d7
8 files changed
+173
-129
No files matched your search
@@ -1404,77 +1404,90 @@ fn coding_agent_hotkey_bridge_loop(inner: Arc<Inner>, rx: mpsc::Receiver<CodingA
|
||||
}
|
||||
let inner_cloned = Arc::clone(&inner);
|
||||
async_runtime::spawn(async move {
|
||||
// 本版面板键与快取用键都走「选中→Claude→回插」;语音 + 面板随后续版本。
|
||||
let _ = evt;
|
||||
handle_coding_agent_quick(&inner_cloned).await;
|
||||
match evt {
|
||||
// 快取用键 = 选中文本润色(选中→Claude 润色→回插替换)。
|
||||
CodingAgentHotkeyEvent::QuickPressed => {
|
||||
handle_coding_agent_quick(&inner_cloned).await;
|
||||
}
|
||||
// 面板键 = Cloud Agent(语音→ASR→Claude→结果弹窗),完整流程开发中。
|
||||
CodingAgentHotkeyEvent::PanelPressed => {
|
||||
handle_coding_agent_panel(&inner_cloned).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 快取用:抓当前选中文本作为指令 → 护栏化无头 Claude → 结果回插光标处。
|
||||
/// 面板键 = Cloud Agent:录音 → ASR → Claude 无头跑小任务 → 结果弹窗。
|
||||
/// 完整语音流程开发中,先给用户一个明确的可见提示,避免静默。
|
||||
async fn handle_coding_agent_panel(inner: &Arc<Inner>) {
|
||||
log::info!("[coding-agent] 面板键:Cloud Agent 语音功能开发中");
|
||||
emit_capsule(
|
||||
inner,
|
||||
CapsuleState::Error,
|
||||
0.0,
|
||||
0,
|
||||
Some("Cloud Agent 语音功能开发中,敬请期待".to_string()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// 快取用:抓当前选中文本 → Claude 润色 → 回插(替换选区)。全程胶囊反馈。
|
||||
async fn handle_coding_agent_quick(inner: &Arc<Inner>) {
|
||||
let prefs = inner.prefs.get();
|
||||
if !prefs.coding_agent_enabled {
|
||||
return;
|
||||
}
|
||||
let selection =
|
||||
tauri::async_runtime::spawn_blocking(crate::selection::capture_selection)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let prompt = match selection {
|
||||
let selection = tauri::async_runtime::spawn_blocking(crate::selection::capture_selection)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let source_text = match selection {
|
||||
Some(ctx) => ctx.text,
|
||||
None => {
|
||||
log::info!("[coding-agent] 快取用:没有选中文本,忽略");
|
||||
log::info!("[coding-agent] 快取用:没有选中文本");
|
||||
emit_capsule(
|
||||
inner,
|
||||
CapsuleState::Error,
|
||||
0.0,
|
||||
0,
|
||||
Some("请先选中文本,再按快捷键".to_string()),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mode = prefs.coding_agent_permission_mode.clone();
|
||||
let settings_json = crate::coding_agent::guard::build_guard_settings_json(&mode, &[]);
|
||||
let settings_path =
|
||||
std::env::temp_dir().join(format!("openless-agent-guard-{}.json", uuid::Uuid::new_v4()));
|
||||
if std::fs::write(
|
||||
&settings_path,
|
||||
serde_json::to_vec(&settings_json).unwrap_or_default(),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
log::warn!("[coding-agent] 写护栏配置失败");
|
||||
return;
|
||||
}
|
||||
log::info!(
|
||||
"[coding-agent] 快取用:润色 {} 字",
|
||||
source_text.chars().count()
|
||||
);
|
||||
emit_capsule(
|
||||
inner,
|
||||
CapsuleState::Polishing,
|
||||
0.0,
|
||||
0,
|
||||
Some("Claude 润色中…".to_string()),
|
||||
None,
|
||||
);
|
||||
|
||||
let cwd = prefs
|
||||
.coding_agent_workdir
|
||||
.clone()
|
||||
.filter(|w| !w.trim().is_empty())
|
||||
.map(std::path::PathBuf::from);
|
||||
if let Some(dir) = &cwd {
|
||||
if let Some(sha) = crate::coding_agent::create_git_snapshot(dir) {
|
||||
log::info!("[coding-agent] 运行前 git 快照 {sha}");
|
||||
}
|
||||
}
|
||||
let prompt = format!(
|
||||
"请润色下面这段文字,使其更通顺自然、表达更清晰,保持原意、语言和事实不变。\
|
||||
直接输出润色后的文本,不要加任何解释、前缀或引号:\n\n{source_text}"
|
||||
);
|
||||
|
||||
let mut req = crate::coding_agent::CodingAgentRequest::new("quick", prompt);
|
||||
req.cwd = cwd;
|
||||
// 纯文本润色:不需要任何工具 → plan 只读、无 guard、便宜快、最可靠。
|
||||
let mut req = crate::coding_agent::CodingAgentRequest::new("quick-polish", prompt);
|
||||
req.model = prefs
|
||||
.coding_agent_model
|
||||
.clone()
|
||||
.filter(|m| !m.trim().is_empty())
|
||||
.or_else(|| Some("sonnet".to_string()));
|
||||
req.permission_mode = parse_coding_agent_permission_mode(&mode);
|
||||
req.settings_json_path = Some(settings_path.clone());
|
||||
req.max_budget_usd = Some(0.5);
|
||||
req.timeout_secs = 120;
|
||||
req.permission_mode = crate::coding_agent::CodingAgentPermissionMode::Plan;
|
||||
req.allowed_tools = Vec::new();
|
||||
req.max_budget_usd = Some(0.2);
|
||||
req.timeout_secs = 60;
|
||||
req.session_persistence = false;
|
||||
req.allowed_tools = vec![
|
||||
"Bash".into(),
|
||||
"Read".into(),
|
||||
"Edit".into(),
|
||||
"Write".into(),
|
||||
"Glob".into(),
|
||||
"Grep".into(),
|
||||
];
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let cancel = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
@@ -1483,20 +1496,32 @@ async fn handle_coding_agent_quick(inner: &Arc<Inner>) {
|
||||
});
|
||||
|
||||
let mut final_text = String::new();
|
||||
let mut error_msg: Option<String> = None;
|
||||
while let Some(ev) = rx.recv().await {
|
||||
if let crate::coding_agent::CodingAgentEvent::Completed { text, .. } = ev {
|
||||
final_text = text;
|
||||
match ev {
|
||||
crate::coding_agent::CodingAgentEvent::Completed { text, .. } => final_text = text,
|
||||
crate::coding_agent::CodingAgentEvent::Error { message, .. } => {
|
||||
error_msg = Some(message)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let _ = run.await;
|
||||
let _ = std::fs::remove_file(&settings_path);
|
||||
let run_result = run.await;
|
||||
|
||||
let final_text = final_text.trim().to_string();
|
||||
if final_text.is_empty() {
|
||||
log::info!("[coding-agent] 快取用:Claude 无文本结果");
|
||||
let msg = error_msg
|
||||
.or_else(|| match run_result {
|
||||
Ok(Err(e)) => Some(e.to_string()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| "Claude 无结果(确认已登录 claude 且额度充足)".to_string());
|
||||
log::warn!("[coding-agent] 快取用失败: {msg}");
|
||||
emit_capsule(inner, CapsuleState::Error, 0.0, 0, Some(msg), None);
|
||||
return;
|
||||
}
|
||||
|
||||
let inserted = final_text.chars().count() as u32;
|
||||
let inner2 = Arc::clone(inner);
|
||||
let restore = prefs.restore_clipboard_after_paste;
|
||||
let paste_shortcut = prefs.paste_shortcut;
|
||||
@@ -1504,16 +1529,8 @@ async fn handle_coding_agent_quick(inner: &Arc<Inner>) {
|
||||
inner2.inserter.insert(&final_text, restore, paste_shortcut)
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
fn parse_coding_agent_permission_mode(s: &str) -> crate::coding_agent::CodingAgentPermissionMode {
|
||||
use crate::coding_agent::CodingAgentPermissionMode as M;
|
||||
match s {
|
||||
"plan" => M::Plan,
|
||||
"default" => M::Default,
|
||||
"bypassPermissions" => M::BypassPermissions,
|
||||
_ => M::AcceptEdits,
|
||||
}
|
||||
log::info!("[coding-agent] 快取用:已回插 {inserted} 字");
|
||||
emit_capsule(inner, CapsuleState::Done, 0.0, 0, None, Some(inserted));
|
||||
}
|
||||
|
||||
fn combo_hotkey_supervisor_loop(inner: Arc<Inner>) {
|
||||
|
||||
@@ -540,6 +540,7 @@ export const en: typeof zhCN = {
|
||||
desc: 'Trigger Claude Code (or OpenCode) by a global hotkey to run quick tasks. Off by default; configure it here.',
|
||||
enable: 'Enable Voice Coding Agent',
|
||||
comingSoonNote: 'Config is saved now; hotkey triggering and the execution flow land in a later version.',
|
||||
hotkeyHint: 'Set the two hotkeys under General → Shortcuts.',
|
||||
provider: 'Agent backend',
|
||||
providerOpenCodeSoon: 'OpenCode (coming soon)',
|
||||
panelHotkey: 'Panel hotkey (voice agent)',
|
||||
@@ -735,6 +736,10 @@ export const en: typeof zhCN = {
|
||||
confirm: 'Confirm capsule insertion',
|
||||
switchStyle: 'Switch to previous style',
|
||||
openApp: 'Open OpenLess',
|
||||
agentPolish: 'Polish selection (Claude)',
|
||||
agentPolishDesc: 'Select text → press → Claude polishes it → replaces the selection.',
|
||||
agentVoice: 'Cloud Agent (voice)',
|
||||
agentVoiceDesc: 'Press → speak → Claude runs the task → result shown in a capsule. (Coming soon)',
|
||||
enable: 'Enable',
|
||||
disable: 'Disable',
|
||||
confirmHint: 'Click ✓ on the capsule',
|
||||
|
||||
@@ -542,6 +542,7 @@ export const ja: typeof zhCN = {
|
||||
desc: 'グローバルホットキーで Claude Code(または OpenCode)を直接起動してクイックタスクを実行。デフォルト無効、ここで設定。',
|
||||
enable: '音声 Agent を有効化',
|
||||
comingSoonNote: '設定はすぐ保存されます。ホットキー起動と実行フローは今後のバージョンで対応。',
|
||||
hotkeyHint: '2 つのホットキーは「一般 → ショートカット」で設定。',
|
||||
provider: 'Agent バックエンド',
|
||||
providerOpenCodeSoon: 'OpenCode(近日対応)',
|
||||
panelHotkey: 'パネルキー(音声 Agent)',
|
||||
@@ -737,6 +738,10 @@ export const ja: typeof zhCN = {
|
||||
confirm: 'カプセル入力を確定',
|
||||
switchStyle: '前のスタイルに切り替え',
|
||||
openApp: 'OpenLess を開く',
|
||||
agentPolish: '選択テキストを推敲(Claude)',
|
||||
agentPolishDesc: 'テキスト選択 → キー → Claude が推敲 → 選択範囲を置換。',
|
||||
agentVoice: 'Cloud Agent(音声)',
|
||||
agentVoiceDesc: 'キー → 話す → Claude がタスク実行 → 結果をカプセル表示。(近日対応)',
|
||||
enable: '有効化',
|
||||
disable: '無効化',
|
||||
confirmHint: '右側の ✓ をクリック',
|
||||
|
||||
@@ -542,6 +542,7 @@ export const ko: typeof zhCN = {
|
||||
desc: '전역 단축키로 Claude Code(또는 OpenCode)를 바로 실행해 빠른 작업 수행. 기본 비활성, 여기서 설정.',
|
||||
enable: '음성 Agent 활성화',
|
||||
comingSoonNote: '설정은 즉시 저장됩니다. 단축키 트리거와 실행 흐름은 이후 버전에서 제공됩니다.',
|
||||
hotkeyHint: '두 단축키는 「일반 → 단축키」에서 설정.',
|
||||
provider: 'Agent 백엔드',
|
||||
providerOpenCodeSoon: 'OpenCode(곧 지원)',
|
||||
panelHotkey: '패널 키(음성 Agent)',
|
||||
@@ -737,6 +738,10 @@ export const ko: typeof zhCN = {
|
||||
confirm: '캡슐 입력 확정',
|
||||
switchStyle: '이전 스타일로 전환',
|
||||
openApp: 'OpenLess 열기',
|
||||
agentPolish: '선택 텍스트 다듬기(Claude)',
|
||||
agentPolishDesc: '텍스트 선택 → 키 → Claude 다듬기 → 선택 영역 교체.',
|
||||
agentVoice: 'Cloud Agent(음성)',
|
||||
agentVoiceDesc: '키 → 말하기 → Claude 작업 실행 → 결과 캡슐 표시. (곧 지원)',
|
||||
enable: '활성화',
|
||||
disable: '비활성화',
|
||||
confirmHint: '오른쪽 ✓ 클릭',
|
||||
|
||||
@@ -538,6 +538,7 @@ export const zhCN = {
|
||||
desc: '按全局热键直接触发 Claude Code(或 OpenCode)执行快速任务。默认关闭,在此配置。',
|
||||
enable: '启用语音 Agent',
|
||||
comingSoonNote: '配置即时保存;热键触发与执行链路随后续版本生效。',
|
||||
hotkeyHint: '两个热键在「通用 → 快捷键」里设置。',
|
||||
provider: 'Agent 后端',
|
||||
providerOpenCodeSoon: 'OpenCode(即将支持)',
|
||||
panelHotkey: '面板键(语音 Agent)',
|
||||
@@ -733,6 +734,10 @@ export const zhCN = {
|
||||
confirm: '胶囊确认插入',
|
||||
switchStyle: '切换上一次风格',
|
||||
openApp: '打开 OpenLess',
|
||||
agentPolish: '选中文本润色(Claude)',
|
||||
agentPolishDesc: '选中文本 → 按键 → Claude 润色 → 替换选区。',
|
||||
agentVoice: 'Cloud Agent(语音)',
|
||||
agentVoiceDesc: '按键 → 说话 → Claude 执行任务 → 结果弹胶囊显示。(开发中)',
|
||||
enable: '启用',
|
||||
disable: '停用',
|
||||
confirmHint: '点击右侧 ✓',
|
||||
|
||||
@@ -540,6 +540,7 @@ export const zhTW: typeof zhCN = {
|
||||
desc: '按全域熱鍵直接觸發 Claude Code(或 OpenCode)執行快速任務。預設關閉,在此設定。',
|
||||
enable: '啟用語音 Agent',
|
||||
comingSoonNote: '設定即時儲存;熱鍵觸發與執行鏈路隨後續版本生效。',
|
||||
hotkeyHint: '兩個熱鍵在「通用 → 快捷鍵」裡設定。',
|
||||
provider: 'Agent 後端',
|
||||
providerOpenCodeSoon: 'OpenCode(即將支援)',
|
||||
panelHotkey: '面板鍵(語音 Agent)',
|
||||
@@ -735,6 +736,10 @@ export const zhTW: typeof zhCN = {
|
||||
confirm: '膠囊確認插入',
|
||||
switchStyle: '切換上一次風格',
|
||||
openApp: '打開 OpenLess',
|
||||
agentPolish: '選取文字潤色(Claude)',
|
||||
agentPolishDesc: '選取文字 → 按鍵 → Claude 潤色 → 取代選取。',
|
||||
agentVoice: 'Cloud Agent(語音)',
|
||||
agentVoiceDesc: '按鍵 → 說話 → Claude 執行任務 → 結果彈膠囊顯示。(開發中)',
|
||||
enable: '啟用',
|
||||
disable: '停用',
|
||||
confirmHint: '點擊右側 ✓',
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// 高级 → 语音 Agent(快速 Agent)配置:启用开关、后端(Claude / OpenCode)、
|
||||
// 两个全局热键、模型 / 权限模式 / 工作目录。配置经 UserPreferences 持久化;
|
||||
// 热键的实时注册与触发链路由 coordinator 接线(随后续版本生效)。
|
||||
// 高级 → 快速 Agent 配置:启用开关、后端(Claude / OpenCode)、模型 / 权限模式 / 工作目录。
|
||||
// 两个热键(选中润色键、Cloud Agent 语音键)在「通用 → 快捷键」里配置。
|
||||
// 配置经 UserPreferences 持久化;启用后 coordinator 才注册热键。
|
||||
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { CodingAgentPermissionMode, CodingAgentProviderId, ShortcutBinding } from '../../lib/types'
|
||||
import type { CodingAgentPermissionMode, CodingAgentProviderId } from '../../lib/types'
|
||||
import { useHotkeySettings } from '../../state/HotkeySettingsContext'
|
||||
import { ShortcutRecorder } from '../../components/ShortcutRecorder'
|
||||
import { Btn, Card } from '../_atoms'
|
||||
import { Card } from '../_atoms'
|
||||
import { SectionDesc, SectionTitle, SettingRow, Toggle, inputStyle } from './shared'
|
||||
|
||||
const PERMISSION_MODES: CodingAgentPermissionMode[] = [
|
||||
@@ -16,9 +15,6 @@ const PERMISSION_MODES: CodingAgentPermissionMode[] = [
|
||||
'bypassPermissions',
|
||||
]
|
||||
|
||||
const DEFAULT_PANEL_HOTKEY: ShortcutBinding = { primary: 'Enter', modifiers: ['cmd', 'shift'] }
|
||||
const DEFAULT_QUICK_HOTKEY: ShortcutBinding = { primary: 'J', modifiers: ['cmd', 'shift'] }
|
||||
|
||||
export function CodingAgentSection() {
|
||||
const { t } = useTranslation()
|
||||
const { prefs, updatePrefs: savePrefs } = useHotkeySettings()
|
||||
@@ -33,55 +29,12 @@ export function CodingAgentSection() {
|
||||
|
||||
const enabled = prefs.codingAgentEnabled
|
||||
|
||||
const setHotkey = (
|
||||
key: 'codingAgentPanelHotkey' | 'codingAgentQuickHotkey',
|
||||
binding: ShortcutBinding | null,
|
||||
) => savePrefs({ ...prefs, [key]: binding })
|
||||
|
||||
const hotkeyRow = (
|
||||
key: 'codingAgentPanelHotkey' | 'codingAgentQuickHotkey',
|
||||
label: string,
|
||||
desc: string,
|
||||
fallback: ShortcutBinding,
|
||||
) => {
|
||||
const value = prefs[key]
|
||||
return (
|
||||
<SettingRow label={label} desc={desc}>
|
||||
{value ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, width: '100%' }}>
|
||||
<ShortcutRecorder value={value} alignRecordButton onSave={b => setHotkey(key, b)} />
|
||||
<button
|
||||
onClick={() => void setHotkey(key, null)}
|
||||
style={{
|
||||
alignSelf: 'flex-start',
|
||||
fontSize: 11,
|
||||
padding: '3px 10px',
|
||||
background: 'transparent',
|
||||
color: 'var(--ol-ink-4)',
|
||||
border: '0.5px solid var(--ol-line-strong)',
|
||||
borderRadius: 6,
|
||||
fontFamily: 'inherit',
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{t('settings.shortcuts.disable', 'Disable')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<Btn variant="blue" size="sm" onClick={() => void setHotkey(key, fallback)}>
|
||||
{t('settings.shortcuts.enable', 'Enable')}
|
||||
</Btn>
|
||||
)}
|
||||
</SettingRow>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<SectionTitle>{t('settings.codingAgent.title')}</SectionTitle>
|
||||
<SectionDesc>{t('settings.codingAgent.desc')}</SectionDesc>
|
||||
|
||||
<SettingRow label={t('settings.codingAgent.enable')} desc={t('settings.codingAgent.comingSoonNote')}>
|
||||
<SettingRow label={t('settings.codingAgent.enable')} desc={t('settings.codingAgent.hotkeyHint')}>
|
||||
<Toggle
|
||||
on={enabled}
|
||||
onToggle={next => void savePrefs({ ...prefs, codingAgentEnabled: next })}
|
||||
@@ -106,19 +59,6 @@ export function CodingAgentSection() {
|
||||
</select>
|
||||
</SettingRow>
|
||||
|
||||
{hotkeyRow(
|
||||
'codingAgentPanelHotkey',
|
||||
t('settings.codingAgent.panelHotkey'),
|
||||
t('settings.codingAgent.panelHotkeyDesc'),
|
||||
DEFAULT_PANEL_HOTKEY,
|
||||
)}
|
||||
{hotkeyRow(
|
||||
'codingAgentQuickHotkey',
|
||||
t('settings.codingAgent.quickHotkey'),
|
||||
t('settings.codingAgent.quickHotkeyDesc'),
|
||||
DEFAULT_QUICK_HOTKEY,
|
||||
)}
|
||||
|
||||
<SettingRow label={t('settings.codingConsole.permissionMode')}>
|
||||
<select
|
||||
value={prefs.codingAgentPermissionMode}
|
||||
|
||||
@@ -177,6 +177,68 @@ export function ShortcutsSection() {
|
||||
</button>
|
||||
)}
|
||||
</SettingRow>
|
||||
<SettingRow label={t('settings.shortcuts.agentPolish')} desc={t('settings.shortcuts.agentPolishDesc')}>
|
||||
{prefs.codingAgentQuickHotkey ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%' }}>
|
||||
<button
|
||||
onClick={() => void savePrefs({ ...prefs, codingAgentQuickHotkey: null })}
|
||||
style={disableBtnStyle}
|
||||
>
|
||||
{t('settings.shortcuts.disable', 'Disable')}
|
||||
</button>
|
||||
<ShortcutRecorder
|
||||
value={prefs.codingAgentQuickHotkey}
|
||||
alignRecordButton
|
||||
onSave={async b => {
|
||||
await savePrefs({ ...prefs, codingAgentQuickHotkey: b });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() =>
|
||||
void savePrefs({
|
||||
...prefs,
|
||||
codingAgentQuickHotkey: { primary: 'J', modifiers: ['cmd', 'shift'] },
|
||||
})
|
||||
}
|
||||
style={enableBtnStyle}
|
||||
>
|
||||
{t('settings.shortcuts.enable', 'Enable')}
|
||||
</button>
|
||||
)}
|
||||
</SettingRow>
|
||||
<SettingRow label={t('settings.shortcuts.agentVoice')} desc={t('settings.shortcuts.agentVoiceDesc')}>
|
||||
{prefs.codingAgentPanelHotkey ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%' }}>
|
||||
<button
|
||||
onClick={() => void savePrefs({ ...prefs, codingAgentPanelHotkey: null })}
|
||||
style={disableBtnStyle}
|
||||
>
|
||||
{t('settings.shortcuts.disable', 'Disable')}
|
||||
</button>
|
||||
<ShortcutRecorder
|
||||
value={prefs.codingAgentPanelHotkey}
|
||||
alignRecordButton
|
||||
onSave={async b => {
|
||||
await savePrefs({ ...prefs, codingAgentPanelHotkey: b });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() =>
|
||||
void savePrefs({
|
||||
...prefs,
|
||||
codingAgentPanelHotkey: { primary: 'Enter', modifiers: ['cmd', 'shift'] },
|
||||
})
|
||||
}
|
||||
style={enableBtnStyle}
|
||||
>
|
||||
{t('settings.shortcuts.enable', 'Enable')}
|
||||
</button>
|
||||
)}
|
||||
</SettingRow>
|
||||
{readonlyRows.map(([k, v]) => (
|
||||
<SettingRow key={k} label={k}>
|
||||
<kbd style={{
|
||||
|
||||
Reference in new issue
Block a user