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": """ "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. 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): 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 { return {
"result": arg1 + arg2, "result": fibonacci_recursive(100),
} }
Here's a code example for Javascript(`main` function MUST be included and exported): Here's a code example for Javascript(`main` function MUST be included and exported):

View File

@ -74,11 +74,11 @@ def rm():
@login_required @login_required
def save(): def save():
req = request.json req = request.json
req["user_id"] = current_user.id
if not isinstance(req["dsl"], str): if not isinstance(req["dsl"], str):
req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False) req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False)
req["dsl"] = json.loads(req["dsl"]) req["dsl"] = json.loads(req["dsl"])
if "id" not in req: if "id" not in req:
req["user_id"] = current_user.id
if UserCanvasService.query(user_id=current_user.id, title=req["title"].strip()): if UserCanvasService.query(user_id=current_user.id, title=req["title"].strip()):
return get_data_error_result(message=f"{req['title'].strip()} already exists.") return get_data_error_result(message=f"{req['title'].strip()} already exists.")
req["id"] = get_uuid() 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 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 graphrag.general.mind_map_extractor import MindMapExtractor
from rag.app.tag import label_question from rag.app.tag import label_question
from rag.prompts.prompt_template import load_prompt
from rag.prompts.prompts import chunks_format from rag.prompts.prompts import chunks_format
@ -389,39 +390,7 @@ def related_questions():
req = request.json req = request.json
question = req["question"] question = req["question"]
chat_mdl = LLMBundle(current_user.id, LLMType.CHAT) chat_mdl = LLMBundle(current_user.id, LLMType.CHAT)
prompt = """ prompt = load_prompt("related_question")
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.
"""
ans = chat_mdl.chat( ans = chat_mdl.chat(
prompt, prompt,
[ [

View File

@ -18,7 +18,9 @@ import re
import time import time
import tiktoken import tiktoken
from flask import Response, jsonify, request from flask import Response, jsonify, request
import trio
from agent.canvas import Canvas from agent.canvas import Canvas
from api import settings
from api.db import LLMType, StatusEnum from api.db import LLMType, StatusEnum
from api.db.db_models import APIToken from api.db.db_models import APIToken
from api.db.services.api_service import API4ConversationService 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.dialog_service import DialogService, ask, chat
from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMBundle 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 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 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 @manager.route("/chats/<chat_id>/sessions", methods=["POST"]) # noqa: F821
@ -855,3 +863,215 @@ def begin_inputs(agent_id):
"prologue": canvas.get_prologue() "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", "llm_name": "gemini-2.5-flash",
"tags": "LLM,CHAT,1024K,IMAGE2TEXT", "tags": "LLM,CHAT,1024K,IMAGE2TEXT",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "image2text", "model_type": "chat",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.5-pro", "llm_name": "gemini-2.5-pro",
"tags": "LLM,CHAT,IMAGE2TEXT,1024K", "tags": "LLM,CHAT,IMAGE2TEXT,1024K",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "image2text", "model_type": "chat",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.5-flash-preview-05-20", "llm_name": "gemini-2.5-flash-lite",
"tags": "LLM,CHAT,1024K,IMAGE2TEXT", "tags": "LLM,CHAT,1024K,IMAGE2TEXT",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "image2text", "model_type": "chat",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.0-flash-001", "llm_name": "gemini-2.0-flash",
"tags": "LLM,CHAT,1024K",
"max_tokens": 1048576,
"model_type": "image2text",
"is_tools": true
},
{
"llm_name": "gemini-2.0-flash-thinking-exp-01-21",
"tags": "LLM,CHAT,1024K", "tags": "LLM,CHAT,1024K",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "chat", "model_type": "chat",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-1.5-flash", "llm_name": "gemini-2.0-flash-lite",
"tags": "LLM,IMAGE2TEXT,1024K", "tags": "LLM,CHAT,1024K",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "image2text" "model_type": "chat",
},
{
"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",
"is_tools": true "is_tools": true
}, },
{ {

View File

@ -57,7 +57,7 @@ async def run_graphrag(
): ):
chunks.append(d["content_with_weight"]) 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( subgraph = await generate_subgraph(
LightKGExt LightKGExt
if "method" not in row["kb_parser_config"].get("graphrag", {}) or row["kb_parser_config"]["graphrag"]["method"] != "general" 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", "groq==0.9.0",
"hanziconv==0.3.2", "hanziconv==0.3.2",
"html-text==0.6.2", "html-text==0.6.2",
"httpx==0.27.2", "httpx[socks]==0.27.2",
"huggingface-hub>=0.25.0,<0.26.0", "huggingface-hub>=0.25.0,<0.26.0",
"infinity-sdk==0.6.0-dev4", "infinity-sdk==0.6.0-dev4",
"infinity-emb>=0.0.66,<0.0.67", "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 return res.text, res.usage_metadata.total_token_count
def chat(self, system, history, gen_conf, images=[]): 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: try:
response = self.model.generate_content( response = self.model.generate_content(
self._form_history(system, history, images), 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 ans = response.text
return ans, response.usage_metadata.total_token_count return ans, response.usage_metadata.total_token_count
except Exception as e: except Exception as e:
return "**ERROR**: " + str(e), 0 return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf, images=[]): def chat_streamly(self, system, history, gen_conf, images=[]):
from transformers import GenerationConfig
ans = "" ans = ""
response = None response = None
try: 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( response = self.model.generate_content(
self._form_history(system, history, images), 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, stream=True,
) )
@ -572,7 +572,7 @@ class GeminiCV(Base):
yield response.usage_metadata.total_token_count yield response.usage_metadata.total_token_count
else: else:
yield 0 yield 0
class NvidiaCV(Base): class NvidiaCV(Base):
_FACTORY_NAME = "NVIDIA" _FACTORY_NAME = "NVIDIA"

View File

@ -100,7 +100,7 @@ class DefaultRerank(Base):
old_dynamic_batch_size = self._dynamic_batch_size old_dynamic_batch_size = self._dynamic_batch_size
if max_batch_size is not None: if max_batch_size is not None:
self._dynamic_batch_size = max_batch_size self._dynamic_batch_size = max_batch_size
res = np.array([], dtype=float) res = np.array(len(pairs), dtype=float)
i = 0 i = 0
while i < len(pairs): while i < len(pairs):
cur_i = i cur_i = i
@ -111,7 +111,7 @@ class DefaultRerank(Base):
try: try:
# call subclass implemented batch processing calculation # call subclass implemented batch processing calculation
batch_scores = self._compute_batch_scores(pairs[i : i + current_batch]) 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 i += current_batch
self._dynamic_batch_size = min(self._dynamic_batch_size * 2, 8) self._dynamic_batch_size = min(self._dynamic_batch_size * 2, 8)
break break
@ -125,8 +125,8 @@ class DefaultRerank(Base):
raise raise
if retry_count >= max_retries: if retry_count >= max_retries:
raise RuntimeError("max retry times, still cannot process batch, please check your GPU memory") 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 self._dynamic_batch_size = old_dynamic_batch_size
return np.array(res) 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 the image is in RGBA mode, convert it to RGB mode before saving it in JPEG format.
if d["image"].mode in ("RGBA", "P"): if d["image"].mode in ("RGBA", "P"):
converted_image = d["image"].convert("RGB") converted_image = d["image"].convert("RGB")
d["image"].close() # Close original image #d["image"].close() # Close original image
d["image"] = converted_image d["image"] = converted_image
try: try:
d["image"].save(output_buffer, format='JPEG') 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" }, { 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]] [[package]]
name = "httpx-sse" name = "httpx-sse"
version = "0.4.1" version = "0.4.1"
@ -5308,7 +5313,7 @@ dependencies = [
{ name = "groq" }, { name = "groq" },
{ name = "hanziconv" }, { name = "hanziconv" },
{ name = "html-text" }, { name = "html-text" },
{ name = "httpx" }, { name = "httpx", extra = ["socks"] },
{ name = "huggingface-hub" }, { name = "huggingface-hub" },
{ name = "infinity-emb" }, { name = "infinity-emb" },
{ name = "infinity-sdk" }, { name = "infinity-sdk" },
@ -5463,7 +5468,7 @@ requires-dist = [
{ name = "groq", specifier = "==0.9.0" }, { name = "groq", specifier = "==0.9.0" },
{ name = "hanziconv", specifier = "==0.3.2" }, { name = "hanziconv", specifier = "==0.3.2" },
{ name = "html-text", specifier = "==0.6.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 = "huggingface-hub", specifier = ">=0.25.0,<0.26.0" },
{ name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" }, { name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" },
{ name = "infinity-sdk", specifier = "==0.6.0.dev4" }, { 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" }, { 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]] [[package]]
name = "sortedcontainers" name = "sortedcontainers"
version = "2.4.0" version = "2.4.0"

View File

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

View File

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

View File

@ -49,7 +49,7 @@ export const RAGFlowAvatar = memo(
const initials = getInitials(name); const initials = getInitials(name);
const { from, to } = name const { from, to } = name
? getColorForName(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 fallbackRef = useRef<HTMLElement>(null);
const [fontSize, setFontSize] = useState('0.875rem'); 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: { search: {
createSearch: 'Create Search', createSearch: 'Create Search',
searchGreeting: 'How can I help you today ', 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: { search: {
createSearch: '新建查詢', createSearch: '新建查詢',
searchGreeting: '今天我能為你做些什麽?', searchGreeting: '今天我能為你做些什麽?',
profile: '隱藏個人資料',
locale: '語言',
embedCode: '嵌入代碼',
id: 'ID',
copySuccess: '複製成功',
welcomeBack: '歡迎回來',
}, },
}, },
}; };

View File

@ -1323,6 +1323,24 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
search: { search: {
createSearch: '新建查询', createSearch: '新建查询',
searchGreeting: '今天我能为你做些什么?', 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'); return pick(initialValues, 'top_n');
case Operator.WenCai: case Operator.WenCai:
return pick(initialValues, 'top_n', 'query_type'); return pick(initialValues, 'top_n', 'query_type');
case Operator.Code:
return {};
default: default:
return initialValues; return initialValues;

View File

@ -7,6 +7,7 @@ import { useNavigate } from 'umi';
import { Agents } from './agent-list'; import { Agents } from './agent-list';
import { SeeAllAppCard } from './application-card'; import { SeeAllAppCard } from './application-card';
import { ChatList } from './chat-list'; import { ChatList } from './chat-list';
import { SearchList } from './search-list';
const IconMap = { const IconMap = {
[Routes.Chats]: 'chat', [Routes.Chats]: 'chat',
@ -56,6 +57,7 @@ export function Applications() {
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{val === Routes.Agents && <Agents></Agents>} {val === Routes.Agents && <Agents></Agents>}
{val === Routes.Chats && <ChatList></ChatList>} {val === Routes.Chats && <ChatList></ChatList>}
{val === Routes.Searches && <SearchList></SearchList>}
{<SeeAllAppCard click={handleNavigate}></SeeAllAppCard>} {<SeeAllAppCard click={handleNavigate}></SeeAllAppCard>}
</div> </div>
</section> </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 { Separator } from '@/components/ui/separator';
import { useFetchDialog, useSetDialog } from '@/hooks/use-chat-request'; import { useFetchDialog, useSetDialog } from '@/hooks/use-chat-request';
import { transformBase64ToFile, transformFile2Base64 } from '@/utils/file-util'; import { transformBase64ToFile, transformFile2Base64 } from '@/utils/file-util';
import {
removeUselessFieldsFromValues,
setLLMSettingEnabledValues,
} from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useEffect } from 'react'; import { useEffect } from 'react';
@ -26,6 +30,7 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
const form = useForm<FormSchemaType>({ const form = useForm<FormSchemaType>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
shouldUnregister: true,
defaultValues: { defaultValues: {
name: '', name: '',
language: 'English', language: 'English',
@ -47,14 +52,18 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
}); });
async function onSubmit(values: FormSchemaType) { async function onSubmit(values: FormSchemaType) {
const icon = values.icon; const nextValues: Record<string, any> = removeUselessFieldsFromValues(
values,
'llm_setting.',
);
const icon = nextValues.icon;
const avatar = const avatar =
Array.isArray(icon) && icon.length > 0 Array.isArray(icon) && icon.length > 0
? await transformFile2Base64(icon[0]) ? await transformFile2Base64(icon[0])
: ''; : '';
setDialog({ setDialog({
...data, ...data,
...values, ...nextValues,
icon: avatar, icon: avatar,
dialog_id: id, dialog_id: id,
}); });
@ -65,9 +74,14 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
} }
useEffect(() => { useEffect(() => {
const llmSettingEnabledValues = setLLMSettingEnabledValues(
data.llm_setting,
);
const nextData = { const nextData = {
...data, ...data,
icon: data.icon ? [transformBase64ToFile(data.icon)] : [], icon: data.icon ? [transformBase64ToFile(data.icon)] : [],
...llmSettingEnabledValues,
}; };
form.reset(nextData as FormSchemaType); form.reset(nextData as FormSchemaType);
}, [data, form]); }, [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 { rerankFormSchema } from '@/components/rerank';
import { vectorSimilarityWeightSchema } from '@/components/similarity-slider'; import { vectorSimilarityWeightSchema } from '@/components/similarity-slider';
import { topnSchema } from '@/components/top-n-item'; import { topnSchema } from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { omit } from 'lodash';
import { z } from 'zod'; import { z } from 'zod';
export function useChatSettingSchema() { export function useChatSettingSchema() {
@ -39,20 +41,23 @@ export function useChatSettingSchema() {
}), }),
prompt_config: promptConfigSchema, prompt_config: promptConfigSchema,
...rerankFormSchema, ...rerankFormSchema,
llm_setting: z.object(omit(LlmSettingSchema, 'llm_id')), llm_setting: z.object(LlmSettingFieldSchema),
...LlmSettingEnabledSchema,
llm_id: z.string().optional(), llm_id: z.string().optional(),
...vectorSimilarityWeightSchema, ...vectorSimilarityWeightSchema,
...topnSchema, ...topnSchema,
meta_data_filter: z meta_data_filter: z
.object({ .object({
method: z.string().optional(), method: z.string().optional(),
manual: z.array( manual: z
z.object({ .array(
key: z.string(), z.object({
op: z.string(), key: z.string(),
value: z.string(), op: z.string(),
}), value: z.string(),
), }),
)
.optional(),
}) })
.optional(), .optional(),
}); });

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Search } from 'lucide-react'; import { Search } from 'lucide-react';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import './index.less'; import './index.less';
import Spotlight from './spotlight'; import Spotlight from './spotlight';
@ -18,6 +19,7 @@ export default function SearchPage({
setSearchText: Dispatch<SetStateAction<string>>; setSearchText: Dispatch<SetStateAction<string>>;
}) { }) {
const { data: userInfo } = useFetchUserInfo(); const { data: userInfo } = useFetchUserInfo();
const { t } = useTranslation();
return ( return (
<section className="relative w-full flex transition-all justify-center items-center mt-32"> <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]"> <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-4 transition-opacity">👋 Hi there</p>
<p className="mb-10 transition-opacity"> <p className="mb-10 transition-opacity">
Welcome back, {userInfo?.nickname} {t('search.welcomeBack')}, {userInfo?.nickname}
</p> </p>
</> </>
)} )}
<div className="relative w-full "> <div className="relative w-full ">
<Input <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" className="w-full rounded-full py-6 px-4 pr-10 text-text-primary text-lg bg-bg-base delay-700"
value={searchText} value={searchText}
onKeyUp={(e) => { onKeyUp={(e) => {
@ -57,7 +59,7 @@ export default function SearchPage({
/> />
<button <button
type="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={() => { onClick={() => {
setIsSearching(!isSearching); setIsSearching(!isSearching);
}} }}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import i18n from '@/locales/config'; import i18n from '@/locales/config';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { import {
ISearchAppDetailProps, ISearchAppDetailProps,
useFetchSearchDetail, useFetchSearchDetail,
@ -9,7 +9,7 @@ import { useGetSharedSearchParams, useSearching } from '../hooks';
import '../index.less'; import '../index.less';
import SearchingView from '../search-view'; import SearchingView from '../search-view';
export default function SearchingPage() { export default function SearchingPage() {
const { tenantId, locale } = useGetSharedSearchParams(); const { tenantId, locale, visibleAvatar } = useGetSharedSearchParams();
const { const {
data: searchData = { data: searchData = {
search_config: { kb_ids: [] }, search_config: { kb_ids: [] },
@ -18,18 +18,25 @@ export default function SearchingPage() {
const searchingParam = useSearching({ const searchingParam = useSearching({
data: searchData, data: searchData,
}); });
const { t } = useTranslation();
// useEffect(() => {
// if (locale) {
// i18n.changeLanguage(locale);
// }
// }, [locale, i18n]);
useEffect(() => { useEffect(() => {
console.log('locale', locale, i18n.language); console.log('locale', locale, i18n.language);
if (locale && i18n.language !== locale) { if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale); i18n.changeLanguage(locale);
} }
}, [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>) { export function buildOptions(data: Record<string, any>) {
return Object.values(data).map((val) => ({ label: val, value: val })); 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;
}