@e-llm-studio/feedback-flow
v0.1.35
Published
Reusable configurable feedback rating and progressive feedback screens
Maintainers
Keywords
Readme
Response feedback components
useFeedbackFlow(requestId, featureId) owns the rating and draft for one response. Render FeedbackPrompt and FeedbackModal with the same controller. The caller decides where to place the prompt and supplies the modal's anchorRef so the popup appears above it.
For chat timelines, FeedbackResponseControls owns that controller per response. Give it the response's requestId, featureId, message index, chat shell and sticky host refs, and viewport state. It renders one prompt at the heading for a newly completed response, beside the response's actions at its end, or in the sticky host while that response is active in the viewport. Each response keeps a separate draft.
const flow = useFeedbackFlow(requestId, featureId);
const promptRef = useRef<HTMLDivElement>(null);
const copy = createFeedbackCopy({
prompt: 'Rate this answer',
timeOptions: [5, 15, 30].map((value) => ({ value, label: `${value} minutes` })),
severityLabels: { critical: 'Blocking' },
});
const theme = { accent: '#6d28d9', text: '#1e293b', muted: '#64748b' };
return <>
<FeedbackPrompt flow={flow} promptRef={promptRef} copy={copy} theme={theme} />
<FeedbackModal
flow={flow}
anchorRef={promptRef}
copy={copy}
theme={theme}
slots={{ gap: <YourOptionalGapContent /> }}
onSubmit={(feedback) => submitFeedback(feedback)}
/>
</>;createFeedbackCopy accepts partial overrides for every visible label, question, hint, placeholder, and option. FeedbackTheme controls the prompt, popup, tooltip, overlay, and severity colors. slots and extraContent allow callers to add their own React content. The onSubmit payload includes the request ID, feature ID, numeric rating, time saved, useful text, gap descriptions, impact levels, and screenshot File objects.
Useful and gap answers shorter than usefulMinimumWords show a configurable helper directly below their question and keep the existing answer in the same textarea. Continue is hidden until the minimum is met. Decimal ratings advance on selection without an Enter hint. Each completed step is saved by POST or PATCH with metadata.feedbackProgress; an interrupted response reopens at its next unanswered step, while only the final save marks feedback complete. The remote template's local tarball must be updated separately to receive these source changes.
FeedbackExperience and FeedbackResponseControls use mockFeedbackApi by default. The mock stores records in browser localStorage (with an in-memory fallback), and fetches an existing record on mount so Review can reopen previously submitted answers. upsertFeedback handles both first submission and later edits; getFeedback looks up a record by query_id, feature_id, and user_email. Supply a custom FeedbackApi through the api prop when a backend is available. The host's onSubmit callback still runs after a successful save.
The API record mirrors the proposed tables: feedback_form, positive_feedback, gaps, and feedback_attachment. Chat query_id comes from tracking metadata; feature feedback only needs featureId. user_email/trace_id come from tracking. IDs and timestamps remain stable on updates. Embeddings are backend-generated and omitted. Screenshot references use mock:// URLs until an upload endpoint exists; the mock does not upload file bytes.
createHttpFeedbackApi({ baseUrl, readBaseUrl, getAuthToken, uploadAttachment }) reads chat feedback with GET /feedback?user_email=...&session_id=... and non-chat feedback with GET /feedback?user_email=...&feature_id=.... When a session ID is supplied, the GET never falls back to feature_id, even if the response query ID is not available yet. It selects the relevant chat record from the session's results by its query_id. POST creates and PATCH /feedback/{feedback_id} updates records. A chat submission requires metadata.queryId and sends query_id; non-chat feedback sends feature_id, never both. If the non-chat feature ID is not a UUID, the adapter derives a stable UUID-shaped ID from the host IDs. getAuthToken supplies a fresh Bearer token for every GET, POST, and PATCH request. readBaseUrl is optional when reads use a different deployment. Browser-local state is only a fallback when GET fails. The request body follows the live API's positive_feedback and negative_feedback arrays. Hosts can provide an upload function; it must upload each File and return its gs:// URL before the feedback request is sent. Original host IDs remain in metadata for matching older records.
Hosts may include metadata.queryId, metadata.sessionId, and metadata.requestId to send top-level query_id, session_id, and request_id with feedback. Chat reads filter by session_id, then match each response by query_id; query_id is never a GET parameter. All three are optional for non-chat integrations. FeedbackExperience.requestId is optional for features; its internal UI identity falls back to featureId, but no request_id or query_id is sent in the feature API payload.
For screenshots, use createFeedbackScreenshotUploader({ baseUrl: VITE_BASE_API_URL, getAuthToken }) as uploadAttachment and createFeedbackScreenshotUrlResolver with the same options as getAttachmentPreviewUrl. The library POSTs the file as multipart form data to /backend/gcs/direct-gcs-upload (or /gcs/direct-gcs-upload when the base URL already ends in /backend) and sends the returned gs_url in the feedback attachment payload. To preview a saved screenshot, it POSTs { gsutil_url: gsUrl } to CW's /gcs/get-signed-url/ and uses the returned short-lived signed_url, rather than the upload response's unsigned public_url. Signed URLs are cached in memory by API base URL, auth token, and GCS path until just before expiry; concurrent requests share one call, and a failed image can force-refresh its URL. Clicking the thumbnail opens a larger preview; the remove icon takes that screenshot out of the draft and subsequent feedback payload without deleting the GCS object. The form shows an error and stays open if upload or feedback submission fails. No project ID is needed for this endpoint.
For mode tracking, pass tracking to FeedbackExperience, or call createFeedbackMetadata(tracking) when using useFeedbackFlow directly. Standard fields are mode, modeId, owner, featureContext, userEmail, and traceId. Put any integration-specific fields under custom; they are included alongside the standard fields in submission.metadata. For example:
<FeedbackExperience
featureId={featureId}
featureAvailabilityModeId={modeId}
featureAvailabilityApi={createHttpFeatureAvailabilityApi({
baseUrl: 'https://devllmstudio.creativeworkspace.ai',
getAuthToken: () => authStore.getState().token,
})}
tracking={{
mode: 'Agent Genie',
modeId,
owner: user.email,
featureContext: 'BlockedIncidents',
}}
onSubmit={(feedback) => saveFeedback(feedback)}
/>The availability API is consulted only when requestId is omitted. It GETs /segment_click_analytics/v1/api/mode-features/{modeId} with the current Bearer token, shares one list request across cards, and matches feature_id. A feature card is hidden until its matching response arrives. Each non-null start_date, end_date, and ui_new_ttl is checked; a null field adds no restriction. Missing features or failed responses hide the card. createMockFeatureAvailabilityApi remains available for tests. The existing metadata prop still works. When both are provided, explicit standard fields in tracking take precedence. The components use default copy when it is omitted or incomplete. FeedbackErrorBoundary can wrap each surface so a rendering error hides only the feedback UI. The modal also tolerates browsers without ResizeObserver and shows a retry message only if the final save fails.
