fix: enable full preview in review flows
This commit is contained in:
@@ -29,7 +29,7 @@ IMAGE_PROVIDER_FALLBACK=gemini
|
|||||||
IMAGE_MODEL=gpt-image-2
|
IMAGE_MODEL=gpt-image-2
|
||||||
MODEL_IMAGE=gpt-image-2
|
MODEL_IMAGE=gpt-image-2
|
||||||
MODEL_TEXT=claude-opus-4-8
|
MODEL_TEXT=claude-opus-4-8
|
||||||
APIPORTS_BASE_URL=https://api.apiyi.com/v1
|
APIPORTS_BASE_URL=https://www.apiports.com/v1
|
||||||
CODEPROXY_BASE_URL=
|
CODEPROXY_BASE_URL=
|
||||||
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta
|
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ IMAGE_PROVIDER_FALLBACK=gemini
|
|||||||
IMAGE_MODEL=gpt-image-2
|
IMAGE_MODEL=gpt-image-2
|
||||||
MODEL_IMAGE=gpt-image-2
|
MODEL_IMAGE=gpt-image-2
|
||||||
MODEL_TEXT=claude-opus-4-8
|
MODEL_TEXT=claude-opus-4-8
|
||||||
APIPORTS_BASE_URL=https://api.apiyi.com/v1
|
APIPORTS_BASE_URL=https://www.apiports.com/v1
|
||||||
CODEPROXY_BASE_URL=
|
CODEPROXY_BASE_URL=
|
||||||
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta
|
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta
|
||||||
APIPORTS_KEY=
|
APIPORTS_KEY=
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export default function ConfirmPage() {
|
|||||||
const [flywheelMsg, setFlywheelMsg] = useState<string | null>(null);
|
const [flywheelMsg, setFlywheelMsg] = useState<string | null>(null);
|
||||||
// 失败任务查看不能空白(#11):记录任务状态+失败原因,failed 时显眼提示
|
// 失败任务查看不能空白(#11):记录任务状态+失败原因,failed 时显眼提示
|
||||||
const [taskMeta, setTaskMeta] = useState<{ status: string; failReason: string } | null>(null);
|
const [taskMeta, setTaskMeta] = useState<{ status: string; failReason: string } | null>(null);
|
||||||
// 已通过/已打回 = 只读查看态(倩倩姐2026-06-26):不提交审核,文案点看全文、图点放大
|
// 已通过/已打回 = 只读查看态(倩倩姐2026-06-26):不提交审核。
|
||||||
|
// 无论是否只读,确认页都允许点文案看全文、点图片放大,避免提审前看不全。
|
||||||
const isReadonly = taskMeta?.status === 'approved' || taskMeta?.status === 'rejected';
|
const isReadonly = taskMeta?.status === 'approved' || taskMeta?.status === 'rejected';
|
||||||
const [zoomImage, setZoomImage] = useState<ImageCandidate | null>(null);
|
const [zoomImage, setZoomImage] = useState<ImageCandidate | null>(null);
|
||||||
const [zoomText, setZoomText] = useState<TextCandidate | null>(null);
|
const [zoomText, setZoomText] = useState<TextCandidate | null>(null);
|
||||||
@@ -170,14 +171,22 @@ export default function ConfirmPage() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{selectedTexts.map((c) => (
|
{selectedTexts.map((c) => (
|
||||||
<div key={c.candidate_id}
|
<div key={c.candidate_id}
|
||||||
onClick={isReadonly ? () => setZoomText(c) : undefined}
|
role="button"
|
||||||
className={`border border-border-default rounded-xl p-4 bg-surface-primary ${isReadonly ? 'cursor-pointer hover:border-brand-orange' : ''}`}>
|
tabIndex={0}
|
||||||
|
onClick={() => setZoomText(c)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
setZoomText(c);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="border border-border-default rounded-xl p-4 bg-surface-primary cursor-pointer hover:border-brand-orange">
|
||||||
<span className="text-xs text-brand-orange font-medium">{c.angle_label}</span>
|
<span className="text-xs text-brand-orange font-medium">{c.angle_label}</span>
|
||||||
<p className={`text-sm text-text-primary mt-1 whitespace-pre-wrap ${isReadonly ? '' : 'line-clamp-6'}`}>
|
<p className="text-sm text-text-primary mt-1 whitespace-pre-wrap line-clamp-6">
|
||||||
{c.content}
|
{c.content}
|
||||||
</p>
|
</p>
|
||||||
<span className="text-xs text-text-tertiary mt-1 block">
|
<span className="text-xs text-text-tertiary mt-1 block">
|
||||||
总分 {c.score.total}{isReadonly ? ' · 点击看全文' : ''}
|
总分 {c.score.total} · 点击看全文
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -186,7 +195,7 @@ export default function ConfirmPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 已选图 */}
|
{/* 已选图 */}
|
||||||
<ConfirmPreviewImages images={selectedImages} onZoom={isReadonly ? setZoomImage : undefined} />
|
<ConfirmPreviewImages images={selectedImages} onZoom={setZoomImage} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 提审被拒:显式红框,不再静默吞错 */}
|
{/* 提审被拒:显式红框,不再静默吞错 */}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
* ReviewCard — 单条待审笔记卡
|
* ReviewCard — 单条待审笔记卡
|
||||||
* 通过(+5) / 打回(-3,必填原因)
|
* 通过(+5) / 打回(-3,必填原因)
|
||||||
*/
|
*/
|
||||||
|
import { useState } from 'react';
|
||||||
import { ReviewQueueItem } from '@/types/index';
|
import { ReviewQueueItem } from '@/types/index';
|
||||||
|
import { ImageLightbox } from '@/components/tasks/ImageLightbox';
|
||||||
import { ScoreDimBars } from '@/components/tasks/ScoreDimBars';
|
import { ScoreDimBars } from '@/components/tasks/ScoreDimBars';
|
||||||
|
import { TextLightbox } from '@/components/tasks/TextLightbox';
|
||||||
|
|
||||||
interface ReviewCardProps {
|
interface ReviewCardProps {
|
||||||
item: ReviewQueueItem;
|
item: ReviewQueueItem;
|
||||||
@@ -14,6 +17,13 @@ interface ReviewCardProps {
|
|||||||
|
|
||||||
export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
||||||
const sets = item.selected_sets?.filter((s) => s.selected_text || s.selected_images.length) ?? [];
|
const sets = item.selected_sets?.filter((s) => s.selected_text || s.selected_images.length) ?? [];
|
||||||
|
const [zoomText, setZoomText] = useState<{
|
||||||
|
content: string;
|
||||||
|
label?: string;
|
||||||
|
score?: number;
|
||||||
|
} | null>(null);
|
||||||
|
const [zoomImage, setZoomImage] = useState<{ url: string; caption?: string } | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className="border border-border-default rounded-xl bg-surface-primary p-4"
|
className="border border-border-default rounded-xl bg-surface-primary p-4"
|
||||||
@@ -28,9 +38,18 @@ export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
|||||||
</div>
|
</div>
|
||||||
<h3 className="font-medium text-text-primary text-sm line-clamp-1">{item.theme}</h3>
|
<h3 className="font-medium text-text-primary text-sm line-clamp-1">{item.theme}</h3>
|
||||||
{sets.length === 0 && item.selected_text && (
|
{sets.length === 0 && item.selected_text && (
|
||||||
<p className="text-xs text-text-secondary mt-1 line-clamp-2">
|
<button
|
||||||
{item.selected_text.content}
|
type="button"
|
||||||
</p>
|
onClick={() => setZoomText({
|
||||||
|
content: item.selected_text!.content,
|
||||||
|
label: item.selected_text!.angle_label,
|
||||||
|
score: item.selected_text!.score?.total,
|
||||||
|
})}
|
||||||
|
className="mt-1 block w-full text-left text-xs text-text-secondary hover:text-brand-orange"
|
||||||
|
>
|
||||||
|
<span className="line-clamp-2">{item.selected_text.content}</span>
|
||||||
|
<span className="mt-1 block text-[11px] text-text-tertiary">点击看全文</span>
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* 质量分:审核可判断(C2)。合格线80=红线,不用组件默认85 */}
|
{/* 质量分:审核可判断(C2)。合格线80=红线,不用组件默认85 */}
|
||||||
{sets.length === 0 && item.selected_text?.score && (
|
{sets.length === 0 && item.selected_text?.score && (
|
||||||
@@ -38,8 +57,18 @@ export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{sets.length === 0 && item.selected_image && (
|
{sets.length === 0 && item.selected_image && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setZoomImage({
|
||||||
|
url: item.selected_image!.url,
|
||||||
|
caption: `已选图 · 套${item.selected_image!.strategy}`,
|
||||||
|
})}
|
||||||
|
className="w-16 h-16 rounded-lg overflow-hidden flex-shrink-0 cursor-zoom-in"
|
||||||
|
aria-label="放大已选图"
|
||||||
|
>
|
||||||
<img src={item.selected_image.url} alt="已选图预览"
|
<img src={item.selected_image.url} alt="已选图预览"
|
||||||
className="w-16 h-16 rounded-lg object-cover flex-shrink-0" />
|
className="h-full w-full object-cover" />
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -53,9 +82,18 @@ export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
|||||||
</div>
|
</div>
|
||||||
{set.selected_text && (
|
{set.selected_text && (
|
||||||
<>
|
<>
|
||||||
<p className="text-xs text-text-secondary line-clamp-2">
|
<button
|
||||||
{set.selected_text.content}
|
type="button"
|
||||||
</p>
|
onClick={() => setZoomText({
|
||||||
|
content: set.selected_text!.content,
|
||||||
|
label: `套${set.strategy} · ${set.selected_text!.angle_label}`,
|
||||||
|
score: set.selected_text!.score?.total,
|
||||||
|
})}
|
||||||
|
className="block w-full text-left text-xs text-text-secondary hover:text-brand-orange"
|
||||||
|
>
|
||||||
|
<span className="line-clamp-2">{set.selected_text.content}</span>
|
||||||
|
<span className="mt-1 block text-[11px] text-text-tertiary">点击看全文</span>
|
||||||
|
</button>
|
||||||
{set.selected_text.score && (
|
{set.selected_text.score && (
|
||||||
<ScoreDimBars score={set.selected_text.score} passLine={80} />
|
<ScoreDimBars score={set.selected_text.score} passLine={80} />
|
||||||
)}
|
)}
|
||||||
@@ -64,12 +102,22 @@ export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
|||||||
{set.selected_images.length > 0 && (
|
{set.selected_images.length > 0 && (
|
||||||
<div className="mt-2 flex gap-2 overflow-x-auto">
|
<div className="mt-2 flex gap-2 overflow-x-auto">
|
||||||
{set.selected_images.filter(Boolean).map((img) => (
|
{set.selected_images.filter(Boolean).map((img) => (
|
||||||
<img
|
<button
|
||||||
key={img!.candidate_id}
|
key={img!.candidate_id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setZoomImage({
|
||||||
|
url: img!.url,
|
||||||
|
caption: `套${set.strategy}${img!.role ? ` · ${img!.role}` : ''}`,
|
||||||
|
})}
|
||||||
|
className="h-16 w-12 flex-shrink-0 cursor-zoom-in overflow-hidden rounded-md"
|
||||||
|
aria-label={`放大套${set.strategy}已选图`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
src={img!.url}
|
src={img!.url}
|
||||||
alt={`套${set.strategy}已选图`}
|
alt={`套${set.strategy}已选图`}
|
||||||
className="h-16 w-12 flex-shrink-0 rounded-md object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -91,6 +139,17 @@ export function ReviewCard({ item, onApprove, onReject }: ReviewCardProps) {
|
|||||||
✗ 打回 + 原因
|
✗ 打回 + 原因
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<TextLightbox
|
||||||
|
text={zoomText?.content ?? null}
|
||||||
|
label={zoomText?.label}
|
||||||
|
score={zoomText?.score}
|
||||||
|
onClose={() => setZoomText(null)}
|
||||||
|
/>
|
||||||
|
<ImageLightbox
|
||||||
|
url={zoomImage?.url ?? null}
|
||||||
|
caption={zoomImage?.caption}
|
||||||
|
onClose={() => setZoomImage(null)}
|
||||||
|
/>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user