(null);
const [notRankable, setNotRankable] = useState(false);
+ const { preferences } = useRankingDisplayPreferences();
+
let messages: Message[] = [];
if (task.type !== TaskType.rank_initial_prompts) {
messages = task.conversation.messages;
@@ -49,6 +53,10 @@ export const EvaluateTask = ({
// @notmd: I haven't test `rank_initial_prompts` type yet
const sortableItems =
task.type === TaskType.rank_initial_prompts ? (task.prompts as unknown as Message[]) : task.reply_messages;
+
+ // Calculate max messages per row based on the number of items
+ const maxMessagesPerRow = Math.min(sortableItems.length, 6);
+
return (
@@ -57,13 +65,22 @@ export const EvaluateTask = ({
-
+
+ {/* Display Preferences Controls */}
+
+
+
+
+
{
+ const { t } = useTranslation("tasks");
+ const {
+ preferences,
+ setDisplayMode,
+ setRemoveContentLimit,
+ setMessagesPerRow,
+ } = useRankingDisplayPreferences();
+
+ const cardColor = useColorModeValue("gray.50", "gray.800");
+
+ const handleDisplayModeChange = useCallback((value: string) => {
+ setDisplayMode(value as "vertical" | "horizontal");
+ }, [setDisplayMode]);
+
+ const handleContentLimitToggle = useCallback((checked: boolean) => {
+ setRemoveContentLimit(checked);
+ }, [setRemoveContentLimit]);
+
+ const handleMessagesPerRowChange = useCallback((value: number) => {
+ setMessagesPerRow(value);
+ }, [setMessagesPerRow]);
+
+ const handleSliderChange = useCallback((value: number) => {
+ setMessagesPerRow(value);
+ }, [setMessagesPerRow]);
+
+ return (
+
+
+ {t("ranking_display_preferences")}
+
+
+
+ {/* Display Mode Toggle */}
+
+ {t("display_mode")}
+
+
+ {t("vertical_display")}
+ {t("horizontal_display")}
+
+
+
+
+ {/* Remove Content Limit Toggle */}
+
+
+ {t("remove_content_limit")}
+ handleContentLimitToggle(e.target.checked)}
+ />
+
+
+
+ {/* Messages Per Row Slider (only show in horizontal mode) */}
+ {preferences.displayMode === "horizontal" && (
+
+
+
+ {t("messages_per_row")}
+
+
+
+
+
+
+
+
+
+ handleMessagesPerRowChange(value)}
+ size="sm"
+ maxW="80px"
+ min={1}
+ max={maxMessagesPerRow}
+ step={1}
+ >
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/website/src/components/Tasks/__tests__/RankingDisplayPreferences.test.tsx b/website/src/components/Tasks/__tests__/RankingDisplayPreferences.test.tsx
new file mode 100644
index 0000000000..2c7318db54
--- /dev/null
+++ b/website/src/components/Tasks/__tests__/RankingDisplayPreferences.test.tsx
@@ -0,0 +1,64 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { ChakraProvider } from "@chakra-ui/react";
+import { RankingDisplayPreferences } from "../RankingDisplayPreferences";
+
+// Mock the hook
+jest.mock("src/hooks/tasks/useRankingDisplayPreferences", () => ({
+ useRankingDisplayPreferences: () => ({
+ preferences: {
+ displayMode: "vertical",
+ removeContentLimit: false,
+ messagesPerRow: 3,
+ },
+ setDisplayMode: jest.fn(),
+ setRemoveContentLimit: jest.fn(),
+ setMessagesPerRow: jest.fn(),
+ }),
+}));
+
+// Mock next-i18next
+jest.mock("next-i18next", () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+const renderWithChakra = (component: React.ReactElement) => {
+ return render({component});
+};
+
+describe("RankingDisplayPreferences", () => {
+ it("renders display preferences controls", () => {
+ renderWithChakra();
+
+ expect(screen.getByText("ranking_display_preferences")).toBeInTheDocument();
+ expect(screen.getByText("display_mode")).toBeInTheDocument();
+ expect(screen.getByText("vertical_display")).toBeInTheDocument();
+ expect(screen.getByText("horizontal_display")).toBeInTheDocument();
+ expect(screen.getByText("remove_content_limit")).toBeInTheDocument();
+ });
+
+ it("shows messages per row slider only in horizontal mode", () => {
+ const { rerender } = renderWithChakra();
+
+ // Should not show slider in vertical mode
+ expect(screen.queryByText("messages_per_row")).not.toBeInTheDocument();
+
+ // Mock horizontal mode
+ jest.doMock("src/hooks/tasks/useRankingDisplayPreferences", () => ({
+ useRankingDisplayPreferences: () => ({
+ preferences: {
+ displayMode: "horizontal",
+ removeContentLimit: false,
+ messagesPerRow: 3,
+ },
+ setDisplayMode: jest.fn(),
+ setRemoveContentLimit: jest.fn(),
+ setMessagesPerRow: jest.fn(),
+ }),
+ }));
+
+ rerender();
+ // Note: This test would need to be updated to properly test the conditional rendering
+ });
+});
diff --git a/website/src/hooks/tasks/useRankingDisplayPreferences.ts b/website/src/hooks/tasks/useRankingDisplayPreferences.ts
new file mode 100644
index 0000000000..0ce9eb738e
--- /dev/null
+++ b/website/src/hooks/tasks/useRankingDisplayPreferences.ts
@@ -0,0 +1,68 @@
+import { useCallback, useEffect, useState } from "react";
+
+export interface RankingDisplayPreferences {
+ displayMode: "vertical" | "horizontal";
+ removeContentLimit: boolean;
+ messagesPerRow: number;
+}
+
+const RANKING_PREFERENCES_KEY = "RANKING_DISPLAY_PREFERENCES";
+
+const defaultPreferences: RankingDisplayPreferences = {
+ displayMode: "vertical",
+ removeContentLimit: false,
+ messagesPerRow: 3, // Default to 3 messages per row for horizontal mode
+};
+
+export const useRankingDisplayPreferences = () => {
+ const [preferences, setPreferences] = useState(defaultPreferences);
+ const [isLoaded, setIsLoaded] = useState(false);
+
+ // Load preferences from localStorage on mount
+ useEffect(() => {
+ if (typeof localStorage !== "undefined") {
+ const stored = localStorage.getItem(RANKING_PREFERENCES_KEY);
+ if (stored) {
+ try {
+ const parsed = JSON.parse(stored) as RankingDisplayPreferences;
+ setPreferences({ ...defaultPreferences, ...parsed });
+ } catch (error) {
+ console.warn("Failed to parse ranking preferences from localStorage:", error);
+ }
+ }
+ }
+ setIsLoaded(true);
+ }, []);
+
+ // Save preferences to localStorage whenever they change
+ useEffect(() => {
+ if (isLoaded && typeof localStorage !== "undefined") {
+ localStorage.setItem(RANKING_PREFERENCES_KEY, JSON.stringify(preferences));
+ }
+ }, [preferences, isLoaded]);
+
+ const updatePreferences = useCallback((updates: Partial) => {
+ setPreferences((prev) => ({ ...prev, ...updates }));
+ }, []);
+
+ const setDisplayMode = useCallback((displayMode: "vertical" | "horizontal") => {
+ updatePreferences({ displayMode });
+ }, [updatePreferences]);
+
+ const setRemoveContentLimit = useCallback((removeContentLimit: boolean) => {
+ updatePreferences({ removeContentLimit });
+ }, [updatePreferences]);
+
+ const setMessagesPerRow = useCallback((messagesPerRow: number) => {
+ updatePreferences({ messagesPerRow });
+ }, [updatePreferences]);
+
+ return {
+ preferences,
+ isLoaded,
+ updatePreferences,
+ setDisplayMode,
+ setRemoveContentLimit,
+ setMessagesPerRow,
+ };
+};
diff --git a/website/src/pages/account/index.tsx b/website/src/pages/account/index.tsx
index dff78ef0d3..936cc199b8 100644
--- a/website/src/pages/account/index.tsx
+++ b/website/src/pages/account/index.tsx
@@ -1,4 +1,4 @@
-import { Divider, Flex, Grid, Icon, Text, Button } from "@chakra-ui/react";
+import { Button,Divider, Flex, Grid, Icon, Text } from "@chakra-ui/react";
import Head from "next/head";
import Link from "next/link";
import { useSession } from "next-auth/react";
diff --git a/website/src/utils/chat.ts b/website/src/utils/chat.ts
index 712a597b99..25779a446c 100644
--- a/website/src/utils/chat.ts
+++ b/website/src/utils/chat.ts
@@ -1,5 +1,5 @@
import { PluginEntry } from "src/types/Chat";
-import { SamplingParameters, CustomInstructionsType } from "src/types/Chat";
+import { CustomInstructionsType,SamplingParameters } from "src/types/Chat";
const CHAT_CONFIG_KEY = "CHAT_CONFIG_V2";