修改并适配硅基流动模型

This commit is contained in:
Wenjie Zhang 2025-02-23 16:38:56 +08:00
parent 78811d2601
commit b2dbc17fec
8 changed files with 224 additions and 144 deletions

View File

@ -59,10 +59,10 @@ class Config(SimpleConfig):
# 模型配置 # 模型配置
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径 ## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
## 如果需要自定义本地模型路径,则在 src/.env 中配置 MODEL_DIR ## 如果需要自定义本地模型路径,则在 src/.env 中配置 MODEL_DIR
self.add_item("model_provider", default="zhipu", des="模型提供商", choices=list(MODEL_NAMES.keys())) self.add_item("model_provider", default="siliconflow", des="模型提供商", choices=list(MODEL_NAMES.keys()))
self.add_item("model_name", default=None, des="模型名称") self.add_item("model_name", default="Qwen/Qwen2.5-7B-Instruct", des="模型名称")
self.add_item("embed_model", default="zhipu-embedding-3", des="Embedding 模型", choices=list(EMBED_MODEL_INFO.keys())) self.add_item("embed_model", default="siliconflow/BAAI/bge-m3", des="Embedding 模型", choices=list(EMBED_MODEL_INFO.keys()))
self.add_item("reranker", default="bge-reranker-v2-m3", des="Re-Ranker 模型", choices=list(RERANKER_LIST.keys())) self.add_item("reranker", default="siliconflow/BAAI/bge-reranker-v2-m3", des="Re-Ranker 模型", choices=list(RERANKER_LIST.keys()))
self.add_item("model_local_paths", default={}, des="本地模型路径") self.add_item("model_local_paths", default={}, des="本地模型路径")
self.add_item("use_rewrite_query", default="off", des="重写查询", choices=["off", "on", "hyde"]) self.add_item("use_rewrite_query", default="off", des="重写查询", choices=["off", "on", "hyde"])
### <<< 默认配置结束 ### <<< 默认配置结束

View File

@ -1,6 +1,5 @@
import os import os
from src.models.embedding import EmbeddingModel
from pymilvus import MilvusClient, MilvusException from pymilvus import MilvusClient, MilvusException
from src.utils import setup_logger, hashstr from src.utils import setup_logger, hashstr
logger = setup_logger("KnowledgeBase") logger = setup_logger("KnowledgeBase")
@ -83,7 +82,7 @@ class KnowledgeBase:
def search(self, query, collection_name, limit=3): def search(self, query, collection_name, limit=3):
query_vectors = self.embed_model.encode_queries([query]) query_vectors = self.embed_model.batch_encode([query])
return self.search_by_vector(query_vectors[0], collection_name, limit) return self.search_by_vector(query_vectors[0], collection_name, limit)
def search_by_vector(self, vector, collection_name, limit=3): def search_by_vector(self, vector, collection_name, limit=3):

View File

@ -1,4 +1,4 @@
from src.models.embedding import Reranker from src.models.rerank_model import get_reranker
from src.utils.logging_config import setup_logger from src.utils.logging_config import setup_logger
logger = setup_logger("server-common") logger = setup_logger("server-common")
@ -12,7 +12,7 @@ class Retriever:
self.model = model self.model = model
if self.config.enable_reranker: if self.config.enable_reranker:
self.reranker = Reranker(config) self.reranker = get_reranker(config)
if self.config.enable_web_search: if self.config.enable_web_search:
from src.utils.web_search import WebSearcher from src.utils.web_search import WebSearcher
@ -110,15 +110,13 @@ class Retriever:
for r in all_kb_res: for r in all_kb_res:
r["file"] = kb.id2file(r["entity"]["file_id"]) r["file"] = kb.id2file(r["entity"]["file_id"])
# use distance threshold to filter results
if meta.get("mode") == "search":
kb_res = all_kb_res
else:
kb_res = [r for r in all_kb_res if r["distance"] > distance_threshold] kb_res = [r for r in all_kb_res if r["distance"] > distance_threshold]
if self.config.enable_reranker: if self.config.enable_reranker and len(kb_res) > 0:
for r in kb_res: texts = [r["entity"]["text"] for r in kb_res]
r["rerank_score"] = self.reranker.compute_score([rw_query, r["entity"]["text"]], normalize=True)[0] rerank_scores = self.reranker.compute_score([rw_query, texts], normalize=True)
for i, r in enumerate(kb_res):
r["rerank_score"] = rerank_scores[i]
kb_res.sort(key=lambda x: x["rerank_score"], reverse=True) kb_res.sort(key=lambda x: x["rerank_score"], reverse=True)
kb_res = [_res for _res in kb_res if _res["rerank_score"] > rerank_threshold] kb_res = [_res for _res in kb_res if _res["rerank_score"] > rerank_threshold]

View File

@ -1,105 +1,125 @@
import os import os
from FlagEmbedding import FlagModel, FlagReranker import json
import requests
from FlagEmbedding import FlagModel
from src.config import EMBED_MODEL_INFO, RERANKER_LIST from src.config import EMBED_MODEL_INFO
from src.utils.logging_config import setup_logger from src.utils.logging_config import setup_logger
from src.utils import hashstr from src.utils import hashstr
logger = setup_logger("EmbeddingModel") logger = setup_logger("EmbeddingModel")
GLOBAL_EMBED_STATE = {} class LocalEmbeddingModel(FlagModel):
def __init__(self, config, **kwargs):
info = EMBED_MODEL_INFO[config.embed_model]
class EmbeddingModel(FlagModel): model_name_or_path = config.model_local_paths.get(info["name"], info.get("default_path"))
def __init__(self, model_info, config, **kwargs): logger.info(f"Loading embedding model {info['name']} from {model_name_or_path}")
self.info = model_info
model_name_or_path = config.model_local_paths.get(model_info["name"], model_info.get("default_path"))
logger.info(f"Loading embedding model {model_info['name']} from {model_name_or_path}")
super().__init__(model_name_or_path, super().__init__(model_name_or_path,
query_instruction_for_retrieval=model_info.get("query_instruction", None), query_instruction_for_retrieval=info.get("query_instruction", None),
use_fp16=False, **kwargs) use_fp16=False, **kwargs)
logger.info(f"Embedding model {model_info['name']} loaded") logger.info(f"Embedding model {info['name']} loaded")
class Reranker(FlagReranker):
def __init__(self, config, **kwargs):
assert config.reranker in RERANKER_LIST.keys(), f"Unsupported Reranker: {config.reranker}, only support {RERANKER_LIST.keys()}"
model_info = RERANKER_LIST[config.reranker]
model_name_or_path = config.model_local_paths.get(model_info["name"], model_info.get("default_path"))
logger.info(f"Loading Reranker model {config.reranker} from {model_name_or_path}")
super().__init__(model_name_or_path, use_fp16=True, **kwargs)
logger.info(f"Reranker model {config.reranker} loaded")
from zhipuai import ZhipuAI from zhipuai import ZhipuAI
class ZhipuEmbedding:
def __init__(self, model_info, config) -> None: class RemoteEmbeddingModel:
self.config = config embed_state = {}
self.model_info = model_info
self.client = ZhipuAI(api_key=os.getenv("ZHIPUAI_API_KEY"))
logger.info("Zhipu Embedding model loaded")
self.query_instruction_for_retrieval = "为这个句子生成表示以用于检索相关文章:"
def predict(self, message): def batch_encode(self, messages, batch_size=20):
data = [] data = []
batch_size = 20
if len(message) > batch_size: if len(messages) > batch_size:
global GLOBAL_EMBED_STATE task_id = hashstr(messages)
task_id = hashstr(message) self.embed_state[task_id] = {
logger.info(f"Creating new state for process {task_id}")
GLOBAL_EMBED_STATE[task_id] = {
'status': 'in-progress', 'status': 'in-progress',
'total': len(message), 'total': len(messages),
'progress': 0 'progress': 0
} }
for i in range(0, len(message), batch_size): for i in range(0, len(messages), batch_size):
if len(message) > batch_size: group_msg = messages[i:i+batch_size]
logger.info(f"Encoding {i} to {i+batch_size} with {len(message)} messages") logger.info(f"Encoding {i} to {i+batch_size} with {len(messages)} messages")
GLOBAL_EMBED_STATE[task_id]['progress'] = i response = self.encode_queries(group_msg)
data.extend(response)
group_msg = message[i:i+batch_size] if len(messages) > batch_size:
self.embed_state[task_id]['progress'] = len(messages)
self.embed_state[task_id]['status'] = 'completed'
return data
class ZhipuEmbedding(RemoteEmbeddingModel):
def __init__(self, config) -> None:
self.config = config
self.model = EMBED_MODEL_INFO[config.embed_model]["name"]
self.client = ZhipuAI(api_key=os.getenv("ZHIPUAI_API_KEY"))
def predict(self, message):
response = self.client.embeddings.create( response = self.client.embeddings.create(
model=self.model_info.get("default_path", None), model=self.model,
input=group_msg, input=message,
) )
data = [a.embedding for a in response.data]
data.extend([a.embedding for a in response.data])
if len(message) > batch_size:
GLOBAL_EMBED_STATE[task_id]['progress'] = len(message)
GLOBAL_EMBED_STATE[task_id]['status'] = 'completed'
return data return data
def encode(self, message): def encode(self, message):
return self.predict(message) return self.predict(message)
def encode_queries(self, queries): def encode_queries(self, queries):
# queries = [self.query_instruction_for_retrieval + query for query in queries]
return self.predict(queries) return self.predict(queries)
class SiliconFlowEmbedding(RemoteEmbeddingModel):
def __init__(self, config) -> None:
self.url = "https://api.siliconflow.cn/v1/embeddings"
self.model = EMBED_MODEL_INFO[config.embed_model]["name"]
api_key = os.getenv("SILICONFLOW_API_KEY")
assert api_key, "SILICONFLOW_API_KEY is required"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def encode(self, message):
payload = self.build_payload(message)
response = requests.request("POST", self.url, json=payload, headers=self.headers)
response = json.loads(response.text)
# logger.debug(f"SiliconFlow Embedding response: {response}")
assert response["data"], f"SiliconFlow Embedding failed: {response}"
data = [a["embedding"] for a in response["data"]]
return data
def encode_queries(self, queries):
return self.encode(queries)
def build_payload(self, message):
return {
"model": self.model,
"input": message,
}
def get_embedding_model(config): def get_embedding_model(config):
if not config.enable_knowledge_base: if not config.enable_knowledge_base:
return None return None
provider, model_name = config.embed_model.split('/', 1)
assert config.embed_model in EMBED_MODEL_INFO.keys(), f"Unsupported embed model: {config.embed_model}, only support {EMBED_MODEL_INFO.keys()}" assert config.embed_model in EMBED_MODEL_INFO.keys(), f"Unsupported embed model: {config.embed_model}, only support {EMBED_MODEL_INFO.keys()}"
logger.debug(f"Loading embedding model {config.embed_model}")
if provider == "local":
model = LocalEmbeddingModel(config)
if config.embed_model in ["bge-large-zh-v1.5"]: if provider == "zhipu":
model = EmbeddingModel(EMBED_MODEL_INFO[config.embed_model], config) model = ZhipuEmbedding(config)
if config.embed_model in ["zhipu-embedding-2", "zhipu-embedding-3"]: if provider == "siliconflow":
model = ZhipuEmbedding(EMBED_MODEL_INFO[config.embed_model], config) model = SiliconFlowEmbedding(config)
return model return model

View File

@ -0,0 +1,70 @@
import os
import json
import requests
import numpy as np
from FlagEmbedding import FlagReranker
from src.config import RERANKER_LIST
from src.utils.logging_config import setup_logger
logger = setup_logger("RerankModel")
class LocalReranker(FlagReranker):
def __init__(self, config, **kwargs):
model_info = RERANKER_LIST[config.reranker]
model_name_or_path = config.model_local_paths.get(model_info["name"], model_info.get("default_path"))
logger.info(f"Loading Reranker model {config.reranker} from {model_name_or_path}")
super().__init__(model_name_or_path, use_fp16=True, **kwargs)
logger.info(f"Reranker model {config.reranker} loaded")
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class SilconFlowReranker():
def __init__(self, config, **kwargs):
self.url = "https://api.siliconflow.cn/v1/rerank"
self.model = RERANKER_LIST[config.reranker]["name"]
api_key = os.getenv("SILICONFLOW_API_KEY")
assert api_key, "SILICONFLOW_API_KEY is required"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def compute_score(self, sentence_pairs, batch_size = 256, max_length = 512, normalize = False):
# TODO 还没实现 batch_size
query, sentences = sentence_pairs[0], sentence_pairs[1]
payload = self.build_payload(query, sentences, max_length)
response = requests.request("POST", self.url, json=payload, headers=self.headers)
response = json.loads(response.text)
logger.debug(f"SiliconFlow Reranker response: {response}")
results = sorted(response["results"], key=lambda x: x["index"])
all_scores = [result["relevance_score"] for result in results]
if normalize:
all_scores = [sigmoid(score) for score in all_scores]
return all_scores
def build_payload(self, query, sentences, max_length = 512):
return {
"model": self.model,
"query": query,
"documents": sentences,
"max_chunks_per_doc": max_length,
}
def get_reranker(config):
assert config.reranker in RERANKER_LIST.keys(), f"Unsupported Reranker: {config.reranker}, only support {RERANKER_LIST.keys()}"
provider, model_name = config.reranker.split('/', 1)
if provider == "local":
return LocalReranker(config)
elif provider == "siliconflow":
return SilconFlowReranker(config)

View File

@ -68,30 +68,33 @@ MODEL_NAMES:
siliconflow: siliconflow:
name: SiliconFlow name: SiliconFlow
url: https://cloud.siliconflow.cn/models url: https://cloud.siliconflow.cn/models
default: meta-llama/Meta-Llama-3.1-8B-Instruct default: Qwen/Qwen2.5-7B-Instruct
env: env:
- SILICONFLOW_API_KEY - SILICONFLOW_API_KEY
models: models:
- meta-llama/Meta-Llama-3.1-8B-Instruct - meta-llama/Meta-Llama-3.1-8B-Instruct
- meta-llama/Meta-Llama-3.1-70B-Instruct - Qwen/Qwen2.5-7B-Instruct
- meta-llama/Meta-Llama-3.1-405B-Instruct
- deepseek-ai/DeepSeek-R1 - deepseek-ai/DeepSeek-R1
- deepseek-ai/DeepSeek-V3
EMBED_MODEL_INFO: EMBED_MODEL_INFO:
bge-m3: local/BAAI/bge-m3:
name: BAAI/bge-m3 name: BAAI/bge-m3
default_path: BAAI/bge-m3 default_path: BAAI/bge-m3
dimension: 1024 dimension: 1024
zhipu-embedding-2: zhipu/zhipu-embedding-2:
name: zhipu-embedding-2 name: embedding-2
default_path: embedding-2
dimension: 1024 dimension: 1024
zhipu-embedding-3: zhipu/zhipu-embedding-3:
name: zhipu-embedding-3 name: embedding-3
default_path: embedding-3
dimension: 2048 dimension: 2048
siliconflow/BAAI/bge-m3:
name: BAAI/bge-m3
dimension: 1024
RERANKER_LIST: RERANKER_LIST:
bge-reranker-v2-m3: local/BAAI/bge-reranker-v2-m3:
name: BAAI/bge-reranker-v2-m3 name: BAAI/bge-reranker-v2-m3
default_path: BAAI/bge-reranker-v2-m3 default_path: BAAI/bge-reranker-v2-m3
siliconflow/BAAI/bge-reranker-v2-m3:
name: BAAI/bge-reranker-v2-m3

View File

@ -46,7 +46,7 @@
<div class="flex-center" @click="meta.summary_title = !meta.summary_title"> <div class="flex-center" @click="meta.summary_title = !meta.summary_title">
总结对话标题 <div @click.stop><a-switch v-model:checked="meta.summary_title" /></div> 总结对话标题 <div @click.stop><a-switch v-model:checked="meta.summary_title" /></div>
</div> </div>
<div class="flex-center" @click="meta.enable_retrieval = !meta.enable_retrieval"> <div class="flex-center" @click="meta.enable_retrieval = !meta.enable_retrieval" v-if="configStore.config.enable_knowledge_base">
启用检索 <div @click.stop><a-switch v-model:checked="meta.enable_retrieval" /></div> 启用检索 <div @click.stop><a-switch v-model:checked="meta.enable_retrieval" /></div>
</div> </div>
<div class="flex-center"> <div class="flex-center">
@ -117,13 +117,13 @@
<div></div> <div></div>
</div> </div>
<div v-else-if="message.status == 'searching' && isStreaming" class="searching-msg"><i>正在检索</i></div> <div v-else-if="message.status == 'searching' && isStreaming" class="searching-msg"><i>正在检索</i></div>
<div v-else-if="message.status == 'reasoning' && isStreaming" class="searching-msg"><i>正在思考 {{ message.reasoning }}</i></div> <div v-else-if="message.status == 'reasoning' && isStreaming" class="searching-msg"><i>正在思考 {{ message.reasoning_content }}</i></div>
<div <div
v-else-if="message.text.length == 0 || message.status == 'error' || (message.status != 'finished' && !isStreaming)" v-else-if="message.text.length == 0 || message.status == 'error' || (message.status != 'finished' && !isStreaming)"
class="err-msg" class="err-msg"
@click="retryMessage(message.id)" @click="retryMessage(message.id)"
> >
请求错误请重试 请求错误请重试{{ message.message }}
</div> </div>
<div v-else <div v-else
v-html="renderMarkdown(message)" v-html="renderMarkdown(message)"
@ -234,15 +234,15 @@ const marked = new Marked(
}) })
); );
const consoleMsg = (message) => console.log(message) const consoleMsg = (msg) => console.log(msg)
onClickOutside(panel, () => setTimeout(() => opts.showPanel = false, 30)) onClickOutside(panel, () => setTimeout(() => opts.showPanel = false, 30))
onClickOutside(modelCard, () => setTimeout(() => opts.showModelCard = false, 30)) onClickOutside(modelCard, () => setTimeout(() => opts.showModelCard = false, 30))
const renderMarkdown = (message) => { const renderMarkdown = (msg) => {
if (message.status === 'loading') { if (msg.status === 'loading') {
return marked.parse(message.text + '🟢') return marked.parse(msg.text + '🟢')
} else { } else {
return marked.parse(message.text) return marked.parse(msg.text)
} }
} }
@ -306,11 +306,11 @@ const generateRandomHash = (length) => {
return hash; return hash;
} }
const appendUserMessage = (message) => { const appendUserMessage = (msg) => {
conv.value.messages.push({ conv.value.messages.push({
id: generateRandomHash(16), id: generateRandomHash(16),
role: 'sent', role: 'sent',
text: message text: msg
}) })
scrollToBottom() scrollToBottom()
} }
@ -320,7 +320,7 @@ const appendAiMessage = (text, refs=null) => {
id: generateRandomHash(16), id: generateRandomHash(16),
role: 'received', role: 'received',
text: text, text: text,
reasoning: '', reasoning_content: '',
refs, refs,
status: "init", status: "init",
meta: {}, meta: {},
@ -329,40 +329,44 @@ const appendAiMessage = (text, refs=null) => {
} }
const updateMessage = (info) => { const updateMessage = (info) => {
const message = conv.value.messages.find((message) => message.id === info.id); const msg = conv.value.messages.find((msg) => msg.id === info.id);
if (message) { if (msg) {
try { try {
// text // text
if (info.text !== null && info.text !== undefined && info.text !== '') { if (info.text !== null && info.text !== undefined && info.text !== '') {
message.text = info.text; msg.text = info.text;
} }
if (info.reasoning !== null && info.reasoning !== undefined && info.reasoning !== '') { if (info.reasoning_content !== null && info.reasoning_content !== undefined && info.reasoning_content !== '') {
message.reasoning = info.reasoning; msg.reasoning_content = info.reasoning_content;
} }
// refs // refs
if (info.refs !== null && info.refs !== undefined) { if (info.refs !== null && info.refs !== undefined) {
message.refs = info.refs; msg.refs = info.refs;
} }
if (info.model_name !== null && info.model_name !== undefined && info.model_name !== '') { if (info.model_name !== null && info.model_name !== undefined && info.model_name !== '') {
message.model_name = info.model_name; msg.model_name = info.model_name;
} }
// status // status
if (info.status !== null && info.status !== undefined && info.status !== '') { if (info.status !== null && info.status !== undefined && info.status !== '') {
message.status = info.status; msg.status = info.status;
} }
if (info.meta !== null && info.meta !== undefined) { if (info.meta !== null && info.meta !== undefined) {
message.meta = info.meta; msg.meta = info.meta;
}
if (info.message !== null && info.message !== undefined) {
msg.message = info.message;
} }
scrollToBottom(); scrollToBottom();
} catch (error) { } catch (error) {
console.error('Error updating message:', error); console.error('Error updating message:', error);
message.status = 'error'; msg.status = 'error';
message.text = '消息更新失败'; msg.text = '消息更新失败';
} }
} else { } else {
console.error('Message not found:', info.id); console.error('Message not found:', info.id);
@ -371,9 +375,9 @@ const updateMessage = (info) => {
const groupRefs = (id) => { const groupRefs = (id) => {
const message = conv.value.messages.find((message) => message.id === id) const msg = conv.value.messages.find((msg) => msg.id === id)
if (message.refs && message.refs.knowledge_base.results.length > 0) { if (msg.refs && msg.refs.knowledge_base.results.length > 0) {
message.groupedResults = message.refs.knowledge_base.results msg.groupedResults = msg.refs.knowledge_base.results
.filter(result => result.file && result.file.filename) .filter(result => result.file && result.file.filename)
.reduce((acc, result) => { .reduce((acc, result) => {
const { filename } = result.file; const { filename } = result.file;
@ -387,11 +391,11 @@ const groupRefs = (id) => {
scrollToBottom() scrollToBottom()
} }
const simpleCall = (message) => { const simpleCall = (msg) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch('/api/chat/call_lite', { fetch('/api/chat/call_lite', {
method: 'POST', method: 'POST',
body: JSON.stringify({ query: message, }), body: JSON.stringify({ query: msg, }),
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}) })
.then((response) => response.json()) .then((response) => response.json())
@ -432,24 +436,11 @@ const fetchChatResponse = (user_input, cur_res_id) => {
const readChunk = () => { const readChunk = () => {
return reader.read().then(({ done, value }) => { return reader.read().then(({ done, value }) => {
if (done) { if (done) {
const message = conv.value.messages.find((message) => message.id === cur_res_id) const msg = conv.value.messages.find((msg) => msg.id === cur_res_id)
console.log(message) console.log(msg)
if (message.meta.enable_retrieval) { if (msg.meta.enable_retrieval) {
console.log("fetching refs") console.log("fetching refs")
fetchRefs(cur_res_id).then((data) => {
console.log(data)
updateMessage({
id: cur_res_id,
refs: data,
status: "finished",
});
groupRefs(cur_res_id); groupRefs(cur_res_id);
})
} else {
updateMessage({
id: cur_res_id,
status: "finished",
});
} }
isStreaming.value = false; isStreaming.value = false;
if (conv.value.messages.length === 2) { renameTitle(); } if (conv.value.messages.length === 2) { renameTitle(); }
@ -468,12 +459,11 @@ const fetchChatResponse = (user_input, cur_res_id) => {
updateMessage({ updateMessage({
id: cur_res_id, id: cur_res_id,
text: data.response, text: data.response,
reasoning: data.reasoning_response, reasoning_content: data.reasoning_content,
model_name: data.model_name,
status: data.status, status: data.status,
meta: data.meta, meta: data.meta,
...data,
}); });
// console.log(data)
// console.log("Last message", conv.value.messages[conv.value.messages.length - 1].text) // console.log("Last message", conv.value.messages[conv.value.messages.length - 1].text)
// console.log("Last message", conv.value.messages[conv.value.messages.length - 1].status) // console.log("Last message", conv.value.messages[conv.value.messages.length - 1].status)
@ -541,7 +531,7 @@ const sendMessage = () => {
const retryMessage = (id) => { const retryMessage = (id) => {
// id message message message // id message message message
const index = conv.value.messages.findIndex(message => message.id === id); const index = conv.value.messages.findIndex(msg => msg.id === id);
const pastMessage = conv.value.messages[index-1] const pastMessage = conv.value.messages[index-1]
console.log("retryMessage", id, pastMessage) console.log("retryMessage", id, pastMessage)
conv.value.inputText = pastMessage.text conv.value.inputText = pastMessage.text
@ -552,8 +542,8 @@ const retryMessage = (id) => {
sendMessage(); sendMessage();
} }
const autoSend = (message) => { const autoSend = (msg) => {
conv.value.inputText = message conv.value.inputText = msg
sendMessage() sendMessage()
} }
@ -750,12 +740,12 @@ watch(
/* animation: slideInUp 0.1s ease-in; */ /* animation: slideInUp 0.1s ease-in; */
.err-msg { .err-msg {
color: #FF6B6B; color: #eb8080;
border: 1px solid #FF6B6B; border: 1px solid #eb8080;
padding: 0.2rem 1rem; padding: 0.5rem 1rem;
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: left;
background: #FFF0F0; background: #FFF5F5;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer; cursor: pointer;
} }

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="refs" v-if="showRefs"> <div class="refs" v-if="showRefs">
<div class="tags"> <div class="tags">
<span class="item btn" @click="copyText(msg.text)"><CopyOutlined /></span>
<!-- <span class="item btn" @click="likeThisResponse(msg)"><LikeOutlined /></span> --> <!-- <span class="item btn" @click="likeThisResponse(msg)"><LikeOutlined /></span> -->
<!-- <span class="item btn" @click="dislikeThisResponse(msg)"><DislikeOutlined /></span> --> <!-- <span class="item btn" @click="dislikeThisResponse(msg)"><DislikeOutlined /></span> -->
<span class="item"><GlobalOutlined /> {{ msg.model_name }}</span> <span class="item"><GlobalOutlined /> {{ msg.model_name }}</span>
<span class="item btn" @click="copyText(msg.text)"><CopyOutlined /></span>
<span <span
class="item btn" class="item btn"
@click="openSubGraph(msg)" @click="openSubGraph(msg)"
@ -165,15 +165,15 @@ const getPercent = (value) => {
display: flex; display: flex;
margin-bottom: 20px; margin-bottom: 20px;
color: var(--gray-500); color: var(--gray-500);
font-size: 14px; font-size: 13px;
gap: 10px; gap: 10px;
.item { .item {
background: var(--gray-100); background: var(--gray-100);
color: var(--gray-800); color: var(--gray-700);
padding: 2px 8px; padding: 2px 8px;
border-radius: 8px; border-radius: 8px;
font-size: 14px; font-size: 13px;
user-select: none; user-select: none;
&.btn { &.btn {