diff --git a/app/components/assessment/editor/MentionDropdown.tsx b/app/components/assessment/editor/MentionDropdown.tsx index 666186b..b6e27bd 100644 --- a/app/components/assessment/editor/MentionDropdown.tsx +++ b/app/components/assessment/editor/MentionDropdown.tsx @@ -2,17 +2,8 @@ import { useLayoutEffect, useState } from "react"; import { createPortal } from "react-dom"; -import { DocumentFileIcon, ImageIcon } from "@/app/components/icons"; -import type { - MentionDropdownProps, - PromptFieldType, -} from "@/app/lib/types/assessment"; - -const TYPE_ICONS: Record = { - text: Aa, - image: , - pdf: , -}; +import RenderFieldType from "./RenderFieldType"; +import type { MentionDropdownProps } from "@/app/lib/types/assessment"; const rowBase = "flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-left text-sm"; @@ -78,7 +69,9 @@ export default function MentionDropdown({ + } label={option.label} isCurrent={option.isCurrent} onPick={() => onPickType(option.type)} diff --git a/app/components/assessment/editor/PreviewPane.tsx b/app/components/assessment/editor/PreviewPane.tsx index c914707..021d8d5 100644 --- a/app/components/assessment/editor/PreviewPane.tsx +++ b/app/components/assessment/editor/PreviewPane.tsx @@ -1,10 +1,7 @@ "use client"; -import { - DocumentFileIcon, - ImageIcon, - LockClosedIcon, -} from "@/app/components/icons"; +import { LockClosedIcon } from "@/app/components/icons"; +import RenderFieldType from "./RenderFieldType"; import { buildPreviewBlocks, fieldTypeOf, @@ -115,14 +112,12 @@ function SegmentView({ if (segment.kind === "plain") return <>{segment.text}; if (segment.kind === "attachment" && segment.name) { - const isImage = fieldTypeOf(fieldTypes, segment.name) === "image"; return ( - {isImage ? ( - - ) : ( - - )} + {segment.name} ); diff --git a/app/components/assessment/editor/RenderFieldType.tsx b/app/components/assessment/editor/RenderFieldType.tsx new file mode 100644 index 0000000..7b0b42b --- /dev/null +++ b/app/components/assessment/editor/RenderFieldType.tsx @@ -0,0 +1,21 @@ +import { DocumentFileIcon, ImageIcon, VideoIcon } from "@/app/components/icons"; +import type { PromptFieldType } from "@/app/lib/types/assessment"; + +export default function RenderFieldType({ + type, + className, + textLabel = "Aa", +}: { + type: PromptFieldType; + className: string; + textLabel?: string; +}) { + if (type === "text") { + return ( + {textLabel} + ); + } + if (type === "image") return ; + if (type === "video") return ; + return ; +} diff --git a/app/components/assessment/wizard/ReviewSaveModal.tsx b/app/components/assessment/wizard/ReviewSaveModal.tsx index 62550fb..263846c 100644 --- a/app/components/assessment/wizard/ReviewSaveModal.tsx +++ b/app/components/assessment/wizard/ReviewSaveModal.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Button, Field, Modal } from "@/app/components/ui"; -import { DocumentFileIcon, ImageIcon } from "@/app/components/icons"; +import RenderFieldType from "@/app/components/assessment/editor/RenderFieldType"; import { fieldTypeOf, TOKEN_RE, @@ -61,13 +61,11 @@ export default function ReviewSaveModal({ key={column} className="inline-flex items-center gap-1.5 rounded-full border border-border bg-bg-secondary px-2.5 py-1 font-mono text-[11px] text-text-primary" > - {type === "text" ? ( - "@" - ) : type === "image" ? ( - - ) : ( - - )} + {column} {type} diff --git a/app/components/icons/common/VideoIcon.tsx b/app/components/icons/common/VideoIcon.tsx new file mode 100644 index 0000000..d5ef033 --- /dev/null +++ b/app/components/icons/common/VideoIcon.tsx @@ -0,0 +1,24 @@ +interface IconProps { + className?: string; + style?: React.CSSProperties; +} + +export default function VideoIcon({ className, style }: IconProps) { + return ( + + + + + ); +} diff --git a/app/components/icons/index.tsx b/app/components/icons/index.tsx index d4b6d4d..ad68c96 100644 --- a/app/components/icons/index.tsx +++ b/app/components/icons/index.tsx @@ -23,6 +23,7 @@ export { default as ErrorCircleIcon } from "./common/ErrorCircleIcon"; export { default as DownloadIcon } from "./common/DownloadIcon"; export { default as HomeIcon } from "./common/HomeIcon"; export { default as ImageIcon } from "./common/ImageIcon"; +export { default as VideoIcon } from "./common/VideoIcon"; export { default as LockClosedIcon } from "./common/LockClosedIcon"; // Evaluations Icons diff --git a/app/hooks/usePromptMentions.ts b/app/hooks/usePromptMentions.ts index 515491a..6cf8497 100644 --- a/app/hooks/usePromptMentions.ts +++ b/app/hooks/usePromptMentions.ts @@ -17,7 +17,7 @@ import type { UsePromptMentionsResult, } from "@/app/lib/types/assessment"; -const TYPE_ORDER: PromptFieldType[] = ["text", "image", "pdf"]; +const TYPE_ORDER: PromptFieldType[] = ["text", "image", "pdf", "video"]; const HOVER_CLOSE_DELAY_MS = 200; diff --git a/app/lib/assessment/configBlob.ts b/app/lib/assessment/configBlob.ts index 519c10e..bc3d0f6 100644 --- a/app/lib/assessment/configBlob.ts +++ b/app/lib/assessment/configBlob.ts @@ -62,7 +62,7 @@ function attachmentsFrom( .filter(([, column]) => column.type !== "text") .map(([name, column]) => ({ column: name, - type: column.type as "image" | "pdf", + type: column.type, format: column.format ?? "url", })); } @@ -156,7 +156,7 @@ function inputSchema( names.map((name) => { const attachment = byColumn.get(name); const column: AssessmentBlobInputColumn = - attachment && attachment.type !== "mixed" + attachment && attachment.type !== "text" ? { type: attachment.type, format: attachment.format ?? "url" } : { type: "text", format: null }; return [name, strict.has(name) ? { ...column, strict: true } : column]; diff --git a/app/lib/assessment/promptTokens.ts b/app/lib/assessment/promptTokens.ts index deea3c5..c78fbad 100644 --- a/app/lib/assessment/promptTokens.ts +++ b/app/lib/assessment/promptTokens.ts @@ -13,6 +13,7 @@ export const PROMPT_FIELD_TYPE_LABELS: Record = { text: "Text", image: "Image (link)", pdf: "PDF (link)", + video: "Video (link)", }; export function tokenName(raw: string): string { @@ -165,7 +166,7 @@ export function fromWireTemplate( return template.replace(TOKEN_RE, (raw) => { const name = tokenName(raw); const type = byColumn.get(name); - return type && type !== "mixed" ? `<${name}>` : `@${name}`; + return type && type !== "text" ? `<${name}>` : `@${name}`; }); } @@ -174,7 +175,7 @@ export function fieldTypesFromAttachments( ): Record { return Object.fromEntries( attachments - .filter((item) => item.type !== "mixed") - .map((item) => [item.column, item.type as PromptFieldType]), + .filter((item) => item.type !== "text") + .map((item) => [item.column, item.type]), ); } diff --git a/app/lib/types/assessment/core.ts b/app/lib/types/assessment/core.ts index b9606ad..8c851c5 100644 --- a/app/lib/types/assessment/core.ts +++ b/app/lib/types/assessment/core.ts @@ -1,4 +1,5 @@ import type { ChangeEvent, DragEvent, RefObject } from "react"; +import type { PromptFieldType } from "./prompt"; export type ValueSetter = (value: T) => void; export type SampleRow = Record; @@ -126,7 +127,7 @@ export interface AssessmentResultsPayload { export interface Attachment { column: string; - type: "image" | "pdf" | "mixed"; + type: PromptFieldType; format: "url" | "base64"; type_column?: string | null; type_value_map?: Record | null; diff --git a/app/lib/types/assessment/prompt.ts b/app/lib/types/assessment/prompt.ts index 67d4c59..a171b28 100644 --- a/app/lib/types/assessment/prompt.ts +++ b/app/lib/types/assessment/prompt.ts @@ -3,7 +3,7 @@ import type { ProviderType } from "@/app/lib/types/configs"; import type { SchemaProperty } from "./core"; import type { AssessorModelSelection, AssessorVersionDetail } from "./assessor"; -export type PromptFieldType = "text" | "image" | "pdf"; +export type PromptFieldType = "text" | "image" | "pdf" | "video"; export type PromptZoneId = "instructions" | "submission";