yaver-feedback-react-native 0.8.11 → 0.8.13
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/app.plugin.js +13 -0
- package/dist/BlackBox.d.ts +1 -0
- package/dist/BlackBox.js +29 -14
- package/dist/DeployPanel.d.ts +7 -0
- package/dist/DeployPanel.js +354 -0
- package/dist/FeedbackModal.js +94 -6
- package/dist/FloatingButton.js +25 -3
- package/dist/P2PClient.d.ts +71 -0
- package/dist/P2PClient.js +254 -0
- package/dist/VibeChatScreen.d.ts +20 -0
- package/dist/VibeChatScreen.js +328 -0
- package/dist/YaverFeedback.d.ts +7 -0
- package/dist/YaverFeedback.js +10 -1
- package/dist/_core/buildFeedbackPrompt.d.ts +13 -0
- package/dist/_core/buildFeedbackPrompt.js +77 -0
- package/dist/capture.d.ts +14 -0
- package/dist/capture.js +29 -0
- package/dist/preferences.d.ts +4 -0
- package/dist/preferences.js +62 -0
- package/dist/types.d.ts +9 -0
- package/dist/upload.d.ts +1 -1
- package/dist/upload.js +8 -4
- package/package.json +14 -2
- package/src/BlackBox.ts +29 -14
- package/src/DeployPanel.tsx +403 -0
- package/src/FeedbackModal.tsx +126 -5
- package/src/FloatingButton.tsx +27 -3
- package/src/P2PClient.ts +247 -1
- package/src/VibeChatScreen.tsx +362 -0
- package/src/YaverFeedback.ts +11 -1
- package/src/_core/buildFeedbackPrompt.ts +102 -0
- package/src/capture.ts +31 -0
- package/src/preferences.ts +56 -0
- package/src/types.ts +9 -0
- package/src/upload.ts +8 -3
package/dist/FloatingButton.js
CHANGED
|
@@ -41,6 +41,14 @@ const FixReport_1 = require("./FixReport");
|
|
|
41
41
|
const BlackBox_1 = require("./BlackBox");
|
|
42
42
|
const DEFAULT_SIZE = 40;
|
|
43
43
|
const DEFAULT_COLOR = '#6366f1';
|
|
44
|
+
// Tablet detection — short-edge dp >= 600 means iPad / 7"+ Android
|
|
45
|
+
// tablet / Z Fold open. The SDK has no app-side responsive context
|
|
46
|
+
// to lean on (it's a guest in third-party apps), so we infer
|
|
47
|
+
// locally and bump the button + panel to tablet sizes.
|
|
48
|
+
const TABLET_SHORT_EDGE = 600;
|
|
49
|
+
function isTabletWindow(width, height) {
|
|
50
|
+
return Math.min(width, height) >= TABLET_SHORT_EDGE;
|
|
51
|
+
}
|
|
44
52
|
const DEFAULT_PANEL_BG = '#2d2d2d';
|
|
45
53
|
/**
|
|
46
54
|
* Draggable debug console button for the Yaver Feedback SDK.
|
|
@@ -71,7 +79,16 @@ const DEFAULT_PANEL_BG = '#2d2d2d';
|
|
|
71
79
|
* - **"quit"** → disable the SDK
|
|
72
80
|
*/
|
|
73
81
|
const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color = DEFAULT_COLOR, showStatusDot = true, style: stylePreset = 'terminal', icon, agentUrl: agentUrlProp, authToken: authTokenProp, healthCheckInterval = 5000, panelBackgroundColor, }) => {
|
|
74
|
-
|
|
82
|
+
// Read window size live so the SDK overlay re-pins itself when
|
|
83
|
+
// the host app rotates or splits. The legacy snapshot via
|
|
84
|
+
// Dimensions.get only ran once and parked the button off-screen
|
|
85
|
+
// after orientation changes on iPad.
|
|
86
|
+
const { width: screenWidth, height: screenHeight } = (0, react_native_1.useWindowDimensions)();
|
|
87
|
+
const isTablet = isTabletWindow(screenWidth, screenHeight);
|
|
88
|
+
// Tablets get a larger touch target and a wider panel — phones
|
|
89
|
+
// keep the existing 40 / 280 defaults so guest apps aren't
|
|
90
|
+
// disrupted on small screens.
|
|
91
|
+
const effectiveSize = isTablet ? Math.max(size, 52) : size;
|
|
75
92
|
const defaultX = initialPosition?.x ?? 10;
|
|
76
93
|
const defaultY = initialPosition?.y ?? 90;
|
|
77
94
|
const pan = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: defaultX, y: defaultY })).current;
|
|
@@ -516,7 +533,12 @@ const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color =
|
|
|
516
533
|
const isTerminal = stylePreset === 'terminal';
|
|
517
534
|
const buttonIcon = icon ?? 'y';
|
|
518
535
|
const btnBg = isConnected ? color : `${color}88`;
|
|
519
|
-
|
|
536
|
+
// Panel sizing — tablets get a wider compact panel (420) and a
|
|
537
|
+
// capped full-size panel (max 720 instead of full window) so the
|
|
538
|
+
// overlay doesn't dwarf the host app on a 12.9" iPad.
|
|
539
|
+
const compactPanelWidth = isTablet ? 420 : 280;
|
|
540
|
+
const fullPanelWidth = isTablet ? Math.min(screenWidth - 24, 720) : screenWidth - 24;
|
|
541
|
+
const panelWidth = fullSize ? fullPanelWidth : compactPanelWidth;
|
|
520
542
|
return (<react_native_1.Animated.View style={[s.root, { transform: [{ translateX: pan.x }, { translateY: pan.y }] }]} {...panResponder.panHandlers}>
|
|
521
543
|
{/* Console panel */}
|
|
522
544
|
{chatOpen && (<react_native_1.View style={[
|
|
@@ -642,7 +664,7 @@ const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color =
|
|
|
642
664
|
<react_native_1.TouchableOpacity style={[
|
|
643
665
|
s.button,
|
|
644
666
|
isTerminal ? s.buttonTerminal : s.buttonMinimal,
|
|
645
|
-
{ backgroundColor: btnBg, width:
|
|
667
|
+
{ backgroundColor: btnBg, width: effectiveSize, height: effectiveSize },
|
|
646
668
|
!isTerminal && { borderRadius: size / 2 },
|
|
647
669
|
]} activeOpacity={0.7} onPress={handleTap}>
|
|
648
670
|
<react_native_1.Text style={[s.buttonIcon, isTerminal && s.mono, { fontSize: 22 }]}>
|
package/dist/P2PClient.d.ts
CHANGED
|
@@ -12,6 +12,22 @@ export interface ReloadAck {
|
|
|
12
12
|
nativeChangesDetected?: boolean;
|
|
13
13
|
changeClass?: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Try to resolve `{projectName, bundleId}` for the running app so the
|
|
17
|
+
* agent can map the reload request to a MobileProject in its scan
|
|
18
|
+
* cache. Order: caller-supplied opts → Expo Constants → react-native
|
|
19
|
+
* NativeModules. None of the lookups throw — missing data just means
|
|
20
|
+
* the agent will fall back to its own dev-server resolution.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveAppIdentity(opts?: {
|
|
23
|
+
projectName?: string;
|
|
24
|
+
bundleId?: string;
|
|
25
|
+
projectPath?: string;
|
|
26
|
+
}): {
|
|
27
|
+
projectName?: string;
|
|
28
|
+
bundleId?: string;
|
|
29
|
+
projectPath?: string;
|
|
30
|
+
};
|
|
15
31
|
/**
|
|
16
32
|
* Lightweight P2P HTTP client for communicating with a Yaver agent.
|
|
17
33
|
*
|
|
@@ -227,5 +243,60 @@ export declare class P2PClient {
|
|
|
227
243
|
timestamp?: number;
|
|
228
244
|
}): Promise<boolean>;
|
|
229
245
|
/** Internal helper for authenticated GET/POST requests. */
|
|
246
|
+
/**
|
|
247
|
+
* Convergence point for ALL feedback surfaces — Tasks tab, in-Yaver
|
|
248
|
+
* native pane, and this standalone SDK all POST the same shape to
|
|
249
|
+
* `/tasks`. Wraps the user's text with the shared prompt builder
|
|
250
|
+
* (see `_core/buildFeedbackPrompt`) so every surface conditions
|
|
251
|
+
* the AI the same way.
|
|
252
|
+
*
|
|
253
|
+
* Returns the agent's response payload (`taskId`, etc.) so callers
|
|
254
|
+
* can wire `streamTaskOutput()` next for live transcript.
|
|
255
|
+
*
|
|
256
|
+
* Inputs:
|
|
257
|
+
* - userPrompt what the user typed
|
|
258
|
+
* - projectName / path optional Hot-Reload project context
|
|
259
|
+
* - runner / model optional preferred coding agent + model
|
|
260
|
+
* - screenshotBase64 optional JPEG base64 (no `data:` prefix)
|
|
261
|
+
* - imageMimeType defaults to "image/jpeg"
|
|
262
|
+
*/
|
|
263
|
+
createFeedbackTask(input: {
|
|
264
|
+
userPrompt: string;
|
|
265
|
+
projectName?: string;
|
|
266
|
+
projectPath?: string;
|
|
267
|
+
runner?: string;
|
|
268
|
+
model?: string;
|
|
269
|
+
screenshotBase64?: string;
|
|
270
|
+
imageMimeType?: string;
|
|
271
|
+
}): Promise<{
|
|
272
|
+
taskId: string;
|
|
273
|
+
raw?: unknown;
|
|
274
|
+
}>;
|
|
275
|
+
/**
|
|
276
|
+
* Subscribe to a task's live stdout/stderr stream. Returns an abort
|
|
277
|
+
* function — call it to detach. The agent emits NDJSON lines on
|
|
278
|
+
* `/tasks/{id}/output`; we surface each line via `onLine`.
|
|
279
|
+
*
|
|
280
|
+
* `onComplete` fires when the agent reports the task entered a
|
|
281
|
+
* terminal status (completed / failed / stopped). After that the
|
|
282
|
+
* caller should stop calling abort().
|
|
283
|
+
*
|
|
284
|
+
* Robust to fetch streaming on Hermes (streams Body via Response.
|
|
285
|
+
* body.getReader on platforms that support it; falls back to
|
|
286
|
+
* polling `/tasks/{id}` every 750 ms if streaming isn't available).
|
|
287
|
+
*/
|
|
288
|
+
streamTaskOutput(taskId: string, onLine: (line: string) => void, onComplete: (status: string) => void): () => void;
|
|
289
|
+
/**
|
|
290
|
+
* Send a follow-up message into an existing task — multi-turn vibe
|
|
291
|
+
* chat. The agent's `/tasks/{id}/resume` accepts the same shape as
|
|
292
|
+
* `/tasks` (description / userPrompt / images), and the existing
|
|
293
|
+
* task picks back up with the same runner + project context.
|
|
294
|
+
*/
|
|
295
|
+
resumeTask(input: {
|
|
296
|
+
taskId: string;
|
|
297
|
+
userPrompt: string;
|
|
298
|
+
screenshotBase64?: string;
|
|
299
|
+
imageMimeType?: string;
|
|
300
|
+
}): Promise<void>;
|
|
230
301
|
private request;
|
|
231
302
|
}
|
package/dist/P2PClient.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.P2PClient = void 0;
|
|
37
|
+
exports.resolveAppIdentity = resolveAppIdentity;
|
|
4
38
|
const react_native_1 = require("react-native");
|
|
5
39
|
/**
|
|
6
40
|
* Try to resolve `{projectName, bundleId}` for the running app so the
|
|
@@ -670,6 +704,186 @@ class P2PClient {
|
|
|
670
704
|
}
|
|
671
705
|
}
|
|
672
706
|
/** Internal helper for authenticated GET/POST requests. */
|
|
707
|
+
/**
|
|
708
|
+
* Convergence point for ALL feedback surfaces — Tasks tab, in-Yaver
|
|
709
|
+
* native pane, and this standalone SDK all POST the same shape to
|
|
710
|
+
* `/tasks`. Wraps the user's text with the shared prompt builder
|
|
711
|
+
* (see `_core/buildFeedbackPrompt`) so every surface conditions
|
|
712
|
+
* the AI the same way.
|
|
713
|
+
*
|
|
714
|
+
* Returns the agent's response payload (`taskId`, etc.) so callers
|
|
715
|
+
* can wire `streamTaskOutput()` next for live transcript.
|
|
716
|
+
*
|
|
717
|
+
* Inputs:
|
|
718
|
+
* - userPrompt what the user typed
|
|
719
|
+
* - projectName / path optional Hot-Reload project context
|
|
720
|
+
* - runner / model optional preferred coding agent + model
|
|
721
|
+
* - screenshotBase64 optional JPEG base64 (no `data:` prefix)
|
|
722
|
+
* - imageMimeType defaults to "image/jpeg"
|
|
723
|
+
*/
|
|
724
|
+
async createFeedbackTask(input) {
|
|
725
|
+
const { buildFeedbackPrompt } = await Promise.resolve().then(() => __importStar(require('./_core/buildFeedbackPrompt')));
|
|
726
|
+
const hasScreenshot = !!(input.screenshotBase64 && input.screenshotBase64.length > 0);
|
|
727
|
+
const description = buildFeedbackPrompt({
|
|
728
|
+
userPrompt: input.userPrompt,
|
|
729
|
+
projectName: input.projectName,
|
|
730
|
+
projectPath: input.projectPath,
|
|
731
|
+
hasScreenshot,
|
|
732
|
+
});
|
|
733
|
+
const images = [];
|
|
734
|
+
if (hasScreenshot && input.screenshotBase64) {
|
|
735
|
+
images.push({
|
|
736
|
+
base64: input.screenshotBase64,
|
|
737
|
+
mimeType: input.imageMimeType ?? 'image/jpeg',
|
|
738
|
+
filename: `yaver-feedback-${Math.floor(Date.now() / 1000)}.jpg`,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
const body = {
|
|
742
|
+
title: input.userPrompt.slice(0, 80),
|
|
743
|
+
description,
|
|
744
|
+
userPrompt: input.userPrompt,
|
|
745
|
+
source: 'mobile-feedback',
|
|
746
|
+
images,
|
|
747
|
+
};
|
|
748
|
+
if (input.projectPath && input.projectPath.trim())
|
|
749
|
+
body.workDir = input.projectPath.trim();
|
|
750
|
+
if (input.projectName && input.projectName.trim())
|
|
751
|
+
body.projectName = input.projectName.trim();
|
|
752
|
+
if (input.runner && input.runner.trim())
|
|
753
|
+
body.runner = input.runner.trim();
|
|
754
|
+
if (input.model && input.model.trim())
|
|
755
|
+
body.model = input.model.trim();
|
|
756
|
+
const resp = await fetch(`${this.baseUrl}/tasks`, {
|
|
757
|
+
method: 'POST',
|
|
758
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
759
|
+
body: JSON.stringify(body),
|
|
760
|
+
});
|
|
761
|
+
if (!resp.ok) {
|
|
762
|
+
const text = await resp.text().catch(() => '');
|
|
763
|
+
throw new Error(`createFeedbackTask HTTP ${resp.status}: ${text}`);
|
|
764
|
+
}
|
|
765
|
+
const json = (await resp.json().catch(() => ({})));
|
|
766
|
+
if (!json.taskId) {
|
|
767
|
+
throw new Error('createFeedbackTask: agent did not return taskId');
|
|
768
|
+
}
|
|
769
|
+
return { taskId: json.taskId, raw: json };
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Subscribe to a task's live stdout/stderr stream. Returns an abort
|
|
773
|
+
* function — call it to detach. The agent emits NDJSON lines on
|
|
774
|
+
* `/tasks/{id}/output`; we surface each line via `onLine`.
|
|
775
|
+
*
|
|
776
|
+
* `onComplete` fires when the agent reports the task entered a
|
|
777
|
+
* terminal status (completed / failed / stopped). After that the
|
|
778
|
+
* caller should stop calling abort().
|
|
779
|
+
*
|
|
780
|
+
* Robust to fetch streaming on Hermes (streams Body via Response.
|
|
781
|
+
* body.getReader on platforms that support it; falls back to
|
|
782
|
+
* polling `/tasks/{id}` every 750 ms if streaming isn't available).
|
|
783
|
+
*/
|
|
784
|
+
streamTaskOutput(taskId, onLine, onComplete) {
|
|
785
|
+
const ctrl = new AbortController();
|
|
786
|
+
let closed = false;
|
|
787
|
+
const close = () => {
|
|
788
|
+
if (closed)
|
|
789
|
+
return;
|
|
790
|
+
closed = true;
|
|
791
|
+
try {
|
|
792
|
+
ctrl.abort();
|
|
793
|
+
}
|
|
794
|
+
catch { /* ignore */ }
|
|
795
|
+
};
|
|
796
|
+
(async () => {
|
|
797
|
+
try {
|
|
798
|
+
const resp = await fetch(`${this.baseUrl}/tasks/${encodeURIComponent(taskId)}/output`, {
|
|
799
|
+
method: 'GET',
|
|
800
|
+
headers: this.authHeaders({ Accept: 'text/event-stream' }),
|
|
801
|
+
signal: ctrl.signal,
|
|
802
|
+
});
|
|
803
|
+
if (!resp.ok) {
|
|
804
|
+
throw new Error(`streamTaskOutput HTTP ${resp.status}`);
|
|
805
|
+
}
|
|
806
|
+
// RN Hermes: Response.body may be undefined. Fall back to
|
|
807
|
+
// polling final state.
|
|
808
|
+
const body = resp.body;
|
|
809
|
+
if (!body || typeof body.getReader !== 'function') {
|
|
810
|
+
await pollTaskUntilDone(this, taskId, onLine, onComplete, () => closed);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const reader = body.getReader();
|
|
814
|
+
const decoder = new TextDecoder();
|
|
815
|
+
let buf = '';
|
|
816
|
+
while (!closed) {
|
|
817
|
+
const { value, done } = await reader.read();
|
|
818
|
+
if (done)
|
|
819
|
+
break;
|
|
820
|
+
buf += decoder.decode(value, { stream: true });
|
|
821
|
+
// SSE frames are separated by \n\n; payloads are `data: <json>\n`.
|
|
822
|
+
let idx = buf.indexOf('\n\n');
|
|
823
|
+
while (idx >= 0) {
|
|
824
|
+
const frame = buf.slice(0, idx);
|
|
825
|
+
buf = buf.slice(idx + 2);
|
|
826
|
+
for (const line of frame.split('\n')) {
|
|
827
|
+
if (line.startsWith('data:')) {
|
|
828
|
+
const payload = line.slice(5).trim();
|
|
829
|
+
if (payload)
|
|
830
|
+
onLine(payload);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
idx = buf.indexOf('\n\n');
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// Stream closed cleanly — query final status.
|
|
837
|
+
try {
|
|
838
|
+
const final = await fetch(`${this.baseUrl}/tasks/${encodeURIComponent(taskId)}`, { headers: this.authHeaders() });
|
|
839
|
+
const j = (await final.json().catch(() => ({})));
|
|
840
|
+
onComplete(j.status ?? 'completed');
|
|
841
|
+
}
|
|
842
|
+
catch {
|
|
843
|
+
onComplete('completed');
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
catch (e) {
|
|
847
|
+
if (!closed) {
|
|
848
|
+
// Surface the error via onLine so the UI shows it inline,
|
|
849
|
+
// then mark complete so the caller stops waiting.
|
|
850
|
+
onLine(`__error__: ${e instanceof Error ? e.message : String(e)}`);
|
|
851
|
+
onComplete('failed');
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
})();
|
|
855
|
+
return close;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Send a follow-up message into an existing task — multi-turn vibe
|
|
859
|
+
* chat. The agent's `/tasks/{id}/resume` accepts the same shape as
|
|
860
|
+
* `/tasks` (description / userPrompt / images), and the existing
|
|
861
|
+
* task picks back up with the same runner + project context.
|
|
862
|
+
*/
|
|
863
|
+
async resumeTask(input) {
|
|
864
|
+
const images = [];
|
|
865
|
+
if (input.screenshotBase64 && input.screenshotBase64.length > 0) {
|
|
866
|
+
images.push({
|
|
867
|
+
base64: input.screenshotBase64,
|
|
868
|
+
mimeType: input.imageMimeType ?? 'image/jpeg',
|
|
869
|
+
filename: `yaver-feedback-followup-${Math.floor(Date.now() / 1000)}.jpg`,
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
const resp = await fetch(`${this.baseUrl}/tasks/${encodeURIComponent(input.taskId)}/resume`, {
|
|
873
|
+
method: 'POST',
|
|
874
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
875
|
+
body: JSON.stringify({
|
|
876
|
+
description: input.userPrompt,
|
|
877
|
+
userPrompt: input.userPrompt,
|
|
878
|
+
source: 'mobile-feedback',
|
|
879
|
+
images,
|
|
880
|
+
}),
|
|
881
|
+
});
|
|
882
|
+
if (!resp.ok) {
|
|
883
|
+
const text = await resp.text().catch(() => '');
|
|
884
|
+
throw new Error(`resumeTask HTTP ${resp.status}: ${text}`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
673
887
|
async request(method, path) {
|
|
674
888
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
675
889
|
method,
|
|
@@ -683,3 +897,43 @@ class P2PClient {
|
|
|
683
897
|
}
|
|
684
898
|
}
|
|
685
899
|
exports.P2PClient = P2PClient;
|
|
900
|
+
/**
|
|
901
|
+
* Fallback path used by streamTaskOutput when the platform's fetch
|
|
902
|
+
* returns a Response with no streaming body (older Hermes builds).
|
|
903
|
+
* Polls `/tasks/{id}` every 750 ms; surfaces newly-appended output
|
|
904
|
+
* lines via `onLine`, fires `onComplete` when status is terminal.
|
|
905
|
+
*/
|
|
906
|
+
async function pollTaskUntilDone(client, taskId, onLine, onComplete, isClosed) {
|
|
907
|
+
// Use bracket access to read the private baseUrl/authHeaders without
|
|
908
|
+
// making them public — confined to this file's scope.
|
|
909
|
+
const c = client;
|
|
910
|
+
let lastLen = 0;
|
|
911
|
+
for (;;) {
|
|
912
|
+
if (isClosed())
|
|
913
|
+
return;
|
|
914
|
+
try {
|
|
915
|
+
const r = await fetch(`${c.baseUrl}/tasks/${encodeURIComponent(taskId)}`, {
|
|
916
|
+
headers: c.authHeaders(),
|
|
917
|
+
});
|
|
918
|
+
const j = (await r.json().catch(() => ({})));
|
|
919
|
+
const all = Array.isArray(j.output) ? j.output : (j.output ? [j.output] : []);
|
|
920
|
+
const flat = all.join('\n');
|
|
921
|
+
if (flat.length > lastLen) {
|
|
922
|
+
const fresh = flat.slice(lastLen);
|
|
923
|
+
lastLen = flat.length;
|
|
924
|
+
for (const ln of fresh.split('\n')) {
|
|
925
|
+
if (ln.length > 0)
|
|
926
|
+
onLine(ln);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (j.status && ['completed', 'failed', 'stopped'].includes(j.status)) {
|
|
930
|
+
onComplete(j.status);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
catch {
|
|
935
|
+
// Transient — keep polling.
|
|
936
|
+
}
|
|
937
|
+
await new Promise((res) => setTimeout(res, 750));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { P2PClient } from './P2PClient';
|
|
3
|
+
export type VibeTurnRole = 'user' | 'assistant' | 'status';
|
|
4
|
+
export interface VibeTurn {
|
|
5
|
+
id: string;
|
|
6
|
+
role: VibeTurnRole;
|
|
7
|
+
text: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
interface Props {
|
|
11
|
+
client: P2PClient;
|
|
12
|
+
initialTaskId: string;
|
|
13
|
+
initialUserPrompt: string;
|
|
14
|
+
onClose?: () => void;
|
|
15
|
+
/** Called when the user taps Reload after a task completes — uses
|
|
16
|
+
* P2PClient.reloadApp() with the active project context. */
|
|
17
|
+
onReload?: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, }: Props): React.JSX.Element;
|
|
20
|
+
export {};
|