Compare commits

..

11 Commits

Author SHA1 Message Date
044daff668 Fix document bug (#393)
### What problem does this PR solve?

As title

### Type of change

- [x] Documentation Update

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-04-16 19:23:09 +08:00
03f8b01b3b fix bug for fasetembed (#392)
### What problem does this PR solve?

Issue link:#325

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-04-16 19:12:12 +08:00
ad6f0a1ce5 feat: add overview (#391)
### What problem does this PR solve?

feat: render stats charts
feat: create api token
feat: delete api token
feat: add ChatApiKeyModal
feat: add RagLineChart


Issue link: #345

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-04-16 19:06:47 +08:00
b3843138f4 change version number in readme (#390)
### What problem does this PR solve?


### Type of change

- [x] Documentation Update
2024-04-16 19:04:29 +08:00
e0bdcbbeba fix: revert #359 (#388)
### What problem does this PR solve?

![图片](https://github.com/infiniflow/ragflow/assets/106524776/e62dc04d-dd72-4ef6-ab1f-a2a219dc197a)

revert #359

Issue link:#359

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-04-16 18:55:51 +08:00
582340a184 Added FAQ why document parsing gets stuck (#385)
### What problem does this PR solve?


### Type of change


- [x] Documentation Update
2024-04-16 16:55:44 +08:00
890561703b Add bce-embedding and fastembed (#383)
### What problem does this PR solve?


Issue link:#326

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-04-16 16:42:19 +08:00
a7be5d4e8b build ragflow image from scratch (#376)
### What problem does this PR solve?

issue: #205 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-04-16 12:29:58 +08:00
c344486aa0 enlarge max file number per user limit (#373)
### What problem does this PR solve?

Issue link:#370

### Type of change

- [x] Refactoring
2024-04-16 10:00:52 +08:00
111501af5e make <xxxx> visiable (#369)
### What problem does this PR solve?


![image](https://github.com/infiniflow/ragflow/assets/106524776/0c526a56-05b1-42f8-8bf5-cb23a97183b8)

make `<xxxx>` visiable
it was misinterpreted as part of the HTML tags

![image](https://github.com/infiniflow/ragflow/assets/106524776/1c42aef0-6989-40c1-b129-47a835b038a7)

Issue link:None

### Type of change

- [ ] Bug Fix (non-breaking change which fixes an issue)
- [ ] New Feature (non-breaking change which adds functionality)
- [ ] Breaking Change (fix or feature that could cause existing
functionality not to work as expected)
- [x] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Test cases
- [ ] Python SDK impacted, Need to update PyPI
- [ ] Other (please describe):
2024-04-16 09:47:57 +08:00
9e75bd4d88 change version number (#368)
### What problem does this PR solve?

Issue link: for release v0.1.0

### Type of change

- [x] Documentation Update
- [x] Refactoring
2024-04-15 19:51:18 +08:00
48 changed files with 1382 additions and 113 deletions

54
Dockerfile.scratch Normal file
View File

@ -0,0 +1,54 @@
FROM ubuntu:22.04
USER root
WORKDIR /ragflow
RUN apt-get update && apt-get install -y wget curl build-essential libopenmpi-dev
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \
bash ~/miniconda.sh -b -p /root/miniconda3 && \
rm ~/miniconda.sh && ln -s /root/miniconda3/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
echo ". /root/miniconda3/etc/profile.d/conda.sh" >> ~/.bashrc && \
echo "conda activate base" >> ~/.bashrc
ENV PATH /root/miniconda3/bin:$PATH
RUN conda create -y --name py11 python=3.11
ENV CONDA_DEFAULT_ENV py11
ENV CONDA_PREFIX /root/miniconda3/envs/py11
ENV PATH $CONDA_PREFIX/bin:$PATH
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get install -y nodejs
RUN apt-get install -y nginx
ADD ./web ./web
ADD ./api ./api
ADD ./conf ./conf
ADD ./deepdoc ./deepdoc
ADD ./rag ./rag
ADD ./requirements.txt ./requirements.txt
RUN apt install openmpi-bin openmpi-common libopenmpi-dev
ENV LD_LIBRARY_PATH /usr/lib/x86_64-linux-gnu/openmpi/lib:$LD_LIBRARY_PATH
RUN rm /root/miniconda3/envs/py11/compiler_compat/ld
RUN cd ./web && npm i && npm run build
RUN conda run -n py11 pip install -i https://mirrors.aliyun.com/pypi/simple/ -r ./requirements.txt
RUN apt-get update && \
apt-get install -y libglib2.0-0 libgl1-mesa-glx && \
rm -rf /var/lib/apt/lists/*
RUN conda run -n py11 pip install -i https://mirrors.aliyun.com/pypi/simple/ ollama
RUN conda run -n py11 python -m nltk.downloader punkt
RUN conda run -n py11 python -m nltk.downloader wordnet
ENV PYTHONPATH=/ragflow/
ENV HF_ENDPOINT=https://hf-mirror.com
ADD docker/entrypoint.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

View File

@ -15,7 +15,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/RAGFLOW-LLM-white?&labelColor=dd0af7"></a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v1.0-brightgreen"
alt="docker pull ragflow:v1.0"></a>
alt="docker pull infiniflow/ragflow:v0.2.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=7d09f1" alt="license">
</a>
@ -55,6 +55,8 @@
## 📌 Latest Features
- 2024-04-16 Add an embedding model 'bce-embedding-base_v1' from [BCEmbedding](https://github.com/netease-youdao/BCEmbedding).
- 2024-04-16 Add [FastEmbed](https://github.com/qdrant/fastembed) is designed for light and speeding embedding.
- 2024-04-11 Support [Xinference](./docs/xinference.md) for local LLM deployment.
- 2024-04-10 Add a new layout recognization model for analyzing Laws documentation.
- 2024-04-08 Support [Ollama](./docs/ollama.md) for local LLM deployment.
@ -171,7 +173,7 @@ To build the Docker images from source:
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
$ docker build -t infiniflow/ragflow:v1.0 .
$ docker build -t infiniflow/ragflow:v0.2.0 .
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d

View File

@ -15,7 +15,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/RAGFLOW-LLM-white?&labelColor=dd0af7"></a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v1.0-brightgreen"
alt="docker pull ragflow:v1.0"></a>
alt="docker pull infiniflow/ragflow:v0.2.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=7d09f1" alt="license">
</a>
@ -55,6 +55,8 @@
## 📌 最新の機能
- 2024-04-16 [BCEmbedding](https://github.com/netease-youdao/BCEmbedding) から埋め込みモデル「bce-embedding-base_v1」を追加します。
- 2024-04-16 [FastEmbed](https://github.com/qdrant/fastembed) は、軽量かつ高速な埋め込み用に設計されています。
- 2024-04-11 ローカル LLM デプロイメント用に [Xinference](./docs/xinference.md) をサポートします。
- 2024-04-10 メソッド「Laws」に新しいレイアウト認識モデルを追加します。
- 2024-04-08 [Ollama](./docs/ollama.md) を使用した大規模モデルのローカライズされたデプロイメントをサポートします。
@ -171,7 +173,7 @@
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
$ docker build -t infiniflow/ragflow:v1.0 .
$ docker build -t infiniflow/ragflow:v0.2.0 .
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d

View File

@ -15,7 +15,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/RAGFLOW-LLM-white?&labelColor=dd0af7"></a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v1.0-brightgreen"
alt="docker pull ragflow:v1.0"></a>
alt="docker pull infiniflow/ragflow:v0.2.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=7d09f1" alt="license">
</a>
@ -55,6 +55,8 @@
## 📌 新增功能
- 2024-04-16 添加嵌入模型 [BCEmbedding](https://github.com/netease-youdao/BCEmbedding) 。
- 2024-04-16 添加 [FastEmbed](https://github.com/qdrant/fastembed) 专为轻型和高速嵌入而设计。
- 2024-04-11 支持用 [Xinference](./docs/xinference.md) 本地化部署大模型。
- 2024-04-10 为Laws版面分析增加了底层模型。
- 2024-04-08 支持用 [Ollama](./docs/ollama.md) 本地化部署大模型。
@ -171,7 +173,7 @@
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
$ docker build -t infiniflow/ragflow:v1.0 .
$ docker build -t infiniflow/ragflow:v0.2.0 .
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d

View File

@ -252,7 +252,7 @@ def retrieval_test():
return get_data_error_result(retmsg="Knowledgebase not found!")
embd_mdl = TenantLLMService.model_instance(
kb.tenant_id, LLMType.EMBEDDING.value)
kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id)
ranks = retrievaler.retrieval(question, embd_mdl, kb.tenant_id, [kb_id], page, size, similarity_threshold,
vector_similarity_weight, top, doc_ids)
for c in ranks["chunks"]:

View File

@ -15,6 +15,7 @@
#
import base64
import os
import pathlib
import re
@ -57,7 +58,7 @@ def upload():
if not e:
return get_data_error_result(
retmsg="Can't find this knowledgebase!")
if DocumentService.get_doc_count(kb.tenant_id) >= 128:
if DocumentService.get_doc_count(kb.tenant_id) >= int(os.environ.get('MAX_FILE_NUM_PER_USER', 8192)):
return get_data_error_result(
retmsg="Exceed the maximum file number of a free user!")

View File

@ -28,7 +28,7 @@ from rag.llm import EmbeddingModel, ChatModel
def factories():
try:
fac = LLMFactoriesService.get_all()
return get_json_result(data=[f.to_dict() for f in fac])
return get_json_result(data=[f.to_dict() for f in fac if f.name not in ["QAnything", "FastEmbed"]])
except Exception as e:
return server_error_response(e)
@ -174,7 +174,7 @@ def list():
llms = [m.to_dict()
for m in llms if m.status == StatusEnum.VALID.value]
for m in llms:
m["available"] = m["fid"] in facts or m["llm_name"].lower() == "flag-embedding"
m["available"] = m["fid"] in facts or m["llm_name"].lower() == "flag-embedding" or m["fid"] in ["QAnything","FastEmbed"]
llm_set = set([m["llm_name"] for m in llms])
for o in objs:

View File

@ -18,7 +18,7 @@ import time
import uuid
from api.db import LLMType, UserTenantRole
from api.db.db_models import init_database_tables as init_web_db, LLMFactories, LLM
from api.db.db_models import init_database_tables as init_web_db, LLMFactories, LLM, TenantLLM
from api.db.services import UserService
from api.db.services.llm_service import LLMFactoriesService, LLMService, TenantLLMService, LLMBundle
from api.db.services.user_service import TenantService, UserTenantService
@ -114,12 +114,16 @@ factory_infos = [{
"logo": "",
"tags": "TEXT EMBEDDING",
"status": "1",
},
{
}, {
"name": "Xinference",
"logo": "",
"tags": "LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION",
"status": "1",
},{
"name": "QAnything",
"logo": "",
"tags": "LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION",
"status": "1",
},
# {
# "name": "文心一言",
@ -254,12 +258,6 @@ def init_llm_factory():
"tags": "LLM,CHAT,",
"max_tokens": 7900,
"model_type": LLMType.CHAT.value
}, {
"fid": factory_infos[4]["name"],
"llm_name": "flag-embedding",
"tags": "TEXT EMBEDDING,",
"max_tokens": 128 * 1000,
"model_type": LLMType.EMBEDDING.value
}, {
"fid": factory_infos[4]["name"],
"llm_name": "moonshot-v1-32k",
@ -325,6 +323,14 @@ def init_llm_factory():
"max_tokens": 2147483648,
"model_type": LLMType.EMBEDDING.value
},
# ------------------------ QAnything -----------------------
{
"fid": factory_infos[7]["name"],
"llm_name": "maidalun1020/bce-embedding-base_v1",
"tags": "TEXT EMBEDDING,",
"max_tokens": 512,
"model_type": LLMType.EMBEDDING.value
},
]
for info in factory_infos:
try:
@ -337,8 +343,10 @@ def init_llm_factory():
except Exception as e:
pass
LLMFactoriesService.filter_delete([LLMFactories.name=="Local"])
LLMService.filter_delete([LLM.fid=="Local"])
LLMFactoriesService.filter_delete([LLMFactories.name == "Local"])
LLMService.filter_delete([LLM.fid == "Local"])
LLMService.filter_delete([LLM.fid == "Moonshot", LLM.llm_name == "flag-embedding"])
TenantLLMService.filter_delete([TenantLLM.llm_factory == "Moonshot", TenantLLM.llm_name == "flag-embedding"])
"""
drop table llm;

View File

@ -80,8 +80,12 @@ def chat(dialog, messages, **kwargs):
raise LookupError("LLM(%s) not found" % dialog.llm_id)
max_tokens = 1024
else: max_tokens = llm[0].max_tokens
kbs = KnowledgebaseService.get_by_ids(dialog.kb_ids)
embd_nms = list(set([kb.embd_id for kb in kbs]))
assert len(embd_nms) == 1, "Knowledge bases use different embedding models."
questions = [m["content"] for m in messages if m["role"] == "user"]
embd_mdl = LLMBundle(dialog.tenant_id, LLMType.EMBEDDING)
embd_mdl = LLMBundle(dialog.tenant_id, LLMType.EMBEDDING, embd_nms[0])
chat_mdl = LLMBundle(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
prompt_config = dialog.prompt_config

View File

@ -66,7 +66,7 @@ class TenantLLMService(CommonService):
raise LookupError("Tenant not found")
if llm_type == LLMType.EMBEDDING.value:
mdlnm = tenant.embd_id
mdlnm = tenant.embd_id if not llm_name else llm_name
elif llm_type == LLMType.SPEECH2TEXT.value:
mdlnm = tenant.asr_id
elif llm_type == LLMType.IMAGE2TEXT.value:
@ -77,9 +77,19 @@ class TenantLLMService(CommonService):
assert False, "LLM type error"
model_config = cls.get_api_key(tenant_id, mdlnm)
if model_config: model_config = model_config.to_dict()
if not model_config:
if llm_type == LLMType.EMBEDDING.value:
llm = LLMService.query(llm_name=llm_name)
if llm and llm[0].fid in ["QAnything", "FastEmbed"]:
model_config = {"llm_factory": llm[0].fid, "api_key":"", "llm_name": llm_name, "api_base": ""}
if not model_config:
if llm_name == "flag-embedding":
model_config = {"llm_factory": "Tongyi-Qianwen", "api_key": "",
"llm_name": llm_name, "api_base": ""}
else:
raise LookupError("Model({}) not authorized".format(mdlnm))
model_config = model_config.to_dict()
if llm_type == LLMType.EMBEDDING.value:
if model_config["llm_factory"] not in EmbeddingModel:
return

View File

@ -41,7 +41,7 @@ class TaskService(CommonService):
Document.size,
Knowledgebase.tenant_id,
Knowledgebase.language,
Tenant.embd_id,
Knowledgebase.embd_id,
Tenant.img2txt_id,
Tenant.asr_id,
cls.model.update_time]

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
import re, copy, time, datetime, demjson, \
import re, copy, time, datetime, demjson3, \
traceback, signal
import numpy as np
from deepdoc.parser.resume.entities import degrees, schools, corporations
@ -197,7 +197,7 @@ def forProj(cv):
def json_loads(line):
return demjson.decode(re.sub(r": *(True|False)", r": '\1'", line))
return demjson3.decode(re.sub(r": *(True|False)", r": '\1'", line))
def forWork(cv):

View File

@ -9,7 +9,7 @@ services:
condition: service_healthy
es01:
condition: service_healthy
image: swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:v1.0
image: swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:v0.2.0
container_name: ragflow-server
ports:
- ${SVR_HTTP_PORT}:9380

View File

@ -9,7 +9,7 @@ services:
condition: service_healthy
es01:
condition: service_healthy
image: infiniflow/ragflow:v1.0
image: infiniflow/ragflow:v0.2.0
container_name: ragflow-server
ports:
- ${SVR_HTTP_PORT}:9380
@ -23,7 +23,7 @@ services:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
environment:
- TZ=${TIMEZONE}
- HF_ENDPOINT=https://huggingface.com
- HF_ENDPOINT=https://huggingface.co
networks:
- ragflow
restart: always

View File

@ -90,6 +90,17 @@ Ignore this warning and continue. All system warnings can be ignored.
Parsing requests have to wait in queue due to limited server resources. We are currently enhancing our algorithms and increasing computing power.
### Why does my document parsing stall at under one percent?
If your RAGFlow is deployed *locally*, try the following:
1. Check the log of your RAGFlow server to see if it is running properly:
```bash
docker logs -f ragflow-server
```
2. Check if the **tast_executor.py** process exist.
3. Check if your RAGFlow server can access hf-mirror.com or huggingface.com.
### How to handle `Index failure`?
An index failure usually indicates an unavailable Elasticsearch service.
@ -108,7 +119,7 @@ $ docker ps
*The system displays the following if all your RAGFlow components are running properly:*
```
5bc45806b680 infiniflow/ragflow:v1.0 "./entrypoint.sh" 11 hours ago Up 11 hours 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:9380->9380/tcp, :::9380->9380/tcp ragflow-server
5bc45806b680 infiniflow/ragflow:v0.2.0 "./entrypoint.sh" 11 hours ago Up 11 hours 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:9380->9380/tcp, :::9380->9380/tcp ragflow-server
91220e3285dd docker.elastic.co/elasticsearch/elasticsearch:8.11.3 "/bin/tini -- /usr/l…" 11 hours ago Up 11 hours (healthy) 9300/tcp, 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp ragflow-es-01
d8c86f06c56b mysql:5.7.18 "docker-entrypoint.s…" 7 days ago Up 16 seconds (healthy) 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp ragflow-mysql
cd29bcb254bc quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z "/usr/bin/docker-ent…" 2 weeks ago Up 11 hours 0.0.0.0:9001->9001/tcp, :::9001->9001/tcp, 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp ragflow-minio

View File

@ -31,7 +31,7 @@ $ docker exec -it ollama ollama run mistral
<img src="https://github.com/infiniflow/ragflow/assets/12318111/a9df198a-226d-4f30-b8d7-829f00256d46" width="1300"/>
</div>
> Base URL: Enter the base URL where the Ollama service is accessible, like, http://<your-ollama-endpoint-domain>:11434
> Base URL: Enter the base URL where the Ollama service is accessible, like, `http://<your-ollama-endpoint-domain>:11434`.
- Use Ollama Models.

View File

@ -31,7 +31,7 @@ $ xinference launch -u mistral --model-name mistral-v0.1 --size-in-billions 7 --
<img src="https://github.com/infiniflow/ragflow/assets/12318111/bcbf4d7a-ade6-44c7-ad5f-0a92c8a73789" width="1300"/>
</div>
> Base URL: Enter the base URL where the Xinference service is accessible, like, http://<your-xinference-endpoint-domain>:9997/v1
> Base URL: Enter the base URL where the Xinference service is accessible, like, `http://<your-xinference-endpoint-domain>:9997/v1`.
- Use Xinference Models.

View File

@ -24,8 +24,8 @@ EmbeddingModel = {
"Xinference": XinferenceEmbed,
"Tongyi-Qianwen": HuEmbedding, #QWenEmbed,
"ZHIPU-AI": ZhipuEmbed,
"Moonshot": HuEmbedding,
"FastEmbed": FastEmbed
"FastEmbed": FastEmbed,
"QAnything": QAnythingEmbed
}

View File

@ -20,7 +20,6 @@ from abc import ABC
from ollama import Client
import dashscope
from openai import OpenAI
from fastembed import TextEmbedding
from FlagEmbedding import FlagModel
import torch
import numpy as np
@ -28,6 +27,7 @@ import numpy as np
from api.utils.file_utils import get_project_base_directory
from rag.utils import num_tokens_from_string
try:
flag_model = FlagModel(os.path.join(
get_project_base_directory(),
@ -82,8 +82,10 @@ class HuEmbedding(Base):
class OpenAIEmbed(Base):
def __init__(self, key, model_name="text-embedding-ada-002", base_url="https://api.openai.com/v1"):
if not base_url: base_url="https://api.openai.com/v1"
def __init__(self, key, model_name="text-embedding-ada-002",
base_url="https://api.openai.com/v1"):
if not base_url:
base_url = "https://api.openai.com/v1"
self.client = OpenAI(api_key=key, base_url=base_url)
self.model_name = model_name
@ -183,10 +185,12 @@ class FastEmbed(Base):
threads: Optional[int] = None,
**kwargs,
):
from fastembed import TextEmbedding
self._model = TextEmbedding(model_name, cache_dir, threads, **kwargs)
def encode(self, texts: list, batch_size=32):
# Using the internal tokenizer to encode the texts and get the total number of tokens
# Using the internal tokenizer to encode the texts and get the total
# number of tokens
encodings = self._model.model.tokenizer.encode_batch(texts)
total_tokens = sum(len(e) for e in encodings)
@ -195,7 +199,8 @@ class FastEmbed(Base):
return np.array(embeddings), total_tokens
def encode_queries(self, text: str):
# Using the internal tokenizer to encode the texts and get the total number of tokens
# Using the internal tokenizer to encode the texts and get the total
# number of tokens
encoding = self._model.model.tokenizer.encode(text)
embedding = next(self._model.query_embed(text)).tolist()
@ -218,3 +223,33 @@ class XinferenceEmbed(Base):
model=self.model_name)
return np.array(res.data[0].embedding), res.usage.total_tokens
class QAnythingEmbed(Base):
_client = None
def __init__(self, key=None, model_name="maidalun1020/bce-embedding-base_v1", **kwargs):
from BCEmbedding import EmbeddingModel as qanthing
if not QAnythingEmbed._client:
try:
print("LOADING BCE...")
QAnythingEmbed._client = qanthing(model_name_or_path=os.path.join(
get_project_base_directory(),
"rag/res/bce-embedding-base_v1"))
except Exception as e:
QAnythingEmbed._client = qanthing(
model_name_or_path=model_name.replace(
"maidalun1020", "InfiniFlow"))
def encode(self, texts: list, batch_size=10):
res = []
token_count = 0
for t in texts:
token_count += num_tokens_from_string(t)
for i in range(0, len(texts), batch_size):
embds = QAnythingEmbed._client.encode(texts[i:i + batch_size])
res.extend(embds)
return np.array(res), token_count
def encode_queries(self, text):
embds = QAnythingEmbed._client.encode([text])
return np.array(embds[0]), num_tokens_from_string(text)

View File

@ -46,7 +46,7 @@ class Dealer:
"k": topk,
"similarity": sim,
"num_candidates": topk * 2,
"query_vector": list(qv)
"query_vector": [float(v) for v in qv]
}
def search(self, req, idxnm, emb_mdl=None):

View File

@ -244,8 +244,9 @@ def main(comm, mod):
for _, r in rows.iterrows():
callback = partial(set_progress, r["id"], r["from_page"], r["to_page"])
try:
embd_mdl = LLMBundle(r["tenant_id"], LLMType.EMBEDDING)
embd_mdl = LLMBundle(r["tenant_id"], LLMType.EMBEDDING, llm_name=r["embd_id"], lang=r["language"])
except Exception as e:
traceback.print_stack(e)
callback(prog=-1, msg=str(e))
continue

View File

@ -19,7 +19,7 @@ cryptography==42.0.5
dashscope==1.14.1
datasets==2.17.1
datrie==0.8.2
demjson==2.2.4
demjson3==3.0.6
dill==0.3.8
distro==1.9.0
elastic-transport==8.12.0
@ -132,3 +132,5 @@ xpinyin==0.7.6
xxhash==3.4.1
yarl==1.9.4
zhipuai==2.0.1
BCEmbedding
loguru==0.7.2

345
web/package-lock.json generated
View File

@ -13,19 +13,21 @@
"antd": "^5.12.7",
"axios": "^1.6.3",
"classnames": "^2.5.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.16",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1",
"react-pdf-highlighter": "^6.1.0",
"react-string-replace": "^1.1.1",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.12.4",
"remark-gfm": "^4.0.0",
"umi": "^4.0.90",
"umi-request": "^1.4.0",
@ -36,6 +38,7 @@
"@react-dev-inspector/umi4-plugin": "^2.0.1",
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/uuid": "^9.0.8",
@ -2676,6 +2679,60 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.8.tgz",
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.6",
"resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.6.tgz",
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.3.tgz",
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@ -2884,6 +2941,15 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-copy-to-clipboard": {
"version": "5.0.7",
"resolved": "https://registry.npmmirror.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.7.tgz",
"integrity": "sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-dom": {
"version": "18.2.18",
"resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.18.tgz",
@ -5832,6 +5898,14 @@
"node": ">=12"
}
},
"node_modules/clsx": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.0.tgz",
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
"engines": {
"node": ">=6"
}
},
"node_modules/coa": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz",
@ -6640,11 +6714,132 @@
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-1.2.4.tgz",
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-polygon": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-1.0.6.tgz",
"integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ=="
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -6705,6 +6900,11 @@
"node": ">=0.10.0"
}
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/decode-named-character-reference": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
@ -7032,6 +7232,15 @@
"utila": "~0.4"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
@ -8151,6 +8360,11 @@
"es5-ext": "~0.10.14"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz",
@ -8356,6 +8570,14 @@
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-equals": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.0.1.tgz",
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz",
@ -9693,6 +9915,14 @@
"node": ">= 0.4"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"engines": {
"node": ">=12"
}
},
"node_modules/intersection-observer": {
"version": "0.12.2",
"resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz",
@ -11925,6 +12155,7 @@
"version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"devOptional": true,
"engines": {
"node": "*"
}
@ -14356,6 +14587,18 @@
"react-dom": "18.2.0"
}
},
"node_modules/react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"dependencies": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": "^15.3.0 || 16 || 17 || 18"
}
},
"node_modules/react-dev-inspector": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz",
@ -14934,6 +15177,20 @@
"react": ">=15"
}
},
"node_modules/react-smooth": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.1.tgz",
"integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-spinkit": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz",
@ -14968,6 +15225,21 @@
"react": ">= 0.14.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz",
@ -15145,6 +15417,41 @@
"node": ">= 12.13.0"
}
},
"node_modules/recharts": {
"version": "2.12.4",
"resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.12.4.tgz",
"integrity": "sha512-dM4skmk4fDKEDjL9MNunxv6zcTxePGVEzRnLDXALRpfJ85JoQ0P0APJ/CoJlmnQI0gPjBlOkjzrwrfQrRST3KA==",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
"react-is": "^16.10.2",
"react-smooth": "^4.0.0",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/recharts/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/recursive-readdir": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
@ -17000,9 +17307,7 @@
"node_modules/tiny-invariant": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
"dev": true,
"peer": true
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
@ -18221,6 +18526,38 @@
"unist-util-stringify-position": "^4.0.0"
}
},
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz",
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/victory-vendor/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/vite": {
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz",

View File

@ -17,19 +17,21 @@
"antd": "^5.12.7",
"axios": "^1.6.3",
"classnames": "^2.5.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.16",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1",
"react-pdf-highlighter": "^6.1.0",
"react-string-replace": "^1.1.1",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.12.4",
"remark-gfm": "^4.0.0",
"umi": "^4.0.90",
"umi-request": "^1.4.0",
@ -40,6 +42,7 @@
"@react-dev-inspector/umi4-plugin": "^2.0.1",
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/uuid": "^9.0.8",

View File

@ -6,6 +6,21 @@ import zh_HK from 'antd/locale/zh_HK';
import React, { ReactNode, useEffect, useState } from 'react';
import storage from './utils/authorizationUtil';
import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import localeData from 'dayjs/plugin/localeData';
import weekday from 'dayjs/plugin/weekday';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import weekYear from 'dayjs/plugin/weekYear';
dayjs.extend(customParseFormat);
dayjs.extend(advancedFormat);
dayjs.extend(weekday);
dayjs.extend(localeData);
dayjs.extend(weekOfYear);
dayjs.extend(weekYear);
const AntLanguageMap = {
en: enUS,
zh: zhCN,

View File

@ -0,0 +1,27 @@
import { useTranslate } from '@/hooks/commonHooks';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { useState } from 'react';
import { CopyToClipboard as Clipboard, Props } from 'react-copy-to-clipboard';
const CopyToClipboard = ({ text }: Props) => {
const [copied, setCopied] = useState(false);
const { t } = useTranslate('common');
const handleCopy = () => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
};
return (
<Tooltip title={copied ? t('copied') : t('copy')}>
<Clipboard text={text} onCopy={handleCopy}>
{copied ? <CheckOutlined /> : <CopyOutlined />}
</Clipboard>
</Tooltip>
);
};
export default CopyToClipboard;

View File

@ -0,0 +1,88 @@
import {
CartesianGrid,
Legend,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart';
const data = [
{
name: 'Page A',
uv: 4000,
pv: 2400,
},
{
name: 'Page B',
uv: 3000,
pv: 1398,
},
{
name: 'Page C',
uv: 2000,
pv: 9800,
},
{
name: 'Page D',
uv: 2780,
pv: 3908,
},
{
name: 'Page E',
uv: 1890,
pv: 4800,
},
{
name: 'Page F',
uv: 2390,
pv: 3800,
},
{
name: 'Page G',
uv: 3490,
pv: 4300,
},
];
interface IProps extends CategoricalChartProps {
data?: Array<{ xAxis: string; yAxis: number }>;
}
const RagLineChart = ({ data }: IProps) => {
return (
<ResponsiveContainer width="100%" height="100%">
<LineChart
// width={500}
// height={300}
data={data}
margin={
{
// top: 5,
// right: 30,
// left: 20,
// bottom: 10,
}
}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="xAxis" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="yAxis"
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
{/* <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> */}
</LineChart>
</ResponsiveContainer>
);
};
export default RagLineChart;

View File

@ -1,4 +1,9 @@
import { IConversation, IDialog } from '@/interfaces/database/chat';
import {
IConversation,
IDialog,
IStats,
IToken,
} from '@/interfaces/database/chat';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'umi';
@ -164,3 +169,82 @@ export const useCompleteConversation = () => {
return completeConversation;
};
// #region API provided for external calls
export const useCreateToken = (dialogId: string) => {
const dispatch = useDispatch();
const createToken = useCallback(() => {
return dispatch<any>({
type: 'chatModel/createToken',
payload: { dialogId },
});
}, [dispatch, dialogId]);
return createToken;
};
export const useListToken = () => {
const dispatch = useDispatch();
const listToken = useCallback(
(dialogId: string) => {
return dispatch<any>({
type: 'chatModel/listToken',
payload: { dialogId },
});
},
[dispatch],
);
return listToken;
};
export const useSelectTokenList = () => {
const tokenList: IToken[] = useSelector(
(state: any) => state.chatModel.tokenList,
);
return tokenList;
};
export const useRemoveToken = () => {
const dispatch = useDispatch();
const removeToken = useCallback(
(payload: { tenantId: string; dialogId: string; tokens: string[] }) => {
return dispatch<any>({
type: 'chatModel/removeToken',
payload: payload,
});
},
[dispatch],
);
return removeToken;
};
export const useFetchStats = () => {
const dispatch = useDispatch();
const fetchStats = useCallback(
(payload: any) => {
return dispatch<any>({
type: 'chatModel/getStats',
payload,
});
},
[dispatch],
);
return fetchStats;
};
export const useSelectStats = () => {
const stats: IStats = useSelector((state: any) => state.chatModel.stats);
return stats;
};
//#endregion

View File

@ -91,3 +91,21 @@ export interface Docagg {
// term_similarity: number;
// vector_similarity: number;
// }
export interface IToken {
create_date: string;
create_time: number;
tenant_id: string;
token: string;
update_date?: any;
update_time?: any;
}
export interface IStats {
pv: [string, number][];
uv: [string, number][];
speed: [string, number][];
tokens: [string, number][];
round: [string, number][];
thumb_up: [string, number][];
}

View File

@ -20,6 +20,8 @@ export default {
language: 'Language',
languageMessage: 'Please input your language!',
languagePlaceholder: 'select your language',
copy: 'Copy',
copied: 'Copied',
},
login: {
login: 'Sign in',
@ -335,6 +337,24 @@ export default {
'This sets the maximum length of the models output, measured in the number of tokens (words or pieces of words).',
quote: 'Show Quote',
quoteTip: 'Should the source of the original text be displayed?',
overview: 'Overview',
pv: 'Number of messages',
uv: 'Active user number',
speed: 'Token output speed',
tokens: 'Consume the token number',
round: 'Session Interaction Number',
thumbUp: 'customer satisfaction',
publicUrl: 'Public URL',
preview: 'Preview',
embedded: 'Embedded',
serviceApiEndpoint: 'Service API Endpoint',
apiKey: 'Api Key',
apiReference: 'Api Reference',
dateRange: 'Date Range:',
backendServiceApi: 'Backend service API',
createNewKey: 'Create new key',
created: 'Created',
action: 'Action',
},
setting: {
profile: 'Profile',

View File

@ -15,11 +15,13 @@ export default {
edit: '編輯',
upload: '上傳',
english: '英語',
chinese: '中文簡體',
traditionalChinese: '中文繁體',
chinese: '簡體中文',
traditionalChinese: '繁體中文',
language: '語言',
languageMessage: '請輸入語言',
languagePlaceholder: '請選擇語言',
copy: '複製',
copied: '複製成功',
},
login: {
login: '登入',
@ -269,7 +271,7 @@ export default {
systemMessage: '請輸入',
systemTip:
'當LLM回答問題時你需要LLM遵循的說明比如角色設計、答案長度和答案語言等。',
topN: 'top n',
topN: 'Top N',
topNTip: `並非所有相似度得分高於“相似度閾值”的塊都會被提供給法學碩士。LLM 只能看到這些“Top N”塊。`,
variable: '變量',
variableTip: `如果您使用对话 API变量可能会帮助您使用不同的策略与客户聊天。
@ -310,6 +312,24 @@ export default {
'這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
quote: '顯示引文',
quoteTip: '是否應該顯示原文出處?',
overview: '概覽',
pv: '消息數',
uv: '活躍用戶數',
speed: 'Token 輸出速度',
tokens: '消耗Token數',
round: '會話互動數',
thumbUp: '用戶滿意度',
publicUrl: '公共url',
preview: '預覽',
embedded: '嵌入',
serviceApiEndpoint: '服務API端點',
apiKey: 'API鍵',
apiReference: 'API參考',
dateRange: '日期範圍:',
backendServiceApi: '後端服務API',
createNewKey: '創建新密鑰',
created: '創建於',
action: '操作',
},
setting: {
profile: '概述',

View File

@ -15,11 +15,13 @@ export default {
edit: '编辑',
upload: '上传',
english: '英文',
chinese: '中文简体',
traditionalChinese: '中文繁体',
chinese: '简体中文',
traditionalChinese: '繁体中文',
language: '语言',
languageMessage: '请输入语言',
languagePlaceholder: '请选择语言',
copy: '复制',
copied: '复制成功',
},
login: {
login: '登录',
@ -326,6 +328,24 @@ export default {
'这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
quote: '显示引文',
quoteTip: '是否应该显示原文出处?',
overview: '概览',
pv: '消息数',
uv: '活跃用户数',
speed: 'Token 输出速度',
tokens: '消耗Token数',
round: '会话互动数',
thumbUp: '用户满意度',
publicUrl: '公共Url',
preview: '预览',
embedded: '嵌入',
serviceApiEndpoint: '服务API端点',
apiKey: 'API键',
apiReference: 'API参考',
dateRange: '日期范围:',
backendServiceApi: '后端服务API',
createNewKey: '创建新密钥',
created: '创建于',
action: '操作',
},
setting: {
profile: '概要',

View File

@ -26,6 +26,7 @@ import ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal';
import { formatDate } from '@/utils/date';
import styles from './index.less';
const KnowledgeFile = () => {
@ -94,6 +95,9 @@ const KnowledgeFile = () => {
title: t('uploadDate'),
dataIndex: 'create_date',
key: 'create_date',
render(value) {
return formatDate(value);
},
},
{
title: t('chunkMethod'),

View File

@ -0,0 +1,70 @@
import CopyToClipboard from '@/components/copy-to-clipboard';
import { useTranslate } from '@/hooks/commonHooks';
import { IModalProps } from '@/interfaces/common';
import { IToken } from '@/interfaces/database/chat';
import { formatDate } from '@/utils/date';
import { DeleteOutlined } from '@ant-design/icons';
import type { TableProps } from 'antd';
import { Button, Modal, Space, Table } from 'antd';
import { useOperateApiKey } from '../hooks';
const ChatApiKeyModal = ({
visible,
dialogId,
hideModal,
}: IModalProps<any> & { dialogId: string }) => {
const { createToken, removeToken, tokenList, listLoading, creatingLoading } =
useOperateApiKey(visible, dialogId);
const { t } = useTranslate('chat');
const columns: TableProps<IToken>['columns'] = [
{
title: 'Token',
dataIndex: 'token',
key: 'token',
render: (text) => <a>{text}</a>,
},
{
title: t('created'),
dataIndex: 'create_date',
key: 'create_date',
render: (text) => formatDate(text),
},
{
title: t('action'),
key: 'action',
render: (_, record) => (
<Space size="middle">
<CopyToClipboard text={record.token}></CopyToClipboard>
<DeleteOutlined
onClick={() => removeToken(record.token, record.tenant_id)}
/>
</Space>
),
},
];
return (
<>
<Modal
title={t('apiKey')}
open={visible}
onCancel={hideModal}
style={{ top: 300 }}
width={'50vw'}
>
<Table
columns={columns}
dataSource={tokenList}
rowKey={'token'}
loading={listLoading}
/>
<Button onClick={createToken} loading={creatingLoading}>
{t('createNewKey')}
</Button>
</Modal>
</>
);
};
export default ChatApiKeyModal;

View File

@ -1,6 +1,6 @@
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
import { PlusOutlined } from '@ant-design/icons';
import { Form, Input, Select, Upload } from 'antd';
import { Form, Input, Select, Switch, Upload } from 'antd';
import classNames from 'classnames';
import { ISegmentedContentProps } from '../interface';
@ -83,6 +83,15 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
>
<Input.TextArea autoSize={{ minRows: 5 }} />
</Form.Item>
<Form.Item
label={t('quote')}
valuePropName="checked"
name={['prompt_config', 'quote']}
tooltip={t('quoteTip')}
initialValue={true}
>
<Switch />
</Form.Item>
<Form.Item
label={t('knowledgeBases')}
name="kb_ids"

View File

@ -172,15 +172,7 @@ const PromptEngine = (
>
<Slider max={30} />
</Form.Item>
<Form.Item
label={t('quote')}
valuePropName="checked"
name={['prompt_config', 'quote']}
tooltip={t('quoteTip')}
initialValue={true}
>
<Switch />
</Form.Item>
<section className={classNames(styles.variableContainer)}>
<Row align={'middle'} justify="end">
<Col span={7} className={styles.variableAlign}>

View File

@ -0,0 +1,21 @@
.chartWrapper {
height: 40vh;
overflow: auto;
}
.chartItem {
height: 300px;
padding: 10px 0 30px;
}
.chartLabel {
display: inline-block;
padding-left: 60px;
padding-bottom: 20px;
}
.linkText {
border-radius: 6px;
padding: 6px 10px;
background-color: #eff8ff;
border: 1px;
}

View File

@ -0,0 +1,97 @@
import LineChart from '@/components/line-chart';
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
import { IModalProps } from '@/interfaces/common';
import { IDialog, IStats } from '@/interfaces/database/chat';
import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd';
import { RangePickerProps } from 'antd/es/date-picker';
import dayjs from 'dayjs';
import camelCase from 'lodash/camelCase';
import ChatApiKeyModal from '../chat-api-key-modal';
import { useFetchStatsOnMount, useSelectChartStatsList } from '../hooks';
import styles from './index.less';
const { Paragraph } = Typography;
const { RangePicker } = DatePicker;
const ChatOverviewModal = ({
visible,
hideModal,
dialog,
}: IModalProps<any> & { dialog: IDialog }) => {
const { t } = useTranslate('chat');
const chartList = useSelectChartStatsList();
const {
visible: apiKeyVisible,
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible);
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
return current && current > dayjs().endOf('day');
};
return (
<>
<Modal
title={t('overview')}
open={visible}
onCancel={hideModal}
width={'100vw'}
>
<Flex vertical gap={'middle'}>
<Card title={dialog.name}>
<Flex gap={8} vertical>
{t('publicUrl')}
<Paragraph copyable className={styles.linkText}>
This is a copyable text.
</Paragraph>
</Flex>
<Space size={'middle'}>
<Button>{t('preview')}</Button>
<Button>{t('embedded')}</Button>
</Space>
</Card>
<Card title={t('backendServiceApi')}>
<Flex gap={8} vertical>
{t('serviceApiEndpoint')}
<Paragraph copyable className={styles.linkText}>
This is a copyable text.
</Paragraph>
</Flex>
<Space size={'middle'}>
<Button onClick={showApiKeyModal}>{t('apiKey')}</Button>
<Button>{t('apiReference')}</Button>
</Space>
</Card>
<Space>
<b>{t('dateRange')}</b>
<RangePicker
disabledDate={disabledDate}
value={pickerValue}
onChange={setPickerValue}
allowClear={false}
/>
</Space>
<div className={styles.chartWrapper}>
{Object.keys(chartList).map((x) => (
<div key={x} className={styles.chartItem}>
<b className={styles.chartLabel}>{t(camelCase(x))}</b>
<LineChart data={chartList[x as keyof IStats]}></LineChart>
</div>
))}
</div>
</Flex>
<ChatApiKeyModal
visible={apiKeyVisible}
hideModal={hideApiKeyModal}
dialogId={dialog.id}
></ChatApiKeyModal>
</Modal>
</>
);
};
export default ChatOverviewModal;

View File

@ -2,22 +2,28 @@ import { MessageType } from '@/constants/chat';
import { fileIconMap } from '@/constants/common';
import {
useCompleteConversation,
useCreateToken,
useFetchConversation,
useFetchConversationList,
useFetchDialog,
useFetchDialogList,
useFetchStats,
useListToken,
useRemoveConversation,
useRemoveDialog,
useRemoveToken,
useSelectConversationList,
useSelectDialogList,
useSelectTokenList,
useSetDialog,
useUpdateConversation,
} from '@/hooks/chatHooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IConversation, IDialog } from '@/interfaces/database/chat';
import { IConversation, IDialog, IStats } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { getFileExtension } from '@/utils';
import dayjs, { Dayjs } from 'dayjs';
import omit from 'lodash/omit';
import {
ChangeEventHandler,
@ -704,3 +710,108 @@ export const useGetSendButtonDisabled = () => {
return dialogId === '' && conversationId === '';
};
//#endregion
//#region API provided for external calls
type RangeValue = [Dayjs | null, Dayjs | null] | null;
export const useFetchStatsOnMount = (visible: boolean) => {
const fetchStats = useFetchStats();
const [pickerValue, setPickerValue] = useState<RangeValue>([
dayjs(),
dayjs().subtract(7, 'day'),
]);
useEffect(() => {
if (visible && Array.isArray(pickerValue) && pickerValue[0]) {
fetchStats({ fromDate: pickerValue[0], toDate: pickerValue[1] });
}
}, [fetchStats, pickerValue, visible]);
return {
pickerValue,
setPickerValue,
};
};
export const useOperateApiKey = (visible: boolean, dialogId: string) => {
const removeToken = useRemoveToken();
const createToken = useCreateToken(dialogId);
const listToken = useListToken();
const tokenList = useSelectTokenList();
const creatingLoading = useOneNamespaceEffectsLoading('chatModel', [
'createToken',
]);
const listLoading = useOneNamespaceEffectsLoading('chatModel', ['list']);
const showDeleteConfirm = useShowDeleteConfirm();
const onRemoveToken = (token: string, tenantId: string) => {
showDeleteConfirm({
onOk: () => removeToken({ dialogId, tokens: [token], tenantId }),
});
};
useEffect(() => {
if (visible && dialogId) {
listToken(dialogId);
}
}, [listToken, dialogId, visible]);
return {
removeToken: onRemoveToken,
createToken,
tokenList,
creatingLoading,
listLoading,
};
};
type ChartStatsType = {
[k in keyof IStats]: Array<{ xAxis: string; yAxis: number }>;
};
export const useSelectChartStatsList = (): ChartStatsType => {
// const stats: IStats = useSelectStats();
const stats = {
pv: [
['2024-06-01', 1],
['2024-07-24', 3],
['2024-09-01', 10],
],
uv: [
['2024-02-01', 0],
['2024-03-01', 99],
['2024-05-01', 3],
],
speed: [
['2024-09-01', 2],
['2024-09-01', 3],
],
tokens: [
['2024-09-01', 1],
['2024-09-01', 3],
],
round: [
['2024-09-01', 0],
['2024-09-01', 3],
],
thumb_up: [
['2024-09-01', 3],
['2024-09-01', 9],
],
};
return Object.keys(stats).reduce((pre, cur) => {
const item = stats[cur as keyof IStats];
if (item.length > 0) {
pre[cur as keyof IStats] = item.map((x) => ({
xAxis: x[0] as string,
yAxis: x[1] as number,
}));
}
return pre;
}, {} as ChartStatsType);
};
//#endregion

View File

@ -35,7 +35,10 @@ import {
useSelectFirstDialogOnMount,
} from './hooks';
import { useTranslate } from '@/hooks/commonHooks';
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
import { useSetSelectedRecord } from '@/hooks/logicHooks';
import { IDialog } from '@/interfaces/database/chat';
import ChatOverviewModal from './chat-overview-modal';
import styles from './index.less';
const Chat = () => {
@ -73,6 +76,12 @@ const Chat = () => {
const dialogLoading = useSelectDialogListLoading();
const conversationLoading = useSelectConversationListLoading();
const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
useFetchDialogOnMount(dialogId, true);
@ -100,6 +109,15 @@ const Chat = () => {
onRemoveDialog([dialogId]);
};
const handleShowOverviewModal =
(dialog: IDialog): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
};
const handleRemoveConversation =
(conversationId: string): MenuItemProps['onClick'] =>
({ domEvent }) => {
@ -141,7 +159,9 @@ const Chat = () => {
},
];
const buildAppItems = (dialogId: string) => {
const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id;
const appItems: MenuProps['items'] = [
{
key: '1',
@ -164,6 +184,17 @@ const Chat = () => {
</Space>
),
},
{ type: 'divider' },
// {
// key: '3',
// onClick: handleShowOverviewModal(dialog),
// label: (
// <Space>
// <ProfileOutlined />
// {t('overview')}
// </Space>
// ),
// },
];
return appItems;
@ -230,7 +261,7 @@ const Chat = () => {
</Space>
{activated === x.id && (
<section>
<Dropdown menu={{ items: buildAppItems(x.id) }}>
<Dropdown menu={{ items: buildAppItems(x) }}>
<ChatAppCube
className={styles.cubeIcon}
></ChatAppCube>
@ -315,6 +346,11 @@ const Chat = () => {
initialName={initialConversationName}
loading={conversationRenameLoading}
></RenameModal>
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
dialog={currentRecord}
></ChatOverviewModal>
</Flex>
);
};

View File

@ -1,7 +1,14 @@
import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
import {
IConversation,
IDialog,
IStats,
IToken,
Message,
} from '@/interfaces/database/chat';
import i18n from '@/locales/config';
import chatService from '@/services/chatService';
import { message } from 'antd';
import omit from 'lodash/omit';
import { DvaModel } from 'umi';
import { v4 as uuid } from 'uuid';
import { IClientConversation, IMessage } from './interface';
@ -13,6 +20,8 @@ export interface ChatModelState {
currentDialog: IDialog;
conversationList: IConversation[];
currentConversation: IClientConversation;
tokenList: IToken[];
stats: IStats;
}
const model: DvaModel<ChatModelState> = {
@ -23,6 +32,8 @@ const model: DvaModel<ChatModelState> = {
currentDialog: <IDialog>{},
conversationList: [],
currentConversation: {} as IClientConversation,
tokenList: [],
stats: {} as IStats,
},
reducers: {
save(state, action) {
@ -60,6 +71,18 @@ const model: DvaModel<ChatModelState> = {
currentConversation: { ...payload, message: messageList },
};
},
setTokenList(state, { payload }) {
return {
...state,
tokenList: payload,
};
},
setStats(state, { payload }) {
return {
...state,
stats: payload,
};
},
},
effects: {
@ -160,6 +183,78 @@ const model: DvaModel<ChatModelState> = {
}
return data.retcode;
},
*createToken({ payload }, { call, put }) {
const { data } = yield call(chatService.createToken, payload);
if (data.retcode === 0) {
yield put({
type: 'listToken',
payload: payload,
});
message.success(i18n.t('message.created'));
}
return data.retcode;
},
*listToken({ payload }, { call, put }) {
const { data } = yield call(chatService.listToken, payload);
if (data.retcode === 0) {
yield put({
type: 'setTokenList',
payload: data.data,
});
}
return data.retcode;
},
*removeToken({ payload }, { call, put }) {
const { data } = yield call(
chatService.removeToken,
omit(payload, ['dialogId']),
);
if (data.retcode === 0) {
yield put({
type: 'listToken',
payload: { dialog_id: payload.dialogId },
});
}
return data.retcode;
},
*getStats({ payload }, { call, put }) {
const { data } = yield call(chatService.getStats, payload);
if (data.retcode === 0) {
yield put({
type: 'setStats',
payload: data.data,
});
}
return data.retcode;
},
*createExternalConversation({ payload }, { call, put }) {
const { data } = yield call(
chatService.createExternalConversation,
payload,
);
if (data.retcode === 0) {
yield put({
type: 'getExternalConversation',
payload: { conversation_id: payload.conversationId },
});
}
return data.retcode;
},
*getExternalConversation({ payload }, { call }) {
const { data } = yield call(
chatService.getExternalConversation,
null,
payload,
);
return data.retcode;
},
*completeExternalConversation({ payload }, { call }) {
const { data } = yield call(
chatService.completeExternalConversation,
payload,
);
return data.retcode;
},
},
};

View File

@ -12,6 +12,13 @@ const {
completeConversation,
listConversation,
removeConversation,
createToken,
listToken,
removeToken,
getStats,
createExternalConversation,
getExternalConversation,
completeExternalConversation,
} = api;
const methods = {
@ -51,6 +58,34 @@ const methods = {
url: removeConversation,
method: 'post',
},
createToken: {
url: createToken,
method: 'post',
},
listToken: {
url: listToken,
method: 'get',
},
removeToken: {
url: removeToken,
method: 'post',
},
getStats: {
url: getStats,
method: 'get',
},
createExternalConversation: {
url: createExternalConversation,
method: 'post',
},
getExternalConversation: {
url: getExternalConversation,
method: 'get',
},
completeExternalConversation: {
url: completeExternalConversation,
method: 'post',
},
} as const;
const chatService = registerServer<keyof typeof methods>(methods, request);

View File

@ -3,7 +3,7 @@ let api_host = `/v1`;
export { api_host };
export default {
// 用户
// user
login: `${api_host}/user/login`,
logout: `${api_host}/user/logout`,
register: `${api_host}/user/register`,
@ -12,21 +12,21 @@ export default {
tenant_info: `${api_host}/user/tenant_info`,
set_tenant_info: `${api_host}/user/set_tenant_info`,
// 模型管理
// llm model
factories_list: `${api_host}/llm/factories`,
llm_list: `${api_host}/llm/list`,
my_llm: `${api_host}/llm/my_llms`,
set_api_key: `${api_host}/llm/set_api_key`,
add_llm: `${api_host}/llm/add_llm`,
//知识库管理
// knowledge base
kb_list: `${api_host}/kb/list`,
create_kb: `${api_host}/kb/create`,
update_kb: `${api_host}/kb/update`,
rm_kb: `${api_host}/kb/rm`,
get_kb_detail: `${api_host}/kb/detail`,
// chunk管理
// chunk
chunk_list: `${api_host}/chunk/list`,
create_chunk: `${api_host}/chunk/create`,
set_chunk: `${api_host}/chunk/set`,
@ -35,7 +35,7 @@ export default {
rm_chunk: `${api_host}/chunk/rm`,
retrieval_test: `${api_host}/chunk/retrieval_test`,
// 文件管理
// document
upload: `${api_host}/document/upload`,
get_document_list: `${api_host}/document/list`,
document_change_status: `${api_host}/document/change_status`,
@ -48,14 +48,22 @@ export default {
get_document_file: `${api_host}/document/get`,
document_upload: `${api_host}/document/upload`,
// chat
setDialog: `${api_host}/dialog/set`,
getDialog: `${api_host}/dialog/get`,
removeDialog: `${api_host}/dialog/rm`,
listDialog: `${api_host}/dialog/list`,
setConversation: `${api_host}/conversation/set`,
getConversation: `${api_host}/conversation/get`,
listConversation: `${api_host}/conversation/list`,
removeConversation: `${api_host}/conversation/rm`,
completeConversation: `${api_host}/conversation/completion`,
// chat for external
createToken: `${api_host}/api/new_token`,
listToken: `${api_host}/api/token_list`,
removeToken: `${api_host}/api/rm`,
getStats: `${api_host}/api/stats`,
createExternalConversation: `${api_host}/api/new_conversation`,
getExternalConversation: `${api_host}/api/conversation`,
completeExternalConversation: `${api_host}/api/completion`,
};

View File

@ -0,0 +1,17 @@
import isObject from 'lodash/isObject';
import snakeCase from 'lodash/snakeCase';
export const isFormData = (data: unknown): data is FormData => {
return data instanceof FormData;
};
export const convertTheKeysOfTheObjectToSnake = (data: unknown) => {
if (isObject(data) && !isFormData(data)) {
return Object.keys(data).reduce<Record<string, any>>((pre, cur) => {
const value = (data as Record<string, any>)[cur];
pre[isFormData(value) ? cur : snakeCase(cur)] = value;
return pre;
}, {});
}
return data;
};

View File

@ -1,20 +1,20 @@
import moment from 'moment';
import dayjs from 'dayjs';
export function today() {
return formatDate(moment());
return formatDate(dayjs());
}
export function lastDay() {
return formatDate(moment().subtract(1, 'days'));
return formatDate(dayjs().subtract(1, 'days'));
}
export function lastWeek() {
return formatDate(moment().subtract(1, 'weeks'));
return formatDate(dayjs().subtract(1, 'weeks'));
}
export function formatDate(date: any) {
if (!date) {
return '';
}
return moment(date).format('DD/MM/YYYY');
return dayjs(date).format('DD/MM/YYYY');
}

View File

@ -8,16 +8,20 @@ const registerServer = <T extends string>(
) => {
const server: Service<T> = {} as Service<T>;
for (let key in opt) {
server[key] = (params) => {
server[key] = (params: any, urlAppendix?: string) => {
let url = opt[key].url;
if (urlAppendix) {
url = url + '/' + urlAppendix;
}
if (opt[key].method === 'post' || opt[key].method === 'POST') {
return request(opt[key].url, {
return request(url, {
method: opt[key].method,
data: params,
});
}
if (opt[key].method === 'get' || opt[key].method === 'GET') {
return request.get(opt[key].url, {
return request.get(url, {
params,
});
}

View File

@ -4,6 +4,7 @@ import authorizationUtil from '@/utils/authorizationUtil';
import { message, notification } from 'antd';
import { history } from 'umi';
import { RequestMethod, extend } from 'umi-request';
import { convertTheKeysOfTheObjectToSnake } from './commonUtil';
const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message
@ -87,10 +88,15 @@ const request: RequestMethod = extend({
request.interceptors.request.use((url: string, options: any) => {
const authorization = authorizationUtil.getAuthorization();
const data = convertTheKeysOfTheObjectToSnake(options.data);
const params = convertTheKeysOfTheObjectToSnake(options.params);
return {
url,
options: {
...options,
// data,
// params,
headers: {
...(options.skipToken ? undefined : { [Authorization]: authorization }),
...options.headers,