import Head from "next/head"; import Image from "next/image"; import { Inter } from "@next/font/google"; import styles from "@/styles/Home.module.css"; import { QueryClientProvider, QueryClient, useQuery, useMutation, } from "@tanstack/react-query"; import { useCallback, useEffect, useState } from "react"; import ReactMarkdown from "react-markdown"; import { useKey } from "react-use"; import cn from "clsx"; // import rehypePrettyCode from "rehype-pretty-code"; // import * as shiki from "shiki"; import remarkGfm from "remark-gfm"; import styled from "styled-components"; // import rehypeHighlight from "rehype-highlight"; // @ts-ignore import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; // @ts-ignore import theme from "react-syntax-highlighter/dist/cjs/styles/prism/vs-dark"; // const CDN_BASE = "https://npm.elemecdn.com/"; // shiki.setCDN(`${CDN_BASE}/shiki@0.14.1/`); const SMD = styled(ReactMarkdown)` blockquote, hr, p { margin-block: 1rem; } h1, h2, h3, h4, h5, h6 { font-weight: bold; margin-block: 1rem; } h1 { font-size: 3rem; color: var(--h1-color); } h2 { font-size: 2.5rem; color: var(--h2-color); } h3 { font-size: 2rem; color: var(--h3-color); } h4 { font-size: 1.5rem; color: var(--h4-color); } h5 { font-size: 1rem; color: var(--h5-color); } h6 { font-size: 0.9rem; color: var(--h6-color); } img { display: block; margin-left: auto; margin-right: auto; } .img-alt { display: block; margin: 0 0 1rem 0; font-size: 16px; text-align: center; } th { font-weight: 600; } thead { border-bottom: 2px solid var(--background-modifier-border); } tr { line-height: normal; padding: 0 4px; background-color: var(--pre-code); } tr:nth-child(0) { padding-top: 4px; } th, td { padding: 0.5em 1em; } td { border-bottom: 1px solid var(--background-modifier-border); } td:not(:last-child) { border-right: 1px solid var(--background-modifier-border); } strong { font-weight: 600; } a { color: var(--text-a); text-decoration: none; } a:hover { color: var(--text-a-hover); text-decoration: none; } blockquote { margin: 1rem 0; padding-inline: 2ch; padding-block: 0.5rem; background-color: var(--pre-code); border-left: 0.5ch solid var(--interactive-accent); } blockquote:has(> blockquote) { padding-bottom: 0; } blockquote > blockquote { margin-bottom: 0; } blockquote:not(:has(> blockquote)) { margin-bottom: 1rem; } hr { background-color: var(--background-modifier-border); height: 1px; border: 0; } ul { list-style-type: revert; } ol { list-style-type: decimal; } ul:not(.contains-task-list), ol:not(.contains-task-list) { padding-left: 2em; } ul.contains-task-list, ol.contains-task-list { margin-left: 0; list-style-type: none; } `; const TextTyper = ({ // now the phrase, interval and HTML element desired will come via props and we have some default values here text = "", skip = 0, onFinish = () => {}, }) => { const [typedText, setTypedText] = useState(""); const interval = 50; const step = Math.ceil(text.length / 100); // @ts-ignore const typingRender = (text, updater, interval) => { let localTypingIndex = skip; let localTyping = text.slice(0, skip); if (text) { let printer = setInterval(() => { if (localTypingIndex < text.length) { updater( (localTyping += text.slice( localTypingIndex, localTypingIndex + step )) ); localTypingIndex += step; document.querySelector("#anchor")?.scrollIntoView(); } else { localTypingIndex = 0; localTyping = ""; clearInterval(printer); onFinish(); } }, interval); } }; useEffect(() => { typingRender(text, setTypedText, interval); }, [text, interval]); return
{typedText}
; }; const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, }, }, }); const p429 = "请求过多,请稍后再试"; const rehypePlugins: any[] = [ // [ // rehypePrettyCode, // { // // theme: { // // dark: "github-dark", // // light: "github-light", // // }, // theme: "github-dark", // keepBackground: true, // }, // ], // rehypeHighlight, ]; const remarkPlugins = [remarkGfm]; const components = { // @ts-ignore code({ node, inline, className, children, ...props }: any) { const match = /language-(\w+)/.exec(className || ""); return !inline && match ? ( {String(children).replace(/\n$/, "")} ) : ( {children} ); }, }; const Markdown = ({ children }: { children: string }) => { return ( {children} ); }; const ask = async ( query: string, context: [string, string][], model: string, jwt?: string ) => { const res = await fetch(`/ai/ask`, { method: "POST", headers: { Authorization: jwt, } as Record, body: JSON.stringify({ query: query, context: context .slice(1) .filter((c) => c[1] !== p429) .map((c) => ({ query: c[0], answer: c[1] })), model, }), }); if (res.status === 429) { return { code: 1, msg: p429 }; } return (await res.json()) as { code: number; msg: string }; }; let contexts = [ [ "hello", "您好!我是小Vaala。我可以回答您的问题、写文章、写作业、翻译,对于一些法律等领域的问题我也可以给你提供信息。", ], ]; const models = ["newbing", "chatvaala", "chatyuan", "chatglm"]; function Home() { const [model, setModel] = useState(models[0]); const [query, setQuery] = useState(""); const [question, setQuestion] = useState(""); const [answer, setAnswer] = useState(""); const [context, setContext] = useState<[string, string][]>( contexts as [string, string][] ); const { data: jwt } = useQuery(["jwt"], async () => { const resp = await fetch(`/auth/jwt`); const res = (await resp.json())["authorization"]; return res; }); useKey("Enter", (e) => { if (!e.shiftKey && !e.metaKey && !e.ctrlKey && !e.isComposing) { e.preventDefault(); handleAsk.mutate(); } }); const handleTypeFinish = useCallback(() => { setQuestion(""); setContext((v) => [...v, [question, answer]]); setAnswer(""); document.querySelector("#anchor")?.scrollIntoView(); }, [query, answer]); const handleAsk = useMutation(async () => { if (query.length === 0) return; setQuestion(query); setQuery(""); setTimeout(() => document.querySelector("#anchor")?.scrollIntoView(), 100); (async () => { const ans = await ask(query, context, model, jwt || "").finally(() => { setTimeout( () => document.querySelector("#anchor")?.scrollIntoView(), 300 ); }); if (ans.code) alert(ans.msg); else setAnswer(ans.msg); })(); }); return ( <> Vaala Chat