yaver-feedback-react-native 0.9.2 → 0.9.4
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 +102 -1
- package/dist/AuthOverlay.d.ts +1 -14
- package/dist/AuthOverlay.js +9 -62
- package/dist/Discovery.js +0 -6
- package/dist/DogfoodRuntime.d.ts +124 -0
- package/dist/DogfoodRuntime.js +273 -0
- package/dist/FeedbackModal.js +347 -61
- package/dist/LoginScreen.d.ts +1 -5
- package/dist/LoginScreen.js +2 -6
- package/dist/MachinePickerScreen.d.ts +1 -3
- package/dist/MachinePickerScreen.js +6 -18
- package/dist/P2PClient.d.ts +116 -3
- package/dist/P2PClient.js +273 -3
- package/dist/P2PDogfoodDriver.d.ts +12 -0
- package/dist/P2PDogfoodDriver.js +118 -0
- package/dist/PairDeviceModal.d.ts +2 -3
- package/dist/ShakeDetector.js +31 -17
- package/dist/VibeChatScreen.d.ts +11 -1
- package/dist/VibeChatScreen.js +200 -41
- package/dist/YaverFeedback.d.ts +34 -0
- package/dist/YaverFeedback.js +124 -19
- package/dist/YaverModeBadge.d.ts +22 -0
- package/dist/YaverModeBadge.js +219 -0
- package/dist/__tests__/AuthDevices.test.js +1 -48
- package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
- package/dist/__tests__/DogfoodRuntime.test.js +116 -0
- package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
- package/dist/__tests__/ReportIdentity.test.d.ts +25 -1
- package/dist/__tests__/ReportIdentity.test.js +34 -22
- package/dist/__tests__/ShakeDetectorWeb.test.d.ts +1 -0
- package/dist/__tests__/ShakeDetectorWeb.test.js +27 -0
- package/dist/__tests__/YaverFeedback.test.js +13 -3
- package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
- package/dist/__tests__/deviceDogfood.test.js +86 -0
- package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
- package/dist/__tests__/dogfoodPolicy.test.js +33 -0
- package/dist/_core/ansi.d.ts +117 -0
- package/dist/_core/ansi.js +468 -0
- package/dist/_core/ansi.test.d.ts +1 -0
- package/dist/_core/ansi.test.js +225 -0
- package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
- package/dist/_core/buildFeedbackPrompt.js +18 -72
- package/dist/_core/constants.d.ts +19 -6
- package/dist/_core/constants.js +20 -7
- package/dist/_core/device.d.ts +9 -23
- package/dist/_core/device.js +13 -28
- package/dist/_core/endpoints.d.ts +0 -7
- package/dist/_core/endpoints.js +0 -7
- package/dist/_core/index.d.ts +4 -0
- package/dist/_core/index.js +4 -0
- package/dist/_core/remoteless.d.ts +44 -0
- package/dist/_core/remoteless.js +75 -0
- package/dist/_core/trace.d.ts +47 -0
- package/dist/_core/trace.js +38 -0
- package/dist/_core/trace.test.d.ts +1 -0
- package/dist/_core/trace.test.js +60 -0
- package/dist/auth.d.ts +3 -60
- package/dist/auth.js +6 -88
- package/dist/deviceDogfood.d.ts +53 -0
- package/dist/deviceDogfood.js +137 -0
- package/dist/dogfoodPolicy.d.ts +25 -0
- package/dist/dogfoodPolicy.js +24 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +24 -8
- package/dist/reloadActions.js +2 -2
- package/dist/types.d.ts +50 -7
- package/package.json +12 -3
- package/src/AuthOverlay.tsx +20 -106
- package/src/Discovery.ts +0 -6
- package/src/DogfoodRuntime.ts +373 -0
- package/src/FeedbackModal.tsx +445 -67
- package/src/LoginScreen.tsx +2 -22
- package/src/MachinePickerScreen.tsx +6 -21
- package/src/P2PClient.ts +347 -4
- package/src/P2PDogfoodDriver.ts +132 -0
- package/src/PairDeviceModal.tsx +2 -3
- package/src/ShakeDetector.ts +31 -18
- package/src/VibeChatScreen.tsx +232 -42
- package/src/YaverFeedback.ts +133 -22
- package/src/YaverModeBadge.tsx +234 -0
- package/src/__tests__/AuthDevices.test.ts +1 -52
- package/src/__tests__/DogfoodRuntime.test.ts +135 -0
- package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
- package/src/__tests__/ReportIdentity.test.ts +36 -27
- package/src/__tests__/ShakeDetectorWeb.test.ts +30 -0
- package/src/__tests__/YaverFeedback.test.ts +15 -3
- package/src/__tests__/deviceDogfood.test.ts +77 -0
- package/src/__tests__/dogfoodPolicy.test.ts +34 -0
- package/src/_core/ansi.test.ts +250 -0
- package/src/_core/ansi.ts +475 -0
- package/src/_core/buildFeedbackPrompt.ts +18 -95
- package/src/_core/constants.ts +20 -6
- package/src/_core/device.ts +13 -30
- package/src/_core/endpoints.ts +0 -7
- package/src/_core/index.ts +4 -0
- package/src/_core/remoteless.ts +110 -0
- package/src/_core/trace.test.ts +58 -0
- package/src/_core/trace.ts +75 -0
- package/src/auth.ts +7 -156
- package/src/deviceDogfood.ts +171 -0
- package/src/dogfoodPolicy.ts +40 -0
- package/src/index.ts +40 -11
- package/src/reloadActions.ts +2 -2
- package/src/types.ts +45 -7
- package/dist/GuestOnboardingScreen.d.ts +0 -8
- package/dist/GuestOnboardingScreen.js +0 -282
- package/src/GuestOnboardingScreen.tsx +0 -307
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createP2PDogfoodDriver = createP2PDogfoodDriver;
|
|
4
|
+
const DogfoodRuntime_1 = require("./DogfoodRuntime");
|
|
5
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
function reportedPreview(status) {
|
|
7
|
+
return String(status.previewUrl || status.bundleUrl || '').trim();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Ready-to-use driver for app owners who authenticate with Yaver's normal
|
|
11
|
+
* account flow. Their UI owns the trigger and preview surface; this adapter
|
|
12
|
+
* owns the existing Projects endpoints and raw build log stream.
|
|
13
|
+
*/
|
|
14
|
+
function createP2PDogfoodDriver(client, options = {}) {
|
|
15
|
+
const startupTimeoutMs = Math.max(1, options.startupTimeoutMs ?? 155000);
|
|
16
|
+
const pollIntervalMs = Math.max(1, options.pollIntervalMs ?? 600);
|
|
17
|
+
return {
|
|
18
|
+
async prepare(context) {
|
|
19
|
+
const close = client.subscribeDogfoodDevEvents((event) => (0, DogfoodRuntime_1.runtimeLogLinesFromDevEvent)(event).forEach((line) => context.log(line)), (health) => {
|
|
20
|
+
if (health)
|
|
21
|
+
context.log({ text: `[logs] ${health.message}`, at: Date.now(), stream: 'system' });
|
|
22
|
+
});
|
|
23
|
+
context.registerCleanup(close, 'transient');
|
|
24
|
+
},
|
|
25
|
+
async start(context) {
|
|
26
|
+
const { project } = context;
|
|
27
|
+
if (project.lane === 'webrtc') {
|
|
28
|
+
context.setPhase('starting', `Finding a native runtime for ${project.name}…`);
|
|
29
|
+
const capabilities = await client.getDogfoodRemoteRuntimeCapabilities(project.workDir, project.framework);
|
|
30
|
+
const nativeTargets = capabilities.targets.filter((target) => target.enabled && target.id !== 'browser-window');
|
|
31
|
+
const target = project.nativeTargetId
|
|
32
|
+
? nativeTargets.find((candidate) => candidate.id === project.nativeTargetId)
|
|
33
|
+
: nativeTargets[0];
|
|
34
|
+
if (!target) {
|
|
35
|
+
throw new DogfoodRuntime_1.DogfoodRuntimeError({
|
|
36
|
+
code: 'DOGFOOD_NATIVE_RUNTIME_UNAVAILABLE',
|
|
37
|
+
error: project.nativeTargetId
|
|
38
|
+
? `Native runtime ${project.nativeTargetId} is not available.`
|
|
39
|
+
: 'No native simulator, emulator, or device runtime is available.',
|
|
40
|
+
remedy: 'Start or install a native runtime on the selected machine, then retry WebRTC; Browser lane remains available.',
|
|
41
|
+
retryable: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
context.setPhase('starting', `Starting ${target.label} over WebRTC…`);
|
|
45
|
+
const session = await client.startDogfoodRemoteRuntime(project.workDir, project.framework, target.id);
|
|
46
|
+
context.registerCleanup(() => client.stopDogfoodRemoteRuntime(session.id), 'session');
|
|
47
|
+
return {
|
|
48
|
+
lane: 'webrtc',
|
|
49
|
+
sessionId: session.id,
|
|
50
|
+
metadata: { target, session, framework: project.framework, workDir: project.workDir },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
context.setPhase(project.lane === 'hermes' ? 'compiling' : 'starting', project.lane === 'hermes' ? `Compiling ${project.name} with Hermes…` : `Starting ${project.name} in the browser…`);
|
|
54
|
+
const status = await client.startDogfoodDevServer({
|
|
55
|
+
framework: project.framework,
|
|
56
|
+
workDir: project.workDir,
|
|
57
|
+
lane: project.lane,
|
|
58
|
+
});
|
|
59
|
+
// Browser Dogfood owns a long-lived dev server. Hermes is a one-shot
|
|
60
|
+
// build + delivery to the Yaver container; calling /dev/stop when that
|
|
61
|
+
// guest exits can kill an unrelated browser preview on the same box.
|
|
62
|
+
if (project.lane === 'browser') {
|
|
63
|
+
context.registerCleanup(() => client.stopDogfoodDevServer(), 'session');
|
|
64
|
+
}
|
|
65
|
+
if (status.error) {
|
|
66
|
+
throw new DogfoodRuntime_1.DogfoodRuntimeError({
|
|
67
|
+
code: 'DOGFOOD_DEV_SERVER_FAILED', error: status.error,
|
|
68
|
+
remedy: 'Fix the named project/runtime error, then retry Dogfood.', retryable: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
let latest = status;
|
|
72
|
+
let reported = reportedPreview(latest);
|
|
73
|
+
if (project.lane === 'browser' && !reported) {
|
|
74
|
+
context.setPhase('compiling', `Compiling ${project.name} for the browser…`);
|
|
75
|
+
const deadline = Date.now() + startupTimeoutMs;
|
|
76
|
+
while (context.isCurrent() && Date.now() < deadline) {
|
|
77
|
+
await delay(pollIntervalMs);
|
|
78
|
+
const polled = await client.getDogfoodDevServerStatus();
|
|
79
|
+
if (!polled)
|
|
80
|
+
continue;
|
|
81
|
+
latest = polled;
|
|
82
|
+
if (latest.error && !latest.building) {
|
|
83
|
+
throw new DogfoodRuntime_1.DogfoodRuntimeError({
|
|
84
|
+
code: 'DOGFOOD_DEV_SERVER_FAILED', error: latest.error,
|
|
85
|
+
remedy: 'Fix the named project/runtime error, then retry Dogfood.', retryable: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
reported = reportedPreview(latest);
|
|
89
|
+
if (reported && (latest.running || latest.serving))
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (!context.isCurrent()) {
|
|
93
|
+
throw new DogfoodRuntime_1.DogfoodRuntimeError({
|
|
94
|
+
code: 'DOGFOOD_ATTEMPT_REPLACED', error: 'A newer Dogfood attempt replaced this compile.',
|
|
95
|
+
remedy: 'Wait for the newer attempt.', retryable: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!reported) {
|
|
99
|
+
throw new DogfoodRuntime_1.DogfoodRuntimeError({
|
|
100
|
+
code: 'DOGFOOD_NO_RENDER_URL',
|
|
101
|
+
error: `The dev server did not report a browser preview URL within ${Math.ceil(startupTimeoutMs / 1000)} seconds.`,
|
|
102
|
+
remedy: 'Read the live npm/compiler output above, fix the named failure, then retry. Flutter uses `-d web-server`; Expo/RN needs react-native-web.',
|
|
103
|
+
retryable: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
lane: project.lane,
|
|
109
|
+
url: reported ? client.resolveDogfoodUrl(reported) : undefined,
|
|
110
|
+
metadata: {
|
|
111
|
+
framework: latest.framework || project.framework,
|
|
112
|
+
workDir: latest.workDir || project.workDir,
|
|
113
|
+
...(project.lane === 'hermes' ? { delivered: latest.running === true } : {}),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -16,9 +16,8 @@ import type { RemoteDevice } from './auth';
|
|
|
16
16
|
* report `needsAuth=false` in /devices/list.
|
|
17
17
|
*
|
|
18
18
|
* This avoids making the user bounce to the Yaver mobile app just to
|
|
19
|
-
* adopt
|
|
20
|
-
*
|
|
21
|
-
* expected account type.
|
|
19
|
+
* adopt one of their own machines. The pair endpoint verifies the signed-in
|
|
20
|
+
* account as the machine owner.
|
|
22
21
|
*/
|
|
23
22
|
export interface PairDeviceModalProps {
|
|
24
23
|
device: RemoteDevice | null;
|
package/dist/ShakeDetector.js
CHANGED
|
@@ -97,6 +97,13 @@ class ShakeDetector {
|
|
|
97
97
|
* want the extra native surface get the Dev-menu path only.
|
|
98
98
|
*/
|
|
99
99
|
subscribeAccelerometer(onShake) {
|
|
100
|
+
// expo-sensors exposes an Accelerometer facade on web, but browsers have
|
|
101
|
+
// no native shake gesture and some versions construct a NativeEventEmitter
|
|
102
|
+
// backed by a module without addListener/removeListeners. Subscribing then
|
|
103
|
+
// crashes the host app's React commit phase. Preserve the SDK's explicit
|
|
104
|
+
// controls on web and skip only this impossible sensor transport.
|
|
105
|
+
if (react_native_1.Platform.OS === 'web')
|
|
106
|
+
return;
|
|
100
107
|
let Accelerometer = null;
|
|
101
108
|
try {
|
|
102
109
|
// Optional peer dep — if it isn't installed, we simply skip this path.
|
|
@@ -113,23 +120,30 @@ class ShakeDetector {
|
|
|
113
120
|
catch {
|
|
114
121
|
// Some platforms reject zero-value intervals; fall through with defaults
|
|
115
122
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
try {
|
|
124
|
+
this.accelSub = Accelerometer.addListener(({ x, y, z }) => {
|
|
125
|
+
// Magnitude of acceleration vector (in g). Subtract 1 so a stationary
|
|
126
|
+
// device reports ~0 rather than the 1g of gravity.
|
|
127
|
+
const mag = Math.sqrt(x * x + y * y + z * z);
|
|
128
|
+
if (mag - 1 < ACCEL_THRESHOLD_G - 1)
|
|
129
|
+
return;
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
this.peakTimestamps.push(now);
|
|
132
|
+
// Drop peaks that fell out of the rolling window.
|
|
133
|
+
while (this.peakTimestamps.length > 0 &&
|
|
134
|
+
now - this.peakTimestamps[0] > ACCEL_WINDOW_MS) {
|
|
135
|
+
this.peakTimestamps.shift();
|
|
136
|
+
}
|
|
137
|
+
if (this.peakTimestamps.length >= ACCEL_MIN_HITS) {
|
|
138
|
+
this.fire(onShake);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// A host may include expo-sensors without linking its native module.
|
|
144
|
+
// Optional shake support must never take down the host application.
|
|
145
|
+
this.accelSub = null;
|
|
146
|
+
}
|
|
133
147
|
}
|
|
134
148
|
}
|
|
135
149
|
exports.ShakeDetector = ShakeDetector;
|
package/dist/VibeChatScreen.d.ts
CHANGED
|
@@ -18,8 +18,18 @@ interface Props {
|
|
|
18
18
|
/** Optional context forwarded to the voice stream so the agent runs
|
|
19
19
|
* the task against the right project / runner / model. */
|
|
20
20
|
project?: string;
|
|
21
|
+
projectPath?: string;
|
|
21
22
|
model?: string;
|
|
22
23
|
runner?: string;
|
|
24
|
+
/** Show the optional voice/STT controls. Defaults to keyboard-only. */
|
|
25
|
+
voiceInputEnabled?: boolean;
|
|
26
|
+
/** Standalone SDK hosts use this to fold back to the floating Y without
|
|
27
|
+
* destroying the live task subscription or transcript state. */
|
|
28
|
+
onMinimize?: () => void;
|
|
29
|
+
codingMachine?: string;
|
|
30
|
+
renderMachine?: string;
|
|
31
|
+
/** Return to the SDK prompt composer to create a separate task/topic. */
|
|
32
|
+
onNewTopic?: () => void;
|
|
23
33
|
}
|
|
24
|
-
export declare function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, model, runner, }: Props): React.JSX.Element;
|
|
34
|
+
export declare function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, projectPath, model, runner, voiceInputEnabled, onMinimize, codingMachine, renderMachine, onNewTopic, }: Props): React.JSX.Element;
|
|
25
35
|
export {};
|
package/dist/VibeChatScreen.js
CHANGED
|
@@ -55,7 +55,8 @@ const react_1 = __importStar(require("react"));
|
|
|
55
55
|
const react_native_1 = require("react-native");
|
|
56
56
|
const voice_1 = require("./voice");
|
|
57
57
|
const capture_1 = require("./capture");
|
|
58
|
-
function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, model, runner, }) {
|
|
58
|
+
function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, projectPath, model, runner, voiceInputEnabled = false, onMinimize, codingMachine, renderMachine, onNewTopic, }) {
|
|
59
|
+
const [activeTab, setActiveTab] = (0, react_1.useState)('chat');
|
|
59
60
|
const [taskId, setTaskId] = (0, react_1.useState)(initialTaskId);
|
|
60
61
|
const [turns, setTurns] = (0, react_1.useState)(() => [
|
|
61
62
|
{
|
|
@@ -72,12 +73,27 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
72
73
|
},
|
|
73
74
|
]);
|
|
74
75
|
const [streamBuffer, setStreamBuffer] = (0, react_1.useState)('');
|
|
76
|
+
const [streamEpoch, setStreamEpoch] = (0, react_1.useState)(0);
|
|
75
77
|
const [status, setStatus] = (0, react_1.useState)('running');
|
|
76
78
|
const [followUp, setFollowUp] = (0, react_1.useState)('');
|
|
77
79
|
const [isResuming, setIsResuming] = (0, react_1.useState)(false);
|
|
78
80
|
const [isReloading, setIsReloading] = (0, react_1.useState)(false);
|
|
81
|
+
const [reloadQueued, setReloadQueued] = (0, react_1.useState)(false);
|
|
82
|
+
const [threads, setThreads] = (0, react_1.useState)(() => [{
|
|
83
|
+
id: initialTaskId,
|
|
84
|
+
title: initialUserPrompt || 'New topic',
|
|
85
|
+
status: 'running',
|
|
86
|
+
}]);
|
|
79
87
|
const scrollRef = (0, react_1.useRef)(null);
|
|
80
88
|
const abortRef = (0, react_1.useRef)(null);
|
|
89
|
+
const refreshThreads = (0, react_1.useCallback)(async () => {
|
|
90
|
+
try {
|
|
91
|
+
const list = await client.listVibeThreads({ projectName: project, projectPath });
|
|
92
|
+
setThreads(list.slice(0, 12));
|
|
93
|
+
}
|
|
94
|
+
catch { /* history is advisory; keep the active conversation usable */ }
|
|
95
|
+
}, [client, project, projectPath]);
|
|
96
|
+
(0, react_1.useEffect)(() => { void refreshThreads(); }, [refreshThreads]);
|
|
81
97
|
// ── Voice vibe coding ──────────────────────────────────────────────
|
|
82
98
|
const [voiceState, setVoiceState] = (0, react_1.useState)('idle');
|
|
83
99
|
const [voiceAvailable, setVoiceAvailable] = (0, react_1.useState)(false);
|
|
@@ -97,6 +113,8 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
97
113
|
const close = client.streamTaskOutput(taskId, (line) => {
|
|
98
114
|
if (!live)
|
|
99
115
|
return;
|
|
116
|
+
if (line.includes('YAVER_THREAD_TITLE'))
|
|
117
|
+
return;
|
|
100
118
|
// Filter our internal error sentinel from the SSE helper.
|
|
101
119
|
if (line.startsWith('__error__:')) {
|
|
102
120
|
setStatus('failed');
|
|
@@ -109,7 +127,7 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
109
127
|
}, (terminal) => {
|
|
110
128
|
if (!live)
|
|
111
129
|
return;
|
|
112
|
-
setStatus(terminal === 'completed' ? 'done' : 'failed');
|
|
130
|
+
setStatus(terminal === 'completed' || terminal === 'review' ? 'done' : 'failed');
|
|
113
131
|
// Move the buffered stream into a real assistant turn so the
|
|
114
132
|
// user sees a stable render and can scroll back, then clear
|
|
115
133
|
// the buffer for any follow-up.
|
|
@@ -127,6 +145,8 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
127
145
|
return next;
|
|
128
146
|
});
|
|
129
147
|
setStreamBuffer('');
|
|
148
|
+
setThreads((prev) => prev.map((thread) => thread.id === taskId ? { ...thread, status: terminal } : thread));
|
|
149
|
+
void refreshThreads();
|
|
130
150
|
});
|
|
131
151
|
abortRef.current = close;
|
|
132
152
|
return () => {
|
|
@@ -136,7 +156,44 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
136
156
|
}
|
|
137
157
|
catch { /* ignore */ }
|
|
138
158
|
};
|
|
139
|
-
}, [client, taskId]);
|
|
159
|
+
}, [client, refreshThreads, streamEpoch, taskId]);
|
|
160
|
+
const selectThread = (0, react_1.useCallback)(async (thread) => {
|
|
161
|
+
abortRef.current?.();
|
|
162
|
+
try {
|
|
163
|
+
const task = await client.getVibeThread(thread.id);
|
|
164
|
+
setTaskId(task.id);
|
|
165
|
+
setStatus(task.status === 'running' || task.status === 'queued' ? 'running' : task.status === 'completed' || task.status === 'review' ? 'done' : 'failed');
|
|
166
|
+
setStreamBuffer('');
|
|
167
|
+
setTurns((task.turns || []).filter((turn) => turn.role === 'user' || turn.role === 'assistant').map((turn, index) => ({
|
|
168
|
+
id: `${task.id}-${index}`,
|
|
169
|
+
role: turn.role,
|
|
170
|
+
text: turn.content,
|
|
171
|
+
timestamp: turn.timestamp ? new Date(turn.timestamp).getTime() : Date.now() + index,
|
|
172
|
+
})));
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
setTurns((prev) => [...prev, { id: `thread-error-${Date.now()}`, role: 'status', text: error instanceof Error ? error.message : 'Could not open topic', timestamp: Date.now() }]);
|
|
176
|
+
}
|
|
177
|
+
}, [client]);
|
|
178
|
+
const removeThread = (0, react_1.useCallback)((thread) => {
|
|
179
|
+
const message = thread.status === 'running' || thread.status === 'queued'
|
|
180
|
+
? 'This also stops the coding turn that is still running.'
|
|
181
|
+
: 'This removes the conversation from your history.';
|
|
182
|
+
react_native_1.Alert.alert('Remove topic?', message, [
|
|
183
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
184
|
+
{ text: 'Remove', style: 'destructive', onPress: () => {
|
|
185
|
+
void client.deleteVibeThread(thread.id).then(() => {
|
|
186
|
+
setThreads((prev) => prev.filter((item) => item.id !== thread.id));
|
|
187
|
+
if (thread.id === taskId)
|
|
188
|
+
onNewTopic?.();
|
|
189
|
+
}).catch((error) => {
|
|
190
|
+
setTurns((prev) => [...prev, { id: `delete-error-${Date.now()}`, role: 'status', text: error instanceof Error ? error.message : 'Could not remove topic', timestamp: Date.now() }]);
|
|
191
|
+
});
|
|
192
|
+
} },
|
|
193
|
+
]);
|
|
194
|
+
}, [client, onNewTopic, taskId]);
|
|
195
|
+
const runningThread = threads.find((thread) => thread.status === 'running' || thread.status === 'queued');
|
|
196
|
+
const codingLocked = status === 'running' || !!runningThread;
|
|
140
197
|
// Auto-scroll the transcript when new content lands.
|
|
141
198
|
(0, react_1.useEffect)(() => {
|
|
142
199
|
const t = setTimeout(() => {
|
|
@@ -146,7 +203,7 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
146
203
|
}, [streamBuffer, turns]);
|
|
147
204
|
const handleSendFollowUp = (0, react_1.useCallback)(async () => {
|
|
148
205
|
const text = followUp.trim();
|
|
149
|
-
if (!text || isResuming)
|
|
206
|
+
if (!text || isResuming || codingLocked)
|
|
150
207
|
return;
|
|
151
208
|
setIsResuming(true);
|
|
152
209
|
// Add user turn immediately for snappy UX.
|
|
@@ -157,17 +214,13 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
157
214
|
]);
|
|
158
215
|
setFollowUp('');
|
|
159
216
|
setStatus('running');
|
|
217
|
+
setThreads((prev) => prev.map((thread) => thread.id === taskId ? { ...thread, status: 'running' } : thread));
|
|
160
218
|
setStreamBuffer('');
|
|
161
219
|
try {
|
|
162
220
|
await client.resumeTask({ taskId, userPrompt: text });
|
|
163
|
-
// resumeTask reuses the same
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
// sentinel and back; cleaner than tearing down + re-attaching
|
|
167
|
-
// the SSE manually.
|
|
168
|
-
const same = taskId;
|
|
169
|
-
setTaskId(`${same}#`);
|
|
170
|
-
setTimeout(() => setTaskId(same), 0);
|
|
221
|
+
// resumeTask reuses the same task id; advance the subscription epoch so
|
|
222
|
+
// the existing SSE path reconnects without inventing an invalid id.
|
|
223
|
+
setStreamEpoch((value) => value + 1);
|
|
171
224
|
}
|
|
172
225
|
catch (e) {
|
|
173
226
|
setStatus('failed');
|
|
@@ -184,10 +237,14 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
184
237
|
finally {
|
|
185
238
|
setIsResuming(false);
|
|
186
239
|
}
|
|
187
|
-
}, [client, followUp, isResuming, taskId]);
|
|
240
|
+
}, [client, codingLocked, followUp, isResuming, taskId]);
|
|
188
241
|
const handleReload = (0, react_1.useCallback)(async () => {
|
|
189
242
|
if (isReloading || !onReload)
|
|
190
243
|
return;
|
|
244
|
+
if (status === 'running') {
|
|
245
|
+
setReloadQueued(true);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
191
248
|
setIsReloading(true);
|
|
192
249
|
try {
|
|
193
250
|
await onReload();
|
|
@@ -206,13 +263,21 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
206
263
|
finally {
|
|
207
264
|
setIsReloading(false);
|
|
208
265
|
}
|
|
209
|
-
}, [isReloading, onReload]);
|
|
266
|
+
}, [isReloading, onReload, status]);
|
|
267
|
+
(0, react_1.useEffect)(() => {
|
|
268
|
+
if (!reloadQueued || status === 'running' || isReloading)
|
|
269
|
+
return;
|
|
270
|
+
setReloadQueued(false);
|
|
271
|
+
void handleReload();
|
|
272
|
+
}, [handleReload, isReloading, reloadQueued, status]);
|
|
210
273
|
// Probe whether voice is usable: deps present (expo-av + expo-file-
|
|
211
274
|
// system + buffer) AND the agent reports STT/TTS ready. Hide the mic
|
|
212
275
|
// entirely otherwise so users never tap a dead button.
|
|
213
276
|
(0, react_1.useEffect)(() => {
|
|
214
277
|
let cancelled = false;
|
|
215
278
|
(async () => {
|
|
279
|
+
if (!voiceInputEnabled)
|
|
280
|
+
return;
|
|
216
281
|
if (!(0, capture_1.isVoiceCaptureSupported)() || !(0, voice_1.isVoiceStreamSupported)())
|
|
217
282
|
return;
|
|
218
283
|
try {
|
|
@@ -235,7 +300,7 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
235
300
|
catch { /* leave hidden */ }
|
|
236
301
|
})();
|
|
237
302
|
return () => { cancelled = true; };
|
|
238
|
-
}, [client]);
|
|
303
|
+
}, [client, voiceInputEnabled]);
|
|
239
304
|
(0, react_1.useEffect)(() => () => { voiceSessionRef.current?.close(); }, []);
|
|
240
305
|
// Local TTS: the agent streams no audio for "local"/"device" engines,
|
|
241
306
|
// so the client speaks the result text with the device synthesizer.
|
|
@@ -371,15 +436,50 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
371
436
|
thinking: 'thinking…',
|
|
372
437
|
speaking: 'speaking…',
|
|
373
438
|
};
|
|
374
|
-
return (<react_native_1.
|
|
439
|
+
return (<react_native_1.KeyboardAvoidingView style={styles.container} behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={0}>
|
|
375
440
|
<react_native_1.View style={styles.header}>
|
|
376
|
-
<react_native_1.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
441
|
+
<react_native_1.View>
|
|
442
|
+
<react_native_1.Text style={styles.title}>Vibing</react_native_1.Text>
|
|
443
|
+
<react_native_1.Text style={styles.routeCaption} numberOfLines={1}>
|
|
444
|
+
{[runner || 'automatic runner', model].filter(Boolean).join(' · ')}
|
|
445
|
+
</react_native_1.Text>
|
|
446
|
+
</react_native_1.View>
|
|
447
|
+
<react_native_1.View style={styles.headerActions}>
|
|
448
|
+
{onMinimize ? <react_native_1.TouchableOpacity onPress={onMinimize} accessibilityLabel="Minimize Vibing"><react_native_1.Text style={styles.close}>−</react_native_1.Text></react_native_1.TouchableOpacity> : null}
|
|
449
|
+
{onClose ? <react_native_1.TouchableOpacity onPress={onClose} accessibilityLabel="End vibe chat"><react_native_1.Text style={styles.close}>✕</react_native_1.Text></react_native_1.TouchableOpacity> : null}
|
|
450
|
+
</react_native_1.View>
|
|
380
451
|
</react_native_1.View>
|
|
381
452
|
|
|
382
|
-
<react_native_1.
|
|
453
|
+
<react_native_1.View style={styles.tabs} accessibilityRole="tablist">
|
|
454
|
+
{['chat', 'settings'].map((tab) => (<react_native_1.TouchableOpacity key={tab} onPress={() => setActiveTab(tab)} style={[styles.tab, activeTab === tab && styles.tabSelected]} accessibilityRole="tab" accessibilityState={{ selected: activeTab === tab }}>
|
|
455
|
+
<react_native_1.Text style={[styles.tabText, activeTab === tab && styles.tabTextSelected]}>{tab === 'chat' ? 'Chat' : 'Settings'}</react_native_1.Text>
|
|
456
|
+
</react_native_1.TouchableOpacity>))}
|
|
457
|
+
</react_native_1.View>
|
|
458
|
+
|
|
459
|
+
{activeTab === 'chat' && threads.length > 1 ? <react_native_1.View style={styles.topicRailWrap}>
|
|
460
|
+
<react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.topicRail}>
|
|
461
|
+
<react_native_1.TouchableOpacity style={styles.newTopicCard} onPress={onNewTopic} accessibilityLabel="Start a new topic">
|
|
462
|
+
<react_native_1.Text style={styles.newTopicPlus}>+</react_native_1.Text>
|
|
463
|
+
<react_native_1.Text style={styles.newTopicText}>New</react_native_1.Text>
|
|
464
|
+
</react_native_1.TouchableOpacity>
|
|
465
|
+
{threads.map((thread) => (<react_native_1.TouchableOpacity key={thread.id} style={[styles.topicCard, thread.id === taskId && styles.topicCardSelected]} onPress={() => { void selectThread(thread); }} accessibilityLabel={`Open ${thread.title}`}>
|
|
466
|
+
<react_native_1.View style={styles.topicCardTopline}>
|
|
467
|
+
<react_native_1.View style={[styles.topicDot, (thread.status === 'running' || thread.status === 'queued') && styles.topicDotLive]}/>
|
|
468
|
+
<react_native_1.Text style={styles.topicStatus}>{thread.status === 'completed' ? 'done' : thread.status}</react_native_1.Text>
|
|
469
|
+
<react_native_1.TouchableOpacity onPress={() => removeThread(thread)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} accessibilityLabel={`Remove ${thread.title}`}><react_native_1.Text style={styles.topicRemove}>×</react_native_1.Text></react_native_1.TouchableOpacity>
|
|
470
|
+
</react_native_1.View>
|
|
471
|
+
<react_native_1.Text style={styles.topicTitle} numberOfLines={2}>{thread.title}</react_native_1.Text>
|
|
472
|
+
</react_native_1.TouchableOpacity>))}
|
|
473
|
+
</react_native_1.ScrollView>
|
|
474
|
+
</react_native_1.View> : activeTab === 'chat' && threads.length === 1 ? <react_native_1.View style={styles.singleTopicBar}>
|
|
475
|
+
<react_native_1.TouchableOpacity style={styles.singleTopicTitleButton} onPress={() => { void selectThread(threads[0]); }} accessibilityLabel={`Open ${threads[0].title}`}>
|
|
476
|
+
<react_native_1.Text style={styles.singleTopicTitle} numberOfLines={1}>{threads[0].title || 'Current topic'}</react_native_1.Text>
|
|
477
|
+
</react_native_1.TouchableOpacity>
|
|
478
|
+
{onNewTopic ? <react_native_1.TouchableOpacity style={styles.singleTopicAction} onPress={onNewTopic} accessibilityLabel="Start a new topic"><react_native_1.Text style={styles.singleTopicActionText}>+</react_native_1.Text></react_native_1.TouchableOpacity> : null}
|
|
479
|
+
<react_native_1.TouchableOpacity style={styles.singleTopicAction} onPress={() => removeThread(threads[0])} accessibilityLabel={`Remove ${threads[0].title}`}><react_native_1.Text style={styles.singleTopicRemove}>×</react_native_1.Text></react_native_1.TouchableOpacity>
|
|
480
|
+
</react_native_1.View> : null}
|
|
481
|
+
|
|
482
|
+
<react_native_1.ScrollView ref={scrollRef} style={[styles.transcript, activeTab !== 'chat' && styles.hidden]} contentContainerStyle={styles.transcriptContent} keyboardShouldPersistTaps="handled">
|
|
383
483
|
{turns.map((turn) => (<react_native_1.View key={turn.id} style={[
|
|
384
484
|
styles.turn,
|
|
385
485
|
turn.role === 'user' && styles.turnUser,
|
|
@@ -401,14 +501,34 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
401
501
|
</react_native_1.View>)}
|
|
402
502
|
</react_native_1.ScrollView>
|
|
403
503
|
|
|
404
|
-
<react_native_1.
|
|
405
|
-
|
|
504
|
+
<react_native_1.ScrollView style={[styles.settings, activeTab !== 'settings' && styles.hidden]} contentContainerStyle={styles.settingsContent}>
|
|
505
|
+
<react_native_1.Text style={styles.settingsLabel}>Coding route</react_native_1.Text>
|
|
506
|
+
<react_native_1.View style={styles.settingsCard}>
|
|
507
|
+
<react_native_1.Text style={styles.settingsMeta}>Coding machine · {codingMachine || 'selected machine'}</react_native_1.Text>
|
|
508
|
+
<react_native_1.Text style={styles.settingsMeta}>Render machine · {renderMachine || codingMachine || 'same machine'}</react_native_1.Text>
|
|
509
|
+
<react_native_1.Text style={styles.settingsTitle}>{runner || 'Automatic runner'}</react_native_1.Text>
|
|
510
|
+
<react_native_1.Text style={styles.settingsMeta}>{model || 'Runner default model'}</react_native_1.Text>
|
|
511
|
+
{project ? <react_native_1.Text style={styles.settingsMeta}>{project}</react_native_1.Text> : null}
|
|
512
|
+
</react_native_1.View>
|
|
513
|
+
<react_native_1.Text style={styles.settingsLabel}>Preview</react_native_1.Text>
|
|
514
|
+
<react_native_1.View style={styles.settingsCard}>
|
|
515
|
+
<react_native_1.Text style={styles.settingsTitle}>Reload rendered app</react_native_1.Text>
|
|
516
|
+
<react_native_1.Text style={styles.settingsMeta}>{reloadQueued ? 'Reload queued. It will run when coding finishes.' : status === 'running' ? 'Reload will run when the current coding turn finishes.' : 'Apply the latest rendered output without ending this chat.'}</react_native_1.Text>
|
|
517
|
+
{onReload ? <react_native_1.TouchableOpacity style={[styles.settingsButton, isReloading && styles.actionBtnDisabled]} onPress={handleReload} disabled={isReloading}>
|
|
518
|
+
<react_native_1.Text style={styles.settingsButtonText}>{isReloading ? 'Reloading…' : reloadQueued ? 'Reload Queued' : status === 'running' ? 'Queue Reload' : 'Reload'}</react_native_1.Text>
|
|
519
|
+
</react_native_1.TouchableOpacity> : null}
|
|
520
|
+
</react_native_1.View>
|
|
521
|
+
{onMinimize ? <react_native_1.TouchableOpacity style={styles.returnButton} onPress={onMinimize} accessibilityLabel="Return to app and keep Vibing running"><react_native_1.Text style={styles.returnButtonText}>Return to App</react_native_1.Text></react_native_1.TouchableOpacity> : null}
|
|
522
|
+
</react_native_1.ScrollView>
|
|
523
|
+
|
|
524
|
+
<react_native_1.View style={[styles.footer, activeTab !== 'chat' && styles.hidden]}>
|
|
525
|
+
{voiceInputEnabled && voiceAvailable && voiceState !== 'idle' && (<react_native_1.Text style={styles.engineCaption}>
|
|
406
526
|
{voiceState === 'recording' ? 'listening' : voiceState === 'uploading' ? 'sending' : voiceState === 'thinking' ? 'agent working' : 'speaking'}
|
|
407
527
|
{activeEngine ? ` · ${activeEngine}` : ` · ${voiceMode === 'flux' ? 'Flux (Deepgram)' : 'Local (whisper)'}`}
|
|
408
528
|
</react_native_1.Text>)}
|
|
409
|
-
<react_native_1.TextInput style={styles.input} value={followUp} onChangeText={setFollowUp} placeholder={
|
|
529
|
+
<react_native_1.TextInput style={styles.input} value={followUp} onChangeText={setFollowUp} placeholder={codingLocked ? runningThread?.id !== taskId ? 'another topic is coding…' : 'wait for the agent…' : 'follow up…'} placeholderTextColor="#666" editable={!codingLocked && !isResuming} multiline/>
|
|
410
530
|
<react_native_1.View style={styles.actions}>
|
|
411
|
-
{voiceAvailable && (<>
|
|
531
|
+
{voiceInputEnabled && voiceAvailable && (<>
|
|
412
532
|
{/* Local ↔ Flux engine toggle. Only shows Flux when the
|
|
413
533
|
agent has a Deepgram key; otherwise the label just
|
|
414
534
|
states "Local" so the active engine is always clear. */}
|
|
@@ -438,18 +558,18 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
438
558
|
<react_native_1.TouchableOpacity style={[
|
|
439
559
|
styles.actionBtn,
|
|
440
560
|
styles.sendBtn,
|
|
441
|
-
(isResuming ||
|
|
442
|
-
]} onPress={handleSendFollowUp} disabled={isResuming ||
|
|
561
|
+
(isResuming || codingLocked || !followUp.trim()) && styles.actionBtnDisabled,
|
|
562
|
+
]} onPress={handleSendFollowUp} disabled={isResuming || codingLocked || !followUp.trim()}>
|
|
443
563
|
<react_native_1.Text style={styles.actionText}>
|
|
444
564
|
{isResuming ? '…' : '↑ send'}
|
|
445
565
|
</react_native_1.Text>
|
|
446
566
|
</react_native_1.TouchableOpacity>
|
|
447
567
|
</react_native_1.View>
|
|
448
568
|
</react_native_1.View>
|
|
449
|
-
</react_native_1.
|
|
569
|
+
</react_native_1.KeyboardAvoidingView>);
|
|
450
570
|
}
|
|
451
571
|
const styles = react_native_1.StyleSheet.create({
|
|
452
|
-
container: { flex: 1, backgroundColor: '#
|
|
572
|
+
container: { flex: 1, backgroundColor: '#f8f8fb' },
|
|
453
573
|
header: {
|
|
454
574
|
flexDirection: 'row',
|
|
455
575
|
alignItems: 'center',
|
|
@@ -458,10 +578,36 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
458
578
|
paddingTop: 14,
|
|
459
579
|
paddingBottom: 8,
|
|
460
580
|
borderBottomWidth: 1,
|
|
461
|
-
borderBottomColor: '
|
|
581
|
+
borderBottomColor: '#e5e5ec',
|
|
462
582
|
},
|
|
463
|
-
title: { color: '#
|
|
464
|
-
|
|
583
|
+
title: { color: '#17171d', fontSize: 17, fontWeight: '700' },
|
|
584
|
+
routeCaption: { color: '#858590', fontSize: 10, marginTop: 2, maxWidth: 250 },
|
|
585
|
+
headerActions: { flexDirection: 'row', alignItems: 'center', gap: 22 },
|
|
586
|
+
close: { color: '#656570', fontSize: 20, fontWeight: '700' },
|
|
587
|
+
tabs: { flexDirection: 'row', gap: 6, marginHorizontal: 12, marginTop: 8, padding: 3, borderRadius: 12, backgroundColor: '#ededf3' },
|
|
588
|
+
tab: { flex: 1, minHeight: 34, borderRadius: 9, alignItems: 'center', justifyContent: 'center' },
|
|
589
|
+
tabSelected: { backgroundColor: '#fff' },
|
|
590
|
+
tabText: { color: '#858590', fontSize: 12, fontWeight: '700' },
|
|
591
|
+
tabTextSelected: { color: '#6252e8' },
|
|
592
|
+
topicRailWrap: { minHeight: 102, borderBottomWidth: 1, borderBottomColor: '#e5e5ec' },
|
|
593
|
+
topicRail: { paddingHorizontal: 12, paddingVertical: 10, gap: 9 },
|
|
594
|
+
singleTopicBar: { minHeight: 40, paddingLeft: 12, paddingRight: 8, flexDirection: 'row', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: '#e5e5ec' },
|
|
595
|
+
singleTopicTitleButton: { flex: 1, minWidth: 0, minHeight: 38, justifyContent: 'center' },
|
|
596
|
+
singleTopicTitle: { color: '#656570', fontSize: 11, fontWeight: '700' },
|
|
597
|
+
singleTopicAction: { width: 34, height: 34, alignItems: 'center', justifyContent: 'center' },
|
|
598
|
+
singleTopicActionText: { color: '#6252e8', fontSize: 20, lineHeight: 22 },
|
|
599
|
+
singleTopicRemove: { color: '#9a9aa5', fontSize: 20, lineHeight: 22 },
|
|
600
|
+
newTopicCard: { width: 72, minHeight: 80, borderRadius: 14, alignItems: 'center', justifyContent: 'center', backgroundColor: '#ebe8ff', borderWidth: 1, borderColor: '#d9d3ff' },
|
|
601
|
+
newTopicPlus: { color: '#6252e8', fontSize: 22, lineHeight: 24 },
|
|
602
|
+
newTopicText: { color: '#6252e8', fontSize: 12, fontWeight: '800' },
|
|
603
|
+
topicCard: { width: 172, minHeight: 80, borderRadius: 14, padding: 10, gap: 7, backgroundColor: '#fff', borderWidth: 1.5, borderColor: '#e1e1e8' },
|
|
604
|
+
topicCardSelected: { borderColor: '#7568f8', backgroundColor: '#faf9ff' },
|
|
605
|
+
topicCardTopline: { flexDirection: 'row', alignItems: 'center', gap: 6 },
|
|
606
|
+
topicDot: { width: 7, height: 7, borderRadius: 4, backgroundColor: '#9ca3af' },
|
|
607
|
+
topicDotLive: { backgroundColor: '#f59e0b' },
|
|
608
|
+
topicStatus: { flex: 1, color: '#858590', fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
|
|
609
|
+
topicRemove: { color: '#9a9aa5', fontSize: 18, lineHeight: 18 },
|
|
610
|
+
topicTitle: { color: '#24242b', fontSize: 13, lineHeight: 17, fontWeight: '700' },
|
|
465
611
|
transcript: { flex: 1 },
|
|
466
612
|
transcriptContent: { padding: 12, paddingBottom: 24 },
|
|
467
613
|
turn: {
|
|
@@ -475,8 +621,8 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
475
621
|
alignSelf: 'flex-end',
|
|
476
622
|
},
|
|
477
623
|
turnAssistant: {
|
|
478
|
-
backgroundColor: '
|
|
479
|
-
borderColor: '
|
|
624
|
+
backgroundColor: '#fff',
|
|
625
|
+
borderColor: '#e4e4ea',
|
|
480
626
|
borderWidth: 1,
|
|
481
627
|
alignSelf: 'flex-start',
|
|
482
628
|
},
|
|
@@ -485,7 +631,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
485
631
|
alignSelf: 'flex-start',
|
|
486
632
|
paddingHorizontal: 4,
|
|
487
633
|
},
|
|
488
|
-
turnText: { color: '#
|
|
634
|
+
turnText: { color: '#28282f', fontSize: 14, lineHeight: 20 },
|
|
489
635
|
spinnerRow: {
|
|
490
636
|
flexDirection: 'row',
|
|
491
637
|
alignItems: 'center',
|
|
@@ -495,15 +641,17 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
495
641
|
spinnerText: { color: '#9ca3af', fontSize: 12, marginLeft: 8 },
|
|
496
642
|
footer: {
|
|
497
643
|
borderTopWidth: 1,
|
|
498
|
-
borderTopColor: '
|
|
644
|
+
borderTopColor: '#e5e5ec',
|
|
499
645
|
padding: 10,
|
|
500
646
|
},
|
|
501
647
|
input: {
|
|
502
648
|
minHeight: 40,
|
|
503
649
|
maxHeight: 120,
|
|
504
|
-
color: '#
|
|
650
|
+
color: '#28282f',
|
|
505
651
|
fontSize: 14,
|
|
506
|
-
backgroundColor: '
|
|
652
|
+
backgroundColor: '#fff',
|
|
653
|
+
borderWidth: 1,
|
|
654
|
+
borderColor: '#e2e2e9',
|
|
507
655
|
borderRadius: 10,
|
|
508
656
|
paddingHorizontal: 12,
|
|
509
657
|
paddingVertical: 8,
|
|
@@ -520,12 +668,23 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
520
668
|
marginLeft: 8,
|
|
521
669
|
},
|
|
522
670
|
actionBtnDisabled: { opacity: 0.5 },
|
|
523
|
-
reloadBtn: { backgroundColor: '
|
|
671
|
+
reloadBtn: { backgroundColor: '#e9e9ef' },
|
|
524
672
|
voiceBtn: { backgroundColor: 'rgba(16,185,129,0.18)' },
|
|
525
673
|
voiceBtnActive: { backgroundColor: '#ef4444' },
|
|
526
|
-
engineToggle: { backgroundColor: '
|
|
527
|
-
engineToggleText: { color: '#
|
|
674
|
+
engineToggle: { backgroundColor: '#e9e9ef', marginRight: 'auto', marginLeft: 0 },
|
|
675
|
+
engineToggleText: { color: '#555561', fontSize: 12, fontWeight: '600' },
|
|
528
676
|
engineCaption: { color: '#9ca3af', fontSize: 11, marginBottom: 6, marginLeft: 4 },
|
|
529
677
|
sendBtn: { backgroundColor: '#7582f5' },
|
|
530
678
|
actionText: { color: '#fff', fontSize: 13, fontWeight: '600' },
|
|
679
|
+
settings: { flex: 1 },
|
|
680
|
+
settingsContent: { padding: 14, gap: 10, paddingBottom: 30 },
|
|
681
|
+
settingsLabel: { color: '#777782', fontSize: 11, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5, marginTop: 4 },
|
|
682
|
+
settingsCard: { borderRadius: 14, padding: 14, gap: 5, backgroundColor: '#fff', borderWidth: 1, borderColor: '#e3e3e9' },
|
|
683
|
+
settingsTitle: { color: '#222229', fontSize: 14, fontWeight: '800' },
|
|
684
|
+
settingsMeta: { color: '#777782', fontSize: 12, lineHeight: 17 },
|
|
685
|
+
settingsButton: { alignSelf: 'flex-start', marginTop: 8, borderRadius: 10, backgroundColor: '#6f58f5', paddingHorizontal: 14, paddingVertical: 9 },
|
|
686
|
+
settingsButtonText: { color: '#fff', fontSize: 12, fontWeight: '800' },
|
|
687
|
+
returnButton: { minHeight: 46, borderRadius: 13, alignItems: 'center', justifyContent: 'center', backgroundColor: '#6f58f5', marginTop: 4 },
|
|
688
|
+
returnButtonText: { color: '#fff', fontWeight: '800' },
|
|
689
|
+
hidden: { display: 'none' },
|
|
531
690
|
});
|