├── public
├── favicon.ico
├── qrcode.png
└── logo.svg
├── .eslintrc.json
├── postcss.config.mjs
├── src
├── lib
│ ├── utils.ts
│ ├── supabase.ts
│ ├── auth.ts
│ ├── errors.ts
│ └── AIStream.ts
├── app
│ ├── layout.tsx
│ ├── page.tsx
│ ├── [locale]
│ │ ├── [name]
│ │ │ ├── IFrame.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── model-judge
│ │ │ ├── InputApiKey.tsx
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── globals.css
│ └── api
│ │ └── all-model
│ │ └── route.tsx
├── components
│ ├── BuyMeACoffee.tsx
│ ├── Layout.tsx
│ ├── RenderCard.tsx
│ ├── ui
│ │ ├── label.tsx
│ │ ├── textarea.tsx
│ │ ├── input.tsx
│ │ ├── toaster.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── form.tsx
│ │ ├── toast.tsx
│ │ └── select.tsx
│ ├── LocaleProvider.tsx
│ ├── GoogleOneTapLogin.tsx
│ ├── LoginDialog.tsx
│ ├── LanguageSwitcher.tsx
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── MermaidDiagram.tsx
│ └── ClientComponent.tsx
├── context
│ ├── next-auth-context.tsx
│ └── common-context.tsx
├── i18n.ts
├── middleware.ts
├── config
│ └── index.ts
└── hooks
│ └── use-toast.ts
├── next.config.mjs
├── .env.example
├── components.json
├── .gitignore
├── tsconfig.json
├── LICENSE
├── package.json
├── tailwind.config.ts
├── README.md
├── README_EN.md
└── messages
├── zh.json
└── en.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flashclub/ModelJudge/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flashclub/ModelJudge/HEAD/public/qrcode.png
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals", "next/typescript"]
3 | }
4 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 |
3 | export default function RootLayout({
4 | children,
5 | }: Readonly<{
6 | children: React.ReactNode;
7 | }>) {
8 | return <>{children}>;
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | // This page only renders when the app is built statically (output: 'export')
4 | export default function RootPage() {
5 | redirect("/zh");
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | import createNextIntlPlugin from 'next-intl/plugin';
2 |
3 | const withNextIntl = createNextIntlPlugin('./src/i18n.ts');
4 |
5 | const nextConfig = {
6 | reactStrictMode: false,
7 | swcMinify: true,
8 | };
9 |
10 | export default withNextIntl(nextConfig);
11 |
--------------------------------------------------------------------------------
/src/lib/supabase.ts:
--------------------------------------------------------------------------------
1 | // import { createServerClient } from "@supabase/ssr";
2 | import { createClient } from "@supabase/supabase-js";
3 |
4 | import config from "@/config";
5 |
6 | export const createSupabaseClient = () => {
7 | return createClient(
8 | config.database.supabaseUrl!,
9 | config.database.supabaseServiceKey!
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 |
2 | # SiliconCloud API Key
3 | SILICONFLOW_KEY=
4 |
5 | # 数据库
6 | SUPABASE_URL=
7 | SUPABASE_SERVICE_KEY=
8 |
9 | # 登陆相关:
10 | # 在控制台执行 openssl rand -base64 32 生成 NEXTAUTH_SECRET
11 | NEXTAUTH_SECRET=
12 |
13 | NEXT_PUBLIC_GOOGLE_CLIENT_ID=
14 | GOOGLE_CLIENT_ID=
15 | GOOGLE_CLIENT_SECRET=
16 |
17 | GITHUB_ID=
18 | GITHUB_SECRET=
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/[locale]/[name]/IFrame.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | export default function IFrame({ promptInfo }: { promptInfo: string }) {
3 | return (
4 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/BuyMeACoffee.tsx:
--------------------------------------------------------------------------------
1 | export default function BuyMeACoffee() {
2 | return (
3 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
--------------------------------------------------------------------------------
/src/context/next-auth-context.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SessionProvider } from "next-auth/react";
4 | import { GoogleOAuthProvider } from "@react-oauth/google";
5 |
6 | export function NextAuthProvider({ children }: { children: React.ReactNode }) {
7 | return (
8 |
9 |
12 | {children}
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import Header from "./Header";
2 | import Footer from "./Footer";
3 | import { Toaster } from "@/components/ui/toaster";
4 | interface LayoutProps {
5 | children: React.ReactNode;
6 | locale: string;
7 | }
8 |
9 | export default function Layout({ children, locale }: LayoutProps) {
10 | return (
11 |
12 |
13 | {children}
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/src/components/RenderCard.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect } from "react";
3 | export default function RenderCard({
4 | promptInfo,
5 | iframeId,
6 | className,
7 | }: {
8 | promptInfo: string;
9 | iframeId: string;
10 | className?: string;
11 | }) {
12 | return (
13 |
14 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | // src/lib/auth.ts
2 | import { AuthOptions } from "next-auth";
3 | import GoogleProvider from "next-auth/providers/google";
4 | import GithubProvider from "next-auth/providers/github";
5 |
6 | export const authOptions: AuthOptions = {
7 | providers: [
8 | GoogleProvider({
9 | clientId: process.env.GOOGLE_CLIENT_ID as string,
10 | clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
11 | }),
12 | GithubProvider({
13 | clientId: process.env.GITHUB_CLIENT_ID as string,
14 | clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
15 | }),
16 | ],
17 | debug: true,
18 | secret: process.env.NEXTAUTH_SECRET,
19 | };
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "baseUrl": ".",
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/src/i18n.ts:
--------------------------------------------------------------------------------
1 | import { getRequestConfig } from "next-intl/server";
2 | import { defineRouting } from "next-intl/routing";
3 | import { createSharedPathnamesNavigation } from "next-intl/navigation";
4 |
5 | export default getRequestConfig(async ({ locale }) => ({
6 | messages: (await import(`../messages/${locale}.json`)).default,
7 | timeZone: "Europe/Vienna",
8 | }));
9 |
10 | export const routing = defineRouting({
11 | // A list of all locales that are supported
12 | locales: ["en", "zh"],
13 |
14 | // Used when no locale matches
15 | defaultLocale: "zh",
16 | });
17 | // Lightweight wrappers around Next.js' navigation APIs
18 | // that will consider the routing configuration
19 | export const { Link, redirect, usePathname, useRouter } =
20 | createSharedPathnamesNavigation(routing);
21 |
--------------------------------------------------------------------------------
/src/app/[locale]/[name]/page.tsx:
--------------------------------------------------------------------------------
1 | import { useTranslations } from "next-intl";
2 | import Head from "next/head";
3 |
4 | export default function Thinker() {
5 | const t = useTranslations("Thinker");
6 | return (
7 |
8 |
9 |
{t("title")}
10 |
11 |
12 |
13 | {t("title")}
14 | {t("description")}
15 |
16 | 你来到了一个不毛之地,这里什么都没有,只有你一个人。
17 | 人类已经灭绝,你成为了最后一个活着的人类。
18 | 你感到孤独,感到绝望,感到无助。人类从哪里来,要到哪里去?
19 | 你希望了解人类的历史,人类的文明,人类的科技,人类的未来。
20 | 你希望了解宇宙的起源,宇宙的演化,宇宙的未来。
21 | 你希望了解生命的起源,生命的演化,生命的未来。
22 | 你希望弄清楚这一切的答案。
23 | 所以,在这里,你想获取所有答案,现在,开始吧。
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/hooks/use-toast"
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | } from "@/components/ui/toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/context/common-context.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { createContext, useState, useContext } from "react";
4 |
5 | export const ContextData = createContext({
6 | theme: "light",
7 | setTheme: () => {},
8 | text: "",
9 | setText: () => {},
10 | parsedData: {},
11 | setParsedData: () => {},
12 | isLoading: false,
13 | setIsLoading: () => {},
14 | });
15 |
16 | export const CommonProvider = ({ children }: any) => {
17 | const [theme, setTheme] = useState("light");
18 | const [text, setText] = useState("");
19 | const [parsedData, setParsedData] = useState({});
20 | const [isLoading, setIsLoading] = useState(false);
21 |
22 | return (
23 |
35 | {children}
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/LocaleProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState, useEffect } from "react";
4 | import { NextIntlClientProvider } from "next-intl";
5 |
6 | export function LocaleProvider({
7 | children,
8 | locale,
9 | messages,
10 | }: {
11 | children: React.ReactNode;
12 | locale: string;
13 | messages: any;
14 | }) {
15 | const [currentLocale, setCurrentLocale] = useState(locale);
16 |
17 | useEffect(() => {
18 | const savedLocale = localStorage.getItem("preferredLanguage");
19 | if (savedLocale && savedLocale !== locale) {
20 | const newPathname = window.location.pathname.replace(
21 | /^\/[^\/]+/,
22 | `/${savedLocale}`
23 | );
24 | window.location.href = newPathname + window.location.search;
25 | } else {
26 | setCurrentLocale(locale);
27 | }
28 | }, [locale]);
29 |
30 | return (
31 |
36 | {children}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 laughing
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/GoogleOneTapLogin.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useState } from "react";
4 | import { useSession, signIn } from "next-auth/react";
5 | import { useGoogleOneTapLogin } from "@react-oauth/google";
6 |
7 | const GoogleOneTapLogin = () => {
8 | const { data: session, status } = useSession();
9 | const [showOneTap, setShowOneTap] = useState(false);
10 |
11 | useGoogleOneTapLogin({
12 | onSuccess: (credentialResponse) => {
13 | signIn("google", { credential: credentialResponse.credential });
14 | },
15 | onError: () => {
16 | console.log("Login Failed");
17 | },
18 | });
19 |
20 | useEffect(() => {
21 | if (status === "unauthenticated") {
22 | setShowOneTap(true);
23 | } else {
24 | setShowOneTap(false);
25 | // @ts-ignore
26 | window.google?.accounts.id.cancel();
27 | }
28 | }, [status]);
29 |
30 | if (!showOneTap) return null;
31 |
32 | return (
33 |
38 | );
39 | };
40 |
41 | export default GoogleOneTapLogin;
42 |
--------------------------------------------------------------------------------
/src/app/[locale]/layout.tsx:
--------------------------------------------------------------------------------
1 | import { getMessages } from "next-intl/server";
2 | import { NextAuthProvider } from "@/context/next-auth-context";
3 | import Layout from "@/components/Layout";
4 | import { LocaleProvider } from "@/components/LocaleProvider";
5 |
6 | import type { Metadata, Viewport } from "next";
7 | export const metadata: Metadata = {
8 | metadataBase: new URL("https://awesomeprompt.net"),
9 | };
10 |
11 | export default async function LocaleLayout({
12 | children,
13 | params: { locale },
14 | }: {
15 | children: React.ReactNode;
16 | params: { locale: string };
17 | }) {
18 | const messages = await getMessages();
19 | // 如果你有谷歌登陆
20 | return (
21 |
22 |
23 |
24 |
25 | {children}
26 |
27 |
28 |
29 |
30 | );
31 | return (
32 |
33 |
34 |
35 | {children}
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import type { NextRequest } from "next/server";
3 | import createMiddleware from "next-intl/middleware";
4 | import { v4 as uuidv4 } from "uuid";
5 | import configData from "@/config";
6 |
7 | const intlMiddleware = createMiddleware({
8 | // 支持的语言列表
9 | locales: configData.supportedLocales,
10 | // 默认语言
11 | defaultLocale: configData.defaultLocale,
12 | });
13 |
14 | export default function middleware(request: NextRequest) {
15 | const response = intlMiddleware(request);
16 | // console.log("process.env.NODE_ENV", process.env.NODE_ENV);
17 |
18 | // 检查是否存在设备ID cookie
19 | if (!request.cookies.has("deviceId")) {
20 | // 如果不存在,生成一个新的设备ID
21 | const deviceId = uuidv4();
22 | (response as NextResponse).cookies.set("deviceId", deviceId, {
23 | httpOnly: true,
24 | secure: process.env.NODE_ENV === "production",
25 | sameSite: "strict",
26 | maxAge: 60 * 60 * 24 * 365, // 1年
27 | });
28 | }
29 |
30 | return response;
31 | }
32 |
33 | export const config = {
34 | // 匹配所有路径除了 /api, /_next, /_vercel, /images, /favicon.ico, /robots.txt
35 | matcher: [
36 | "/((?!api|_next|_vercel|images|logo.svg|.*\\.png|favicon.ico|robots.txt|sitemap.xml).*)",
37 | ],
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/LoginDialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import * as React from "react";
3 | import { useTranslations } from "next-intl";
4 | import { signIn } from "next-auth/react";
5 | import { Button } from "./ui/button";
6 | import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog";
7 |
8 | interface LoginDialogProps {
9 | isOpen: boolean;
10 | onOpenChange: (open: boolean) => void;
11 | message?: string;
12 | }
13 |
14 | export default function LoginDialog({
15 | isOpen,
16 | onOpenChange,
17 | message,
18 | }: LoginDialogProps) {
19 | const t = useTranslations("LoginDialog");
20 |
21 | return (
22 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "model-judge",
3 | "version": "0.1.0",
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start",
8 | "lint": "next lint"
9 | },
10 | "dependencies": {
11 | "@hookform/resolvers": "^3.9.0",
12 | "@radix-ui/react-dialog": "^1.1.1",
13 | "@radix-ui/react-icons": "^1.3.0",
14 | "@radix-ui/react-label": "^2.1.0",
15 | "@radix-ui/react-select": "^2.1.1",
16 | "@radix-ui/react-slot": "^1.1.0",
17 | "@radix-ui/react-toast": "^1.2.2",
18 | "@react-oauth/google": "^0.12.1",
19 | "@supabase/ssr": "^0.5.1",
20 | "@supabase/supabase-js": "^2.45.4",
21 | "@tailwindcss/typography": "^0.5.15",
22 | "class-variance-authority": "^0.7.0",
23 | "clsx": "^2.1.1",
24 | "eventsource-parser": "^2.0.1",
25 | "html-react-parser": "^5.1.16",
26 | "lucide-react": "^0.440.0",
27 | "mermaid": "^11.3.0",
28 | "next": "14.2.10",
29 | "next-auth": "^4.24.7",
30 | "next-intl": "^3.19.1",
31 | "react": "^18",
32 | "react-dom": "^18",
33 | "react-hook-form": "^7.53.0",
34 | "react-markdown": "^9.0.1",
35 | "tailwind-merge": "^2.5.2",
36 | "tailwindcss-animate": "^1.0.7",
37 | "uuid": "^10.0.0",
38 | "yahoo-finance2": "^2.11.3",
39 | "zod": "^3.23.8"
40 | },
41 | "devDependencies": {
42 | "@types/node": "^20.16.1",
43 | "@types/react": "^18.3.4",
44 | "@types/react-dom": "^18.3.0",
45 | "@types/uuid": "^10.0.0",
46 | "eslint": "^8.57.0",
47 | "eslint-config-next": "14.2.6",
48 | "postcss": "^8.4.41",
49 | "tailwindcss": "^3.4.10",
50 | "typescript": "^5.5.4"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/LanguageSwitcher.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import * as React from "react";
3 | import { useRouter, usePathname } from "next/navigation";
4 |
5 | import { Globe } from "lucide-react";
6 | import {
7 | Select,
8 | SelectContent,
9 | SelectItem,
10 | SelectTrigger,
11 | SelectValue,
12 | } from "@/components/ui/select";
13 |
14 | const languages = [
15 | { code: "en", name: "English" },
16 | { code: "zh", name: "中文" },
17 | ];
18 |
19 | const LanguageSwitcher: React.FC = () => {
20 | const router = useRouter();
21 | const pathname = usePathname();
22 | const currentLang = pathname.split("/")[1];
23 |
24 | const changeLanguage = (locale: string) => {
25 | localStorage.setItem("preferredLanguage", locale);
26 | const currentPathname = pathname.split("/").slice(2).join("/");
27 | const newPath = `/${locale}/${currentPathname}`;
28 | router.push(newPath);
29 | };
30 |
31 | return (
32 |
52 | );
53 | };
54 |
55 | export default LanguageSwitcher;
56 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | type Config = {
2 | nextAuth: {
3 | secret: string | undefined;
4 | };
5 | auth: {
6 | google: {
7 | clientId: string | undefined;
8 | clientSecret: string | undefined;
9 | };
10 | github: {
11 | clientId: string | undefined;
12 | clientSecret: string | undefined;
13 | };
14 | };
15 | fal: {
16 | apiKey: string | undefined;
17 | };
18 | database: {
19 | supabaseUrl: string | undefined;
20 | supabaseServiceKey: string | undefined;
21 | };
22 | freeUsageLimit: number;
23 | defaultLocale: string;
24 | supportedLocales: string[];
25 | imageGenerationSettings: {
26 | defaultInferenceSteps: number;
27 | defaultGuidanceScale: number;
28 | defaultNumImages: number;
29 | maxNumImages: number;
30 | defaultEnableSafetyChecker: boolean;
31 | };
32 | };
33 |
34 | const config: Config = {
35 | nextAuth: {
36 | secret: process.env.NEXTAUTH_SECRET,
37 | },
38 | auth: {
39 | google: {
40 | clientId: process.env.GOOGLE_CLIENT_ID,
41 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
42 | },
43 | github: {
44 | clientId: process.env.GITHUB_ID,
45 | clientSecret: process.env.GITHUB_SECRET,
46 | },
47 | },
48 | fal: {
49 | apiKey: process.env.FAL_KEY,
50 | },
51 | database: {
52 | supabaseUrl: process.env.SUPABASE_URL,
53 | supabaseServiceKey: process.env.SUPABASE_SERVICE_KEY,
54 | },
55 | freeUsageLimit: process.env.FREE_USAGE_LIMIT
56 | ? parseInt(process.env.FREE_USAGE_LIMIT, 10)
57 | : 5,
58 | defaultLocale: "zh",
59 | supportedLocales: ["en", "zh"],
60 | imageGenerationSettings: {
61 | defaultInferenceSteps: 28,
62 | defaultGuidanceScale: 3.5,
63 | defaultNumImages: 1,
64 | maxNumImages: 4,
65 | defaultEnableSafetyChecker: true,
66 | },
67 | };
68 |
69 | export default config;
70 |
--------------------------------------------------------------------------------
/src/lib/errors.ts:
--------------------------------------------------------------------------------
1 | export class OpenAIError extends Error {
2 | type: string;
3 | param: string;
4 | code: string;
5 |
6 | constructor(message: string, type: string, param: string, code: string) {
7 | super(message);
8 | this.name = "OpenAIError";
9 | this.type = type;
10 | this.param = param;
11 | this.code = code;
12 |
13 | // This is necessary for proper stack trace in TypeScript
14 | Object.setPrototypeOf(this, OpenAIError.prototype);
15 | }
16 |
17 | getFullMessage(): string {
18 | return `OpenAI API Error:
19 | Message: ${this.message}
20 | Type: ${this.type}
21 | Param: ${this.param}
22 | Code: ${this.code}`;
23 | }
24 | }
25 |
26 | // You can also define specific error types if needed
27 | export class OpenAIAuthenticationError extends OpenAIError {
28 | constructor(message: string) {
29 | super(message, "authentication_error", "", "401");
30 | this.name = "OpenAIAuthenticationError";
31 | }
32 | }
33 |
34 | export class OpenAIRateLimitError extends OpenAIError {
35 | constructor(message: string) {
36 | super(message, "rate_limit_error", "", "429");
37 | this.name = "OpenAIRateLimitError";
38 | }
39 | }
40 |
41 | export class OpenAIInvalidRequestError extends OpenAIError {
42 | constructor(message: string, param: string) {
43 | super(message, "invalid_request_error", param, "400");
44 | this.name = "OpenAIInvalidRequestError";
45 | }
46 | }
47 |
48 | // Helper function to create the appropriate error based on the API response
49 | export function createOpenAIError(error: any): OpenAIError {
50 | if (error.type === "authentication_error") {
51 | return new OpenAIAuthenticationError(error.message);
52 | } else if (error.type === "rate_limit_error") {
53 | return new OpenAIRateLimitError(error.message);
54 | } else if (error.type === "invalid_request_error") {
55 | return new OpenAIInvalidRequestError(error.message, error.param);
56 | } else {
57 | return new OpenAIError(error.message, error.type, error.param, error.code);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/src/app/[locale]/model-judge/InputApiKey.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState } from "react";
3 | import { Button } from "@/components/ui/button";
4 | import { Input } from "@/components/ui/input";
5 | import {
6 | Dialog,
7 | DialogContent,
8 | DialogHeader,
9 | DialogTitle,
10 | DialogFooter,
11 | } from "@/components/ui/dialog";
12 | import { useToast } from "@/hooks/use-toast";
13 | import { ToastAction } from "@/components/ui/toast";
14 | export default function InputApiKey({ cookies }: { cookies: any }) {
15 | const [apiKey, setApiKey] = useState("");
16 | const [isApiKeyDialogOpen, setIsApiKeyDialogOpen] = useState(false);
17 | const [isApiKeySaved, setIsApiKeySaved] = useState(false);
18 | const { toast } = useToast();
19 | const saveApiKey = () => {
20 | setIsApiKeySaved(true);
21 | setIsApiKeyDialogOpen(false);
22 | localStorage.setItem("apiKey", apiKey);
23 | // 设置cookie
24 | document.cookie = `api_key=${apiKey}; path=/; max-age=31536000; SameSite=Strict; Secure`;
25 | // 刷新页面以使cookie生效
26 | // window.location.reload();
27 | toast({
28 | title: "API key已保存",
29 | description: "下次访问无需再次输入",
30 | });
31 | };
32 | return (
33 | <>
34 |
37 |
38 |
59 | >
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | mode: "jit",
11 | theme: {
12 | extend: {
13 | colors: {
14 | background: "hsl(var(--background))",
15 | foreground: "hsl(var(--foreground))",
16 | card: {
17 | DEFAULT: "hsl(var(--card))",
18 | foreground: "hsl(var(--card-foreground))",
19 | },
20 | popover: {
21 | DEFAULT: "hsl(var(--popover))",
22 | foreground: "hsl(var(--popover-foreground))",
23 | },
24 | primary: {
25 | DEFAULT: "hsl(var(--primary))",
26 | foreground: "hsl(var(--primary-foreground))",
27 | },
28 | secondary: {
29 | DEFAULT: "hsl(var(--secondary))",
30 | foreground: "hsl(var(--secondary-foreground))",
31 | },
32 | muted: {
33 | DEFAULT: "hsl(var(--muted))",
34 | foreground: "hsl(var(--muted-foreground))",
35 | },
36 | accent: {
37 | DEFAULT: "hsl(var(--accent))",
38 | foreground: "hsl(var(--accent-foreground))",
39 | },
40 | destructive: {
41 | DEFAULT: "hsl(var(--destructive))",
42 | foreground: "hsl(var(--destructive-foreground))",
43 | },
44 | border: "hsl(var(--border))",
45 | input: "hsl(var(--input))",
46 | ring: "hsl(var(--ring))",
47 | chart: {
48 | "1": "hsl(var(--chart-1))",
49 | "2": "hsl(var(--chart-2))",
50 | "3": "hsl(var(--chart-3))",
51 | "4": "hsl(var(--chart-4))",
52 | "5": "hsl(var(--chart-5))",
53 | },
54 | },
55 | borderRadius: {
56 | lg: "var(--radius)",
57 | md: "calc(var(--radius) - 2px)",
58 | sm: "calc(var(--radius) - 4px)",
59 | },
60 | },
61 | },
62 | plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
63 | };
64 | export default config;
65 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | /* color: var(--foreground); */
7 | /* background: var(--background); */
8 | font-family: Arial, Helvetica, sans-serif;
9 | }
10 |
11 | @layer utilities {
12 | .text-balance {
13 | text-wrap: balance;
14 | }
15 | }
16 |
17 | @layer base {
18 | :root {
19 | --background: 0 0% 100%;
20 | --foreground: 0 0% 3.9%;
21 | --card: 0 0% 100%;
22 | --card-foreground: 0 0% 3.9%;
23 | --popover: 0 0% 100%;
24 | --popover-foreground: 0 0% 3.9%;
25 | --primary: 0 0% 9%;
26 | --primary-foreground: 0 0% 98%;
27 | --secondary: 0 0% 96.1%;
28 | --secondary-foreground: 0 0% 9%;
29 | --muted: 0 0% 96.1%;
30 | --muted-foreground: 0 0% 45.1%;
31 | --accent: 0 0% 96.1%;
32 | --accent-foreground: 0 0% 9%;
33 | --destructive: 0 84.2% 60.2%;
34 | --destructive-foreground: 0 0% 98%;
35 | --border: 0 0% 89.8%;
36 | --input: 0 0% 89.8%;
37 | --ring: 0 0% 3.9%;
38 | --chart-1: 12 76% 61%;
39 | --chart-2: 173 58% 39%;
40 | --chart-3: 197 37% 24%;
41 | --chart-4: 43 74% 66%;
42 | --chart-5: 27 87% 67%;
43 | --radius: 0.5rem;
44 | }
45 | .dark {
46 | --background: 0 0% 3.9%;
47 | --foreground: 0 0% 98%;
48 | --card: 0 0% 3.9%;
49 | --card-foreground: 0 0% 98%;
50 | --popover: 0 0% 3.9%;
51 | --popover-foreground: 0 0% 98%;
52 | --primary: 0 0% 98%;
53 | --primary-foreground: 0 0% 9%;
54 | --secondary: 0 0% 14.9%;
55 | --secondary-foreground: 0 0% 98%;
56 | --muted: 0 0% 14.9%;
57 | --muted-foreground: 0 0% 63.9%;
58 | --accent: 0 0% 14.9%;
59 | --accent-foreground: 0 0% 98%;
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 0 0% 98%;
62 | --border: 0 0% 14.9%;
63 | --input: 0 0% 14.9%;
64 | --ring: 0 0% 83.1%;
65 | --chart-1: 220 70% 50%;
66 | --chart-2: 160 60% 45%;
67 | --chart-3: 30 80% 55%;
68 | --chart-4: 280 65% 60%;
69 | --chart-5: 340 75% 55%;
70 | }
71 | }
72 |
73 | @layer base {
74 | * {
75 | @apply border-border;
76 | }
77 | body {
78 | @apply bg-background text-foreground;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/flashclub/ModelJudge/blob/main/README_EN.md)
2 |
3 | 本项目抽离于 [AwesomePrompt](https://awesomeprompt.net/zh/all-model),感谢 [SiliconCloud](https://cloud.siliconflow.cn/i/h5JiyFm0) 提供的免费 API 服务。目前注册即送 2000 万 token。
4 |
5 | # 模型判官 🧑⚖️
6 |
7 | 欢迎来到模型判官项目! 这是一个基于 Next.js 构建的 AI 模型评估平台。
8 | 输入问题选择三个模型,生成回答,由第四个模型给出评分和最终回答! 🚀
9 | 在线体验:[模型判官](https://modeljudge.awesomeprompt.net/zh)
10 |
11 | ## 项目特色 ✨
12 |
13 | - 多模型对比: 同时对比多个 AI 模型的回答 🤖🆚🤖
14 | - 评分机制: 由第四个模型给出评分和最终回答 📊
15 | - 实时流式响应: 快速获取 AI 的回答,无需等待 ⚡
16 | - 国际化支持: 支持中文和英文界面 🌍
17 | - 响应式设计: 在各种设备上都能完美展示 📱💻
18 | - 用户认证: 支持 Google 和 GitHub 登录 🔐(可选)
19 |
20 | ## 快速开始 🏁
21 |
22 | 1. 克隆项目:
23 |
24 | ```bash
25 | git clone git@github.com:flashclub/ModelJudge.git
26 | ```
27 |
28 | 2. 安装依赖:
29 |
30 | ```bash
31 | npm install
32 | ```
33 |
34 | 3. 创建 `.env` 文件,并填入 SiliconCloud API Key:
35 |
36 | ```bash
37 | SILICONFLOW_KEY=your_api_key
38 | ```
39 |
40 | 4. 运行开发服务器:
41 |
42 | ```bash
43 | npm run dev
44 | ```
45 |
46 | 5. 打开浏览器访问 [http://localhost:3000](http://localhost:3000) 即可看到项目运行效果!
47 |
48 | ## 技术栈 🛠️
49 |
50 | - [Next.js](https://nextjs.org/) - React 框架
51 | - [Tailwind CSS](https://tailwindcss.com/) - 样式框架
52 | - [next-intl](https://next-intl-docs.vercel.app/) - 国际化解决方案
53 | - [NextAuth.js](https://next-auth.js.org/) - 认证库(可选)
54 | - [Supabase](https://supabase.com/) - 后端数据库(可选)
55 |
56 | ## 项目结构 📁
57 |
58 | ```bash
59 | src/
60 | ├── app/ # 应用主目录
61 | ├── components/ # React 组件
62 | ├── config/ # 配置文件
63 | ├── context/ # React Context
64 | ├── lib/ # 工具函数
65 | └── messages/ # 国际化文本
66 | ```
67 |
68 | ## 贡献指南 🤝
69 |
70 | 我们欢迎任何形式的贡献! 如果你有好的想法或发现了 bug,请随时提出 issue 或发起 pull request。
71 |
72 | ## 致谢
73 |
74 | 1. 感谢 [SiliconCloud](https://cloud.siliconflow.cn/i/h5JiyFm0)。
75 |
76 | ## 许可证 📄
77 |
78 | 本项目采用 MIT 许可证。详情请查看 [LICENSE](LICENSE) 文件。
79 |
80 | 让我们一起打造更棒的 AI 模型评估平台吧! 🎉
81 |
82 | ## 赞助 💰
83 |
84 | 如果你喜欢这个项目,想要支持,可以通过以下方式赞助我:
85 |
86 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/app/[locale]/model-judge/page.tsx:
--------------------------------------------------------------------------------
1 | import { Suspense } from "react";
2 | import ClientComponent from "@/components/ClientComponent";
3 | import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
4 | import { Metadata } from "next";
5 | import InputApiKey from "./InputApiKey";
6 | import { cookies } from "next/headers";
7 | const INTL_NAMESPACE = "AllModel";
8 |
9 | const fetchAllModel = async () => {
10 | const options = {
11 | method: "GET",
12 | headers: {
13 | accept: "application/json",
14 | Authorization: `Bearer ${process.env.SILICONFLOW_KEY}`,
15 | },
16 | };
17 |
18 | const data = await fetch(
19 | "https://api.siliconflow.cn/v1/models?type=text&sub_type=chat",
20 | options
21 | )
22 | .then((response) => response.json())
23 | .catch((error) => {
24 | console.log(error);
25 | });
26 | // console.log("所有模型", data);
27 |
28 | return data;
29 | };
30 |
31 | export const generateMetadata = async ({
32 | params: { locale },
33 | }: {
34 | params: { locale: string };
35 | }): Promise => {
36 | unstable_setRequestLocale(locale);
37 | const t = await getTranslations({ locale, namespace: INTL_NAMESPACE });
38 | return {
39 | title: t("title"),
40 | description: t("description"),
41 | };
42 | };
43 |
44 | export default async function AllModel({
45 | params: { locale },
46 | }: {
47 | params: { locale: string };
48 | }) {
49 | const t = await getTranslations({ locale, namespace: INTL_NAMESPACE });
50 | const data = await fetchAllModel();
51 |
52 | console.log("api data--", data);
53 | return (
54 |
55 |
56 |
{t("infoCard")}
57 |
{t("description")}
58 |
59 | 由于访问量激增,如遇到评分服务无法访问,请稍后再试。或输入自己密钥使用。
60 | (您的密钥不会被记录)。免费申请密钥请访问:
61 |
65 | SiliconCloud
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/src/app/api/all-model/route.tsx:
--------------------------------------------------------------------------------
1 | // 如果有数据库需求,请打开注释
2 | import { OpenAIStream } from "@/lib/AIStream";
3 | // import { createClient } from "@/lib/supabase";
4 | import config from "@/config";
5 |
6 | export const runtime = "edge";
7 | export async function POST(req: Request) {
8 | const {
9 | question,
10 | model,
11 | modelIndex,
12 | system,
13 | id: deviceId,
14 | } = await req.json();
15 | // let supabase = null;
16 | // if (!config.database.supabaseUrl || !config.database.supabaseServiceKey) {
17 | // // throw new Error("Missing Supabase URL or service key in configuration");
18 | // } else {
19 | // supabase = createClient();
20 | // }
21 |
22 | const messages = [
23 | {
24 | role: "user",
25 | content: question,
26 | },
27 | ];
28 | if (system) {
29 | messages.unshift({
30 | role: "system",
31 | content: system,
32 | });
33 | }
34 | // 从请求的cookie中读取API密钥
35 | const cookies = req.headers.get("cookie");
36 | let apiKey = "";
37 | if (cookies) {
38 | const apiKeyCookie = cookies
39 | .split(";")
40 | .find((cookie) => cookie.trim().startsWith("api_key="));
41 | if (apiKeyCookie) {
42 | apiKey = apiKeyCookie.split("=")[1].trim();
43 | }
44 | }
45 |
46 | // 如果cookie中没有API密钥,则使用环境变量中的密钥
47 | if (!apiKey) {
48 | apiKey = process.env.SILICONFLOW_KEY || "";
49 | }
50 |
51 | // 确保我们有一个有效的API密钥
52 | if (!apiKey) {
53 | throw new Error("未找到有效的API密钥");
54 | }
55 | console.log("apiKey: ", apiKey);
56 | // console.log("messages: ", messages);
57 | let aiUrl = "https://api.siliconflow.cn/v1/chat/completions";
58 | const stream = await OpenAIStream({
59 | model,
60 | url: aiUrl,
61 | messages,
62 | apiKey,
63 | callback: async (text) => {
64 | if (text) {
65 | // console.log("text: ", text);
66 | // console.log("model: ", model);
67 | // if (supabase) {
68 | // const { data, error } = await supabase
69 | // .from("prompt_all_model")
70 | // .upsert(
71 | // {
72 | // question_id: deviceId,
73 | // [modelIndex]: {
74 | // name: model,
75 | // response: text,
76 | // },
77 | // question,
78 | // },
79 | // { onConflict: "question_id" }
80 | // );
81 | // console.log("data: ", data);
82 | // console.log("error: ", error);
83 | // }
84 | }
85 | },
86 | });
87 | return new Response(stream);
88 | }
89 |
--------------------------------------------------------------------------------
/src/lib/AIStream.ts:
--------------------------------------------------------------------------------
1 | import { createParser } from "eventsource-parser";
2 | import { OpenAIError } from "./errors";
3 |
4 | export const OpenAIStream = async ({
5 | addModelNameInResult,
6 | model,
7 | url,
8 | messages,
9 | apiKey,
10 | callback,
11 | }: {
12 | addModelNameInResult?: boolean;
13 | model: string;
14 | url: string;
15 | messages: any[];
16 | apiKey?: string;
17 | callback?: (allText: string) => void | Promise;
18 | }) => {
19 | const res = await fetch(url, {
20 | headers: {
21 | "Content-Type": "application/json",
22 | Authorization: `Bearer ${apiKey || process.env.SILICONFLOW_KEY}`,
23 | },
24 | method: "POST",
25 | body: JSON.stringify({ model, messages, stream: true }),
26 | });
27 |
28 | if (!res.ok) {
29 | await handleErrorResponse(res);
30 | }
31 |
32 | const encoder = new TextEncoder();
33 | const decoder = new TextDecoder();
34 |
35 | const textChunks: string[] = [];
36 |
37 | const stream = new ReadableStream({
38 | async start(controller) {
39 | const onParse = createParseHandler(controller, encoder, textChunks);
40 | const parser = createParser(onParse);
41 |
42 | for await (const chunk of res.body as any) {
43 | parser.feed(decoder.decode(chunk));
44 | }
45 |
46 | // 流结束时,执行回调函数
47 | const allText = textChunks.join("");
48 | try {
49 | if (callback) {
50 | await Promise.resolve(callback(allText));
51 | }
52 | } catch (error) {
53 | console.error("Error in stream end callback:", error);
54 | }
55 |
56 | controller.close();
57 | },
58 | });
59 |
60 | return stream;
61 | };
62 |
63 | const createParseHandler =
64 | (
65 | controller: ReadableStreamDefaultController,
66 | encoder: TextEncoder,
67 | textChunks: string[]
68 | ) =>
69 | (event: any) => {
70 | if (event.type === "event") {
71 | const data = event.data;
72 | if (data === "[DONE]") return;
73 |
74 | try {
75 | const json = JSON.parse(data);
76 | // console.log("aiStream json", json);
77 | if (json.choices[0].finish_reason != null) return;
78 |
79 | const text = json.choices[0].delta.content;
80 | textChunks.push(text);
81 | controller.enqueue(encoder.encode(text));
82 | } catch (e) {
83 | controller.error(e);
84 | }
85 | }
86 | };
87 |
88 | const handleErrorResponse = async (res: Response) => {
89 | const result = await res.json();
90 | if (result.error) {
91 | throw new OpenAIError(
92 | result.error.message,
93 | result.error.type,
94 | result.error.param,
95 | result.error.code
96 | );
97 | } else {
98 | throw new Error(`API returned an error: ${result.statusText}`);
99 | }
100 | };
101 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | [简体中文](https://github.com/flashclub/ModelJudge)
2 | This project is derived from [AwesomePrompt](https://awesomeprompt.net/zh/all-model), thanks to [SiliconCloud](https://cloud.siliconflow.cn/i/h5JiyFm0) for providing free API services. Currently, registration comes with 20 million free tokens.
3 |
4 | # Model Judge 🧑⚖️
5 |
6 | Welcome to the Model Judge project! This is an AI model evaluation platform built with Next.js.
7 | Input a question, select three models to generate answers, and let a fourth model provide scores and a final answer! 🚀
8 | Online experience: [Model Judge](https://modeljudge.awesomeprompt.net/en)
9 |
10 | ## Features ✨
11 |
12 | - Multi-model comparison: Compare answers from multiple AI models simultaneously 🤖🆚🤖
13 | - Scoring mechanism: Let a fourth model provide scores and a final answer 📊
14 | - Real-time streaming responses: Get AI answers quickly without waiting ⚡
15 | - Internationalization: Supports Chinese and English interfaces 🌍
16 | - Responsive design: Perfect display on various devices 📱💻
17 | - User authentication: Supports Google and GitHub login 🔐 (optional)
18 |
19 | ## Quick Start 🏁
20 |
21 | 1. Clone the project:
22 |
23 | ```bash
24 | git clone git@github.com:flashclub/ModelJudge.git
25 | ```
26 |
27 | 2. Install dependencies:
28 |
29 | ```bash
30 | npm install
31 | ```
32 |
33 | 3. Create a `.env` file and add your SiliconCloud API Key:
34 |
35 | ```bash
36 | SILICONFLOW_KEY=your_api_key
37 | ```
38 |
39 | 4. Run the development server:
40 |
41 | ```bash
42 | npm run dev
43 | ```
44 |
45 | 5. Open your browser and visit [http://localhost:3000](http://localhost:3000) to see the project in action!
46 |
47 | ## Tech Stack 🛠️
48 |
49 | - [Next.js](https://nextjs.org/) - React framework
50 | - [Tailwind CSS](https://tailwindcss.com/) - Styling framework
51 | - [next-intl](https://next-intl-docs.vercel.app/) - Internationalization solution
52 | - [NextAuth.js](https://next-auth.js.org/) - Authentication library (optional)
53 | - [Supabase](https://supabase.com/) - Backend database (optional)
54 |
55 | ## Project Structure 📁
56 |
57 | ```bash
58 | src/
59 | ├── app/ # Main application directory
60 | ├── components/ # React components
61 | ├── config/ # Configuration files
62 | ├── context/ # React Context
63 | ├── lib/ # Utility functions
64 | └── messages/ # Internationalization texts
65 | ```
66 |
67 | ## Contribution Guidelines 🤝
68 |
69 | We welcome any form of contribution! If you have good ideas or find bugs, please feel free to open an issue or submit a pull request.
70 |
71 | ## Acknowledgements
72 |
73 | 1. Thanks to [SiliconCloud](https://cloud.siliconflow.cn/i/h5JiyFm0).
74 |
75 | ## License 📄
76 |
77 | This project is licensed under the MIT License. For more details, please see the [LICENSE](LICENSE) file.
78 |
79 | Let's build a better AI model evaluation platform together! 🎉
80 |
81 | ## Sponsorship 💰
82 |
83 | If you like the library and want to support, you can do it by buying me a coffee at
84 |
85 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Link from "next/link";
3 | import { useLocale, useTranslations } from "next-intl";
4 |
5 | import LanguageSwitcher from "./LanguageSwitcher";
6 | import { Button } from "./ui/button";
7 | import Image from "next/image";
8 | import { useSession, signOut } from "next-auth/react";
9 | import LoginDialog from "./LoginDialog";
10 | import { useState, useEffect } from "react";
11 | import { Brain, BarChart2 } from "lucide-react";
12 |
13 | const Header = ({ locale }: { locale: string }) => {
14 | const t = useTranslations("Header");
15 | const { data: session, status } = useSession();
16 | // console.log("Session status:", status, "Session data:", session);
17 | const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false);
18 |
19 | useEffect(() => {
20 | const handleScroll = (event: Event) => {
21 | event.preventDefault();
22 | const target = event.target as HTMLAnchorElement;
23 | const targetId = target.getAttribute("href");
24 | if (targetId && targetId.startsWith("#")) {
25 | const element = document.getElementById(targetId.slice(1));
26 | if (element) {
27 | element.scrollIntoView({ behavior: "smooth" });
28 | // 更新 URL
29 | history.pushState(null, "", targetId);
30 | }
31 | }
32 | };
33 |
34 | const links = document.querySelectorAll('a[href^="#"]');
35 | links.forEach((link) => {
36 | link.addEventListener("click", handleScroll);
37 | });
38 |
39 | return () => {
40 | links.forEach((link) => {
41 | link.removeEventListener("click", handleScroll);
42 | });
43 | };
44 | }, []);
45 |
46 | return (
47 |
91 | );
92 | };
93 |
94 | export default Header;
95 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import { useTranslations } from "next-intl";
4 | import BuyMeACoffee from "./BuyMeACoffee";
5 | import { Github } from "lucide-react";
6 | const INTL_NAMESPACE = "Footer";
7 | const Footer = ({ locale }: { locale: string }) => {
8 | const t = useTranslations(INTL_NAMESPACE);
9 |
10 | const projectLinks = [
11 | { href: `/${locale}/model-judge`, label: t("infoCard") },
12 | { href: "https://github.com/flashclub/ModelJudge", label: "Github" },
13 | ];
14 | const friendLinks = [
15 | { href: "https://awesomeprompt.net/zh/all-model", label: "AwesomePrompt" },
16 | { href: "https://cloud.siliconflow.cn/i/h5JiyFm0", label: "SiliconCloud" },
17 | ];
18 | return (
19 |
90 | );
91 | };
92 |
93 | export default Footer;
94 |
--------------------------------------------------------------------------------
/messages/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "AllModel": {
3 | "loading": "加载中...",
4 | "waitingAnswer": "等待回答...",
5 | "start": "开始",
6 | "title": "模型判官 | modeljudge.awesomeprompt.net",
7 | "description": "Awesome Prompt 为您带来模型判官,这里有几十种模型,模型之间孰强孰弱孰优孰劣,来这里一决高下",
8 | "infoCard": "模型判官",
9 | "slogan": "模型判官,模型之间孰强孰弱孰优孰劣,来这里一决高下",
10 | "view": "查看",
11 | "backHome": "返回",
12 | "noQuestion": "请输入问题",
13 | "noModel": "请选择模型",
14 | "inputQuestion": "请输入问题",
15 | "selectModel": "请选择模型",
16 | "contestModel": "当前模型:",
17 | "aiJudge": "模型判官",
18 | "startNow": "立即体验",
19 | "whatIsModelJudge": "什么是模型判官",
20 | "whatIsModelJudgeDesc": "模型判官是一款革命性的AI模型评测工具,帮助您快速找到最适合的AI模型。",
21 | "howToUseModelJudge": "如何使用模型判官",
22 | "howToUseModelJudgeDesc1": "输入任意问题或任务描述",
23 | "howToUseModelJudgeDesc2": "从可用模型列表中选择三种不同的AI模型进行测试",
24 | "howToUseModelJudgeDesc3": "等待所选的三种模型完成回答",
25 | "howToUseModelJudgeDesc4": "系统会自动调用第四种模型(评判模型)对三种模型的回答进行评估和打分",
26 | "howToUseModelJudgeDesc5": "查看评估结果,包括每个模型的得分和详细评价",
27 | "howToUseModelJudgeDesc6": "基于评估结果,选择最适合您需求的模型",
28 | "tip": "提示:",
29 | "tipDesc": "为获得最佳结果,请尽可能清晰、具体地描述您的问题或任务。这将帮助AI模型提供更准确、相关的回答。",
30 | "whyUseModelJudge": "为什么使用模型判官",
31 | "reduceTrialCost": "减少试错成本",
32 | "reduceTrialCostDesc": "随着AI的兴起,越来越多的模型涌现,用户在选择模型时往往无从下手。模型判官帮助您快速找到最适合自己需求的模型,节省宝贵的时间和资源。",
33 | "saveTime": "节省时间",
34 | "saveTimeDesc": "在选择模型时,用户往往需要花费大量的时间去尝试不同的模型。模型判官让您一次性测试多个模型,快速获得结果,大大提高了选择效率。",
35 | "improveEfficiency": "提高效率",
36 | "improveEfficiencyDesc": "模型判官支持一次使用多达4个模型,包括千问,DeepSeek,零义万物等几十个模型。只需轻点几下,便可马上获得全面的评估结果。",
37 | "getAccurateResult": "获得准确结果",
38 | "getAccurateResultDesc": "模型判官通过调用第四种模型(评判模型)对三种模型的回答进行评估和打分,确保了评估结果的准确性和可靠性。",
39 | "modelJudgeStandard": "模型判官的评分标准",
40 | "modelJudgeStandardDesc": "模型判官的评分是基于多个维度进行综合评估,包括:",
41 | "modelJudgeStandardDesc1": "准确性:模型提供的信息是否准确无误",
42 | "modelJudgeStandardDesc2": "相关性:模型的回答是否与问题紧密相关",
43 | "modelJudgeStandardDesc3": "逻辑性:模型的回答是否具有清晰的逻辑结构",
44 | "modelJudgeStandardDesc4": "创造性:模型是否能提供独特、创新的见解",
45 | "modelJudgeStandardDesc5": "完整性:模型的回答是否全面、详尽",
46 | "modelJudgeStandardDesc6": "实用性:模型的回答是否具有实际应用价值",
47 | "modelJudgeStandardDesc7": "这种全面的评估方法可以帮助您更好地了解每个模型的优势和局限性,从而做出明智的选择。当您在选择模型时,可以参考模型判官的评分和评价,选择最适合自己需求的模型。"
48 | },
49 | "Header": {
50 | "home": "首页",
51 | "login": "登录",
52 | "logout": "登出"
53 | },
54 | "MermaidDiagram": {
55 | "start": "开始",
56 | "question": "用户输入问题或任务描述",
57 | "selectModel": "选择三种AI模型进行测试",
58 | "model1": "模型1处理",
59 | "model2": "模型2处理",
60 | "model3": "模型3处理",
61 | "model1Answer": "模型1回答",
62 | "model2Answer": "模型2回答",
63 | "model3Answer": "模型3回答",
64 | "judgeModel": "评判模型(第四个模型)评估",
65 | "generateScore": "生成评分和详细评价",
66 | "viewScore": "用户查看每个模型的得分和评价",
67 | "selectBestModel": "用户选择最适合的模型",
68 | "end": "结束"
69 | },
70 | "LoginDialog": {
71 | "title": "登录",
72 | "description": "请先登录,再进行操作"
73 | },
74 | "Footer": {
75 | "projectSource": "本项目抽离于",
76 | "projectSupport": "感谢 ",
77 | "projectFreeApi": " 提供的免费API服务。",
78 | "projectOpenSource": "项目目前已开源,欢迎访问",
79 | "cooperation": "商务合作",
80 | "wechat": "微信",
81 | "wechatQRCode": "微信二维码",
82 | "wechatQRCodeDesc": "欢迎联系我们进行商务合作",
83 | "projectLinks": "项目链接",
84 | "friendLinks": "友情链接",
85 | "connectLinks": "商务合作",
86 | "infoCard": "模型判官"
87 | },
88 | "ModelVs": {
89 | "title": "模型辩论 | modeldebate.awesomeprompt.net",
90 | "description": "模型辩论,模型之间争得面红耳赤拍桌子瞪眼睛,你支持谁?"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/MermaidDiagram.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useRef } from "react";
4 | import mermaid from "mermaid";
5 | import { useTranslations } from "next-intl";
6 | export default function MermaidDiagram() {
7 | const t = useTranslations("MermaidDiagram");
8 | const ref = useRef(null);
9 | const chart = `
10 | %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#007bff', 'primaryTextColor': '#000000', 'primaryBorderColor': '#007bff', 'lineColor': '#333333', 'secondaryColor': '#f8f9fa', 'tertiaryColor': '#f1f3f5', 'fontSize': '18px'}}}%%
11 | flowchart LR
12 | subgraph
13 | A("${t("start")}") --> B("${t("question")}")
14 | B --> C("${t("selectModel")}")
15 | C --> D("${t("model1")}")
16 | C --> E("${t("model2")}")
17 | C --> F("${t("model3")}")
18 | end
19 | subgraph
20 | D --> G("${t("model1Answer")}")
21 | E --> H("${t("model2Answer")}")
22 | F --> I("${t("model3Answer")}")
23 | G & H & I --> J("${t("judgeModel")}")
24 | end
25 | subgraph
26 | J --> L("${t("generateScore")}")
27 | L --> M("${t("viewScore")}")
28 | M --> N("${t("selectBestModel")}")
29 | N --> O("${t("end")}")
30 | end
31 |
32 | classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,rx:10,ry:10
33 | classDef input fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,rx:10,ry:10
34 | classDef process fill:#fff3e0,stroke:#e65100,stroke-width:2px,rx:10,ry:10
35 | classDef output fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,rx:10,ry:10
36 | classDef model fill:#fce4ec,stroke:#c2185b,stroke-width:2px,rx:10,ry:10
37 | classDef decision fill:#fff8e1,stroke:#ffa000,stroke-width:2px,rx:10,ry:10
38 |
39 | class A,O default
40 | class B,M input
41 | class C,J,K process
42 | class L,N output
43 | class D,E,F,G,H,I model
44 | `;
45 |
46 | useEffect(() => {
47 | mermaid.initialize({
48 | startOnLoad: true,
49 | theme: "base",
50 | securityLevel: "loose",
51 | flowchart: {
52 | curve: "basis",
53 | padding: 15,
54 | nodeSpacing: 30,
55 | rankSpacing: 20,
56 | },
57 | });
58 |
59 | if (ref.current) {
60 | mermaid.render("mermaid-svg", chart).then((result) => {
61 | if (ref.current) {
62 | ref.current.innerHTML = result.svg;
63 |
64 | // 获取 SVG 元素
65 | const svg = ref.current.querySelector("svg");
66 | if (svg) {
67 | // 移除 SVG 的宽度和高度属性,允许它自由缩放
68 | svg.removeAttribute("width");
69 | svg.removeAttribute("height");
70 |
71 | // 设置 viewBox 以保持比例
72 | const bbox = svg.getBBox();
73 | svg.setAttribute("viewBox", `0 0 ${bbox.width} ${bbox.height}`);
74 |
75 | // 添加保持宽高比的样式
76 | svg.style.width = "100%";
77 | svg.style.height = "auto";
78 | svg.style.maxWidth = "100%";
79 | svg.style.display = "block";
80 | }
81 | }
82 | });
83 | }
84 | }, [chart]);
85 |
86 | return (
87 |
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { Cross2Icon } from "@radix-ui/react-icons"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogTrigger,
116 | DialogClose,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/src/hooks/use-toast.ts:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | // Inspired by react-hot-toast library
4 | import * as React from "react"
5 |
6 | import type {
7 | ToastActionElement,
8 | ToastProps,
9 | } from "@/components/ui/toast"
10 |
11 | const TOAST_LIMIT = 1
12 | const TOAST_REMOVE_DELAY = 1000000
13 |
14 | type ToasterToast = ToastProps & {
15 | id: string
16 | title?: React.ReactNode
17 | description?: React.ReactNode
18 | action?: ToastActionElement
19 | }
20 |
21 | const actionTypes = {
22 | ADD_TOAST: "ADD_TOAST",
23 | UPDATE_TOAST: "UPDATE_TOAST",
24 | DISMISS_TOAST: "DISMISS_TOAST",
25 | REMOVE_TOAST: "REMOVE_TOAST",
26 | } as const
27 |
28 | let count = 0
29 |
30 | function genId() {
31 | count = (count + 1) % Number.MAX_SAFE_INTEGER
32 | return count.toString()
33 | }
34 |
35 | type ActionType = typeof actionTypes
36 |
37 | type Action =
38 | | {
39 | type: ActionType["ADD_TOAST"]
40 | toast: ToasterToast
41 | }
42 | | {
43 | type: ActionType["UPDATE_TOAST"]
44 | toast: Partial
45 | }
46 | | {
47 | type: ActionType["DISMISS_TOAST"]
48 | toastId?: ToasterToast["id"]
49 | }
50 | | {
51 | type: ActionType["REMOVE_TOAST"]
52 | toastId?: ToasterToast["id"]
53 | }
54 |
55 | interface State {
56 | toasts: ToasterToast[]
57 | }
58 |
59 | const toastTimeouts = new Map>()
60 |
61 | const addToRemoveQueue = (toastId: string) => {
62 | if (toastTimeouts.has(toastId)) {
63 | return
64 | }
65 |
66 | const timeout = setTimeout(() => {
67 | toastTimeouts.delete(toastId)
68 | dispatch({
69 | type: "REMOVE_TOAST",
70 | toastId: toastId,
71 | })
72 | }, TOAST_REMOVE_DELAY)
73 |
74 | toastTimeouts.set(toastId, timeout)
75 | }
76 |
77 | export const reducer = (state: State, action: Action): State => {
78 | switch (action.type) {
79 | case "ADD_TOAST":
80 | return {
81 | ...state,
82 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
83 | }
84 |
85 | case "UPDATE_TOAST":
86 | return {
87 | ...state,
88 | toasts: state.toasts.map((t) =>
89 | t.id === action.toast.id ? { ...t, ...action.toast } : t
90 | ),
91 | }
92 |
93 | case "DISMISS_TOAST": {
94 | const { toastId } = action
95 |
96 | // ! Side effects ! - This could be extracted into a dismissToast() action,
97 | // but I'll keep it here for simplicity
98 | if (toastId) {
99 | addToRemoveQueue(toastId)
100 | } else {
101 | state.toasts.forEach((toast) => {
102 | addToRemoveQueue(toast.id)
103 | })
104 | }
105 |
106 | return {
107 | ...state,
108 | toasts: state.toasts.map((t) =>
109 | t.id === toastId || toastId === undefined
110 | ? {
111 | ...t,
112 | open: false,
113 | }
114 | : t
115 | ),
116 | }
117 | }
118 | case "REMOVE_TOAST":
119 | if (action.toastId === undefined) {
120 | return {
121 | ...state,
122 | toasts: [],
123 | }
124 | }
125 | return {
126 | ...state,
127 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
128 | }
129 | }
130 | }
131 |
132 | const listeners: Array<(state: State) => void> = []
133 |
134 | let memoryState: State = { toasts: [] }
135 |
136 | function dispatch(action: Action) {
137 | memoryState = reducer(memoryState, action)
138 | listeners.forEach((listener) => {
139 | listener(memoryState)
140 | })
141 | }
142 |
143 | type Toast = Omit
144 |
145 | function toast({ ...props }: Toast) {
146 | const id = genId()
147 |
148 | const update = (props: ToasterToast) =>
149 | dispatch({
150 | type: "UPDATE_TOAST",
151 | toast: { ...props, id },
152 | })
153 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
154 |
155 | dispatch({
156 | type: "ADD_TOAST",
157 | toast: {
158 | ...props,
159 | id,
160 | open: true,
161 | onOpenChange: (open) => {
162 | if (!open) dismiss()
163 | },
164 | },
165 | })
166 |
167 | return {
168 | id: id,
169 | dismiss,
170 | update,
171 | }
172 | }
173 |
174 | function useToast() {
175 | const [state, setState] = React.useState(memoryState)
176 |
177 | React.useEffect(() => {
178 | listeners.push(setState)
179 | return () => {
180 | const index = listeners.indexOf(setState)
181 | if (index > -1) {
182 | listeners.splice(index, 1)
183 | }
184 | }
185 | }, [state])
186 |
187 | return {
188 | ...state,
189 | toast,
190 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
191 | }
192 | }
193 |
194 | export { useToast, toast }
195 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { Slot } from "@radix-ui/react-slot"
6 | import {
7 | Controller,
8 | ControllerProps,
9 | FieldPath,
10 | FieldValues,
11 | FormProvider,
12 | useFormContext,
13 | } from "react-hook-form"
14 |
15 | import { cn } from "@/lib/utils"
16 | import { Label } from "@/components/ui/label"
17 |
18 | const Form = FormProvider
19 |
20 | type FormFieldContextValue<
21 | TFieldValues extends FieldValues = FieldValues,
22 | TName extends FieldPath = FieldPath
23 | > = {
24 | name: TName
25 | }
26 |
27 | const FormFieldContext = React.createContext(
28 | {} as FormFieldContextValue
29 | )
30 |
31 | const FormField = <
32 | TFieldValues extends FieldValues = FieldValues,
33 | TName extends FieldPath = FieldPath
34 | >({
35 | ...props
36 | }: ControllerProps) => {
37 | return (
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | const useFormField = () => {
45 | const fieldContext = React.useContext(FormFieldContext)
46 | const itemContext = React.useContext(FormItemContext)
47 | const { getFieldState, formState } = useFormContext()
48 |
49 | const fieldState = getFieldState(fieldContext.name, formState)
50 |
51 | if (!fieldContext) {
52 | throw new Error("useFormField should be used within ")
53 | }
54 |
55 | const { id } = itemContext
56 |
57 | return {
58 | id,
59 | name: fieldContext.name,
60 | formItemId: `${id}-form-item`,
61 | formDescriptionId: `${id}-form-item-description`,
62 | formMessageId: `${id}-form-item-message`,
63 | ...fieldState,
64 | }
65 | }
66 |
67 | type FormItemContextValue = {
68 | id: string
69 | }
70 |
71 | const FormItemContext = React.createContext(
72 | {} as FormItemContextValue
73 | )
74 |
75 | const FormItem = React.forwardRef<
76 | HTMLDivElement,
77 | React.HTMLAttributes
78 | >(({ className, ...props }, ref) => {
79 | const id = React.useId()
80 |
81 | return (
82 |
83 |
84 |
85 | )
86 | })
87 | FormItem.displayName = "FormItem"
88 |
89 | const FormLabel = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => {
93 | const { error, formItemId } = useFormField()
94 |
95 | return (
96 |
102 | )
103 | })
104 | FormLabel.displayName = "FormLabel"
105 |
106 | const FormControl = React.forwardRef<
107 | React.ElementRef,
108 | React.ComponentPropsWithoutRef
109 | >(({ ...props }, ref) => {
110 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
111 |
112 | return (
113 |
124 | )
125 | })
126 | FormControl.displayName = "FormControl"
127 |
128 | const FormDescription = React.forwardRef<
129 | HTMLParagraphElement,
130 | React.HTMLAttributes
131 | >(({ className, ...props }, ref) => {
132 | const { formDescriptionId } = useFormField()
133 |
134 | return (
135 |
141 | )
142 | })
143 | FormDescription.displayName = "FormDescription"
144 |
145 | const FormMessage = React.forwardRef<
146 | HTMLParagraphElement,
147 | React.HTMLAttributes
148 | >(({ className, children, ...props }, ref) => {
149 | const { error, formMessageId } = useFormField()
150 | const body = error ? String(error?.message) : children
151 |
152 | if (!body) {
153 | return null
154 | }
155 |
156 | return (
157 |
163 | {body}
164 |
165 | )
166 | })
167 | FormMessage.displayName = "FormMessage"
168 |
169 | export {
170 | useFormField,
171 | Form,
172 | FormItem,
173 | FormLabel,
174 | FormControl,
175 | FormDescription,
176 | FormMessage,
177 | FormField,
178 | }
179 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Cross2Icon } from "@radix-ui/react-icons"
5 | import * as ToastPrimitives from "@radix-ui/react-toast"
6 | import { cva, type VariantProps } from "class-variance-authority"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const ToastProvider = ToastPrimitives.Provider
11 |
12 | const ToastViewport = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, ...props }, ref) => (
16 |
24 | ))
25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
26 |
27 | const toastVariants = cva(
28 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
29 | {
30 | variants: {
31 | variant: {
32 | default: "border bg-background text-foreground",
33 | destructive:
34 | "destructive group border-destructive bg-destructive text-destructive-foreground",
35 | },
36 | },
37 | defaultVariants: {
38 | variant: "default",
39 | },
40 | }
41 | )
42 |
43 | const Toast = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef &
46 | VariantProps
47 | >(({ className, variant, ...props }, ref) => {
48 | return (
49 |
54 | )
55 | })
56 | Toast.displayName = ToastPrimitives.Root.displayName
57 |
58 | const ToastAction = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => (
62 |
70 | ))
71 | ToastAction.displayName = ToastPrimitives.Action.displayName
72 |
73 | const ToastClose = React.forwardRef<
74 | React.ElementRef,
75 | React.ComponentPropsWithoutRef
76 | >(({ className, ...props }, ref) => (
77 |
86 |
87 |
88 | ))
89 | ToastClose.displayName = ToastPrimitives.Close.displayName
90 |
91 | const ToastTitle = React.forwardRef<
92 | React.ElementRef,
93 | React.ComponentPropsWithoutRef
94 | >(({ className, ...props }, ref) => (
95 |
100 | ))
101 | ToastTitle.displayName = ToastPrimitives.Title.displayName
102 |
103 | const ToastDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | ToastDescription.displayName = ToastPrimitives.Description.displayName
114 |
115 | type ToastProps = React.ComponentPropsWithoutRef
116 |
117 | type ToastActionElement = React.ReactElement
118 |
119 | export {
120 | type ToastProps,
121 | type ToastActionElement,
122 | ToastProvider,
123 | ToastViewport,
124 | Toast,
125 | ToastTitle,
126 | ToastDescription,
127 | ToastClose,
128 | ToastAction,
129 | }
130 |
--------------------------------------------------------------------------------
/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "AllModel": {
3 | "loading": "Loading...",
4 | "waitingAnswer": "Waiting Answer...",
5 | "start": "Start",
6 | "title": "Model Judge | awesomeprompt.net",
7 | "description": "Awesome Prompt brings you Model Judge, based on AI capabilities and our powerful Prompt, just one sentence, wait a moment, and you can generate it.",
8 | "infoCard": "Model Judge",
9 | "slogan": "Model Judge, just one sentence, wait a moment, and you can generate it.",
10 | "view": "View",
11 | "backHome": "Back Home",
12 | "noQuestion": "Please enter a question",
13 | "noModel": "Please select a model",
14 | "inputQuestion": "Please enter a question",
15 | "selectModel": "Please select a model",
16 | "contestModel": "Current Model:",
17 | "aiJudge": "Model Judge",
18 | "startNow": "Start Now",
19 | "whatIsModelJudge": "What is Model Judge",
20 | "whatIsModelJudgeDesc": "Model Judge is a revolutionary AI model evaluation tool that helps you quickly find the most suitable AI model.",
21 | "howToUseModelJudge": "How to use Model Judge",
22 | "howToUseModelJudgeDesc1": "Enter any question or task description",
23 | "howToUseModelJudgeDesc2": "Select three different AI models from the available model list for testing",
24 | "howToUseModelJudgeDesc3": "Wait for the selected three models to complete their answers",
25 | "howToUseModelJudgeDesc4": "The system will automatically call the fourth model (judging model) to evaluate and score the answers of the three models",
26 | "howToUseModelJudgeDesc5": "View the evaluation results, including the scores and detailed evaluations of each model",
27 | "howToUseModelJudgeDesc6": "Based on the evaluation results, select the most suitable model for your needs",
28 | "tip": "Tip:",
29 | "tipDesc": "To get the best results, please describe your question or task as clearly and specifically as possible. This will help AI models provide more accurate and relevant answers.",
30 | "whyUseModelJudge": "Why use Model Judge",
31 | "reduceTrialCost": "Reduce Trial Cost",
32 | "reduceTrialCostDesc": "With the rise of AI, more and more models have emerged. Users often have no idea how to choose a model. Model Judge helps you quickly find the most suitable model for your needs, saving valuable time and resources.",
33 | "saveTime": "Save Time",
34 | "saveTimeDesc": "When choosing a model, users often need to spend a lot of time trying different models. Model Judge allows you to test multiple models at once, quickly obtain results, greatly improving the selection efficiency.",
35 | "improveEfficiency": "Improve Efficiency",
36 | "improveEfficiencyDesc": "Model Judge supports up to four models at a time, including models such as DeepSeek, Zero-One, and many more. Simply click a few times, and you can get a comprehensive evaluation result immediately.",
37 | "getAccurateResult": "Get Accurate Result",
38 | "getAccurateResultDesc": "Model Judge uses the fourth model (judging model) to evaluate and score the answers of the three models, ensuring the accuracy and reliability of the evaluation results.",
39 | "modelJudgeStandard": "Model Judge's Scoring Criteria",
40 | "modelJudgeStandardDesc": "The score of Model Judge is based on multiple dimensions, including:",
41 | "modelJudgeStandardDesc1": "Accuracy: Whether the information provided by the model is accurate and error-free",
42 | "modelJudgeStandardDesc2": "Relevance: Whether the model's answer is closely related to the question",
43 | "modelJudgeStandardDesc3": "Logic: Whether the model's answer has a clear logical structure",
44 | "modelJudgeStandardDesc4": "Completeness: Whether the model's answer is comprehensive and detailed",
45 | "modelJudgeStandardDesc5": "Creativity: Whether the model can provide unique and innovative insights",
46 | "modelJudgeStandardDesc6": "Practicality: Whether the model's answer has practical application value",
47 | "modelJudgeStandardDesc7": "This comprehensive evaluation method helps you better understand the strengths and limitations of each model, allowing you to make a wise choice when selecting a model. When choosing a model, you can refer to the scores and evaluations of Model Judge, selecting the most suitable model for your needs."
48 | },
49 | "Header": {
50 | "home": "Home",
51 | "login": "Login",
52 | "logout": "Logout"
53 | },
54 | "MermaidDiagram": {
55 | "start": "Start",
56 | "question": "User Input Question or Task Description",
57 | "selectModel": "Select Three AI Models for Testing",
58 | "model1": "Model 1 Processing",
59 | "model2": "Model 2 Processing",
60 | "model3": "Model 3 Processing",
61 | "model1Answer": "Model 1 Answer",
62 | "model2Answer": "Model 2 Answer",
63 | "model3Answer": "Model 3 Answer",
64 | "judgeModel": "Judge Model (Fourth Model) Evaluation",
65 | "generateScore": "Generate Score and Detailed Evaluation",
66 | "viewScore": "User View Each Model's Score and Evaluation",
67 | "selectBestModel": "User Select the Most Suitable Model",
68 | "end": "End"
69 | },
70 | "LoginDialog": {
71 | "title": "Login",
72 | "description": "Please login first"
73 | },
74 | "Footer": {
75 | "projectSource": "This project is separated from",
76 | "projectSupport": "Thanks for",
77 | "projectFreeApi": "free API service.",
78 | "projectOpenSource": "The project is currently open source, welcome to visit",
79 | "cooperation": "Cooperation",
80 | "wechat": "Wechat",
81 | "wechatQRCode": "Wechat QR Code",
82 | "wechatQRCodeDesc": "Welcome to contact us for business cooperation",
83 | "projectLinks": "Project Links",
84 | "friendLinks": "Friend Links",
85 | "connectLinks": "Connect Links",
86 | "infoCard": "Model Judge"
87 | },
88 | "ModelVs": {
89 | "title": "Model Debate | modeldebate.awesomeprompt.net",
90 | "description": "Model Debate, model between model, who will win?"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | CaretSortIcon,
6 | CheckIcon,
7 | ChevronDownIcon,
8 | ChevronUpIcon,
9 | } from "@radix-ui/react-icons"
10 | import * as SelectPrimitive from "@radix-ui/react-select"
11 |
12 | import { cn } from "@/lib/utils"
13 |
14 | const Select = SelectPrimitive.Root
15 |
16 | const SelectGroup = SelectPrimitive.Group
17 |
18 | const SelectValue = SelectPrimitive.Value
19 |
20 | const SelectTrigger = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, children, ...props }, ref) => (
24 | span]:line-clamp-1",
28 | className
29 | )}
30 | {...props}
31 | >
32 | {children}
33 |
34 |
35 |
36 |
37 | ))
38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39 |
40 | const SelectScrollUpButton = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 |
53 |
54 | ))
55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56 |
57 | const SelectScrollDownButton = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 |
70 |
71 | ))
72 | SelectScrollDownButton.displayName =
73 | SelectPrimitive.ScrollDownButton.displayName
74 |
75 | const SelectContent = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, position = "popper", ...props }, ref) => (
79 |
80 |
91 |
92 |
99 | {children}
100 |
101 |
102 |
103 |
104 | ))
105 | SelectContent.displayName = SelectPrimitive.Content.displayName
106 |
107 | const SelectLabel = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, ...props }, ref) => (
111 |
116 | ))
117 | SelectLabel.displayName = SelectPrimitive.Label.displayName
118 |
119 | const SelectItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | SelectItem.displayName = SelectPrimitive.Item.displayName
140 |
141 | const SelectSeparator = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef
144 | >(({ className, ...props }, ref) => (
145 |
150 | ))
151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
152 |
153 | export {
154 | Select,
155 | SelectGroup,
156 | SelectValue,
157 | SelectTrigger,
158 | SelectContent,
159 | SelectLabel,
160 | SelectItem,
161 | SelectSeparator,
162 | SelectScrollUpButton,
163 | SelectScrollDownButton,
164 | }
165 |
--------------------------------------------------------------------------------
/src/app/[locale]/page.tsx:
--------------------------------------------------------------------------------
1 | import { Suspense } from "react";
2 | import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
3 | import { Metadata } from "next";
4 | import Link from "next/link";
5 | import { Button } from "@/components/ui/button";
6 | import {
7 | ArrowRight,
8 | Brain,
9 | Clock,
10 | Zap,
11 | HelpCircle,
12 | Users,
13 | Target,
14 | } from "lucide-react";
15 |
16 | import MermaidDiagram from "@/components/MermaidDiagram";
17 | const INTL_NAMESPACE = "AllModel";
18 |
19 | export const generateMetadata = async ({
20 | params: { locale },
21 | }: {
22 | params: { locale: string };
23 | }): Promise => {
24 | unstable_setRequestLocale(locale);
25 | const t = await getTranslations({ locale, namespace: INTL_NAMESPACE });
26 | return {
27 | title: t("title"),
28 | description: t("description"),
29 | };
30 | };
31 |
32 | export default async function AllModel({
33 | params: { locale },
34 | }: {
35 | params: { locale: string };
36 | }) {
37 | const t = await getTranslations({ locale, namespace: INTL_NAMESPACE });
38 |
39 | return (
40 | {t("loading")}}
42 | >
43 |
44 |
45 |
46 |
47 | {t("infoCard")}
48 |
49 |
50 | {t("description")}
51 |
52 |
53 |
56 |
57 |
58 |
59 |
60 |
61 | {t("whatIsModelJudge")}
62 |
63 |
64 |
65 |
66 |
67 | {t("whatIsModelJudgeDesc")}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | {t("howToUseModelJudge")}
77 |
78 |
79 |
80 |
81 |
82 | - {t("howToUseModelJudgeDesc1")}
83 | - {t("howToUseModelJudgeDesc2")}
84 | - {t("howToUseModelJudgeDesc3")}
85 | - {t("howToUseModelJudgeDesc4")}
86 | - {t("howToUseModelJudgeDesc5")}
87 | - {t("howToUseModelJudgeDesc6")}
88 |
89 |
90 |
91 |
{t("tip")}
92 |
{t("tipDesc")}
93 |
94 |
95 |
96 |
97 |
98 |
99 | {t("whyUseModelJudge")}
100 |
101 |
102 | }
104 | title={t("reduceTrialCost")}
105 | description={t("reduceTrialCostDesc")}
106 | />
107 | }
109 | title={t("saveTime")}
110 | description={t("saveTimeDesc")}
111 | />
112 | }
114 | title={t("improveEfficiency")}
115 | description={t("improveEfficiencyDesc")}
116 | />
117 |
118 |
119 |
120 |
121 |
122 | {t("modelJudgeStandard")}
123 |
124 |
125 |
126 |
127 |
128 | {t("modelJudgeStandardDesc")}
129 |
130 |
131 | - {t("modelJudgeStandardDesc1")}
132 | - {t("modelJudgeStandardDesc2")}
133 | - {t("modelJudgeStandardDesc3")}
134 | - {t("modelJudgeStandardDesc4")}
135 | - {t("modelJudgeStandardDesc5")}
136 | - {t("modelJudgeStandardDesc6")}
137 |
138 |
{t("modelJudgeStandardDesc7")}
139 |
140 |
141 |
142 |
143 |
144 |
145 | );
146 | }
147 |
148 | function FeatureCard({ icon, title, description }: any) {
149 | return (
150 |
151 |
{icon}
152 |
{title}
153 |
{description}
154 |
155 | );
156 | }
157 |
--------------------------------------------------------------------------------
/src/components/ClientComponent.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState, useEffect } from "react";
4 | import { Input } from "@/components/ui/input";
5 | import { Button } from "@/components/ui/button";
6 | import { Textarea } from "@/components/ui/textarea";
7 | import { Card } from "@/components/ui/card";
8 | import { v4 as uuidv4 } from "uuid";
9 |
10 | import {
11 | Select,
12 | SelectTrigger,
13 | SelectContent,
14 | SelectItem,
15 | SelectValue,
16 | } from "@/components/ui/select";
17 | import ReactMarkdown from "react-markdown";
18 | import { useTranslations } from "next-intl";
19 |
20 | interface Model {
21 | id: string;
22 | object: string;
23 | created: number;
24 | owned_by: string;
25 | }
26 |
27 | interface ClientComponentProps {
28 | models: Model[];
29 | }
30 |
31 | export default function ClientComponent({ models }: ClientComponentProps) {
32 | const t = useTranslations("AllModel");
33 | const [question, setQuestion] = useState("");
34 | const [answers, setAnswers] = useState({ a: "", b: "", c: "", d: "" });
35 | const [loading, setLoading] = useState(false);
36 | const [deviceId, setDeviceId] = useState(uuidv4());
37 | const [selectedModela, setSelectedModela] = useState("");
38 | const [selectedModelb, setSelectedModelb] = useState("");
39 | const [selectedModelc, setSelectedModelc] = useState("");
40 | const [selectedModeld, setSelectedModeld] = useState(
41 | "Qwen/Qwen2.5-72B-Instruct"
42 | );
43 | const handleModelChange = (model: string, value: string) => {
44 | if (model === "a") {
45 | setSelectedModela(value);
46 | } else if (model === "b") {
47 | setSelectedModelb(value);
48 | } else if (model === "c") {
49 | setSelectedModelc(value);
50 | } else if (model === "d") {
51 | setSelectedModeld(value);
52 | }
53 | console.log("selectedModela", selectedModela);
54 | console.log("selectedModelb", selectedModelb);
55 | console.log("selectedModelc", selectedModelc);
56 | };
57 |
58 | const streamResponse = async (
59 | response: Response,
60 | key: keyof typeof answers
61 | ) => {
62 | const reader = response.body?.getReader();
63 | const decoder = new TextDecoder();
64 | let done = false;
65 | let partialResult = "";
66 |
67 | while (!done && reader) {
68 | const { value, done: doneReading } = await reader.read();
69 | done = doneReading;
70 | if (value) {
71 | const chunk = decoder.decode(value);
72 | partialResult += chunk;
73 | setAnswers((prev) => ({
74 | ...prev,
75 | [key]: partialResult,
76 | }));
77 | }
78 | }
79 |
80 | return partialResult;
81 | };
82 | const fetchOptions = (
83 | model: { modelIndex: string; name: string },
84 | system?: string
85 | ) => ({
86 | method: "POST",
87 | headers: {
88 | "Content-Type": "application/json",
89 | },
90 | body: JSON.stringify({
91 | question,
92 | model: model.name,
93 | modelIndex: model.modelIndex,
94 | system,
95 | id: deviceId,
96 | }),
97 | });
98 | const handleStart = async () => {
99 | if (!question) {
100 | alert(t("noQuestion"));
101 | return;
102 | }
103 |
104 | if (!selectedModela || !selectedModelb || !selectedModelc) {
105 | alert(t("noModel"));
106 | return;
107 | }
108 | setLoading(true);
109 | setAnswers({ a: "", b: "", c: "", d: "" });
110 | setDeviceId(uuidv4());
111 | try {
112 | const [responseA, responseB, responseC] = await Promise.all([
113 | fetch(
114 | "/api/all-model",
115 | fetchOptions({ modelIndex: "model_a", name: selectedModela })
116 | ),
117 | fetch(
118 | "/api/all-model",
119 | fetchOptions({ modelIndex: "model_b", name: selectedModelb })
120 | ),
121 | fetch(
122 | "/api/all-model",
123 | fetchOptions({ modelIndex: "model_c", name: selectedModelc })
124 | ),
125 | ]);
126 |
127 | const [resultA, resultB, resultC] = await Promise.all([
128 | streamResponse(responseA, "a"),
129 | streamResponse(responseB, "b"),
130 | streamResponse(responseC, "c"),
131 | ]);
132 |
133 | const updatedAnswers = { a: resultA, b: resultB, c: resultC, d: "" };
134 | console.log("answers all", updatedAnswers);
135 |
136 | await setD(updatedAnswers);
137 | } catch (error) {
138 | console.error("错误:", error);
139 | } finally {
140 | setLoading(false);
141 | }
142 | };
143 | const setD = async (currentAnswers: typeof answers) => {
144 | // console.log("currentAnswers", currentAnswers);
145 |
146 | const system = `
147 | 你是一位以尖锐和挑衅风格著称的专业评论员。你的任务是查看大语言模型的回答,
148 | 并根据这些回答点评。要尖锐和挑衅,稍微刻薄一点。不要让人感到尴尬。
149 | 针对 ${question} 这个问题
150 | ${selectedModela} 的回答是:${currentAnswers.a},
151 |
152 | ${selectedModelb} 的回答是:${currentAnswers.b},
153 |
154 | ${selectedModelc} 的回答是:${currentAnswers.c},
155 |
156 | 请你分别总结三个模型回答的内容
157 | 并且指出他们回答的
158 |
159 | 优点,缺点,得分(满分100),并给出最终的答案。
160 | `;
161 | console.log("system", system);
162 | const responseD = await fetch(
163 | "/api/all-model",
164 | fetchOptions({ modelIndex: "model_d", name: selectedModeld }, system)
165 | );
166 | await streamResponse(responseD, "d");
167 | };
168 | return (
169 |
170 |
171 |
182 |
183 |
184 |
185 | {["a", "b", "c"].map((model) => (
186 |
187 |
188 |
211 |
212 |
213 |
214 | {t("contestModel")}
215 | {model === "a"
216 | ? selectedModela
217 | : model === "b"
218 | ? selectedModelb
219 | : selectedModelc}
220 |
221 |
222 |
223 |
224 |
225 | {answers[model as keyof typeof answers] || t("waitingAnswer")}
226 |
227 |
228 |
229 | ))}
230 |
231 |
232 |
233 |
248 |
249 |
250 |
251 | {t("aiJudge")} : {selectedModeld}
252 |
253 |
254 |
255 |
256 | {answers.d || t("waitingAnswer")}
257 |
258 |
259 |
260 |
261 |
262 | );
263 | }
264 |
--------------------------------------------------------------------------------