@iaforged/context-code 1.1.4 → 1.1.7
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/README.md +32 -8
- package/dist/src/commands/init.js +91 -219
- package/dist/src/commands/voice/index.js +6 -7
- package/dist/src/commands/voice/voice.js +87 -43
- package/dist/src/commands.js +1 -3
- package/dist/src/components/LogoV2/VoiceModeNotice.js +1 -1
- package/dist/src/components/PromptInput/VoiceIndicator.js +4 -4
- package/dist/src/components/Spinner.js +18 -18
- package/dist/src/constants/spinnerVerbs.js +9 -9
- package/dist/src/hooks/usePasteHandler.js +8 -8
- package/dist/src/hooks/useVoice.js +93 -805
- package/dist/src/hooks/useVoiceEnabled.js +3 -15
- package/dist/src/hooks/useVoiceIntegration.js +6 -25
- package/dist/src/keybindings/defaultBindings.js +9 -6
- package/dist/src/screens/REPL.js +10 -22
- package/dist/src/services/localDictation.js +520 -0
- package/dist/src/services/voice.js +9 -7
- package/dist/src/state/AppState.js +1 -3
- package/dist/src/tools/ConfigTool/ConfigTool.js +12 -15
- package/dist/src/tools/ConfigTool/supportedSettings.js +2 -2
- package/dist/src/utils/imagePaste.js +11 -5
- package/dist/src/utils/model/model.js +2 -0
- package/dist/src/utils/settings/types.js +2 -2
- package/dist/src/voice/voiceModeEnabled.js +5 -25
- package/dist/vendor/audio-capture/arm64-darwin/audio-capture.node +0 -0
- package/dist/vendor/audio-capture/arm64-linux/audio-capture.node +0 -0
- package/dist/vendor/audio-capture/arm64-win32/audio-capture.node +0 -0
- package/dist/vendor/audio-capture/x64-darwin/audio-capture.node +0 -0
- package/dist/vendor/audio-capture/x64-linux/audio-capture.node +0 -0
- package/dist/vendor/audio-capture/x64-win32/audio-capture.node +0 -0
- package/dist/vendor/audio-capture-src/index.js +114 -0
- package/dist/vendor/audio-capture-src/index.ts +155 -0
- package/docs/comandos.md +132 -121
- package/package.json +1 -1
|
@@ -1,31 +1,84 @@
|
|
|
1
1
|
import { normalizeLanguageForSTT } from '../../hooks/useVoice.js';
|
|
2
2
|
import { getShortcutDisplay } from '../../keybindings/shortcutFormat.js';
|
|
3
3
|
import { logEvent } from '../../services/analytics/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { checkLocalDictationConfiguration, getLocalDictationStatus, installLocalDictation, } from '../../services/localDictation.js';
|
|
5
5
|
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
|
6
6
|
import { settingsChangeDetector } from '../../utils/settings/changeDetector.js';
|
|
7
7
|
import { getInitialSettings, updateSettingsForSource, } from '../../utils/settings/settings.js';
|
|
8
8
|
import { isVoiceModeEnabled } from '../../voice/voiceModeEnabled.js';
|
|
9
9
|
const LANG_HINT_MAX_SHOWS = 2;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
function parseArgs(args) {
|
|
11
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
12
|
+
if (tokens.length === 0) {
|
|
13
|
+
return { action: 'toggle' };
|
|
14
|
+
}
|
|
15
|
+
const [command, ...rest] = tokens;
|
|
16
|
+
switch (command.toLowerCase()) {
|
|
17
|
+
case 'install':
|
|
18
|
+
case 'instalar':
|
|
19
|
+
return { action: 'install', value: rest.join(' ').trim() || undefined };
|
|
20
|
+
case 'status':
|
|
21
|
+
case 'estado':
|
|
22
|
+
return { action: 'status' };
|
|
23
|
+
case 'help':
|
|
24
|
+
case 'ayuda':
|
|
25
|
+
return { action: 'help' };
|
|
26
|
+
default:
|
|
27
|
+
return { action: 'help' };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function getUsageText() {
|
|
31
|
+
return [
|
|
32
|
+
'Uso de /dictar:',
|
|
33
|
+
'/dictar',
|
|
34
|
+
'/dictar install [modelo]',
|
|
35
|
+
'/dictar status',
|
|
36
|
+
'',
|
|
37
|
+
'Modelo por defecto: base (multidioma).',
|
|
38
|
+
'Ejemplos:',
|
|
39
|
+
'- /dictar install',
|
|
40
|
+
'- /dictar install base',
|
|
41
|
+
].join('\n');
|
|
42
|
+
}
|
|
43
|
+
export const call = async (args) => {
|
|
12
44
|
if (!isVoiceModeEnabled()) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
45
|
+
return {
|
|
46
|
+
type: 'text',
|
|
47
|
+
value: 'El modo de dictado no esta disponible.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const parsed = parseArgs(args);
|
|
51
|
+
if (parsed.action === 'help') {
|
|
52
|
+
return { type: 'text', value: getUsageText() };
|
|
53
|
+
}
|
|
54
|
+
if (parsed.action === 'status') {
|
|
55
|
+
return {
|
|
56
|
+
type: 'text',
|
|
57
|
+
value: await getLocalDictationStatus(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (parsed.action === 'install') {
|
|
61
|
+
try {
|
|
62
|
+
const result = await installLocalDictation(parsed.value);
|
|
16
63
|
return {
|
|
17
64
|
type: 'text',
|
|
18
|
-
value:
|
|
65
|
+
value: `Instalacion completada.\n` +
|
|
66
|
+
`Backend: ${result.executablePath}\n` +
|
|
67
|
+
`Modelo: ${result.modelPath}\n` +
|
|
68
|
+
`Release: ${result.releaseTag}\n\n` +
|
|
69
|
+
`Ahora ejecuta /dictar para activarlo.`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
type: 'text',
|
|
75
|
+
value: `No se pudo instalar el backend de dictado.\n` +
|
|
76
|
+
`${error instanceof Error ? error.message : String(error)}`,
|
|
19
77
|
};
|
|
20
78
|
}
|
|
21
|
-
return {
|
|
22
|
-
type: 'text',
|
|
23
|
-
value: 'Voice mode is not available.',
|
|
24
|
-
};
|
|
25
79
|
}
|
|
26
80
|
const currentSettings = getInitialSettings();
|
|
27
81
|
const isCurrentlyEnabled = currentSettings.voiceEnabled === true;
|
|
28
|
-
// Toggle OFF — no checks needed
|
|
29
82
|
if (isCurrentlyEnabled) {
|
|
30
83
|
const result = updateSettingsForSource('userSettings', {
|
|
31
84
|
voiceEnabled: false,
|
|
@@ -33,70 +86,63 @@ export const call = async () => {
|
|
|
33
86
|
if (result.error) {
|
|
34
87
|
return {
|
|
35
88
|
type: 'text',
|
|
36
|
-
value: '
|
|
89
|
+
value: 'No se pudo actualizar la configuracion. Revisa tu archivo de settings.',
|
|
37
90
|
};
|
|
38
91
|
}
|
|
39
92
|
settingsChangeDetector.notifyChange('userSettings');
|
|
40
93
|
logEvent('tengu_voice_toggled', { enabled: false });
|
|
41
94
|
return {
|
|
42
95
|
type: 'text',
|
|
43
|
-
value: '
|
|
96
|
+
value: 'Modo de dictado desactivado.',
|
|
44
97
|
};
|
|
45
98
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const { checkRecordingAvailability } = await import('../../services/voice.js');
|
|
49
|
-
// Check recording availability (microphone access)
|
|
50
|
-
const recording = await checkRecordingAvailability();
|
|
51
|
-
if (!recording.available) {
|
|
99
|
+
const dictation = await checkLocalDictationConfiguration();
|
|
100
|
+
if (!dictation.available) {
|
|
52
101
|
return {
|
|
53
102
|
type: 'text',
|
|
54
|
-
value:
|
|
103
|
+
value: dictation.error ?? 'El dictado local no esta configurado.',
|
|
55
104
|
};
|
|
56
105
|
}
|
|
57
|
-
|
|
58
|
-
|
|
106
|
+
const { checkRecordingAvailability, checkVoiceDependencies, requestMicrophonePermission } = await import('../../services/voice.js');
|
|
107
|
+
const recording = await checkRecordingAvailability();
|
|
108
|
+
if (!recording.available) {
|
|
59
109
|
return {
|
|
60
110
|
type: 'text',
|
|
61
|
-
value:
|
|
111
|
+
value: recording.reason ??
|
|
112
|
+
'El modo de dictado no esta disponible en este entorno.',
|
|
62
113
|
};
|
|
63
114
|
}
|
|
64
|
-
// Check for recording tools
|
|
65
|
-
const { checkVoiceDependencies, requestMicrophonePermission } = await import('../../services/voice.js');
|
|
66
115
|
const deps = await checkVoiceDependencies();
|
|
67
116
|
if (!deps.available) {
|
|
68
117
|
const hint = deps.installCommand
|
|
69
|
-
? `\
|
|
70
|
-
: '\
|
|
118
|
+
? `\nInstala las herramientas de audio con: ${deps.installCommand}`
|
|
119
|
+
: '\nInstala manualmente las herramientas de grabacion de audio.';
|
|
71
120
|
return {
|
|
72
121
|
type: 'text',
|
|
73
|
-
value: `No
|
|
122
|
+
value: `No se encontro una herramienta de grabacion de audio.${hint}`,
|
|
74
123
|
};
|
|
75
124
|
}
|
|
76
|
-
// Probe mic access so the OS permission dialog fires now rather than
|
|
77
|
-
// on the user's first hold-to-talk activation.
|
|
78
125
|
if (!(await requestMicrophonePermission())) {
|
|
79
126
|
let guidance;
|
|
80
127
|
if (process.platform === 'win32') {
|
|
81
|
-
guidance = '
|
|
128
|
+
guidance = 'Configuracion > Privacidad > Microfono';
|
|
82
129
|
}
|
|
83
130
|
else if (process.platform === 'linux') {
|
|
84
|
-
guidance =
|
|
131
|
+
guidance = 'la configuracion de audio del sistema';
|
|
85
132
|
}
|
|
86
133
|
else {
|
|
87
|
-
guidance = 'System Settings
|
|
134
|
+
guidance = 'System Settings > Privacy & Security > Microphone';
|
|
88
135
|
}
|
|
89
136
|
return {
|
|
90
137
|
type: 'text',
|
|
91
|
-
value: `
|
|
138
|
+
value: `El acceso al microfono esta denegado. Habilitalo en ${guidance} y luego ejecuta /dictar otra vez.`,
|
|
92
139
|
};
|
|
93
140
|
}
|
|
94
|
-
// All checks passed — enable voice
|
|
95
141
|
const result = updateSettingsForSource('userSettings', { voiceEnabled: true });
|
|
96
142
|
if (result.error) {
|
|
97
143
|
return {
|
|
98
144
|
type: 'text',
|
|
99
|
-
value: '
|
|
145
|
+
value: 'No se pudo actualizar la configuracion. Revisa tu archivo de settings.',
|
|
100
146
|
};
|
|
101
147
|
}
|
|
102
148
|
settingsChangeDetector.notifyChange('userSettings');
|
|
@@ -104,17 +150,15 @@ export const call = async () => {
|
|
|
104
150
|
const key = getShortcutDisplay('voice:pushToTalk', 'Chat', 'Space');
|
|
105
151
|
const stt = normalizeLanguageForSTT(currentSettings.language);
|
|
106
152
|
const cfg = getGlobalConfig();
|
|
107
|
-
// Reset the hint counter whenever the resolved STT language changes
|
|
108
|
-
// (including first-ever enable, where lastLanguage is undefined).
|
|
109
153
|
const langChanged = cfg.voiceLangHintLastLanguage !== stt.code;
|
|
110
154
|
const priorCount = langChanged ? 0 : (cfg.voiceLangHintShownCount ?? 0);
|
|
111
|
-
const showHint =
|
|
155
|
+
const showHint = priorCount < LANG_HINT_MAX_SHOWS;
|
|
112
156
|
let langNote = '';
|
|
113
157
|
if (stt.fellBackFrom) {
|
|
114
|
-
langNote = `
|
|
158
|
+
langNote = ` Nota: "${stt.fellBackFrom}" no esta soportado por el backend local; se usara deteccion automatica.`;
|
|
115
159
|
}
|
|
116
160
|
else if (showHint) {
|
|
117
|
-
langNote = `
|
|
161
|
+
langNote = ` Idioma de dictado: ${stt.code} (/config para cambiarlo).`;
|
|
118
162
|
}
|
|
119
163
|
if (langChanged || showHint) {
|
|
120
164
|
saveGlobalConfig(prev => ({
|
|
@@ -125,6 +169,6 @@ export const call = async () => {
|
|
|
125
169
|
}
|
|
126
170
|
return {
|
|
127
171
|
type: 'text',
|
|
128
|
-
value: `
|
|
172
|
+
value: `Modo de dictado activado. Manten ${key} presionado para grabar.${langNote}`,
|
|
129
173
|
};
|
|
130
174
|
};
|
package/dist/src/commands.js
CHANGED
|
@@ -77,9 +77,7 @@ const bridge = feature('BRIDGE_MODE')
|
|
|
77
77
|
const remoteControlServerCommand = feature('DAEMON') && feature('BRIDGE_MODE')
|
|
78
78
|
? require('./commands/remoteControlServer/index.js').default
|
|
79
79
|
: null;
|
|
80
|
-
const voiceCommand =
|
|
81
|
-
? require('./commands/voice/index.js').default
|
|
82
|
-
: null;
|
|
80
|
+
const voiceCommand = require('./commands/voice/index.js').default;
|
|
83
81
|
const forceSnip = feature('HISTORY_SNIP')
|
|
84
82
|
? require('./commands/force-snip.js').default
|
|
85
83
|
: null;
|
|
@@ -57,7 +57,7 @@ function VoiceModeNoticeInner() {
|
|
|
57
57
|
}
|
|
58
58
|
let t2;
|
|
59
59
|
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
|
60
|
-
t2 = _jsxs(Box, { paddingLeft: 2, children: [_jsx(AnimatedAsterisk, {}), _jsx(Text, { dimColor: true, children: "
|
|
60
|
+
t2 = _jsxs(Box, { paddingLeft: 2, children: [_jsx(AnimatedAsterisk, {}), _jsx(Text, { dimColor: true, children: " El dictado local ya esta disponible \u00B7 usa /dictar install para prepararlo o /dictar para activarlo" })] });
|
|
61
61
|
$[3] = t2;
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
@@ -40,7 +40,7 @@ function VoiceIndicatorImpl(t0) {
|
|
|
40
40
|
{
|
|
41
41
|
let t1;
|
|
42
42
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
43
|
-
t1 = _jsx(Text, { dimColor: true, children: "
|
|
43
|
+
t1 = _jsx(Text, { dimColor: true, children: "dictando..." });
|
|
44
44
|
$[0] = t1;
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
@@ -77,7 +77,7 @@ export function VoiceWarmupHint() {
|
|
|
77
77
|
}
|
|
78
78
|
let t0;
|
|
79
79
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
80
|
-
t0 = _jsx(Text, { dimColor: true, children: "
|
|
80
|
+
t0 = _jsx(Text, { dimColor: true, children: "sigue manteniendo..." });
|
|
81
81
|
$[0] = t0;
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
@@ -93,7 +93,7 @@ function ProcessingShimmer() {
|
|
|
93
93
|
if (reducedMotion) {
|
|
94
94
|
let t0;
|
|
95
95
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
96
|
-
t0 = _jsx(Text, { color: "warning", children: "
|
|
96
|
+
t0 = _jsx(Text, { color: "warning", children: "Dictado: procesando..." });
|
|
97
97
|
$[0] = t0;
|
|
98
98
|
}
|
|
99
99
|
else {
|
|
@@ -115,7 +115,7 @@ function ProcessingShimmer() {
|
|
|
115
115
|
const color = t0;
|
|
116
116
|
let t1;
|
|
117
117
|
if ($[3] !== color) {
|
|
118
|
-
t1 = _jsx(Text, { color: color, children: "
|
|
118
|
+
t1 = _jsx(Text, { color: color, children: "Dictado: procesando..." });
|
|
119
119
|
$[3] = color;
|
|
120
120
|
$[4] = t1;
|
|
121
121
|
}
|
|
@@ -39,11 +39,11 @@ const SPINNER_FRAMES = [...DEFAULT_CHARACTERS, ...[...DEFAULT_CHARACTERS].revers
|
|
|
39
39
|
function translateSpinnerTip(tip) {
|
|
40
40
|
if (tip ===
|
|
41
41
|
'Double-tap esc to rewind the code and/or conversation to a previous point in time') {
|
|
42
|
-
return 'Pulsa dos veces Esc para rebobinar el
|
|
42
|
+
return 'Pulsa dos veces Esc para rebobinar el codigo y/o la conversacion a un punto anterior en el tiempo';
|
|
43
43
|
}
|
|
44
44
|
if (tip ===
|
|
45
45
|
'Double-tap esc to rewind the conversation to a previous point in time') {
|
|
46
|
-
return 'Pulsa dos veces Esc para rebobinar la
|
|
46
|
+
return 'Pulsa dos veces Esc para rebobinar la conversacion a un punto anterior en el tiempo';
|
|
47
47
|
}
|
|
48
48
|
return tip;
|
|
49
49
|
}
|
|
@@ -52,18 +52,18 @@ function translateSpinnerTip(tip) {
|
|
|
52
52
|
// violate Rules of Hooks (the inner variant calls ~10 more hooks).
|
|
53
53
|
export function SpinnerWithVerb(props) {
|
|
54
54
|
const isBriefOnly = useAppState(s => s.isBriefOnly);
|
|
55
|
-
// REPL overrides isBriefOnly
|
|
55
|
+
// REPL overrides isBriefOnly→false when viewing a teammate transcript
|
|
56
56
|
// (see isBriefOnly={viewedTeammateTask ? false : isBriefOnly}). That
|
|
57
|
-
// prop isn't threaded here, so replicate the gate from the store
|
|
57
|
+
// prop isn't threaded here, so replicate the gate from the store —
|
|
58
58
|
// teammate view needs the real spinner (which shows teammate status).
|
|
59
59
|
const viewingAgentTaskId = useAppState(s_0 => s_0.viewingAgentTaskId);
|
|
60
|
-
// Hoisted to mount-time
|
|
60
|
+
// Hoisted to mount-time — this component re-renders at animation framerate.
|
|
61
61
|
const briefEnvEnabled = feature('KAIROS') || feature('KAIROS_BRIEF') ?
|
|
62
62
|
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
|
63
63
|
useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_BRIEF), []) : false;
|
|
64
|
-
// Runtime gate mirrors isBriefEnabled() but inlined
|
|
64
|
+
// Runtime gate mirrors isBriefEnabled() but inlined — importing from
|
|
65
65
|
// BriefTool.ts would leak tool-name strings into external builds. Single
|
|
66
|
-
// spinner instance
|
|
66
|
+
// spinner instance → hooks stay unconditional (two subs, negligible).
|
|
67
67
|
if ((feature('KAIROS') || feature('KAIROS_BRIEF')) && (getKairosActive() || getUserMsgOptIn() && (briefEnvEnabled || getFeatureValue_CACHED_MAY_BE_STALE('tengu_kairos_brief', false))) && isBriefOnly && !viewingAgentTaskId) {
|
|
68
68
|
return _jsx(BriefSpinner, { mode: props.mode, overrideMessage: props.overrideMessage });
|
|
69
69
|
}
|
|
@@ -73,7 +73,7 @@ function SpinnerWithVerbInner({ mode, loadingStartTimeRef, totalPausedMsRef, pau
|
|
|
73
73
|
const settings = useSettings();
|
|
74
74
|
const reducedMotion = settings.prefersReducedMotion ?? false;
|
|
75
75
|
// NOTE: useAnimationFrame(50) lives in SpinnerAnimationRow, not here.
|
|
76
|
-
// This component only re-renders when props or app state change
|
|
76
|
+
// This component only re-renders when props or app state change —
|
|
77
77
|
// it is no longer on the 50ms clock. All `time`-derived values
|
|
78
78
|
// (frame, glimmer, stalled intensity, token counter, thinking shimmer,
|
|
79
79
|
// elapsed-time timer) are computed inside the child.
|
|
@@ -139,7 +139,7 @@ function SpinnerWithVerbInner({ mode, loadingStartTimeRef, totalPausedMsRef, pau
|
|
|
139
139
|
// Leader's own verb (always the leader's, regardless of who is foregrounded)
|
|
140
140
|
const leaderVerb = overrideMessage ?? currentTodo?.activeForm ?? currentTodo?.subject ?? randomVerb;
|
|
141
141
|
const effectiveVerb = foregroundedTeammate && !foregroundedTeammate.isIdle ? foregroundedTeammate.spinnerVerb ?? randomVerb : leaderVerb;
|
|
142
|
-
const message = effectiveVerb + '
|
|
142
|
+
const message = effectiveVerb + '...';
|
|
143
143
|
// Track CLI activity when spinner is active
|
|
144
144
|
useEffect(() => {
|
|
145
145
|
const operationId = 'spinner-' + mode;
|
|
@@ -166,11 +166,11 @@ function SpinnerWithVerbInner({ mode, loadingStartTimeRef, totalPausedMsRef, pau
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
// Stale read of the refs for showBtwTip below
|
|
169
|
+
// Stale read of the refs for showBtwTip below — we're off the 50ms clock
|
|
170
170
|
// so this only updates when props/app state change, which is sufficient for
|
|
171
171
|
// a coarse 30s threshold.
|
|
172
172
|
const elapsedSnapshot = pauseStartTimeRef.current !== null ? pauseStartTimeRef.current - loadingStartTimeRef.current - totalPausedMsRef.current : Date.now() - loadingStartTimeRef.current - totalPausedMsRef.current;
|
|
173
|
-
// Leader token count for TeammateSpinnerTree
|
|
173
|
+
// Leader token count for TeammateSpinnerTree — read raw (non-animated) from
|
|
174
174
|
// the ref. The tree is only shown when teammates are running; teammate
|
|
175
175
|
// progress updates to s.tasks trigger re-renders that keep this fresh.
|
|
176
176
|
const leaderTokenCount = Math.round(responseLengthRef.current / 4);
|
|
@@ -179,7 +179,7 @@ function SpinnerWithVerbInner({ mode, loadingStartTimeRef, totalPausedMsRef, pau
|
|
|
179
179
|
const messageColor = overrideColor ?? defaultColor;
|
|
180
180
|
const shimmerColor = overrideShimmerColor ?? defaultShimmerColor;
|
|
181
181
|
// Compute TTFT string here (off the 50ms animation clock) and pass to
|
|
182
|
-
// SpinnerAnimationRow so it folds into the `(thought for Ns
|
|
182
|
+
// SpinnerAnimationRow so it folds into the `(thought for Ns · ...)` status
|
|
183
183
|
// line instead of taking a separate row. apiMetricsRef is a ref so this
|
|
184
184
|
// doesn't trigger re-renders; we pick up updates on the parent's ~25x/turn
|
|
185
185
|
// re-render cadence, same as the old ApiMetricsLine did.
|
|
@@ -188,14 +188,14 @@ function SpinnerWithVerbInner({ mode, loadingStartTimeRef, totalPausedMsRef, pau
|
|
|
188
188
|
ttftText = computeTtftText(apiMetricsRef.current);
|
|
189
189
|
}
|
|
190
190
|
// When leader is idle but teammates are running (and we're viewing the leader),
|
|
191
|
-
// show a static dim idle display instead of the animated spinner
|
|
191
|
+
// show a static dim idle display instead of the animated spinner — otherwise
|
|
192
192
|
// useStalledAnimation detects no new tokens after 3s and turns the spinner red.
|
|
193
193
|
if (leaderIsIdle && hasRunningTeammates && !foregroundedTeammate) {
|
|
194
|
-
return _jsxs(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: [_jsx(Box, { flexDirection: "row", flexWrap: "wrap", marginTop: 1, width: "100%", children: _jsxs(Text, { dimColor: true, children: [TEARDROP_ASTERISK, " Inactivo", !allIdle && ' ·
|
|
194
|
+
return _jsxs(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: [_jsx(Box, { flexDirection: "row", flexWrap: "wrap", marginTop: 1, width: "100%", children: _jsxs(Text, { dimColor: true, children: [TEARDROP_ASTERISK, " Inactivo", !allIdle && ' · companeros ejecutandose'] }) }), showSpinnerTree && _jsx(TeammateSpinnerTree, { selectedIndex: selectedIPAgentIndex, isInSelectionMode: viewSelectionMode === 'selecting-agent', allIdle: allIdle, leaderTokenCount: leaderTokenCount, leaderIdleText: "Inactivo" })] });
|
|
195
195
|
}
|
|
196
196
|
// When viewing an idle teammate, show static idle display instead of animated spinner
|
|
197
197
|
if (foregroundedTeammate?.isIdle) {
|
|
198
|
-
const idleText = allIdle ? `${TEARDROP_ASTERISK}
|
|
198
|
+
const idleText = allIdle ? `${TEARDROP_ASTERISK} Trabajo durante ${formatDuration(Date.now() - foregroundedTeammate.startTime)}` : `${TEARDROP_ASTERISK} Inactivo`;
|
|
199
199
|
return _jsxs(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: [_jsx(Box, { flexDirection: "row", flexWrap: "wrap", marginTop: 1, width: "100%", children: _jsx(Text, { dimColor: true, children: idleText }) }), showSpinnerTree && hasRunningTeammates && _jsx(TeammateSpinnerTree, { selectedIndex: selectedIPAgentIndex, isInSelectionMode: viewSelectionMode === 'selecting-agent', allIdle: allIdle, leaderVerb: leaderIsIdle ? undefined : leaderVerb, leaderIdleText: leaderIsIdle ? 'Inactivo' : undefined, leaderTokenCount: leaderTokenCount })] });
|
|
200
200
|
}
|
|
201
201
|
// Time-based tip overrides: coarse thresholds so a stale ref read (we're
|
|
@@ -205,8 +205,8 @@ function SpinnerWithVerbInner({ mode, loadingStartTimeRef, totalPausedMsRef, pau
|
|
|
205
205
|
const tipsEnabled = settings.spinnerTipsEnabled !== false;
|
|
206
206
|
const showClearTip = tipsEnabled && elapsedSnapshot > 1_800_000;
|
|
207
207
|
const showBtwTip = tipsEnabled && elapsedSnapshot > 30_000 && !getGlobalConfig().btwUseCount;
|
|
208
|
-
const effectiveTip = contextTipsActive ? undefined : showClearTip && !nextTask ? 'Usa /clear para empezar de cero al cambiar de tema y liberar contexto' : showBtwTip && !nextTask ? "Usa /btw para hacer una pregunta
|
|
209
|
-
// Budget text (ant-only)
|
|
208
|
+
const effectiveTip = contextTipsActive ? undefined : showClearTip && !nextTask ? 'Usa /clear para empezar de cero al cambiar de tema y liberar contexto' : showBtwTip && !nextTask ? "Usa /btw para hacer una pregunta rapida sin interrumpir el trabajo actual de Context" : spinnerTip;
|
|
209
|
+
// Budget text (ant-only) — shown above the tip line
|
|
210
210
|
let budgetText = null;
|
|
211
211
|
if (feature('TOKEN_BUDGET')) {
|
|
212
212
|
const budget = getCurrentTurnTokenBudget();
|
|
@@ -428,7 +428,7 @@ export function Spinner() {
|
|
|
428
428
|
if (reducedMotion) {
|
|
429
429
|
let t0;
|
|
430
430
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
431
|
-
t0 = _jsx(Text, { color: "text", children: "
|
|
431
|
+
t0 = _jsx(Text, { color: "text", children: "*" });
|
|
432
432
|
$[0] = t0;
|
|
433
433
|
}
|
|
434
434
|
else {
|
|
@@ -58,7 +58,7 @@ export const SPINNER_VERBS = [
|
|
|
58
58
|
'Descifrando',
|
|
59
59
|
'Deliberando',
|
|
60
60
|
'Determinando',
|
|
61
|
-
'
|
|
61
|
+
'Entreteniendose',
|
|
62
62
|
'Desconcertando',
|
|
63
63
|
'Haciendo',
|
|
64
64
|
'Garabateando',
|
|
@@ -137,7 +137,7 @@ export const SPINNER_VERBS = [
|
|
|
137
137
|
'Polinizando',
|
|
138
138
|
'Ponderando',
|
|
139
139
|
'Pontificando',
|
|
140
|
-
'
|
|
140
|
+
'Avalanzandose',
|
|
141
141
|
'Precipitando',
|
|
142
142
|
'Haciendo magia',
|
|
143
143
|
'Procesando',
|
|
@@ -155,17 +155,17 @@ export const SPINNER_VERBS = [
|
|
|
155
155
|
'Salteando',
|
|
156
156
|
'Correteando',
|
|
157
157
|
'Cargando',
|
|
158
|
-
'
|
|
158
|
+
'Escabullendose',
|
|
159
159
|
'Sazonando',
|
|
160
160
|
'Traveseando',
|
|
161
|
-
'
|
|
161
|
+
'Meneandose',
|
|
162
162
|
'Cociendo a fuego lento',
|
|
163
163
|
'Escapando',
|
|
164
164
|
'Bocetando',
|
|
165
|
-
'
|
|
165
|
+
'Deslizandose',
|
|
166
166
|
'Aplastando',
|
|
167
167
|
'Bailando en calcetines',
|
|
168
|
-
'
|
|
168
|
+
'Espeleologia',
|
|
169
169
|
'Girando',
|
|
170
170
|
'Brotando',
|
|
171
171
|
'Estofando',
|
|
@@ -178,8 +178,8 @@ export const SPINNER_VERBS = [
|
|
|
178
178
|
'Pensando',
|
|
179
179
|
'Tronando',
|
|
180
180
|
'Trasteando',
|
|
181
|
-
'Haciendo
|
|
182
|
-
'Poniendo del
|
|
181
|
+
'Haciendo tonterias',
|
|
182
|
+
'Poniendo del reves',
|
|
183
183
|
'Transfigurando',
|
|
184
184
|
'Transmutando',
|
|
185
185
|
'Retorciendo',
|
|
@@ -187,7 +187,7 @@ export const SPINNER_VERBS = [
|
|
|
187
187
|
'Desplegando',
|
|
188
188
|
'Desenredando',
|
|
189
189
|
'Vibrando',
|
|
190
|
-
'
|
|
190
|
+
'Contoneandose',
|
|
191
191
|
'Vagando',
|
|
192
192
|
'Deformando',
|
|
193
193
|
'Chirimboleando',
|
|
@@ -118,9 +118,10 @@ export function usePasteHandler({ onPaste, onInput, onImagePaste, }) {
|
|
|
118
118
|
});
|
|
119
119
|
return { chunks: [], timeoutId: null };
|
|
120
120
|
}
|
|
121
|
-
// If paste is empty
|
|
122
|
-
//
|
|
123
|
-
|
|
121
|
+
// If paste is empty, try resolving it as an image from the clipboard.
|
|
122
|
+
// Some terminals/platforms deliver image paste as an empty bracketed
|
|
123
|
+
// paste instead of text content.
|
|
124
|
+
if (onImagePaste && pastedText.length === 0) {
|
|
124
125
|
checkClipboardForImage();
|
|
125
126
|
return { chunks: [], timeoutId: null };
|
|
126
127
|
}
|
|
@@ -162,11 +163,10 @@ export function usePasteHandler({ onPaste, onInput, onImagePaste, }) {
|
|
|
162
163
|
.split(/ (?=\/|[A-Za-z]:\\)/)
|
|
163
164
|
.flatMap(part => part.split('\n'))
|
|
164
165
|
.some(line => isImageFilePath(line.trim()));
|
|
165
|
-
// Handle empty paste
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
if (isFromPaste && input.length === 0 && isMacOS && onImagePaste) {
|
|
166
|
+
// Handle empty paste as a potential clipboard image.
|
|
167
|
+
// Some terminals emit an empty bracketed paste sequence when the clipboard
|
|
168
|
+
// contains an image instead of text.
|
|
169
|
+
if (isFromPaste && input.length === 0 && onImagePaste) {
|
|
170
170
|
checkClipboardForImage();
|
|
171
171
|
// Reset isPasting since there's no text content to process
|
|
172
172
|
setIsPasting(false);
|