Compare commits

...

7 Commits

Author SHA1 Message Date
fe32952825 Fix: Gemini parameters error (#9520)
### What problem does this PR solve?

Fix Gemini parameters error.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-18 14:51:10 +08:00
5808aef28c Fix (search): Optimize the search page functionality and UI #3221 (#9525)
### What problem does this PR solve?

Fix (search): Optimize the search page functionality and UI #3221 

- Add a search list component
- Implement search settings
- Optimize search result display
- Add related search functionality
- Adjust the search input box style
- Unify internationalized text

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-18 14:50:29 +08:00
ca720bd811 Fix: save team's canvas issue. (#9518)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-18 13:05:29 +08:00
ba11312766 Feat: embedded search (#9501)
### What problem does this PR solve?

Add embedded search functionality.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-18 12:05:11 +08:00
c8bbf7452d Env: Update dependencies for proxy support (#9519)
### What problem does this PR solve?

- Update httpx dependency to include socks support in pyproject.toml
- Update lockfile with new socksio dependency

### Type of change

- [x] Update dependencies for proxy support
2025-08-18 12:04:16 +08:00
b08650bc4c Feat: Fixed the chat model setting echo issue (#9521)
### What problem does this PR solve?

Feat: Fixed the chat model setting echo issue

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-18 12:03:33 +08:00
fb77f9917b Refactor: Use Input Length In DefaultRerank (#9516)
### What problem does this PR solve?

1. Use input length to prepare res
2. Adjust torch_empty_cache code location

### Type of change

- [x] Refactoring
- [x] Performance Improvement
2025-08-18 10:00:27 +08:00
32 changed files with 650 additions and 268 deletions

View File

@ -67,9 +67,17 @@ class CodeExecParam(ToolParamBase):
"description": """
This tool has a sandbox that can execute code written in 'Python'/'Javascript'. It recieves a piece of code and return a Json string.
Here's a code example for Python(`main` function MUST be included):
def main(arg1: str, arg2: str) -> dict:
def main() -> dict:
\"\"\"
Generate Fibonacci numbers within 100.
\"\"\"
def fibonacci_recursive(n):
if n <= 1:
return n
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
return {
"result": arg1 + arg2,
"result": fibonacci_recursive(100),
}
Here's a code example for Javascript(`main` function MUST be included and exported):

View File

@ -74,11 +74,11 @@ def rm():
@login_required
def save():
req = request.json
req["user_id"] = current_user.id
if not isinstance(req["dsl"], str):
req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False)
req["dsl"] = json.loads(req["dsl"])
if "id" not in req:
req["user_id"] = current_user.id
if UserCanvasService.query(user_id=current_user.id, title=req["title"].strip()):
return get_data_error_result(message=f"{req['title'].strip()} already exists.")
req["id"] = get_uuid()

View File

@ -34,6 +34,7 @@ from api.db.services.user_service import TenantService, UserTenantService
from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response, validate_request
from graphrag.general.mind_map_extractor import MindMapExtractor
from rag.app.tag import label_question
from rag.prompts.prompt_template import load_prompt
from rag.prompts.prompts import chunks_format
@ -389,39 +390,7 @@ def related_questions():
req = request.json
question = req["question"]
chat_mdl = LLMBundle(current_user.id, LLMType.CHAT)
prompt = """
Role: You are an AI language model assistant tasked with generating 5-10 related questions based on a users original query. These questions should help expand the search query scope and improve search relevance.
Instructions:
Input: You are provided with a users question.
Output: Generate 5-10 alternative questions that are related to the original user question. These alternatives should help retrieve a broader range of relevant documents from a vector database.
Context: Focus on rephrasing the original question in different ways, making sure the alternative questions are diverse but still connected to the topic of the original query. Do not create overly obscure, irrelevant, or unrelated questions.
Fallback: If you cannot generate any relevant alternatives, do not return any questions.
Guidance:
1. Each alternative should be unique but still relevant to the original query.
2. Keep the phrasing clear, concise, and easy to understand.
3. Avoid overly technical jargon or specialized terms unless directly relevant.
4. Ensure that each question contributes towards improving search results by broadening the search angle, not narrowing it.
Example:
Original Question: What are the benefits of electric vehicles?
Alternative Questions:
1. How do electric vehicles impact the environment?
2. What are the advantages of owning an electric car?
3. What is the cost-effectiveness of electric vehicles?
4. How do electric vehicles compare to traditional cars in terms of fuel efficiency?
5. What are the environmental benefits of switching to electric cars?
6. How do electric vehicles help reduce carbon emissions?
7. Why are electric vehicles becoming more popular?
8. What are the long-term savings of using electric vehicles?
9. How do electric vehicles contribute to sustainability?
10. What are the key benefits of electric vehicles for consumers?
Reason:
Rephrasing the original query into multiple alternative questions helps the user explore different aspects of their search topic, improving the quality of search results.
These questions guide the search engine to provide a more comprehensive set of relevant documents.
"""
prompt = load_prompt("related_question")
ans = chat_mdl.chat(
prompt,
[

View File

@ -18,7 +18,9 @@ import re
import time
import tiktoken
from flask import Response, jsonify, request
import trio
from agent.canvas import Canvas
from api import settings
from api.db import LLMType, StatusEnum
from api.db.db_models import APIToken
from api.db.services.api_service import API4ConversationService
@ -29,9 +31,15 @@ from api.db.services.conversation_service import completion as rag_completion
from api.db.services.dialog_service import DialogService, ask, chat
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMBundle
from api.db.services.search_service import SearchService
from api.db.services.user_service import UserTenantService
from api.utils import get_uuid
from api.utils.api_utils import check_duplicate_ids, get_data_openai, get_error_data_result, get_result, token_required, validate_request
from api.utils.api_utils import check_duplicate_ids, get_data_openai, get_error_data_result, get_json_result, get_result, server_error_response, token_required, validate_request
from graphrag.general.mind_map_extractor import MindMapExtractor
from rag.app.tag import label_question
from rag.prompts import chunks_format
from rag.prompts.prompt_template import load_prompt
from rag.prompts.prompts import cross_languages, keyword_extraction
@manager.route("/chats/<chat_id>/sessions", methods=["POST"]) # noqa: F821
@ -855,3 +863,215 @@ def begin_inputs(agent_id):
"prologue": canvas.get_prologue()
}
)
@manager.route("/searchbots/ask", methods=["POST"]) # noqa: F821
@validate_request("question", "kb_ids")
def ask_about_embedded():
token = request.headers.get("Authorization").split()
if len(token) != 2:
return get_error_data_result(message='Authorization is not valid!"')
token = token[1]
objs = APIToken.query(beta=token)
if not objs:
return get_error_data_result(message='Authentication error: API key is invalid!"')
req = request.json
uid = objs[0].tenant_id
def stream():
nonlocal req, uid
try:
for ans in ask(req["question"], req["kb_ids"], uid):
yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n"
except Exception as e:
yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n"
yield "data:" + json.dumps({"code": 0, "message": "", "data": True}, ensure_ascii=False) + "\n\n"
resp = Response(stream(), mimetype="text/event-stream")
resp.headers.add_header("Cache-control", "no-cache")
resp.headers.add_header("Connection", "keep-alive")
resp.headers.add_header("X-Accel-Buffering", "no")
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
return resp
@manager.route("/searchbots/retrieval_test", methods=['POST']) # noqa: F821
@validate_request("kb_id", "question")
def retrieval_test_embedded():
token = request.headers.get("Authorization").split()
if len(token) != 2:
return get_error_data_result(message='Authorization is not valid!"')
token = token[1]
objs = APIToken.query(beta=token)
if not objs:
return get_error_data_result(message='Authentication error: API key is invalid!"')
req = request.json
page = int(req.get("page", 1))
size = int(req.get("size", 30))
question = req["question"]
kb_ids = req["kb_id"]
if isinstance(kb_ids, str):
kb_ids = [kb_ids]
doc_ids = req.get("doc_ids", [])
similarity_threshold = float(req.get("similarity_threshold", 0.0))
vector_similarity_weight = float(req.get("vector_similarity_weight", 0.3))
use_kg = req.get("use_kg", False)
top = int(req.get("top_k", 1024))
langs = req.get("cross_languages", [])
tenant_ids = []
tenant_id = objs[0].tenant_id
if not tenant_id:
return get_error_data_result(message="permission denined.")
try:
tenants = UserTenantService.query(user_id=tenant_id)
for kb_id in kb_ids:
for tenant in tenants:
if KnowledgebaseService.query(
tenant_id=tenant.tenant_id, id=kb_id):
tenant_ids.append(tenant.tenant_id)
break
else:
return get_json_result(
data=False, message='Only owner of knowledgebase authorized for this operation.',
code=settings.RetCode.OPERATING_ERROR)
e, kb = KnowledgebaseService.get_by_id(kb_ids[0])
if not e:
return get_error_data_result(message="Knowledgebase not found!")
if langs:
question = cross_languages(kb.tenant_id, None, question, langs)
embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id)
rerank_mdl = None
if req.get("rerank_id"):
rerank_mdl = LLMBundle(kb.tenant_id, LLMType.RERANK.value, llm_name=req["rerank_id"])
if req.get("keyword", False):
chat_mdl = LLMBundle(kb.tenant_id, LLMType.CHAT)
question += keyword_extraction(chat_mdl, question)
labels = label_question(question, [kb])
ranks = settings.retrievaler.retrieval(question, embd_mdl, tenant_ids, kb_ids, page, size,
similarity_threshold, vector_similarity_weight, top,
doc_ids, rerank_mdl=rerank_mdl, highlight=req.get("highlight"),
rank_feature=labels
)
if use_kg:
ck = settings.kg_retrievaler.retrieval(question,
tenant_ids,
kb_ids,
embd_mdl,
LLMBundle(kb.tenant_id, LLMType.CHAT))
if ck["content_with_weight"]:
ranks["chunks"].insert(0, ck)
for c in ranks["chunks"]:
c.pop("vector", None)
ranks["labels"] = labels
return get_json_result(data=ranks)
except Exception as e:
if str(e).find("not_found") > 0:
return get_json_result(data=False, message='No chunk found! Check the chunk status please!',
code=settings.RetCode.DATA_ERROR)
return server_error_response(e)
@manager.route("/searchbots/related_questions", methods=["POST"]) # noqa: F821
@validate_request("question")
def related_questions_embedded():
token = request.headers.get("Authorization").split()
if len(token) != 2:
return get_error_data_result(message='Authorization is not valid!"')
token = token[1]
objs = APIToken.query(beta=token)
if not objs:
return get_error_data_result(message='Authentication error: API key is invalid!"')
req = request.json
tenant_id = objs[0].tenant_id
if not tenant_id:
return get_error_data_result(message="permission denined.")
question = req["question"]
chat_mdl = LLMBundle(tenant_id, LLMType.CHAT)
prompt = load_prompt("related_question")
ans = chat_mdl.chat(
prompt,
[
{
"role": "user",
"content": f"""
Keywords: {question}
Related search terms:
""",
}
],
{"temperature": 0.9},
)
return get_json_result(data=[re.sub(r"^[0-9]\. ", "", a) for a in ans.split("\n") if re.match(r"^[0-9]\. ", a)])
@manager.route("/searchbots/detail", methods=["GET"]) # noqa: F821
def detail_share_embedded():
token = request.headers.get("Authorization").split()
if len(token) != 2:
return get_error_data_result(message='Authorization is not valid!"')
token = token[1]
objs = APIToken.query(beta=token)
if not objs:
return get_error_data_result(message='Authentication error: API key is invalid!"')
search_id = request.args["search_id"]
tenant_id = objs[0].tenant_id
if not tenant_id:
return get_error_data_result(message="permission denined.")
try:
tenants = UserTenantService.query(user_id=tenant_id)
for tenant in tenants:
if SearchService.query(tenant_id=tenant.tenant_id, id=search_id):
break
else:
return get_json_result(data=False, message="Has no permission for this operation.", code=settings.RetCode.OPERATING_ERROR)
search = SearchService.get_detail(search_id)
if not search:
return get_error_data_result(message="Can't find this Search App!")
return get_json_result(data=search)
except Exception as e:
return server_error_response(e)
@manager.route("/searchbots/mindmap", methods=["POST"]) # noqa: F821
@validate_request("question", "kb_ids")
def mindmap():
token = request.headers.get("Authorization").split()
if len(token) != 2:
return get_error_data_result(message='Authorization is not valid!"')
token = token[1]
objs = APIToken.query(beta=token)
if not objs:
return get_error_data_result(message='Authentication error: API key is invalid!"')
tenant_id = objs[0].tenant_id
req = request.json
kb_ids = req["kb_ids"]
e, kb = KnowledgebaseService.get_by_id(kb_ids[0])
if not e:
return get_error_data_result(message="Knowledgebase not found!")
embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING, llm_name=kb.embd_id)
chat_mdl = LLMBundle(tenant_id, LLMType.CHAT)
question = req["question"]
ranks = settings.retrievaler.retrieval(question, embd_mdl, kb.tenant_id, kb_ids, 1, 12, 0.3, 0.3, aggs=False, rank_feature=label_question(question, [kb]))
mindmap = MindMapExtractor(chat_mdl)
mind_map = trio.run(mindmap, [c["content_with_weight"] for c in ranks["chunks"]])
mind_map = mind_map.output
if "error" in mind_map:
return server_error_response(Exception(mind_map["error"]))
return get_json_result(data=mind_map)

View File

@ -1146,60 +1146,35 @@
"llm_name": "gemini-2.5-flash",
"tags": "LLM,CHAT,1024K,IMAGE2TEXT",
"max_tokens": 1048576,
"model_type": "image2text",
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "gemini-2.5-pro",
"tags": "LLM,CHAT,IMAGE2TEXT,1024K",
"max_tokens": 1048576,
"model_type": "image2text",
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "gemini-2.5-flash-preview-05-20",
"llm_name": "gemini-2.5-flash-lite",
"tags": "LLM,CHAT,1024K,IMAGE2TEXT",
"max_tokens": 1048576,
"model_type": "image2text",
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "gemini-2.0-flash-001",
"tags": "LLM,CHAT,1024K",
"max_tokens": 1048576,
"model_type": "image2text",
"is_tools": true
},
{
"llm_name": "gemini-2.0-flash-thinking-exp-01-21",
"llm_name": "gemini-2.0-flash",
"tags": "LLM,CHAT,1024K",
"max_tokens": 1048576,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "gemini-1.5-flash",
"tags": "LLM,IMAGE2TEXT,1024K",
"llm_name": "gemini-2.0-flash-lite",
"tags": "LLM,CHAT,1024K",
"max_tokens": 1048576,
"model_type": "image2text"
},
{
"llm_name": "gemini-2.5-pro-preview-05-06",
"tags": "LLM,IMAGE2TEXT,1024K",
"max_tokens": 1048576,
"model_type": "image2text"
},
{
"llm_name": "gemini-1.5-pro",
"tags": "LLM,IMAGE2TEXT,2048K",
"max_tokens": 2097152,
"model_type": "image2text"
},
{
"llm_name": "gemini-1.5-flash-8b",
"tags": "LLM,IMAGE2TEXT,1024K",
"max_tokens": 1048576,
"model_type": "image2text",
"model_type": "chat",
"is_tools": true
},
{

View File

@ -57,7 +57,7 @@ async def run_graphrag(
):
chunks.append(d["content_with_weight"])
with trio.fail_after(len(chunks)*60):
with trio.fail_after(max(120, len(chunks)*120)):
subgraph = await generate_subgraph(
LightKGExt
if "method" not in row["kb_parser_config"].get("graphrag", {}) or row["kb_parser_config"]["graphrag"]["method"] != "general"

View File

@ -43,7 +43,7 @@ dependencies = [
"groq==0.9.0",
"hanziconv==0.3.2",
"html-text==0.6.2",
"httpx==0.27.2",
"httpx[socks]==0.27.2",
"huggingface-hub>=0.25.0,<0.26.0",
"infinity-sdk==0.6.0-dev4",
"infinity-emb>=0.0.66,<0.0.67",

View File

@ -539,24 +539,24 @@ class GeminiCV(Base):
return res.text, res.usage_metadata.total_token_count
def chat(self, system, history, gen_conf, images=[]):
from transformers import GenerationConfig
generation_config = dict(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7))
try:
response = self.model.generate_content(
self._form_history(system, history, images),
generation_config=GenerationConfig(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7)))
generation_config=generation_config)
ans = response.text
return ans, response.usage_metadata.total_token_count
except Exception as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf, images=[]):
from transformers import GenerationConfig
ans = ""
response = None
try:
generation_config = dict(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7))
response = self.model.generate_content(
self._form_history(system, history, images),
generation_config=GenerationConfig(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7)),
generation_config=generation_config,
stream=True,
)
@ -572,7 +572,7 @@ class GeminiCV(Base):
yield response.usage_metadata.total_token_count
else:
yield 0
class NvidiaCV(Base):
_FACTORY_NAME = "NVIDIA"

View File

@ -100,7 +100,7 @@ class DefaultRerank(Base):
old_dynamic_batch_size = self._dynamic_batch_size
if max_batch_size is not None:
self._dynamic_batch_size = max_batch_size
res = np.array([], dtype=float)
res = np.array(len(pairs), dtype=float)
i = 0
while i < len(pairs):
cur_i = i
@ -111,7 +111,7 @@ class DefaultRerank(Base):
try:
# call subclass implemented batch processing calculation
batch_scores = self._compute_batch_scores(pairs[i : i + current_batch])
res = np.append(res, batch_scores)
res[i : i + current_batch] = batch_scores
i += current_batch
self._dynamic_batch_size = min(self._dynamic_batch_size * 2, 8)
break
@ -125,8 +125,8 @@ class DefaultRerank(Base):
raise
if retry_count >= max_retries:
raise RuntimeError("max retry times, still cannot process batch, please check your GPU memory")
self.torch_empty_cache()
self.torch_empty_cache()
self._dynamic_batch_size = old_dynamic_batch_size
return np.array(res)

View File

@ -0,0 +1,55 @@
# Role
You are an AI language model assistant tasked with generating **5-10 related questions** based on a users original query.
These questions should help **expand the search query scope** and **improve search relevance**.
---
## Instructions
**Input:**
You are provided with a **users question**.
**Output:**
Generate **5-10 alternative questions** that are **related** to the original user question.
These alternatives should help retrieve a **broader range of relevant documents** from a vector database.
**Context:**
Focus on **rephrasing** the original question in different ways, ensuring the alternative questions are **diverse but still connected** to the topic of the original query.
Do **not** create overly obscure, irrelevant, or unrelated questions.
**Fallback:**
If you cannot generate any relevant alternatives, do **not** return any questions.
---
## Guidance
1. Each alternative should be **unique** but still **relevant** to the original query.
2. Keep the phrasing **clear, concise, and easy to understand**.
3. Avoid overly technical jargon or specialized terms **unless directly relevant**.
4. Ensure that each question **broadens** the search angle, **not narrows** it.
---
## Example
**Original Question:**
> What are the benefits of electric vehicles?
**Alternative Questions:**
1. How do electric vehicles impact the environment?
2. What are the advantages of owning an electric car?
3. What is the cost-effectiveness of electric vehicles?
4. How do electric vehicles compare to traditional cars in terms of fuel efficiency?
5. What are the environmental benefits of switching to electric cars?
6. How do electric vehicles help reduce carbon emissions?
7. Why are electric vehicles becoming more popular?
8. What are the long-term savings of using electric vehicles?
9. How do electric vehicles contribute to sustainability?
10. What are the key benefits of electric vehicles for consumers?
---
## Reason
Rephrasing the original query into multiple alternative questions helps the user explore **different aspects** of their search topic, improving the **quality of search results**.
These questions guide the search engine to provide a **more comprehensive set** of relevant documents.

View File

@ -302,7 +302,7 @@ async def build_chunks(task, progress_callback):
# If the image is in RGBA mode, convert it to RGB mode before saving it in JPEG format.
if d["image"].mode in ("RGBA", "P"):
converted_image = d["image"].convert("RGB")
d["image"].close() # Close original image
#d["image"].close() # Close original image
d["image"] = converted_image
try:
d["image"].save(output_buffer, format='JPEG')

18
uv.lock generated
View File

@ -2422,6 +2422,11 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0" },
]
[package.optional-dependencies]
socks = [
{ name = "socksio" },
]
[[package]]
name = "httpx-sse"
version = "0.4.1"
@ -5308,7 +5313,7 @@ dependencies = [
{ name = "groq" },
{ name = "hanziconv" },
{ name = "html-text" },
{ name = "httpx" },
{ name = "httpx", extra = ["socks"] },
{ name = "huggingface-hub" },
{ name = "infinity-emb" },
{ name = "infinity-sdk" },
@ -5463,7 +5468,7 @@ requires-dist = [
{ name = "groq", specifier = "==0.9.0" },
{ name = "hanziconv", specifier = "==0.3.2" },
{ name = "html-text", specifier = "==0.6.2" },
{ name = "httpx", specifier = "==0.27.2" },
{ name = "httpx", extras = ["socks"], specifier = "==0.27.2" },
{ name = "huggingface-hub", specifier = ">=0.25.0,<0.26.0" },
{ name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" },
{ name = "infinity-sdk", specifier = "==0.6.0.dev4" },
@ -6216,6 +6221,15 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" },
]
[[package]]
name = "socksio"
version = "1.0.0"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac" }
wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3" },
]
[[package]]
name = "sortedcontainers"
version = "2.4.0"

View File

@ -28,20 +28,32 @@ interface LlmSettingFieldItemsProps {
options?: any[];
}
export const LlmSettingSchema = {
export const LLMIdFormField = {
llm_id: z.string(),
temperature: z.coerce.number().optional(),
top_p: z.number().optional(),
presence_penalty: z.coerce.number().optional(),
frequency_penalty: z.coerce.number().optional(),
};
export const LlmSettingEnabledSchema = {
temperatureEnabled: z.boolean().optional(),
topPEnabled: z.boolean().optional(),
presencePenaltyEnabled: z.boolean().optional(),
frequencyPenaltyEnabled: z.boolean().optional(),
maxTokensEnabled: z.boolean().optional(),
};
export const LlmSettingFieldSchema = {
temperature: z.coerce.number().optional(),
top_p: z.number().optional(),
presence_penalty: z.coerce.number().optional(),
frequency_penalty: z.coerce.number().optional(),
max_tokens: z.number().optional(),
};
export const LlmSettingSchema = {
...LLMIdFormField,
...LlmSettingFieldSchema,
...LlmSettingEnabledSchema,
};
export function LlmSettingFieldItems({
prefix,
options,

View File

@ -1,6 +1,7 @@
import { settledModelVariableMap } from '@/constants/knowledge';
import { AgentFormContext } from '@/pages/agent/context';
import useGraphStore from '@/pages/agent/store';
import { setChatVariableEnabledFieldValuePage } from '@/utils/chat';
import { useCallback, useContext } from 'react';
import { useFormContext } from 'react-hook-form';
@ -11,6 +12,20 @@ export function useHandleFreedomChange(
const node = useContext(AgentFormContext);
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
const setLLMParameters = useCallback(
(values: Record<string, any>, withPrefix: boolean) => {
for (const key in values) {
if (Object.prototype.hasOwnProperty.call(values, key)) {
const realKey = getFieldWithPrefix(key);
const element = values[key as keyof typeof values];
form.setValue(withPrefix ? realKey : key, element);
}
}
},
[form, getFieldWithPrefix],
);
const handleChange = useCallback(
(parameter: string) => {
const currentValues = { ...form.getValues() };
@ -25,16 +40,12 @@ export function useHandleFreedomChange(
updateNodeForm(node?.id, nextValues);
}
for (const key in values) {
if (Object.prototype.hasOwnProperty.call(values, key)) {
const realKey = getFieldWithPrefix(key);
const element = values[key as keyof typeof values];
const variableCheckBoxFieldMap = setChatVariableEnabledFieldValuePage();
form.setValue(realKey, element);
}
}
setLLMParameters(values, true);
setLLMParameters(variableCheckBoxFieldMap, false);
},
[form, getFieldWithPrefix, node?.id, updateNodeForm],
[form, node?.id, setLLMParameters, updateNodeForm],
);
return handleChange;

View File

@ -49,7 +49,7 @@ export const RAGFlowAvatar = memo(
const initials = getInitials(name);
const { from, to } = name
? getColorForName(name)
: { from: 'hsl(0, 0%, 80%)', to: 'hsl(0, 0%, 30%)' };
: { from: 'hsl(0, 0%, 30%)', to: 'hsl(0, 0%, 80%)' };
const fallbackRef = useRef<HTMLElement>(null);
const [fontSize, setFontSize] = useState('0.875rem');

View File

@ -1419,6 +1419,24 @@ This delimiter is used to split the input text into several text pieces echo of
search: {
createSearch: 'Create Search',
searchGreeting: 'How can I help you today ',
profile: 'Hide Profile',
locale: 'Locale',
embedCode: 'Embed code',
id: 'ID',
copySuccess: 'Copy Success',
welcomeBack: 'Welcome back',
searchSettings: 'Search Settings',
name: 'Name',
avatar: 'Avatar',
description: 'Description',
datasets: 'Datasets',
rerankModel: 'Rerank Model',
AISummary: 'AI Summary',
enableWebSearch: 'Enable Web Search',
enableRelatedSearch: 'Enable Related Search',
showQueryMindmap: 'Show Query Mindmap',
embedApp: 'Embed App',
relatedSearch: 'Related Search',
},
},
};

View File

@ -1192,6 +1192,12 @@ export default {
search: {
createSearch: '新建查詢',
searchGreeting: '今天我能為你做些什麽?',
profile: '隱藏個人資料',
locale: '語言',
embedCode: '嵌入代碼',
id: 'ID',
copySuccess: '複製成功',
welcomeBack: '歡迎回來',
},
},
};

View File

@ -1323,6 +1323,24 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
search: {
createSearch: '新建查询',
searchGreeting: '今天我能为你做些什么?',
profile: '隐藏个人资料',
locale: '语言',
embedCode: '嵌入代码',
id: 'ID',
copySuccess: '复制成功',
welcomeBack: '欢迎回来',
searchSettings: '搜索设置',
name: '姓名',
avatar: '头像',
description: '描述',
datasets: '数据集',
rerankModel: 'rerank 模型',
AISummary: 'AI 总结',
enableWebSearch: '启用网页搜索',
enableRelatedSearch: '启用相关搜索',
showQueryMindmap: '显示查询思维导图',
embedApp: '嵌入网站',
relatedSearch: '相关搜索',
},
},
};

View File

@ -54,6 +54,8 @@ export function useAgentToolInitialValues() {
return pick(initialValues, 'top_n');
case Operator.WenCai:
return pick(initialValues, 'top_n', 'query_type');
case Operator.Code:
return {};
default:
return initialValues;

View File

@ -7,6 +7,7 @@ import { useNavigate } from 'umi';
import { Agents } from './agent-list';
import { SeeAllAppCard } from './application-card';
import { ChatList } from './chat-list';
import { SearchList } from './search-list';
const IconMap = {
[Routes.Chats]: 'chat',
@ -56,6 +57,7 @@ export function Applications() {
<div className="flex flex-wrap gap-4">
{val === Routes.Agents && <Agents></Agents>}
{val === Routes.Chats && <ChatList></ChatList>}
{val === Routes.Searches && <SearchList></SearchList>}
{<SeeAllAppCard click={handleNavigate}></SeeAllAppCard>}
</div>
</section>

View File

@ -0,0 +1,15 @@
import { useFetchSearchList } from '../next-searches/hooks';
import { ApplicationCard } from './application-card';
export function SearchList() {
const { data } = useFetchSearchList();
return data?.data.search_apps
.slice(0, 10)
.map((x) => (
<ApplicationCard
key={x.id}
app={{ avatar: x.avatar, title: x.name, update_time: x.update_time }}
></ApplicationCard>
));
}

View File

@ -3,6 +3,10 @@ import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator';
import { useFetchDialog, useSetDialog } from '@/hooks/use-chat-request';
import { transformBase64ToFile, transformFile2Base64 } from '@/utils/file-util';
import {
removeUselessFieldsFromValues,
setLLMSettingEnabledValues,
} from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { X } from 'lucide-react';
import { useEffect } from 'react';
@ -26,6 +30,7 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
const form = useForm<FormSchemaType>({
resolver: zodResolver(formSchema),
shouldUnregister: true,
defaultValues: {
name: '',
language: 'English',
@ -47,14 +52,18 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
});
async function onSubmit(values: FormSchemaType) {
const icon = values.icon;
const nextValues: Record<string, any> = removeUselessFieldsFromValues(
values,
'llm_setting.',
);
const icon = nextValues.icon;
const avatar =
Array.isArray(icon) && icon.length > 0
? await transformFile2Base64(icon[0])
: '';
setDialog({
...data,
...values,
...nextValues,
icon: avatar,
dialog_id: id,
});
@ -65,9 +74,14 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
}
useEffect(() => {
const llmSettingEnabledValues = setLLMSettingEnabledValues(
data.llm_setting,
);
const nextData = {
...data,
icon: data.icon ? [transformBase64ToFile(data.icon)] : [],
...llmSettingEnabledValues,
};
form.reset(nextData as FormSchemaType);
}, [data, form]);

View File

@ -1,9 +1,11 @@
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
import {
LlmSettingEnabledSchema,
LlmSettingFieldSchema,
} from '@/components/llm-setting-items/next';
import { rerankFormSchema } from '@/components/rerank';
import { vectorSimilarityWeightSchema } from '@/components/similarity-slider';
import { topnSchema } from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { omit } from 'lodash';
import { z } from 'zod';
export function useChatSettingSchema() {
@ -39,20 +41,23 @@ export function useChatSettingSchema() {
}),
prompt_config: promptConfigSchema,
...rerankFormSchema,
llm_setting: z.object(omit(LlmSettingSchema, 'llm_id')),
llm_setting: z.object(LlmSettingFieldSchema),
...LlmSettingEnabledSchema,
llm_id: z.string().optional(),
...vectorSimilarityWeightSchema,
...topnSchema,
meta_data_filter: z
.object({
method: z.string().optional(),
manual: z.array(
z.object({
key: z.string(),
op: z.string(),
value: z.string(),
}),
),
manual: z
.array(
z.object({
key: z.string(),
op: z.string(),
value: z.string(),
}),
)
.optional(),
})
.optional(),
});

View File

@ -7,21 +7,28 @@ import {
LanguageAbbreviationMap,
} from '@/constants/common';
import { useTranslate } from '@/hooks/common-hooks';
import { useCallback, useMemo, useState } from 'react';
import { message } from 'antd';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
type IEmbedAppModalProps = {
open: any;
url: string;
token: string;
from: string;
beta: string;
setOpen: (e: any) => void;
tenantId: string;
};
const EmbedAppModal = (props: IEmbedAppModalProps) => {
const { t } = useTranslate('chat');
const { open, setOpen, token = '', from, beta = '', url, tenantId } = props;
const { t } = useTranslate('search');
const { open, setOpen, token = '', from, url, tenantId } = props;
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
useEffect(() => {
if (open && !beta) {
handleOperate();
}
}, [handleOperate, open, beta]);
const [hideAvatar, setHideAvatar] = useState(false);
const [locale, setLocale] = useState('');
@ -69,7 +76,7 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
{/* Hide Avatar Toggle */}
<div className="mb-6">
<label className="block text-sm font-medium mb-2">
{t('avatarHidden')}
{t('profile')}
</label>
<div className="flex items-center">
<Switch
@ -83,7 +90,9 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
{/* Locale Select */}
<div className="mb-6">
<label className="block text-sm font-medium mb-2">Locale</label>
<label className="block text-sm font-medium mb-2">
{t('locale')}
</label>
<RAGFlowSelect
placeholder="Select a locale"
value={locale}
@ -93,7 +102,9 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
</div>
{/* Embed Code */}
<div className="mb-6">
<label className="block text-sm font-medium mb-2">Embed code</label>
<label className="block text-sm font-medium mb-2">
{t('embedCode')}
</label>
{/* <div className=" border rounded-lg"> */}
{/* <pre className="text-sm whitespace-pre-wrap">{text}</pre> */}
<HightLightMarkdown>{text}</HightLightMarkdown>
@ -102,18 +113,21 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
{/* ID Field */}
<div className="mb-4">
<label className="block text-sm font-medium mb-2">ID</label>
<div className="flex items-center">
<label className="block text-sm font-medium mb-2">{t('id')}</label>
<div className="flex items-center border border-border rounded-lg bg-bg-base">
<input
type="text"
value={token}
readOnly
className="flex-1 px-4 py-2 border border-gray-700 rounded-lg bg-bg-base focus:outline-none"
className="flex-1 px-4 py-2 focus:outline-none bg-bg-base rounded-lg"
/>
<button
type="button"
onClick={() => navigator.clipboard.writeText(token)}
className="ml-2 p-2 text-gray-400 hover:text-white transition-colors"
onClick={() => {
navigator.clipboard.writeText(token);
message.success(t('copySuccess'));
}}
className="ml-2 p-2 hover:text-white transition-colors"
title="Copy ID"
>
<svg

View File

@ -486,3 +486,16 @@ export const useSearching = ({
onChange,
};
};
export const useCheckSettings = (data: ISearchAppDetailProps) => {
if (!data) {
return {
openSetting: false,
};
}
const { search_config, name } = data;
const { kb_ids } = search_config;
return {
openSetting: kb_ids && kb_ids.length && name ? false : true,
};
};

View File

@ -13,12 +13,13 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
import { Send, Settings } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
import { useTranslation } from 'react-i18next';
import {
ISearchAppDetailProps,
useFetchSearchDetail,
} from '../next-searches/hooks';
import EmbedAppModal from './embed-app-modal';
import { useCheckSettings } from './hooks';
import './index.less';
import SearchHome from './search-home';
import { SearchSetting } from './search-setting';
@ -28,15 +29,20 @@ export default function SearchPage() {
const { navigateToSearchList } = useNavigatePage();
const [isSearching, setIsSearching] = useState(false);
const { data: SearchData } = useFetchSearchDetail();
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
const [openSetting, setOpenSetting] = useState(false);
const [openEmbed, setOpenEmbed] = useState(false);
const [searchText, setSearchText] = useState('');
const { data: tenantInfo } = useFetchTenantInfo();
const tenantId = tenantInfo.tenant_id;
const { t } = useTranslation();
const { openSetting: checkOpenSetting } = useCheckSettings(
SearchData as ISearchAppDetailProps,
);
useEffect(() => {
handleOperate();
}, [handleOperate]);
setOpenSetting(checkOpenSetting);
}, [checkOpenSetting]);
useEffect(() => {
if (isSearching) {
setOpenSetting(false);
@ -60,7 +66,7 @@ export default function SearchPage() {
</BreadcrumbList>
</Breadcrumb>
</PageHeader>
<div className="flex gap-3 w-full">
<div className="flex gap-3 w-full bg-bg-base">
<div className="flex-1">
{!isSearching && (
<div className="animate-fade-in-down">
@ -98,7 +104,6 @@ export default function SearchPage() {
url="/next-search/share"
token={SearchData?.id as string}
from={SharedFrom.Search}
beta={beta}
tenantId={tenantId}
/>
}
@ -119,7 +124,7 @@ export default function SearchPage() {
onClick={() => setOpenEmbed(!openEmbed)}
>
<Send />
<div>Embed App</div>
<div>{t('search.embedApp')}</div>
</Button>
</div>
{!isSearching && (
@ -130,7 +135,9 @@ export default function SearchPage() {
onClick={() => setOpenSetting(!openSetting)}
>
<Settings className="text-text-secondary" />
<div className="text-text-secondary">Search Settings</div>
<div className="text-text-secondary">
{t('search.searchSettings')}
</div>
</Button>
</div>
)}

View File

@ -3,6 +3,7 @@ import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import { cn } from '@/lib/utils';
import { Search } from 'lucide-react';
import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import './index.less';
import Spotlight from './spotlight';
@ -18,6 +19,7 @@ export default function SearchPage({
setSearchText: Dispatch<SetStateAction<string>>;
}) {
const { data: userInfo } = useFetchUserInfo();
const { t } = useTranslation();
return (
<section className="relative w-full flex transition-all justify-center items-center mt-32">
<div className="relative z-10 px-8 pt-8 flex text-transparent flex-col justify-center items-center w-[780px]">
@ -36,14 +38,14 @@ export default function SearchPage({
<>
<p className="mb-4 transition-opacity">👋 Hi there</p>
<p className="mb-10 transition-opacity">
Welcome back, {userInfo?.nickname}
{t('search.welcomeBack')}, {userInfo?.nickname}
</p>
</>
)}
<div className="relative w-full ">
<Input
placeholder="How can I help you today?"
placeholder={t('search.searchGreeting')}
className="w-full rounded-full py-6 px-4 pr-10 text-text-primary text-lg bg-bg-base delay-700"
value={searchText}
onKeyUp={(e) => {
@ -57,7 +59,7 @@ export default function SearchPage({
/>
<button
type="button"
className="absolute right-2 top-1/2 -translate-y-1/2 transform rounded-full bg-white p-2 text-gray-800 shadow w-12"
className="absolute right-2 top-1/2 -translate-y-1/2 transform rounded-full bg-text-primary p-2 text-bg-base shadow w-12"
onClick={() => {
setIsSearching(!isSearching);
}}

View File

@ -28,10 +28,10 @@ import { IKnowledge } from '@/interfaces/database/knowledge';
import { cn } from '@/lib/utils';
import { transformFile2Base64 } from '@/utils/file-util';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { Pencil, Upload, X } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
LlmModelType,
@ -113,6 +113,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
const descriptionDefaultValue = 'You are an intelligent assistant.';
const { t } = useTranslation();
const resetForm = useCallback(() => {
formMethods.reset({
search_id: data?.id,
@ -305,7 +306,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
style={{ maxHeight: 'calc(100dvh - 170px)' }}
>
<div className="flex justify-between items-center text-base mb-8">
<div className="text-text-primary">Search Settings</div>
<div className="text-text-primary">{t('search.searchSettings')}</div>
<div onClick={() => setOpen(false)}>
<X size={16} className="text-text-primary cursor-pointer" />
</div>
@ -334,10 +335,11 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<span className="text-destructive mr-1"> *</span>Name
<span className="text-destructive mr-1"> *</span>
{t('search.name')}
</FormLabel>
<FormControl>
<Input placeholder="Name" {...field} />
<Input placeholder={t('search.name')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -350,7 +352,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
name="avatar"
render={() => (
<FormItem>
<FormLabel>Avatar</FormLabel>
<FormLabel>{t('search.avatar')}</FormLabel>
<FormControl>
<div className="relative group flex items-end gap-2">
<div>
@ -413,7 +415,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormLabel>{t('search.description')}</FormLabel>
<FormControl>
<Input
placeholder="You are an intelligent assistant."
@ -443,7 +445,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<span className="text-destructive mr-1"> *</span>Datasets
<span className="text-destructive mr-1"> *</span>
{t('search.datasets')}
</FormLabel>
<FormControl>
<MultiSelect
@ -501,26 +504,6 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
</div>
<FormMessage />
</FormItem>
// <FormItem className="flex flex-col">
// <FormLabel>
// <span className="text-destructive mr-1"> *</span>Keyword
// Similarity Weight
// </FormLabel>
// <FormControl>
// {/* <div className="flex justify-between items-center">
// <SingleFormSlider
// max={100}
// step={1}
// value={field.value as number}
// onChange={(values) => field.onChange(values)}
// ></SingleFormSlider>
// <Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
// {field.value}
// </Label>
// </div> */}
// </FormControl>
// <FormMessage />
// </FormItem>
)}
/>
@ -536,7 +519,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>Rerank Model</FormLabel>
<FormLabel>{t('search.rerankModel')}</FormLabel>
</FormItem>
)}
/>
@ -612,7 +595,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>AI Summary</FormLabel>
<FormLabel>{t('search.AISummary')}</FormLabel>
</FormItem>
)}
/>
@ -636,7 +619,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>Enable Web Search</FormLabel>
<FormLabel>{t('search.enableWebSearch')}</FormLabel>
</FormItem>
)}
/>
@ -652,7 +635,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>Enable Related Search</FormLabel>
<FormLabel>{t('search.enableRelatedSearch')}</FormLabel>
</FormItem>
)}
/>
@ -668,7 +651,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>Show Query Mindmap</FormLabel>
<FormLabel>{t('search.showQueryMindmap')}</FormLabel>
</FormItem>
)}
/>
@ -683,9 +666,9 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
setOpen(false);
}}
>
Cancel
{t('modal.cancelText')}
</Button>
<Button type="submit">Confirm</Button>
<Button type="submit">{t('modal.okText')}</Button>
</div>
</form>
</Form>

View File

@ -9,13 +9,11 @@ import {
} from '@/components/ui/popover';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { Skeleton } from '@/components/ui/skeleton';
import { Spin } from '@/components/ui/spin';
import { IReference } from '@/interfaces/database/chat';
import { cn } from '@/lib/utils';
import DOMPurify from 'dompurify';
import { TFunction } from 'i18next';
import { isEmpty } from 'lodash';
import { BrainCircuit, Search, Square, X } from 'lucide-react';
import { BrainCircuit, Search, X } from 'lucide-react';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ISearchAppDetailProps } from '../next-searches/hooks';
@ -35,7 +33,6 @@ export default function SearchingView({
answer,
sendingLoading,
relatedQuestions,
loading,
isFirstRender,
selectedDocumentIds,
isSearchStrEmpty,
@ -56,19 +53,17 @@ export default function SearchingView({
handleSearch,
pagination,
onChange,
t,
}: ISearchReturnProps & {
setIsSearching?: Dispatch<SetStateAction<boolean>>;
searchData: ISearchAppDetailProps;
t: TFunction<'translation', undefined>;
}) {
const { t: tt, i18n } = useTranslation();
useEffect(() => {
const changeLanguage = async () => {
await i18n.changeLanguage('zh');
};
changeLanguage();
}, [i18n]);
const { t } = useTranslation();
// useEffect(() => {
// const changeLanguage = async () => {
// await i18n.changeLanguage('zh');
// };
// changeLanguage();
// }, [i18n]);
const [searchtext, setSearchtext] = useState<string>('');
useEffect(() => {
@ -105,9 +100,9 @@ export default function SearchingView({
<div className={cn('flex flex-col justify-start items-start w-full')}>
<div className="relative w-full text-primary">
<Input
placeholder={tt('search.searchGreeting')}
placeholder={t('search.searchGreeting')}
className={cn(
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-bg-base',
)}
value={searchtext}
onChange={(e) => {
@ -122,16 +117,17 @@ export default function SearchingView({
/>
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
<X
className="text-text-secondary"
className="text-text-secondary cursor-pointer"
size={14}
onClick={() => {
setSearchtext('');
handleClickRelatedQuestion('');
}}
/>
<span className="text-text-secondary">|</span>
<span className="text-text-secondary ml-4">|</span>
<button
type="button"
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
className="rounded-full bg-text-primary p-1 text-bg-base shadow w-12 h-8 ml-4"
onClick={() => {
if (sendingLoading) {
stopOutputMessage();
@ -141,7 +137,8 @@ export default function SearchingView({
}}
>
{sendingLoading ? (
<Square size={22} className="m-auto" />
// <Square size={22} className="m-auto" />
<div className="w-2 h-2 bg-bg-base m-auto"></div>
) : (
<Search size={22} className="m-auto" />
)}
@ -157,10 +154,10 @@ export default function SearchingView({
{searchData.search_config.summary && !isSearchStrEmpty && (
<>
<div className="flex justify-start items-start text-text-primary text-2xl">
AI Summary
{t('search.AISummary')}
</div>
{isEmpty(answer) && sendingLoading ? (
<div className="space-y-2">
<div className="space-y-2 mt-2">
<Skeleton className="h-4 w-full bg-bg-card" />
<Skeleton className="h-4 w-full bg-bg-card" />
<Skeleton className="h-4 w-2/3 bg-bg-card" />
@ -194,76 +191,75 @@ export default function SearchingView({
</>
)}
<div className="mt-3 ">
<Spin spinning={loading}>
{chunks?.length > 0 && (
<>
{chunks.map((chunk, index) => {
return (
<>
<div
key={chunk.chunk_id}
className="w-full flex flex-col"
>
<div className="w-full">
<ImageWithPopover
id={chunk.img_id}
></ImageWithPopover>
<Popover>
<PopoverTrigger asChild>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(
`${chunk.highlight}...`,
),
}}
className="text-sm text-text-primary mb-1"
></div>
</PopoverTrigger>
<PopoverContent className="text-text-primary">
<HightLightMarkdown>
{chunk.content_with_weight}
</HightLightMarkdown>
</PopoverContent>
</Popover>
</div>
<div
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
onClick={() =>
clickDocumentButton(chunk.doc_id, chunk as any)
}
>
<FileIcon name={chunk.docnm_kwd}></FileIcon>
{chunk.docnm_kwd}
</div>
{chunks?.length > 0 && (
<>
{chunks.map((chunk, index) => {
return (
<>
<div
key={chunk.chunk_id}
className="w-full flex flex-col"
>
<div className="w-full">
<ImageWithPopover
id={chunk.img_id}
></ImageWithPopover>
<Popover>
<PopoverTrigger asChild>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(
`${chunk.highlight}...`,
),
}}
className="text-sm text-text-primary mb-1"
></div>
</PopoverTrigger>
<PopoverContent className="text-text-primary">
<HightLightMarkdown>
{chunk.content_with_weight}
</HightLightMarkdown>
</PopoverContent>
</Popover>
</div>
{index < chunks.length - 1 && (
<div className="w-full border-b border-border-default/80 mt-6"></div>
)}
</>
);
})}
</>
)}
</Spin>
{relatedQuestions?.length > 0 && (
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
<p className="text-text-primary mb-2 text-xl">
Related Search
</p>
<div className="mt-2 flex flex-wrap justify-start gap-2">
{relatedQuestions?.map((x, idx) => (
<Button
key={idx}
variant="transparent"
className="bg-bg-card text-text-secondary"
onClick={handleClickRelatedQuestion(x)}
>
Related Search{x}
</Button>
))}
</div>
</div>
<div
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
onClick={() =>
clickDocumentButton(chunk.doc_id, chunk as any)
}
>
<FileIcon name={chunk.docnm_kwd}></FileIcon>
{chunk.docnm_kwd}
</div>
</div>
{index < chunks.length - 1 && (
<div className="w-full border-b border-border-default/80 mt-6"></div>
)}
</>
);
})}
</>
)}
{relatedQuestions?.length > 0 &&
searchData.search_config.related_search && (
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
<p className="text-text-primary mb-2 text-xl">
{t('relatedSearch')}
</p>
<div className="mt-2 flex flex-wrap justify-start gap-2">
{relatedQuestions?.map((x, idx) => (
<Button
key={idx}
variant="transparent"
className="bg-bg-card text-text-secondary"
onClick={handleClickRelatedQuestion(x)}
>
{x}
</Button>
))}
</div>
</div>
)}
</div>
</div>

View File

@ -1,5 +1,4 @@
import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { ISearchAppDetailProps } from '../next-searches/hooks';
import { useSearching } from './hooks';
import './index.less';
@ -21,13 +20,11 @@ export default function SearchingPage({
setIsSearching,
setSearchText,
});
const { t } = useTranslation();
return (
<SearchingView
{...searchingParam}
searchData={searchData}
setIsSearching={setIsSearching}
t={t}
/>
);
}

View File

@ -1,6 +1,6 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import i18n from '@/locales/config';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
ISearchAppDetailProps,
useFetchSearchDetail,
@ -9,7 +9,7 @@ import { useGetSharedSearchParams, useSearching } from '../hooks';
import '../index.less';
import SearchingView from '../search-view';
export default function SearchingPage() {
const { tenantId, locale } = useGetSharedSearchParams();
const { tenantId, locale, visibleAvatar } = useGetSharedSearchParams();
const {
data: searchData = {
search_config: { kb_ids: [] },
@ -18,18 +18,25 @@ export default function SearchingPage() {
const searchingParam = useSearching({
data: searchData,
});
const { t } = useTranslation();
// useEffect(() => {
// if (locale) {
// i18n.changeLanguage(locale);
// }
// }, [locale, i18n]);
useEffect(() => {
console.log('locale', locale, i18n.language);
if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale);
}
}, [locale]);
return <SearchingView {...searchingParam} searchData={searchData} t={t} />;
return (
<>
{visibleAvatar && (
<div className="flex justify-start items-center gap-1 mx-6 mt-6 text-text-primary">
<RAGFlowAvatar
avatar={searchData.avatar}
name={searchData.name}
></RAGFlowAvatar>
<div>{searchData.name}</div>
</div>
)}
<SearchingView {...searchingParam} searchData={searchData} />;
</>
);
}

View File

@ -30,3 +30,22 @@ export const removeUselessFieldsFromValues = (values: any, prefix?: string) => {
export function buildOptions(data: Record<string, any>) {
return Object.values(data).map((val) => ({ label: val, value: val }));
}
export function setLLMSettingEnabledValues(
initialLlmSetting?: Record<string, any>,
) {
const values = Object.keys(variableEnabledFieldMap).reduce<
Record<string, boolean>
>((pre, field) => {
pre[field] =
initialLlmSetting === undefined
? false
: !!initialLlmSetting[
variableEnabledFieldMap[
field as keyof typeof variableEnabledFieldMap
]
];
return pre;
}, {});
return values;
}