diff --git a/src/config/__init__.py b/src/config/__init__.py index dbd9f8a5..b98038c4 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -51,7 +51,7 @@ class Config(SimpleConfig): ## 如果需要自定义路径,则在 config/base.yaml 中配置 model_local_paths self.add_item("model_provider", default="zhipu", des="模型提供商", choices=["qianfan", "vllm", "zhipu", "deepseek", "dashscope"]) self.add_item("model_name", default=None, des="模型名称") - self.add_item("embed_model", default="bge-large-zh-v1.5", des="Embedding 模型", choices=["bge-large-zh-v1.5", "zhipu"]) + self.add_item("embed_model", default="zhipu-embedding-3", des="Embedding 模型", choices=list(EMBED_MODEL_INFO.keys())) self.add_item("reranker", default="bge-reranker-v2-m3", des="Re-Ranker 模型", choices=["bge-reranker-v2-m3"]) self.add_item("model_local_paths", default={}, des="本地模型路径") ### <<< 默认配置结束 @@ -92,22 +92,29 @@ class Config(SimpleConfig): """根据传入的文件覆盖掉默认配置""" logger.info(f"Loading config from {self.filename}") if self.filename is not None and os.path.exists(self.filename): + if self.filename.endswith(".json"): with open(self.filename, 'r') as f: content = f.read() if content: - self.update(json.loads(content)) + local_config = json.loads(content) + local_config.pop("_config_items") + self.update(local_config) else: print(f"{self.filename} is empty.") + elif self.filename.endswith(".yaml"): with open(self.filename, 'r') as f: content = f.read() if content: - self.update(yaml.safe_load(content)) + local_config = yaml.safe_load(content) + local_config.pop("_config_items") + self.update(local_config) else: print(f"{self.filename} is empty.") else: logger.warning(f"Unknown config file type {self.filename}") + else: logger.warning(f"\n\n{'='*70}\n{'Config file not found':^70}\n{'You can custum your config in `' + self.filename + '`':^70}\n{'='*70}\n\n") @@ -154,7 +161,7 @@ MODEL_NAMES = { "ERNIE-Speed-128K", "ERNIE-Tiny-8K", "ERNIE-Lite-8K", - "ERNIE-4.0-8K-Latest" + "ERNIE-4.0-8K-Latest", "Yi-34B-Chat", ], @@ -170,7 +177,30 @@ MODEL_NAMES = { "llama3.1-8b-instruct", "llama3-8b-instruct", "llama3.1-405b-instruct", - "baichuan2-7b-chat-v1", "qwen2-0.5b-instruct" ] +} + + +EMBED_MODEL_INFO = { + "bge-large-zh-v1.5": SimpleConfig({ + "name": "bge-large-zh-v1.5", + "default_path": "BAAI/bge-large-zh-v1.5", + "dimension": 1024, + "query_instruction": "为这个句子生成表示以用于检索相关文章:", + }), + "zhipu-embedding-2": SimpleConfig({ + "name": "zhipu-embedding-2", + "default_path": "embedding-2", + "dimension": 1024, + }), + "zhipu-embedding-3": SimpleConfig({ + "name": "zhipu-embedding-3", + "default_path": "embedding-3", + "dimension": 2048, + }), +} + +RERANKER_LIST = { + "bge-reranker-v2-m3": "BAAI/bge-reranker-v2-m3", } \ No newline at end of file diff --git a/src/core/database.py b/src/core/database.py index c8dd43d8..05b7b432 100644 --- a/src/core/database.py +++ b/src/core/database.py @@ -9,10 +9,11 @@ logger = setup_logger("DataBaseManager") class DataBaseLite: - def __init__(self, name, description, db_type, **kwargs) -> None: + def __init__(self, name, description, db_type, dimension=None, **kwargs) -> None: self.name = name self.description = description self.db_type = db_type + self.dimension = dimension self.db_id = kwargs.get("db_id", hashstr(name)) self.metaname = kwargs.get("metaname", f"{db_type[:1]}{hashstr(name)}") self.metadata = kwargs.get("metaname", {}) @@ -37,7 +38,8 @@ class DataBaseLite: "embed_model": self.embed_model, "metaname": self.metaname, "metadata": self.metadata, - "files": self.files + "files": self.files, + "dimension": self.dimension } def to_json(self): @@ -77,6 +79,12 @@ class DataBaseManager: "graph": data["graph"] } + # 检查所有文件,如果出现状态是 processing 的,那么设置为 failed + for db in self.data["databases"]: + for file in db.files: + if file["status"] == "processing" or file["status"] == "waiting": + file["status"] = "failed" + def _save_databases(self): """将数据库的信息保存到本地的文件里面""" self._update_database() @@ -109,10 +117,14 @@ class DataBaseManager: else: return {"message": "Graph base not enabled", "graph": {}} - def create_database(self, database_name, description, db_type): - new_database = DataBaseLite(database_name, description, db_type, embed_model=self.config.embed_model) + def create_database(self, database_name, description, db_type, dimension): + new_database = DataBaseLite(database_name, + description, + db_type, + embed_model=self.config.embed_model, + dimension=dimension) - self.knowledge_base.add_collection(new_database.metaname) + self.knowledge_base.add_collection(new_database.metaname, dimension) self.data["databases"].append(new_database) self._save_databases() return self.get_databases() @@ -129,29 +141,36 @@ class DataBaseManager: # filenames = [f["filename"] for f in db.files] # if os.path.basename(file) in filenames: # continue - db.files.append({ + new_file = { "file_id": "file_" + hashstr(file + str(time.time())), "filename": os.path.basename(file), "path": file, "type": file.split(".")[-1], "status": "waiting", "created_at": time.time() - }) - new_files.append((len(db.files) - 1, file)) + } + db.files.append(new_file) + new_files.append(new_file) - for idx, file in new_files: + for new_file in new_files: + file_id = new_file["file_id"] + idx = [idx for idx, f in enumerate(db.files) if f["file_id"] == file_id][0] db.files[idx]["status"] = "processing" try: - text = self.read_text(file) + text = self.read_text(new_file["path"]) chunks = self.chunking(text) self.knowledge_base.add_documents( docs=chunks, collection_name=db.metaname, - file_id=db.files[idx]["file_id"]) + file_id=file_id) + + idx = [idx for idx, f in enumerate(db.files) if f["file_id"] == file_id][0] db.files[idx]["status"] = "done" + except Exception as e: logger.error(f"Failed to add documents to collection {db.metaname}, {e}") + idx = [idx for idx, f in enumerate(db.files) if f["file_id"] == file_id][0] db.files[idx]["status"] = "failed" self._save_databases() diff --git a/src/core/knowledgebase.py b/src/core/knowledgebase.py index 7c0d5113..327384f0 100644 --- a/src/core/knowledgebase.py +++ b/src/core/knowledgebase.py @@ -18,7 +18,6 @@ class KnowledgeBase: self.client = MilvusClient(self.milvus_path) def _init_config(self, config): - self.vector_dim = 1024 # 暂时不知道这个和 embedding model 的 embedding 大小有什么关系 self.milvus_path = os.path.join(config.save_dir, "data/vector_base/milvus.db") os.makedirs(os.path.dirname(self.milvus_path), exist_ok=True) @@ -40,14 +39,14 @@ class KnowledgeBase: # collection["id"] = hashstr(collection_name) return collection - def add_collection(self, collection_name): + def add_collection(self, collection_name, dimension=None): if self.client.has_collection(collection_name=collection_name): logger.warning(f"Collection {collection_name} already exists, drop it") self.client.drop_collection(collection_name=collection_name) self.client.create_collection( collection_name=collection_name, - dimension=self.vector_dim, # The vectors we will use in this demo has 768 dimensions + dimension= dimension, # The vectors we will use in this demo has 768 dimensions ) def add_documents(self, docs, collection_name, **kwargs): @@ -55,8 +54,8 @@ class KnowledgeBase: # 检查 collection 是否存在 import random if not self.client.has_collection(collection_name=collection_name): - logger.warning(f"Collection {collection_name} not found, create it") - self.add_collection(collection_name) + logger.error(f"Collection {collection_name} not found, create it") + # self.add_collection(collection_name) vectors = self.embed_model.encode(docs) diff --git a/src/core/retriever.py b/src/core/retriever.py index 6f74c7f4..2025f3a2 100644 --- a/src/core/retriever.py +++ b/src/core/retriever.py @@ -66,29 +66,31 @@ class Retriever: def query_knowledgebase(self, query, history, refs): """查询知识库""" - rw_query = self.rewrite_query(query, history, refs) kb_res = [] final_res = [] - if refs["meta"].get("db_name") and self.config.enable_knowledge_base: + if not refs["meta"].get("db_name") or not self.config.enable_knowledge_base: + return {"results": final_res, "all_results": kb_res, "rw_query": query, "message": "Knowledge base is disabled"} - db_name = refs["meta"]["db_name"] - kb = self.dbm.metaname2db[db_name] - limit = refs["meta"].get("queryCount", 10) + rw_query = self.rewrite_query(query, history, refs) - kb_res = self.dbm.knowledge_base.search(rw_query, db_name, limit=limit) + db_name = refs["meta"]["db_name"] + kb = self.dbm.metaname2db[db_name] + limit = refs["meta"].get("queryCount", 10) + + kb_res = self.dbm.knowledge_base.search(rw_query, db_name, limit=limit) + for r in kb_res: + r["file"] = kb.id2file(r["entity"]["file_id"]) + + if self.config.enable_reranker: + RERANK_THRESHOLD = 0.1 for r in kb_res: - r["file"] = kb.id2file(r["entity"]["file_id"]) + r["rerank_score"] = self.reranker.compute_score([query, r["entity"]["text"]], normalize=True) + kb_res.sort(key=lambda x: x["rerank_score"], reverse=True) + final_res = [_res for _res in kb_res if _res["rerank_score"] > RERANK_THRESHOLD] - if self.config.enable_reranker: - RERANK_THRESHOLD = 0.1 - for r in kb_res: - r["rerank_score"] = self.reranker.compute_score([query, r["entity"]["text"]], normalize=True) - kb_res.sort(key=lambda x: x["rerank_score"], reverse=True) - final_res = [_res for _res in kb_res if _res["rerank_score"] > RERANK_THRESHOLD] - - else: - final_res = kb_res[:5] + else: + final_res = kb_res[:5] return {"results": final_res, "all_results": kb_res, "rw_query": rw_query} diff --git a/src/models/chat_model.py b/src/models/chat_model.py index da3c7765..be612215 100644 --- a/src/models/chat_model.py +++ b/src/models/chat_model.py @@ -11,8 +11,6 @@ class OpenAIBase(): self.model_name = model_name def predict(self, message, stream=False): - - logger.debug(message) if isinstance(message, str): messages=[{"role": "user", "content": message}] else: diff --git a/src/models/embedding.py b/src/models/embedding.py index 64c6e173..e7c482d9 100644 --- a/src/models/embedding.py +++ b/src/models/embedding.py @@ -1,37 +1,24 @@ import os from FlagEmbedding import FlagModel, FlagReranker +from src.config import EMBED_MODEL_INFO, RERANKER_LIST from src.utils.logging_config import setup_logger logger = setup_logger("EmbeddingModel") -SUPPORT_LIST = { - "bge-large-zh-v1.5": "BAAI/bge-large-zh-v1.5", - "zhipu": "embedding-2", -} - -RERANKER_LIST = { - "bge-reranker-v2-m3": "BAAI/bge-reranker-v2-m3", -} - -QUERY_INSTRUCTION = { - "bge-large-zh-v1.5": "为这个句子生成表示以用于检索相关文章:", -} class EmbeddingModel(FlagModel): - def __init__(self, config, **kwargs): - - assert config.embed_model in SUPPORT_LIST.keys(), f"Unsupported embed model: {config.embed_model}, only support {SUPPORT_LIST}" - - model_name_or_path = config.model_local_paths.get(config.embed_model, SUPPORT_LIST[config.embed_model]) - logger.info(f"Loading embedding model {config.embed_model} from {model_name_or_path}") + def __init__(self, model_info, config, **kwargs): + self.info = model_info + model_name_or_path = config.model_local_paths.get(model_info.name, model_info.default_path) + logger.info(f"Loading embedding model {model_info.name} from {model_name_or_path}") super().__init__(model_name_or_path, - query_instruction_for_retrieval=QUERY_INSTRUCTION[config.embed_model], + query_instruction_for_retrieval=model_info.get("query_instruction", None), use_fp16=False, **kwargs) - logger.info(f"Embedding model {config.embed_model} loaded") + logger.info(f"Embedding model {model_info.name} loaded") class Reranker(FlagReranker): @@ -50,18 +37,27 @@ from zhipuai import ZhipuAI class ZhipuEmbedding: - def __init__(self, config) -> None: + def __init__(self, model_info, config) -> None: self.config = config + self.model_info = model_info self.client = ZhipuAI(api_key=os.getenv("ZHIPUAPI")) logger.info("Zhipu Embedding model loaded") self.query_instruction_for_retrieval = "为这个句子生成表示以用于检索相关文章:" def predict(self, message): - response = self.client.embeddings.create( - model=SUPPORT_LIST[self.config.embed_model], - input=message - ) - return [a["embedding"] for a in response["data"]] + + data = [] + + for i in range(0, len(message), 10): + group_msg = message[i:i+10] + response = self.client.embeddings.create( + model=self.model_info.default_path, + input=group_msg, + ) + + data.extend([a.embedding for a in response.data]) + + return data def encode(self, message): return self.predict(message) @@ -75,7 +71,12 @@ def get_embedding_model(config): if not config.enable_knowledge_base: return None - if config.embed_model == "zhipu": - return ZhipuEmbedding(config) - else: - return EmbeddingModel(config) \ No newline at end of file + assert config.embed_model in EMBED_MODEL_INFO.keys(), f"Unsupported embed model: {config.embed_model}, only support {EMBED_MODEL_INFO.keys()}" + + if config.embed_model in ["bge-large-zh-v1.5"]: + model = EmbeddingModel(EMBED_MODEL_INFO[config.embed_model], config) + + if config.embed_model in ["zhipu-embedding-2", "zhipu-embedding-3"]: + model = ZhipuEmbedding(EMBED_MODEL_INFO[config.embed_model], config) + + return model \ No newline at end of file diff --git a/src/utils/logging_config.py b/src/utils/logging_config.py index 6f4849ce..2eb64b50 100644 --- a/src/utils/logging_config.py +++ b/src/utils/logging_config.py @@ -3,21 +3,23 @@ import os from datetime import datetime -# DATETIME = datetime.now().strftime('%Y-%m-%d-%H%M%S') -DATETIME = "debug" # 为了方便,调试的时候输出到 debug.log 文件 +DATETIME = datetime.now().strftime('%Y-%m-%d-%H%M%S') +# DATETIME = "debug" # 为了方便,调试的时候输出到 debug.log 文件 +LOG_FILE = f'log/project-{DATETIME}.log' -def setup_logger(name, log_file=None, level=logging.DEBUG, console=False): - - if log_file is None: - log_file = f'log/project-{DATETIME}.log' +def setup_logger(name, level=logging.DEBUG, console=False): os.makedirs("log", exist_ok=True) """Function to setup logger with the given name and log file.""" logger = logging.getLogger(name) logger.setLevel(level) + # 清除已有的 Handler,防止重复添加 + if logger.hasHandlers(): + logger.handlers.clear() + # File handler for logging to a file - file_handler = logging.FileHandler(log_file) + file_handler = logging.FileHandler(LOG_FILE) file_handler.setLevel(level) # Formatter for the logs @@ -34,6 +36,7 @@ def setup_logger(name, log_file=None, level=logging.DEBUG, console=False): return logger + # Setup the root logger logger = setup_logger('Athena') diff --git a/src/views/common_view.py b/src/views/common_view.py index d9445401..3eec200f 100644 --- a/src/views/common_view.py +++ b/src/views/common_view.py @@ -29,7 +29,6 @@ def chat(): request_data = json.loads(request.data) query = request_data['query'] meta = request_data.get('meta') - logger.debug(f"Web query: {query}") history_manager = HistoryManager(request_data['history']) new_query, refs = startup.retriever(query, history_manager.messages, meta) @@ -63,7 +62,7 @@ def call(): request_data = json.loads(request.data) query = request_data['query'] response = startup.model.predict(query) - logger.debug(f"\n\n\nCall query: \n{query} \n\nResponse: \n{response.content}\n\n") + logger.debug({"query": query, "response": response.content}) return jsonify({ "response": response.content, @@ -83,4 +82,13 @@ def update_config(): @common.route('/restart', methods=['POST']) def restart(): startup.restart() - return jsonify({"message": "Restarted!"}) \ No newline at end of file + return jsonify({"message": "Restarted!"}) + + +@common.route('/log', methods=['GET']) +def get_log(): + from src.utils.logging_config import LOG_FILE + with open(LOG_FILE, 'r') as f: + log = f.read() + + return jsonify({"log": log}) \ No newline at end of file diff --git a/src/views/database_view.py b/src/views/database_view.py index 8516cdb9..43bf1b75 100644 --- a/src/views/database_view.py +++ b/src/views/database_view.py @@ -27,8 +27,9 @@ def create_database(): database_name = data.get('database_name') description = data.get('description') db_type = data.get('db_type') + dimension = data.get('dimension') logger.debug(f"Create database {database_name}") - database = startup.dbm.create_database(database_name, description, db_type) + database = startup.dbm.create_database(database_name, description, db_type, dimension=dimension) return jsonify(database) @db.route('/', methods=['DELETE']) @@ -103,7 +104,7 @@ def upload_file(): if file: upload_dir = os.path.join(startup.config.save_dir, "data/uploads") os.makedirs(upload_dir, exist_ok=True) - filename = f"{hashstr(file.filename, 6, with_salt=True)}_{file.filename}" + filename = f"{hashstr(file.filename, 4, with_salt=True)}_{file.filename}" file_path = os.path.join(upload_dir, filename) file.save(file_path) return jsonify({'message': 'File successfully uploaded', 'file_path': file_path}), 200 diff --git a/web/package.json b/web/package.json index ddb72617..e95d555b 100644 --- a/web/package.json +++ b/web/package.json @@ -19,8 +19,10 @@ "d3": "^7.8.3", "echarts": "^5.4.2", "echarts-gl": "^2.0.9", + "highlight.js": "^11.10.0", "less": "^4.1.3", "marked": "^13.0.2", + "marked-highlight": "^2.1.4", "pinia": "^2.0.32", "vue": "^3.2.47", "vue-router": "^4.1.6" diff --git a/web/public/home.png b/web/public/home.png index 9c87dbf5..2460f5fb 100644 Binary files a/web/public/home.png and b/web/public/home.png differ diff --git a/web/src/assets/base.css b/web/src/assets/base.css index 6e5c9c8d..dbc51192 100644 --- a/web/src/assets/base.css +++ b/web/src/assets/base.css @@ -1,5 +1,17 @@ /* color palette from */ :root { + --main-900: #003A51; + --main-800: #004F69; + --main-700: #00637F; + --main-600: #1B7796; + --main-500: #2C86A8; + --main-400: #4e99b9; + --main-300: #6AADCB; + --main-200: #8CC6E1; + --main-100: #ABE0F7; + --main-50: #CDF5FF; + --main-25: #E6FAFF; + --c-white: #ffffff; --c-white-soft: #f8f8f8; --c-white-mute: #f2f2f2; @@ -8,60 +20,28 @@ --c-black-soft: #222222; --c-black-mute: #282828; - --c-indigo: #2c3e50; - --c-black-light-1: #333333; --c-black-light-2: #454545; --c-black-light-3: #666666; --c-black-light-4: #999999; - --c-text-light-1: var(--c-indigo); - --c-text-light-2: rgba(60, 66, 70, 0.66); - --c-text-dark-1: var(--c-white); + --c-text-light-1: var(--c-black); + --c-text-dark-1: #0D0D0D; --c-text-dark-2: #b8b8b8; -} - -/* semantic color variables for this project */ -:root { - --color-background: var(--c-white); - --color-background-soft: var(--c-white-soft); - --color-background-mute: var(--c-white-mute); - - --color-border: var(--c-black-light-2); - --color-border-hover: var(--c-black-light-1); - - --color-heading: var(--c-text-light-1); --color-text: var(--c-black); - --section-gap: 160px; - - --main-color: #005f77; + --main-color: var(--main-700); --main-color-dark: #004d5c; - --main-light-1: #007f96; - --main-light-2: #D3EAED; - --main-light-3: #EDF4F5; - --main-light-4: #F2F6F7; + --main-light-1: #0076AB; + --main-light-2: #DAEAED; + --main-light-3: #EDF0F1; + --main-light-4: #F2F5F5; --main-light-5: #F7FAFB; --main-light-6: #FAFDFD; --min-width: 400px; - --min-header-width: 80px; --error-color: #f50a0d; } -/* @media (prefers-color-scheme: dark) { - :root { - --color-background: var(--c-black); - --color-background-soft: var(--c-black-soft); - --color-background-mute: var(--c-black-mute); - - --color-border: var(--c-black-dark-2); - --color-border-hover: var(--c-black-dark-1); - - --color-heading: var(--c-text-dark-1); - --color-text: var(--c-text-dark-2); - } -} */ - *, *::before, *::after { diff --git a/web/src/assets/theme.js b/web/src/assets/theme.js index 3e727f59..6f5c25eb 100644 --- a/web/src/assets/theme.js +++ b/web/src/assets/theme.js @@ -2,6 +2,6 @@ export const themeConfig = { token: { colorPrimary: '#005F77', colorInfo: '#191919', - wireframe: true + fontFamily: "'HarmonyOS Sans SC', Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;" }, } \ No newline at end of file diff --git a/web/src/components/ChatComponent.vue b/web/src/components/ChatComponent.vue index 5861a5e1..93f51c8a 100644 --- a/web/src/components/ChatComponent.vue +++ b/web/src/components/ChatComponent.vue @@ -14,7 +14,7 @@ class="newchat nav-btn" @click="$emit('newconv')" > - 新对话 + 新对话 {{ configStore.config?.model_name }}
@@ -107,53 +107,17 @@ :class="message.role" >

{{ message.text }}

+
+
+
+
+

-
- - {{ filename }} - -
-

文件名: {{ results[0].file.filename }}

-

文件类型: {{ results[0].file.type }}

-

创建时间: {{ new Date(results[0].file.created_at * 1000).toLocaleString() }}

-
-
-

ID: #{{ res.id }}

-

- 相似度距离: -

- -
-

-

- 重排序分数: -

- -
-

-

{{ res.entity.text }}

-
-
-
-
+
@@ -169,7 +133,7 @@
-

即便强如雅典娜也可能会出错,请注意辨别内容的可靠性 模型供应商:{{ configStore.config?.model_provider }}:{{ configStore.config?.model_name }}

+

请注意辨别内容的可靠性 模型供应商:{{ configStore.config?.model_provider }}: {{ configStore.config?.model_name }}

@@ -190,10 +154,16 @@ import { PlusCircleOutlined, FolderOutlined, FolderOpenOutlined, + GlobalOutlined, + FileTextOutlined, } from '@ant-design/icons-vue' import { onClickOutside } from '@vueuse/core' -import { marked } from 'marked'; +import { Marked } from 'marked'; +import { markedHighlight } from 'marked-highlight'; import { useConfigStore } from '@/stores/config' +import RefsComponent from '@/components/RefsComponent.vue' +import hljs from 'highlight.js'; +import 'highlight.js/styles/github.css'; const props = defineProps({ conv: Object, @@ -232,16 +202,30 @@ const meta = reactive(JSON.parse(localStorage.getItem('meta')) || { history_round: 5, }) -// 更多选项可以在 marked 文档中找到:https://marked.js.org/ -marked.setOptions({ - gfm: true, - breaks: true, - tables: true, -}); +const marked = new Marked( + { + gfm: true, + breaks: true, + tables: true, + }, + markedHighlight({ + langPrefix: 'hljs language-', + highlight(code) { + return hljs.highlightAuto(code).value; + } + }) +); + -const renderMarkdown = (text) => marked(text) const consoleMsg = (message) => console.log(message) onClickOutside(panel, () => setTimeout(() => opts.showPanel = false, 30)) +const renderMarkdown = (message) => { + if (message.status === 'loading') { + return marked.parse(message.text + '🟢') + } else { + return marked.parse(message.text) + } +} const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -307,22 +291,24 @@ const appendAiMessage = (message, refs=null) => { role: 'received', text: message, refs, - status: "querying" + status: "querying", + model_name: configStore.config.model_name }) scrollToBottom() } const updateMessage = (text, id, refs, status) => { - const message = conv.value.messages.find((message) => message.id === id) + const message = conv.value.messages.find((message) => message.id === id); if (message) { - message.text = text - message.refs = refs - message.status = status + message.refs = refs; + message.status = status; + message.text = text; } else { - console.error('Message not found') + console.error('Message not found'); } - scrollToBottom() -} + scrollToBottom(); +}; + const updateStatus = (id, status) => { const message = conv.value.messages.find((message) => message.id === id) @@ -332,7 +318,6 @@ const updateStatus = (id, status) => { console.error('Message not found') } - console.log("updateStatus", message, message.refs.knowledge_base.results.length > 0) if (message.refs.knowledge_base.results.length > 0) { message.groupedResults = message.refs.knowledge_base.results.reduce((acc, result) => { const { filename } = result.file; @@ -373,7 +358,7 @@ const sendMessage = () => { if (user_input) { isStreaming.value = true appendUserMessage(user_input) - appendAiMessage("···", null) + appendAiMessage("", null) const cur_res_id = conv.value.messages[conv.value.messages.length - 1].id conv.value.inputText = '' meta.db_name = opts.databases[meta.selectedKB]?.metaname @@ -585,7 +570,7 @@ watch( .chat-box { width: 100%; - max-width: 1100px; + max-width: 900px; margin: 0 auto; flex-grow: 1; padding: 1rem; @@ -593,25 +578,24 @@ watch( flex-direction: column; .message-box { - max-width: 95%; display: inline-block; - border-radius: 0.8rem; + border-radius: 1.5rem; margin: 0.8rem 0; - padding: 1rem; + padding: 0.625rem 1.25rem; user-select: text; word-break: break-word; font-size: 16px; font-variation-settings: 'wght' 400, 'opsz' 10.5; font-weight: 400; box-sizing: border-box; - color: #0D0D0D; + color: black; /* box-shadow: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12), 0px 1.6px 3.6px rgba(0, 0, 0, 0.16); */ /* animation: slideInUp 0.1s ease-in; */ } .message-box.sent { - background-color: #efefef; line-height: 24px; + max-width: 95%; background: var(--main-light-3); align-self: flex-end; } @@ -619,12 +603,13 @@ watch( .message-box.received { color: initial; width: fit-content; - padding-top: 16px; - background-color: #F5F7F8; text-align: left; word-wrap: break-word; - margin-bottom: 0; + margin: 0; padding-bottom: 0; + padding-top: 16px; + padding-left: 0; + padding-right: 0; text-align: justify; } @@ -639,55 +624,9 @@ watch( margin-bottom: 0; } - .refs { - margin-bottom: 20px; - .filetag:hover { - cursor: pointer; - } - } } -.retrieval-detail { - .fileinfo { - margin-bottom: 20px; - padding: 10px; - background: var(--main-light-3); - border-radius: 8px; - p { - margin: 0; - line-height: 1.5; - } - } - - .result-item { - margin-bottom: 20px; - padding: 10px; - border: 1px solid #e8e8e8; - border-radius: 8px; - background: var(--main-light-4); - - .result-id, - .result-distance, - .result-rerank-score, - .result-text-label, - .result-text { - margin: 5px 0; - } - - .scorebar { - margin-left: 10px; - display: inline-block; - width: 200px; - padding-bottom: 2px; - - - & > * { - margin: 0; - } - } - } -} @@ -696,23 +635,23 @@ watch( bottom: 0; width: 100%; margin: 0 auto; - padding: 0.5rem 2rem; + padding: 4px 2rem 0 2rem; background: white; .input-box { display: flex; width: 100%; - max-width: 1100px; + max-width: 900px; margin: 0 auto; align-items: flex-end; - background-color: #F5F7F8; + background-color: var(--main-light-4); border-radius: 2rem; height: auto; padding: 0.5rem; &:focus-within { - background-color: #F2F2F2; + background-color: #ECF1F2; } .user-input { @@ -762,9 +701,10 @@ watch( background-color: transparent; border: none; height: 2.5rem; - background-color: black; + background-color: var(--main-color); border-radius: 3rem; color: white; + transition: background-color 0.3s; &:hover { color: white; @@ -772,7 +712,7 @@ watch( } button:disabled { - background: #D7D7D7; + background: #E0E0E0; cursor: not-allowed; } @@ -803,8 +743,37 @@ button:disabled { border-radius: 4px; } -.slide-out-left{-webkit-animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both;animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both} -.swing-in-top-fwd {-webkit-animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;} +.loading-dots { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.loading-dots div { + width: 10px; + height: 10px; + margin: 0 5px; + background-color: #333; + border-radius: 50%; + animation: loading 0.8s infinite ease-in-out both; +} + +.loading-dots div:nth-child(1) { + animation-delay: 0s; +} + +.loading-dots div:nth-child(2) { + animation-delay: 0.2s; +} + +.loading-dots div:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes loading {0%,80%,100%{transform:scale(0.5);}40%{transform:scale(1);}} + +.slide-out-left{-webkit-animation:slide-out-left .2s cubic-bezier(.55,.085,.68,.53) both;animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both} +.swing-in-top-fwd {-webkit-animation: swing-in-top-fwd 0.2s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;} @-webkit-keyframes swing-in-top-fwd{0%{-webkit-transform:rotateX(-100deg);transform:rotateX(-100deg);-webkit-transform-origin:top;transform-origin:top;opacity:0}100%{-webkit-transform:rotateX(0deg);transform:rotateX(0deg);-webkit-transform-origin:top;transform-origin:top;opacity:1}}@keyframes swing-in-top-fwd{0%{-webkit-transform:rotateX(-100deg);transform:rotateX(-100deg);-webkit-transform-origin:top;transform-origin:top;opacity:0}100%{-webkit-transform:rotateX(0deg);transform:rotateX(0deg);-webkit-transform-origin:top;transform-origin:top;opacity:1}} @-webkit-keyframes slide-out-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform:translateX(-1000px);transform:translateX(-1000px);opacity:0}}@keyframes slide-out-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform:translateX(-1000px);transform:translateX(-1000px);opacity:0}} @@ -851,3 +820,16 @@ button:disabled { } } + + \ No newline at end of file diff --git a/web/src/components/DebugComponent.vue b/web/src/components/DebugComponent.vue new file mode 100644 index 00000000..19ac9500 --- /dev/null +++ b/web/src/components/DebugComponent.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/web/src/components/RefsComponent.vue b/web/src/components/RefsComponent.vue new file mode 100644 index 00000000..3e83fae8 --- /dev/null +++ b/web/src/components/RefsComponent.vue @@ -0,0 +1,143 @@ + + + + + + diff --git a/web/src/layouts/AppLayout.vue b/web/src/layouts/AppLayout.vue index e747bf5e..8e6dc9f7 100644 --- a/web/src/layouts/AppLayout.vue +++ b/web/src/layouts/AppLayout.vue @@ -1,5 +1,5 @@ - diff --git a/web/src/views/SettingView.vue b/web/src/views/SettingView.vue index 7c6353fb..98ab0f6f 100644 --- a/web/src/views/SettingView.vue +++ b/web/src/views/SettingView.vue @@ -12,7 +12,7 @@ 需要重启 - @@ -29,7 +29,7 @@ 需要重启 - 需要重启 - @@ -63,7 +63,7 @@ 需要重启 -