ai
  • index
  • 1.欢迎来到LlamaIndex
  • 2.高级概念
  • 3.安装与设置
  • 4.入门教程(使用OpenAI)
  • 5.入门教程(使用本地LLMs)
  • 6.构建一个LLM应用
  • 7.使用LLMs
  • pydantic
  • asyncio
  • apikey
  • 8.RAG简介
  • 9.加载数据
  • 10.索引
  • 11.存储
  • 12.查询
  • weaviate
  • Cohere
  • warnings
  • WeaviateStart
  • spacy
  • 使用LlamaIndex构建全栈Web应用指南
  • back2
  • back4
  • front2
  • front4
  • front6
  • front8
  • llamaindex_backend
  • llamaindex_frontend
  • 1.初始化项目
  • 2.安装依赖
  • 3. 绘制布局
    • 3.1. DocumentTools.tsx
    • 3.2. DocumentUploader.tsx
    • 3.3. DocumentViewer.tsx
    • 3.4. App.tsx
    • 3.5. main.tsx
  • 4. 上传文件
    • 4.1. insertDocument.tsx
    • 4.2. DocumentUploader.tsx
  • 5. 文件列表
    • 5.1. fetchDocuments.tsx
    • 5.2. DocumentTools.tsx
    • 5.3. DocumentUploader.tsx
    • 5.4. DocumentViewer.tsx
  • 7. 搜索
    • 7.1. queryIndex.tsx
    • 7.2. IndexQuery.tsx
    • 7.3. App.tsx

1.初始化项目 #

npm create vite@latest

2.安装依赖 #

npm install react-spinners --save

3. 绘制布局 #

3.1. DocumentTools.tsx #

src/components/DocumentTools.tsx

import React from 'react';
import DocumentUploader from './DocumentUploader';
import DocumentViewer from './DocumentViewer';

const DocumentTools: React.FC = () => {
  return (
    <div>
      <DocumentUploader />
      <DocumentViewer />
    </div>
  );
};

export default DocumentTools;

3.2. DocumentUploader.tsx #

src/components/DocumentUploader.tsx

const DocumentUploader = () => {

  return (
    <div>
      <input
        type='file'
        name='file-input'
        id='file-input'
        accept='.pdf,.txt,.json,.md'
      />
      <label htmlFor='file-input'>
        Upload file
      </label>

      <div>
        {'Select a file to insert'}
      </div>

      <button>
        Submit
      </button>
    </div>
  );
};

export default DocumentUploader;

3.3. DocumentViewer.tsx #

src/components/DocumentViewer.tsx

const DocumentViewer = () => {
  return (
    <div>
      <div>
        <div>
          <p>Upload your first document!</p>
          <p>You will see the title and content here</p>
        </div>
      </div>
    </div>
  );
};

export default DocumentViewer;

3.4. App.tsx #

src/App.tsx

+import DocumentTools from './components/DocumentTools';
function App() {
  return (
+   <div>
+     <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', padding: '20px' }}>
+       <DocumentTools />
      </div>
+   </div>
+ );
}

+export default App;

3.5. main.tsx #

src/main.tsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

4. 上传文件 #

4.1. insertDocument.tsx #

src/apis/insertDocument.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

+// 导入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<File>();
+ // 定义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 (
    <div>
+     {/* 文件输入框,接受pdf、txt、json、md格式文件,绑定changeHandler */}
      <input
        type='file'
        name='file-input'
        id='file-input'
        accept='.pdf,.txt,.json,.md'
+       onChange={changeHandler}
      />
+     {/* 文件上传标签,点击可触发文件选择 */}
      <label htmlFor='file-input'>
        Upload file
      </label>

+     {/* 显示已选择的文件名,否则提示选择文件 */}
      <div>
+       {isFilePicked && selectedFile ? selectedFile.name : 'Select a file to insert'}
      </div>

+     {/* 如果已选择文件且未加载,显示提交按钮 */}
+     {isFilePicked && !isLoading && (
+       <button onClick={handleSubmission}>
+         Submit
+       </button>
+     )}
+     {/* 如果正在加载,显示加载动画 */}
+     {isLoading && <CircleLoader />}
    </div>
  );
};

+// 导出DocumentUploader组件
export default DocumentUploader;

5. 文件列表 #

5.1. fetchDocuments.tsx #

src/apis/fetchDocuments.tsx

// 定义Document类型,包含id和text两个字段
export type Document = {
  id: string;
  text: string;
};

// 定义异步函数fetchDocuments,返回Document类型的数组
const fetchDocuments = async (): Promise<Document[]> => {
  // 发送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

+// 逐行中文注释如下

+// 导入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<Document[]>([]);

+ // 组件首次挂载时,获取文档列表
+ 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 (
    <div>
+     {/* 渲染文档上传组件,并传递setRefreshViewer用于刷新文档列表 */}
+     <DocumentUploader setRefreshViewer={setRefreshViewer} />
+     {/* 渲染文档查看组件,并传递当前文档列表 */}
+     <DocumentViewer documentList={documentList} />
    </div>
  );
};

+// 导出DocumentTools组件,供其他模块使用
export default DocumentTools;

5.3. DocumentUploader.tsx #

src/components/DocumentUploader.tsx

// 导入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<File>();
  // 定义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 (
    <div>
      {/* 文件输入框,接受pdf、txt、json、md格式文件,绑定changeHandler */}
      <input
        type='file'
        name='file-input'
        id='file-input'
        accept='.pdf,.txt,.json,.md'
        onChange={changeHandler}
      />
      {/* 文件上传标签,点击可触发文件选择 */}
      <label htmlFor='file-input'>
        Upload file
      </label>

      {/* 显示已选择的文件名,否则提示选择文件 */}
      <div>
        {isFilePicked && selectedFile ? selectedFile.name : 'Select a file to insert'}
      </div>

      {/* 如果已选择文件且未加载,显示提交按钮 */}
      {isFilePicked && !isLoading && (
        <button onClick={handleSubmission}>
          Submit
        </button>
      )}
      {/* 如果正在加载,显示加载动画 */}
      {isLoading && <CircleLoader />}
    </div>
  );
};

// 导出DocumentUploader组件
export default DocumentUploader;

5.4. DocumentViewer.tsx #

src/components/DocumentViewer.tsx

+// 导入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 (
+     <div key={document.id}>
+       <p>{id}</p>
+       <p>{text}</p>
+     </div>
+   );
+ });
+ // 在文档列表前插入标题元素
+ documentListElems = prepend(
+   documentListElems,
+   <div key='viewer_title'>
+     <p>My Documents</p>
+   </div>,
+ );
+ // 渲染组件
  return (
    <div>
+     {/* 如果有文档则显示文档列表,否则显示提示信息 */}
+     {documentListElems.length > 1 ? (
+       documentListElems
+     ) : (
        <div>
          <p>Upload your first document!</p>
          <p>You will see the title and content here</p>
        </div>
+     )}
    </div>
  );
};

+// 导出DocumentViewer组件
export default DocumentViewer;

7. 搜索 #

7.1. queryIndex.tsx #

src/apis/queryIndex.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<QueryResponse>
const queryIndex = async (query: string): Promise<QueryResponse> => {
  // 创建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

// 引入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<ResponseSources[]>([]);

  // 处理输入框回车事件的函数
  const handleQuery = (e: React.KeyboardEvent<HTMLInputElement>) => {
    // 如果按下的是回车键
    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 (
      <div key={source.doc_id}>
        <p>{nodeTitle}</p>
        <p>{nodeText}</p>
        <p>Similarity={source.similarity}, start={source.start}, end={source.end}</p>
      </div>
    );
  });

  // 组件的渲染部分
  return (
    <div>
      {/* 查询输入区域 */}
      <div>
        <label htmlFor='query-text'>Ask a question!</label>
        <input
          type='text'
          name='query-text'
          placeholder='Enter a question here'
          onKeyDown={handleQuery}
        />
      </div>

      {/* 加载动画 */}
      {isLoading && <CircleLoader />}

      {/* 查询结果展示区域 */}
      <div>
        <div>Query Response</div>
        <div>{responseText || 'Enter a question to get a response...'}</div>
      </div>

      {/* 查询来源展示区域 */}
      <div>
        <div>Response Sources</div>
        <div>
          {/* 如果有来源则显示,否则显示暂无来源 */}
          {sourceElems.length > 0 ? sourceElems : (
            <div>No sources available yet</div>
          )}
        </div>
      </div>
    </div>
  );
};

// 导出IndexQuery组件
export default IndexQuery;

7.3. App.tsx #

src/App.tsx

+// 引入DocumentTools组件
import DocumentTools from './components/DocumentTools';
+// 引入IndexQuery组件
+import IndexQuery from './components/IndexQuery';

+// 定义App主组件
function App() {
  return (
+   // 外层div容器
    <div>
+     {/* 使用CSS Grid布局,分为两列,设置间距和内边距 */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', padding: '20px' }}>
+       {/* 左侧为DocumentTools组件 */}
        <DocumentTools />
+       {/* 右侧为IndexQuery组件 */}
+       <IndexQuery />
      </div>
    </div>
  );
}

+// 导出App组件作为默认导出
export default App;

访问验证

请输入访问令牌

Token不正确,请重新输入