forked from farhoodlabs/paperclip
Add feedback voting and thumbs capture flow
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { DocumentRevision, Issue, IssueDocument } from "@paperclipai/shared";
|
||||
import type {
|
||||
DocumentRevision,
|
||||
FeedbackDataSharingPreference,
|
||||
FeedbackVote,
|
||||
FeedbackVoteValue,
|
||||
Issue,
|
||||
IssueDocument,
|
||||
} from "@paperclipai/shared";
|
||||
import { useLocation } from "@/lib/router";
|
||||
import { ApiError } from "../api/client";
|
||||
import { issuesApi } from "../api/issues";
|
||||
@@ -9,6 +16,7 @@ import { queryKeys } from "../lib/queryKeys";
|
||||
import { cn, relativeTime } from "../lib/utils";
|
||||
import { MarkdownBody } from "./MarkdownBody";
|
||||
import { MarkdownEditor, type MentionOption } from "./MarkdownEditor";
|
||||
import { OutputFeedbackButtons } from "./OutputFeedbackButtons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
@@ -101,14 +109,26 @@ function documentHasUnsavedChanges(doc: IssueDocument, draft: DraftState | null)
|
||||
export function IssueDocumentsSection({
|
||||
issue,
|
||||
canDeleteDocuments,
|
||||
feedbackVotes = [],
|
||||
feedbackDataSharingPreference = "prompt",
|
||||
feedbackTermsUrl = null,
|
||||
mentions,
|
||||
imageUploadHandler,
|
||||
onVote,
|
||||
extraActions,
|
||||
}: {
|
||||
issue: Issue;
|
||||
canDeleteDocuments: boolean;
|
||||
feedbackVotes?: FeedbackVote[];
|
||||
feedbackDataSharingPreference?: FeedbackDataSharingPreference;
|
||||
feedbackTermsUrl?: string | null;
|
||||
mentions?: MentionOption[];
|
||||
imageUploadHandler?: (file: File) => Promise<string>;
|
||||
onVote?: (
|
||||
revisionId: string,
|
||||
vote: FeedbackVoteValue,
|
||||
options?: { allowSharing?: boolean; reason?: string },
|
||||
) => Promise<void>;
|
||||
extraActions?: ReactNode;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -207,6 +227,15 @@ export function IssueDocumentsSection({
|
||||
});
|
||||
}, [documents]);
|
||||
|
||||
const feedbackVoteByTargetId = useMemo(() => {
|
||||
const map = new Map<string, FeedbackVoteValue>();
|
||||
for (const feedbackVote of feedbackVotes) {
|
||||
if (feedbackVote.targetType !== "issue_document_revision") continue;
|
||||
map.set(feedbackVote.targetId, feedbackVote.vote);
|
||||
}
|
||||
return map;
|
||||
}, [feedbackVotes]);
|
||||
|
||||
const hasRealPlan = sortedDocuments.some((doc) => doc.key === "plan");
|
||||
const isEmpty = sortedDocuments.length === 0 && !issue.legacyPlanDocument;
|
||||
const newDocumentKeyError =
|
||||
@@ -718,6 +747,7 @@ export function IssueDocumentsSection({
|
||||
const displayedRevisionNumber = selectedHistoricalRevision?.revisionNumber ?? doc.latestRevisionNumber;
|
||||
const displayedUpdatedAt = selectedHistoricalRevision?.createdAt ?? doc.updatedAt;
|
||||
const showTitle = !isPlanKey(doc.key) && !!displayedTitle.trim() && !titlesMatchKey(displayedTitle, doc.key);
|
||||
const canVoteOnDocument = Boolean(doc.latestRevisionId && doc.updatedByAgentId && !doc.updatedByUserId && onVote);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1053,6 +1083,16 @@ export function IssueDocumentsSection({
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
{canVoteOnDocument && doc.latestRevisionId ? (
|
||||
<OutputFeedbackButtons
|
||||
activeVote={feedbackVoteByTargetId.get(doc.latestRevisionId) ?? null}
|
||||
sharingPreference={feedbackDataSharingPreference}
|
||||
termsUrl={feedbackTermsUrl}
|
||||
onVote={(vote: FeedbackVoteValue, options?: { allowSharing?: boolean; reason?: string }) =>
|
||||
onVote?.(doc.latestRevisionId!, vote, options) ?? Promise.resolve()
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user