# llamaindex_frontend **Repository Path**: zhang_renyang/llamaindex_frontend ## Basic Information - **Project Name**: llamaindex_frontend - **Description**: llamaindex_frontend - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-03 - **Last Updated**: 2025-08-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 1.初始化项目 ```js npm create vite@latest ``` ## 2.安装依赖 ```bash npm install react-spinners --save ``` ## 3. 绘制布局 ### 3.1. DocumentTools.tsx src/components/DocumentTools.tsx ```tsx import React from 'react'; import DocumentUploader from './DocumentUploader'; import DocumentViewer from './DocumentViewer'; const DocumentTools: React.FC = () => { return (
); }; export default DocumentTools; ``` ### 3.2. DocumentUploader.tsx src/components/DocumentUploader.tsx ```tsx const DocumentUploader = () => { return (
{'Select a file to insert'}
); }; export default DocumentUploader; ``` ### 3.3. DocumentViewer.tsx src/components/DocumentViewer.tsx ```tsx const DocumentViewer = () => { return (

Upload your first document!

You will see the title and content here

); }; export default DocumentViewer; ``` ### 3.4. App.tsx src/App.tsx ```diff +import DocumentTools from './components/DocumentTools'; function App() { return ( +
+
+
+
+ ); } +export default App; ``` ### 3.5. main.tsx src/main.tsx ```diff import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' createRoot(document.getElementById('root')!).render( , ) ``` ## 4. 上传文件 ### 4.1. insertDocument.tsx src/apis/insertDocument.tsx ```tsx // 定义一个异步函数insertDocument,接收一个File类型的参数file const insertDocument = async (file: File) => { // 创建一个FormData对象,用于构建表单数据 const formData = new FormData(); // 将文件添加到FormData对象中,字段名为'file' formData.append('file', file); // 发送POST请求到指定的后端接口,上传文件 const response = await fetch('http://localhost:5601/uploadFile', { mode: 'cors', // 设置CORS模式,允许跨域请求 method: 'POST', // 请求方法为POST body: formData, // 请求体为formData }); // 获取响应的文本内容(异步) const responseText = response.text(); // 返回响应文本 return responseText; }; // 导出insertDocument函数,供其他模块使用 export default insertDocument; ``` ### 4.2. DocumentUploader.tsx src/components/DocumentUploader.tsx ```diff +// 导入React中的ChangeEvent类型和useState钩子 +import { type ChangeEvent, useState } from 'react'; +// 导入react-spinners库中的CircleLoader组件,用于加载动画 +import { CircleLoader } from 'react-spinners'; +// 导入自定义的insertDocument函数,用于上传文档 +import insertDocument from '../apis/insertDocument'; +// 定义HTMLInputEvent接口,继承自ChangeEvent,并指定target类型 +interface HTMLInputEvent extends ChangeEvent { + target: HTMLInputElement & EventTarget; +} +// 定义DocumentUploader组件 const DocumentUploader = () => { + // 定义selectedFile状态,存储用户选择的文件 + const [selectedFile, setSelectedFile] = useState(); + // 定义isFilePicked状态,标记是否已选择文件 + const [isFilePicked, setIsFilePicked] = useState(false); + // 定义isLoading状态,标记是否正在上传 + const [isLoading, setIsLoading] = useState(false); + // 文件选择事件处理函数 + const changeHandler = (event: HTMLInputEvent) => { + // 判断event.target和event.target.files是否存在 + if (event.target && event.target.files) { + // 设置selectedFile为用户选择的第一个文件 + setSelectedFile(event.target.files[0]); + // 标记已选择文件 + setIsFilePicked(true); + } + }; + // 文件提交处理函数 + const handleSubmission = () => { + // 如果已选择文件 + if (selectedFile) { + // 设置加载状态为true + setIsLoading(true); + // 调用insertDocument上传文件 + insertDocument(selectedFile).then(() => { + // 上传完成后重置selectedFile + setSelectedFile(undefined); + // 重置文件选择状态 + setIsFilePicked(false); + // 关闭加载动画 + setIsLoading(false); + }); + } + }; + // 组件渲染部分 return (
+ {/* 文件输入框,接受pdf、txt、json、md格式文件,绑定changeHandler */} + {/* 文件上传标签,点击可触发文件选择 */} + {/* 显示已选择的文件名,否则提示选择文件 */}
+ {isFilePicked && selectedFile ? selectedFile.name : 'Select a file to insert'}
+ {/* 如果已选择文件且未加载,显示提交按钮 */} + {isFilePicked && !isLoading && ( + + )} + {/* 如果正在加载,显示加载动画 */} + {isLoading && }
); }; +// 导出DocumentUploader组件 export default DocumentUploader; ``` ## 5. 文件列表 ### 5.1. fetchDocuments.tsx src/apis/fetchDocuments.tsx ```tsx // 定义Document类型,包含id和text两个字段 export type Document = { id: string; text: string; }; // 定义异步函数fetchDocuments,返回Document类型的数组 const fetchDocuments = async (): Promise => { // 发送GET请求到本地5601端口的/getDocuments接口,允许跨域 const response = await fetch('http://localhost:5601/getDocuments', { mode: 'cors' }); // 如果响应状态不是ok,返回空数组 if (!response.ok) { return []; } // 将响应内容解析为Document数组类型 const documentList = (await response.json()) as Document[]; // 返回文档列表 return documentList; }; // 导出fetchDocuments函数作为默认导出 export default fetchDocuments; ``` ### 5.2. DocumentTools.tsx src/components/DocumentTools.tsx ```diff +// 逐行中文注释如下 +// 导入React中的useEffect和useState钩子函数 +import { useEffect, useState } from 'react'; +// 导入文档上传组件 import DocumentUploader from './DocumentUploader'; +// 导入文档查看组件 import DocumentViewer from './DocumentViewer'; +// 导入获取文档列表的API和Document类型定义 +import fetchDocuments, { type Document } from '../apis/fetchDocuments'; +// 定义DocumentTools函数组件 const DocumentTools: React.FC = () => { + // 定义refreshViewer状态,用于控制文档查看器是否需要刷新 + const [refreshViewer, setRefreshViewer] = useState(false); + // 定义documentList状态,用于存储文档列表 + const [documentList, setDocumentList] = useState([]); + // 组件首次挂载时,获取文档列表 + useEffect(() => { + // 调用fetchDocuments获取文档数据 + fetchDocuments().then((documents) => { + // 设置文档列表状态 + setDocumentList(documents); + }); + }, []); + // 当refreshViewer为true时,重新获取文档列表并重置refreshViewer + useEffect(() => { + // 判断refreshViewer是否为true + if (refreshViewer) { + // 重置refreshViewer为false + setRefreshViewer(false); + // 重新获取文档列表 + fetchDocuments().then((documents) => { + // 更新文档列表状态 + setDocumentList(documents); + }); + } + }, [refreshViewer]); + // 渲染上传组件和查看组件 return (
+ {/* 渲染文档上传组件,并传递setRefreshViewer用于刷新文档列表 */} + + {/* 渲染文档查看组件,并传递当前文档列表 */} +
); }; +// 导出DocumentTools组件,供其他模块使用 export default DocumentTools; ``` ### 5.3. DocumentUploader.tsx src/components/DocumentUploader.tsx ```diff // 导入React中的ChangeEvent类型和useState钩子 import { type ChangeEvent, useState } from 'react'; // 导入react-spinners库中的CircleLoader组件,用于加载动画 import { CircleLoader } from 'react-spinners'; // 导入自定义的insertDocument函数,用于上传文档 import insertDocument from '../apis/insertDocument'; // 定义HTMLInputEvent接口,继承自ChangeEvent,并指定target类型 interface HTMLInputEvent extends ChangeEvent { target: HTMLInputElement & EventTarget; } +// 定义DocumentUploader组件的props类型 +type DocumentUploaderProps = { + setRefreshViewer: (refresh: boolean) => void; +}; // 定义DocumentUploader组件 +const DocumentUploader = ({ setRefreshViewer }: DocumentUploaderProps) => { // 定义selectedFile状态,存储用户选择的文件 const [selectedFile, setSelectedFile] = useState(); // 定义isFilePicked状态,标记是否已选择文件 const [isFilePicked, setIsFilePicked] = useState(false); // 定义isLoading状态,标记是否正在上传 const [isLoading, setIsLoading] = useState(false); // 文件选择事件处理函数 const changeHandler = (event: HTMLInputEvent) => { // 判断event.target和event.target.files是否存在 if (event.target && event.target.files) { // 设置selectedFile为用户选择的第一个文件 setSelectedFile(event.target.files[0]); // 标记已选择文件 setIsFilePicked(true); } }; // 文件提交处理函数 const handleSubmission = () => { // 如果已选择文件 if (selectedFile) { // 设置加载状态为true setIsLoading(true); // 调用insertDocument上传文件 insertDocument(selectedFile).then(() => { + // 刷新viewer + setRefreshViewer(true); // 上传完成后重置selectedFile setSelectedFile(undefined); // 重置文件选择状态 setIsFilePicked(false); // 关闭加载动画 setIsLoading(false); }); } }; // 组件渲染部分 return (
{/* 文件输入框,接受pdf、txt、json、md格式文件,绑定changeHandler */} {/* 文件上传标签,点击可触发文件选择 */} {/* 显示已选择的文件名,否则提示选择文件 */}
{isFilePicked && selectedFile ? selectedFile.name : 'Select a file to insert'}
{/* 如果已选择文件且未加载,显示提交按钮 */} {isFilePicked && !isLoading && ( )} {/* 如果正在加载,显示加载动画 */} {isLoading && }
); }; // 导出DocumentUploader组件 export default DocumentUploader; ``` ### 5.4. DocumentViewer.tsx src/components/DocumentViewer.tsx ```diff +// 导入JSX类型,用于类型标注 +import type { JSX } from 'react'; +// 导入Document类型,用于文档数据类型标注 +import type { Document } from '../apis/fetchDocuments'; +// 定义文档标题最大长度常量 +const MAX_TITLE_LENGTH = 32; +// 定义文档内容最大长度常量 +const MAX_DOC_LENGTH = 150; +// 定义DocumentViewer组件的props类型,包含文档列表 +type DocumentViewerProps = { + documentList: Document[]; +}; +// 定义DocumentViewer组件,接收文档列表作为参数 +const DocumentViewer = ({ documentList }: DocumentViewerProps) => { + // 定义prepend函数,用于在数组前插入一个元素 + const prepend = (array: JSX.Element[], value: JSX.Element): JSX.Element[] => { + // 复制原数组 + const newArray = array.slice(); + // 在数组前插入新元素 + newArray.unshift(value); + // 返回新数组 + return newArray; + }; + // 遍历文档列表,生成对应的JSX元素数组 + let documentListElems = documentList.map((document) => { + // 处理文档id,超出最大长度则截断并添加省略号 + const id = + document.id.length < MAX_TITLE_LENGTH + ? document.id + : document.id.substring(0, MAX_TITLE_LENGTH) + '...'; + // 处理文档内容,超出最大长度则截断并添加省略号 + const text = + document.text.length < MAX_DOC_LENGTH + ? document.text + : document.text.substring(0, MAX_DOC_LENGTH) + '...'; + // 返回每个文档的JSX结构 + return ( +
+

{id}

+

{text}

+
+ ); + }); + // 在文档列表前插入标题元素 + documentListElems = prepend( + documentListElems, +
+

My Documents

+
, + ); + // 渲染组件 return (
+ {/* 如果有文档则显示文档列表,否则显示提示信息 */} + {documentListElems.length > 1 ? ( + documentListElems + ) : (

Upload your first document!

You will see the title and content here

+ )}
); }; +// 导出DocumentViewer组件 export default DocumentViewer; ``` ## 7. 搜索 ### 7.1. queryIndex.tsx src/apis/queryIndex.tsx ```tsx // 定义响应来源的数据类型,包括文本、文档ID、起始位置、结束位置和相似度 export type ResponseSources = { text: string; doc_id: string; start: number; end: number; similarity: number; }; // 定义查询响应的数据类型,包括返回文本和来源数组 export type QueryResponse = { text: string; sources: ResponseSources[]; }; // 定义异步函数queryIndex,接收查询字符串参数,返回Promise const queryIndex = async (query: string): Promise => { // 创建URL对象,指向本地查询接口 const queryURL = new URL('http://localhost:5601/query?'); // 将查询文本作为参数添加到URL中 queryURL.searchParams.append('text', query); // 发送fetch请求,mode设置为'cors'以支持跨域 const response = await fetch(queryURL, { mode: 'cors' }); // 如果响应状态不是ok,返回错误信息和空来源数组 if (!response.ok) { return { text: 'Error in query', sources: [] }; } // 解析响应体为JSON,并断言为QueryResponse类型 const queryResponse = (await response.json()) as QueryResponse; // 返回解析后的查询响应 return queryResponse; }; // 导出queryIndex函数作为默认导出 export default queryIndex; ``` ### 7.2. IndexQuery.tsx src/components/IndexQuery.tsx ```tsx // 引入React的useState钩子 import { useState } from 'react'; // 引入react-spinners中的CircleLoader组件,用于加载动画 import { CircleLoader } from 'react-spinners'; // 引入自定义的queryIndex函数和类型ResponseSources import queryIndex, { type ResponseSources } from '../apis/queryIndex'; // 定义IndexQuery组件 const IndexQuery = () => { // 定义isLoading状态,表示是否正在加载 const [isLoading, setLoading] = useState(false); // 定义responseText状态,存储查询返回的文本 const [responseText, setResponseText] = useState(''); // 定义responseSources状态,存储查询返回的来源数组 const [responseSources, setResponseSources] = useState([]); // 处理输入框回车事件的函数 const handleQuery = (e: React.KeyboardEvent) => { // 如果按下的是回车键 if (e.key === 'Enter') { // 设置加载状态为true setLoading(true); // 调用queryIndex函数进行查询 queryIndex(e.currentTarget.value).then((response) => { // 查询完成后设置加载状态为false setLoading(false); // 设置返回的文本 setResponseText(response.text); // 设置返回的来源数组 setResponseSources(response.sources); }); } }; // 根据responseSources生成对应的JSX元素数组 const sourceElems = responseSources.map((source) => { // 如果文档ID长度大于28,截断并加省略号,否则直接显示 const nodeTitle = source.doc_id.length > 28 ? source.doc_id.substring(0, 28) + '...' : source.doc_id; // 如果文本长度大于150,截断为130并加省略号,否则直接显示 const nodeText = source.text.length > 150 ? source.text.substring(0, 130) + '...' : source.text; // 返回每个来源的JSX结构 return (

{nodeTitle}

{nodeText}

Similarity={source.similarity}, start={source.start}, end={source.end}

); }); // 组件的渲染部分 return (
{/* 查询输入区域 */}
{/* 加载动画 */} {isLoading && } {/* 查询结果展示区域 */}
Query Response
{responseText || 'Enter a question to get a response...'}
{/* 查询来源展示区域 */}
Response Sources
{/* 如果有来源则显示,否则显示暂无来源 */} {sourceElems.length > 0 ? sourceElems : (
No sources available yet
)}
); }; // 导出IndexQuery组件 export default IndexQuery; ``` ### 7.3. App.tsx src/App.tsx ```diff +// 引入DocumentTools组件 import DocumentTools from './components/DocumentTools'; +// 引入IndexQuery组件 +import IndexQuery from './components/IndexQuery'; +// 定义App主组件 function App() { return ( + // 外层div容器
+ {/* 使用CSS Grid布局,分为两列,设置间距和内边距 */}
+ {/* 左侧为DocumentTools组件 */} + {/* 右侧为IndexQuery组件 */} +
); } +// 导出App组件作为默认导出 export default App; ```