初始化提交
This commit is contained in:
+4
-2
@@ -1,6 +1,8 @@
|
||||
# The backend API for chat endpoint.
|
||||
NEXT_PUBLIC_CHAT_API=http://localhost:8000/api/chat
|
||||
|
||||
# Let's the user change indexes in LlamaCloud projects
|
||||
NEXT_PUBLIC_USE_LLAMACLOUD=true
|
||||
PHOENIX_SERVER_URL=http://localhost:6006/
|
||||
|
||||
# Let's the user change indexes in LlamaCloud projects
|
||||
NEXT_PUBLIC_USE_LLAMACLOUD=false
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import Image from "next/image";
|
||||
|
||||
const phoenixUrl = process.env.PHOENIX_SERVER_URL;
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by editing
|
||||
<code className="font-mono font-bold">app/page.tsx</code>
|
||||
<code className="font-mono font-bold"><a href="javascript:location.reload();">清空当前会话</a></code>
|
||||
</p>
|
||||
<div className="fixed bottom-0 left-0 mb-4 flex h-auto w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:w-auto lg:bg-none lg:mb-0">
|
||||
<a
|
||||
href="https://www.llamaindex.ai/"
|
||||
href={phoenixUrl}
|
||||
target="_blank"
|
||||
className="flex items-center justify-center font-nunito text-lg font-bold gap-2"
|
||||
>
|
||||
<span>Built by LlamaIndex</span>
|
||||
<code className="font-mono font-bold">打开监控平台</code>
|
||||
<Image
|
||||
className="rounded-xl"
|
||||
src="/llama.png"
|
||||
|
||||
@@ -14,13 +14,13 @@ export default function ChatActions(
|
||||
{props.showStop && (
|
||||
<Button variant="outline" size="sm" onClick={props.stop}>
|
||||
<PauseCircle className="mr-2 h-4 w-4" />
|
||||
Stop generating
|
||||
停止
|
||||
</Button>
|
||||
)}
|
||||
{props.showReload && (
|
||||
<Button variant="outline" size="sm" onClick={props.reload}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Regenerate
|
||||
重新生成
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function ChatInput(
|
||||
|
||||
const handleUploadFile = async (file: File) => {
|
||||
if (imageUrl || files.length > 0) {
|
||||
alert("You can only upload one file at a time.");
|
||||
alert("同一时刻只能上传一个文件。");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -99,10 +99,11 @@ export default function ChatInput(
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-full items-start justify-between gap-4 ">
|
||||
<Input
|
||||
<textarea
|
||||
autoFocus
|
||||
rows={2}
|
||||
name="message"
|
||||
placeholder="Type a message"
|
||||
placeholder="请输入消息"
|
||||
className="flex-1"
|
||||
value={props.input}
|
||||
onChange={props.handleInputChange}
|
||||
@@ -119,7 +120,7 @@ export default function ChatInput(
|
||||
<LlamaCloudSelector setRequestData={setRequestData} />
|
||||
)}
|
||||
<Button type="submit" disabled={props.isLoading || !props.input.trim()}>
|
||||
Send message
|
||||
发送
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -17,7 +17,7 @@ export function ChatEvents({
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const buttonLabel = isOpen ? "Hide events" : "Show events";
|
||||
const buttonLabel = isOpen ? "隐藏" : "详情";
|
||||
|
||||
const EventIcon = isOpen ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
|
||||
@@ -9,23 +9,33 @@ import {
|
||||
import { useCopyToClipboard } from "../hooks/use-copy-to-clipboard";
|
||||
import { SourceData } from "../index";
|
||||
import PdfDialog from "../widgets/PdfDialog";
|
||||
import { useClientConfig } from "../hooks/use-config";
|
||||
|
||||
const SCORE_THRESHOLD = 0.3;
|
||||
|
||||
function SourceNumberButton({ index }: { index: number }) {
|
||||
function truncateNumber(num: number | undefined, precision: number): number {
|
||||
if (num == undefined || num == 0) return 0;
|
||||
const factor = Math.pow(10, precision);
|
||||
return Math.trunc(num * factor) / factor;
|
||||
}
|
||||
|
||||
function SourceNumberButton({ index, score }: { index: number, score: number | undefined }) {
|
||||
return (
|
||||
<div className="text-xs w-5 h-5 rounded-full bg-gray-100 mb-2 flex items-center justify-center hover:text-white hover:bg-primary hover:cursor-pointer">
|
||||
{index + 1}
|
||||
<div className="text-xs w-45 h-45 rounded-full bg-gray-100 mb-2 flex items-center justify-center hover:text-white hover:bg-primary hover:cursor-pointer">
|
||||
{truncateNumber(score, 2)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type NodeInfo = {
|
||||
id: string;
|
||||
score?: number;
|
||||
text: string;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export function ChatSources({ data }: { data: SourceData }) {
|
||||
const { backend } = useClientConfig();
|
||||
const sources: NodeInfo[] = useMemo(() => {
|
||||
// aggregate nodes by url or file_path (get the highest one by score)
|
||||
const nodesByPath: { [path: string]: NodeInfo } = {};
|
||||
@@ -36,6 +46,8 @@ export function ChatSources({ data }: { data: SourceData }) {
|
||||
.forEach((node) => {
|
||||
const nodeInfo = {
|
||||
id: node.id,
|
||||
score: node.score,
|
||||
text: node.text,
|
||||
url: node.url,
|
||||
};
|
||||
const key = nodeInfo.url ?? nodeInfo.id; // use id as key for UNKNOWN type
|
||||
@@ -51,7 +63,7 @@ export function ChatSources({ data }: { data: SourceData }) {
|
||||
|
||||
return (
|
||||
<div className="space-x-2 text-sm">
|
||||
<span className="font-semibold">Sources:</span>
|
||||
<span className="font-semibold">来源:</span>
|
||||
<div className="inline-flex gap-1 items-center">
|
||||
{sources.map((nodeInfo: NodeInfo, index: number) => {
|
||||
if (nodeInfo.url?.endsWith(".pdf")) {
|
||||
@@ -59,8 +71,8 @@ export function ChatSources({ data }: { data: SourceData }) {
|
||||
<PdfDialog
|
||||
key={nodeInfo.id}
|
||||
documentId={nodeInfo.id}
|
||||
url={nodeInfo.url!}
|
||||
trigger={<SourceNumberButton index={index} />}
|
||||
url={backend+nodeInfo.url}
|
||||
trigger={<SourceNumberButton index={index} score={nodeInfo.score} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -68,9 +80,9 @@ export function ChatSources({ data }: { data: SourceData }) {
|
||||
<div key={nodeInfo.id}>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<SourceNumberButton index={index} />
|
||||
<SourceNumberButton index={index} score={nodeInfo.score}/>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-[320px]">
|
||||
<HoverCardContent className="w-[450px]">
|
||||
<NodeInfo nodeInfo={nodeInfo} />
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
@@ -83,6 +95,7 @@ export function ChatSources({ data }: { data: SourceData }) {
|
||||
}
|
||||
|
||||
function NodeInfo({ nodeInfo }: { nodeInfo: NodeInfo }) {
|
||||
const { backend } = useClientConfig();
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 1000 });
|
||||
|
||||
if (nodeInfo.url) {
|
||||
@@ -92,10 +105,10 @@ function NodeInfo({ nodeInfo }: { nodeInfo: NodeInfo }) {
|
||||
<div className="flex items-center my-2">
|
||||
<a
|
||||
className="hover:text-blue-900 truncate"
|
||||
href={nodeInfo.url}
|
||||
href={backend+nodeInfo.url}
|
||||
target="_blank"
|
||||
>
|
||||
<span>{nodeInfo.url}</span>
|
||||
<span>{nodeInfo.text}</span>
|
||||
</a>
|
||||
<Button
|
||||
onClick={() => copyToClipboard(nodeInfo.url!)}
|
||||
@@ -116,8 +129,7 @@ function NodeInfo({ nodeInfo }: { nodeInfo: NodeInfo }) {
|
||||
// node generated by unknown loader, implement renderer by analyzing logged out metadata
|
||||
return (
|
||||
<p>
|
||||
Sorry, unknown node type. Please add a new renderer in the NodeInfo
|
||||
component.
|
||||
对不起, 未知文件类型. 无法打开当前的来源文件。
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function ChatTools({ data }: { data: ToolData }) {
|
||||
if (toolOutput.isError) {
|
||||
return (
|
||||
<div className="border-l-2 border-red-400 pl-2">
|
||||
There was an error when calling the tool {toolCall.name} with input:{" "}
|
||||
调用工具“ {toolCall.name} ”时发生错误,参数:{" "}
|
||||
<br />
|
||||
{JSON.stringify(toolCall.input)}
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||
3,
|
||||
true,
|
||||
)}${fileExtension}`;
|
||||
const fileName = window.prompt("Enter file name" || "", suggestedFileName);
|
||||
const fileName = window.prompt("请输入文件名称" || "", suggestedFileName);
|
||||
|
||||
if (!fileName) {
|
||||
// User pressed cancel on prompt.
|
||||
@@ -99,7 +99,7 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||
<div className="flex items-center space-x-1">
|
||||
<Button variant="ghost" onClick={downloadAsFile} size="icon">
|
||||
<Download />
|
||||
<span className="sr-only">Download</span>
|
||||
<span className="sr-only">下载</span>
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" onClick={onCopy}>
|
||||
{isCopied ? (
|
||||
@@ -107,7 +107,7 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
<span className="sr-only">Copy code</span>
|
||||
<span className="sr-only">复制代码</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface ChatHandler {
|
||||
data?: any;
|
||||
},
|
||||
) => void;
|
||||
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleInputChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
reload?: () => void;
|
||||
stop?: () => void;
|
||||
onFileUpload?: (file: File) => Promise<void>;
|
||||
|
||||
@@ -63,7 +63,7 @@ export function useFile() {
|
||||
...requestParams,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) throw new Error("Failed to upload document.");
|
||||
if (!response.ok) throw new Error("上传文件时发生错误。");
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
@@ -109,7 +109,7 @@ export function useFile() {
|
||||
}
|
||||
|
||||
const filetype = docMineTypeMap[file.type];
|
||||
if (!filetype) throw new Error("Unsupported document type.");
|
||||
if (!filetype) throw new Error("不支持的文件类型。");
|
||||
const newDoc: Omit<DocumentFile, "content"> = {
|
||||
id: uuidv4(),
|
||||
filetype,
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function FileUploader({
|
||||
const allowedExtensions = config?.allowedExtensions;
|
||||
const defaultCheckExtension = (extension: string) => {
|
||||
if (allowedExtensions && !allowedExtensions.includes(extension)) {
|
||||
return `Invalid file type. Please select a file with one of these formats: ${allowedExtensions!.join(
|
||||
return `无效的文件类型。请选择一个以下格式的文件: ${allowedExtensions!.join(
|
||||
",",
|
||||
)}`;
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export default function FileUploader({
|
||||
|
||||
if (isFileSizeExceeded(file)) {
|
||||
return onFileUploadError(
|
||||
`File size exceeded. Limit is ${fileSizeLimit / 1024 / 1024} MB`,
|
||||
`文件尺寸超标。请选择不大于 ${fileSizeLimit / 1024 / 1024} MB 的文件。`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import "./markdown.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
const inter = Inter({ subsets: ["latin", "latin-ext"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Llama App",
|
||||
description: "Generated by create-llama",
|
||||
title: "博微造价工程文件知识问答",
|
||||
description: "博微技术中心搭建的造价工程文件知识问答",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -16,7 +16,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import ChatSection from "./components/chat-section";
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="h-screen w-screen flex justify-center items-center background-gradient">
|
||||
<div className="space-y-2 lg:space-y-10 w-[90%] lg:w-[60rem]">
|
||||
<div className="space-y-2 lg:space-y-10 w-[90%] lg:w-[90rem]">
|
||||
<Header />
|
||||
<div className="h-[65vh] flex">
|
||||
<ChatSection />
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
npm run dev
|
||||
Reference in New Issue
Block a user