Support Feishu credentials in system settings
This commit is contained in:
@@ -163,7 +163,7 @@ const nullableModelFields = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type SystemSettingValue = string | number | boolean;
|
type SystemSettingValue = string | number | boolean;
|
||||||
type SystemSettingType = "number" | "switch" | "text";
|
type SystemSettingType = "number" | "switch" | "text" | "password";
|
||||||
|
|
||||||
interface SystemSettingDefinition {
|
interface SystemSettingDefinition {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -289,6 +289,22 @@ const systemSettingSections: SystemSettingSection[] = [
|
|||||||
placeholder: "例如 https://example.com/search",
|
placeholder: "例如 https://example.com/search",
|
||||||
description: "如使用独立飞书检索适配服务,在这里填写调用地址。",
|
description: "如使用独立飞书检索适配服务,在这里填写调用地址。",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "feishu_app_id",
|
||||||
|
label: "飞书应用 AppID",
|
||||||
|
type: "text",
|
||||||
|
defaultValue: "",
|
||||||
|
placeholder: "填写飞书开放平台应用 AppID",
|
||||||
|
description: "用于后端直读飞书知识库;系统设置优先于环境变量。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "feishu_app_secret",
|
||||||
|
label: "飞书应用 AppSecret",
|
||||||
|
type: "password",
|
||||||
|
defaultValue: "",
|
||||||
|
placeholder: "填写飞书开放平台应用 AppSecret",
|
||||||
|
description: "用于后端获取飞书 tenant_access_token,保存后立即生效。",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "feishu_timeout_seconds",
|
key: "feishu_timeout_seconds",
|
||||||
label: "飞书请求超时(秒)",
|
label: "飞书请求超时(秒)",
|
||||||
@@ -787,7 +803,7 @@ function normalizeSystemSettingValue(setting: (typeof systemSettingDefinitions)[
|
|||||||
if (setting.type === "switch") {
|
if (setting.type === "switch") {
|
||||||
return ["1", "true", "yes", "on", "启用"].includes(rawValue.toLowerCase());
|
return ["1", "true", "yes", "on", "启用"].includes(rawValue.toLowerCase());
|
||||||
}
|
}
|
||||||
if (setting.type === "text") {
|
if (setting.type === "text" || setting.type === "password") {
|
||||||
return rawValue;
|
return rawValue;
|
||||||
}
|
}
|
||||||
const parsed = Number(rawValue);
|
const parsed = Number(rawValue);
|
||||||
@@ -1302,6 +1318,13 @@ function buildChatQuery() {
|
|||||||
v-model="systemSettingValues[setting.key]"
|
v-model="systemSettingValues[setting.key]"
|
||||||
:placeholder="setting.placeholder"
|
:placeholder="setting.placeholder"
|
||||||
/>
|
/>
|
||||||
|
<el-input
|
||||||
|
v-else-if="setting.type === 'password'"
|
||||||
|
v-model="systemSettingValues[setting.key]"
|
||||||
|
:placeholder="setting.placeholder"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
<el-switch v-else v-model="systemSettingValues[setting.key]" />
|
<el-switch v-else v-model="systemSettingValues[setting.key]" />
|
||||||
<small>{{ setting.description }}</small>
|
<small>{{ setting.description }}</small>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
import hashlib
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
@@ -19,33 +20,36 @@ if TYPE_CHECKING:
|
|||||||
# 飞书 API 基地址
|
# 飞书 API 基地址
|
||||||
FEISHU_OPEN_API = "https://open.feishu.cn"
|
FEISHU_OPEN_API = "https://open.feishu.cn"
|
||||||
|
|
||||||
# tenant_access_token 缓存
|
# tenant_access_token 缓存,按应用凭证隔离
|
||||||
_token_cache: dict[str, Any] = {"token": "", "expires_at": 0.0}
|
_token_cache: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FeishuRetrievalConfig:
|
class FeishuRetrievalConfig:
|
||||||
search_url: str
|
search_url: str
|
||||||
|
app_id: str
|
||||||
|
app_secret: str
|
||||||
timeout_seconds: int
|
timeout_seconds: int
|
||||||
retry_count: int
|
retry_count: int
|
||||||
|
|
||||||
|
|
||||||
def _get_tenant_token() -> str:
|
def _get_tenant_token(config: FeishuRetrievalConfig) -> str:
|
||||||
"""获取飞书 tenant_access_token,自动缓存"""
|
"""获取飞书 tenant_access_token,自动缓存"""
|
||||||
settings = get_settings()
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
cache_key = _token_cache_key(config.app_id, config.app_secret)
|
||||||
|
cached = _token_cache.get(cache_key)
|
||||||
|
|
||||||
if _token_cache["token"] and now < _token_cache["expires_at"]:
|
if cached and cached["token"] and now < cached["expires_at"]:
|
||||||
return _token_cache["token"]
|
return cached["token"]
|
||||||
|
|
||||||
url = f"{FEISHU_OPEN_API}/open-apis/auth/v3/tenant_access_token/internal"
|
url = f"{FEISHU_OPEN_API}/open-apis/auth/v3/tenant_access_token/internal"
|
||||||
payload = {
|
payload = {
|
||||||
"app_id": settings.feishu_app_id,
|
"app_id": config.app_id,
|
||||||
"app_secret": settings.feishu_app_secret,
|
"app_secret": config.app_secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = httpx.post(url, json=payload, timeout=settings.feishu_timeout_seconds)
|
resp = httpx.post(url, json=payload, timeout=config.timeout_seconds)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|
||||||
@@ -58,22 +62,25 @@ def _get_tenant_token() -> str:
|
|||||||
token = data["tenant_access_token"]
|
token = data["tenant_access_token"]
|
||||||
expire = data.get("expire", 7200)
|
expire = data.get("expire", 7200)
|
||||||
|
|
||||||
_token_cache["token"] = token
|
_token_cache[cache_key] = {
|
||||||
_token_cache["expires_at"] = now + expire - 300 # 提前5分钟过期
|
"token": token,
|
||||||
|
"expires_at": now + expire - 300, # 提前5分钟过期
|
||||||
|
}
|
||||||
|
|
||||||
return token
|
return token
|
||||||
except httpx.HTTPError as exc:
|
except httpx.HTTPError as exc:
|
||||||
raise ExternalServiceError(f"获取飞书Token网络错误:{exc}", provider="feishu") from exc
|
raise ExternalServiceError(f"获取飞书Token网络错误:{exc}", provider="feishu") from exc
|
||||||
|
|
||||||
|
|
||||||
def _feishu_get(path: str, params: dict | None = None) -> dict:
|
def _feishu_get(path: str, params: dict | None = None, config: FeishuRetrievalConfig | None = None) -> dict:
|
||||||
"""封装飞书 GET 请求"""
|
"""封装飞书 GET 请求"""
|
||||||
token = _get_tenant_token()
|
if config is None:
|
||||||
|
config = _feishu_retrieval_config(None)
|
||||||
|
token = _get_tenant_token(config)
|
||||||
url = f"{FEISHU_OPEN_API}{path}"
|
url = f"{FEISHU_OPEN_API}{path}"
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
settings = get_settings()
|
|
||||||
|
|
||||||
resp = httpx.get(url, headers=headers, params=params, timeout=settings.feishu_timeout_seconds)
|
resp = httpx.get(url, headers=headers, params=params, timeout=config.timeout_seconds)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|
||||||
@@ -85,14 +92,15 @@ def _feishu_get(path: str, params: dict | None = None) -> dict:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def _feishu_post(path: str, payload: dict | None = None) -> dict:
|
def _feishu_post(path: str, payload: dict | None = None, config: FeishuRetrievalConfig | None = None) -> dict:
|
||||||
"""封装飞书 POST 请求"""
|
"""封装飞书 POST 请求"""
|
||||||
token = _get_tenant_token()
|
if config is None:
|
||||||
|
config = _feishu_retrieval_config(None)
|
||||||
|
token = _get_tenant_token(config)
|
||||||
url = f"{FEISHU_OPEN_API}{path}"
|
url = f"{FEISHU_OPEN_API}{path}"
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
settings = get_settings()
|
|
||||||
|
|
||||||
resp = httpx.post(url, headers=headers, json=payload, timeout=settings.feishu_timeout_seconds)
|
resp = httpx.post(url, headers=headers, json=payload, timeout=config.timeout_seconds)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|
||||||
@@ -180,16 +188,16 @@ def _parse_docx_content(blocks: list[dict]) -> str:
|
|||||||
return "".join(texts).strip()
|
return "".join(texts).strip()
|
||||||
|
|
||||||
|
|
||||||
def _get_docx_content(doc_token: str) -> str:
|
def _get_docx_content(doc_token: str, config: FeishuRetrievalConfig) -> str:
|
||||||
"""读取单个飞书云文档内容"""
|
"""读取单个飞书云文档内容"""
|
||||||
data = _feishu_get(f"/open-apis/docx/v1/documents/{doc_token}/blocks")
|
data = _feishu_get(f"/open-apis/docx/v1/documents/{doc_token}/blocks", config=config)
|
||||||
items = data.get("data", {}).get("items", [])
|
items = data.get("data", {}).get("items", [])
|
||||||
return _parse_docx_content(items)
|
return _parse_docx_content(items)
|
||||||
|
|
||||||
|
|
||||||
def _get_wiki_node_info(node_token: str) -> dict:
|
def _get_wiki_node_info(node_token: str, config: FeishuRetrievalConfig) -> dict:
|
||||||
"""获取 Wiki 节点信息,返回 space_id 和 obj_token"""
|
"""获取 Wiki 节点信息,返回 space_id 和 obj_token"""
|
||||||
data = _feishu_get("/open-apis/wiki/v2/spaces/get_node", params={"token": node_token})
|
data = _feishu_get("/open-apis/wiki/v2/spaces/get_node", params={"token": node_token}, config=config)
|
||||||
node = data.get("data", {}).get("node", {})
|
node = data.get("data", {}).get("node", {})
|
||||||
return {
|
return {
|
||||||
"space_id": node.get("space_id", ""),
|
"space_id": node.get("space_id", ""),
|
||||||
@@ -200,7 +208,7 @@ def _get_wiki_node_info(node_token: str) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_wiki_children(node_token: str, page_size: int = 50) -> list[dict]:
|
def _get_wiki_children(node_token: str, config: FeishuRetrievalConfig, page_size: int = 50) -> list[dict]:
|
||||||
"""获取 Wiki 节点的子节点列表"""
|
"""获取 Wiki 节点的子节点列表"""
|
||||||
all_children = []
|
all_children = []
|
||||||
page_token = ""
|
page_token = ""
|
||||||
@@ -212,7 +220,7 @@ def _get_wiki_children(node_token: str, page_size: int = 50) -> list[dict]:
|
|||||||
if page_token:
|
if page_token:
|
||||||
params["page_token"] = page_token
|
params["page_token"] = page_token
|
||||||
|
|
||||||
data = _feishu_get("/open-apis/wiki/v2/spaces/nodes/get_child_nodes", params=params)
|
data = _feishu_get("/open-apis/wiki/v2/spaces/nodes/get_child_nodes", params=params, config=config)
|
||||||
children = data.get("data", {}).get("items", [])
|
children = data.get("data", {}).get("items", [])
|
||||||
all_children.extend(children)
|
all_children.extend(children)
|
||||||
|
|
||||||
@@ -269,13 +277,13 @@ class FeishuKnowledgeService:
|
|||||||
return cls._retrieve_from_search(question, scopes, config)
|
return cls._retrieve_from_search(question, scopes, config)
|
||||||
except ExternalServiceError as exc:
|
except ExternalServiceError as exc:
|
||||||
try:
|
try:
|
||||||
return cls._retrieve_from_feishu(question, scopes)
|
return cls._retrieve_from_feishu(question, scopes, config)
|
||||||
except ExternalServiceError as fallback_exc:
|
except ExternalServiceError as fallback_exc:
|
||||||
raise ExternalServiceError(
|
raise ExternalServiceError(
|
||||||
f"{exc};飞书直读也失败:{fallback_exc}",
|
f"{exc};飞书直读也失败:{fallback_exc}",
|
||||||
provider="feishu",
|
provider="feishu",
|
||||||
) from fallback_exc
|
) from fallback_exc
|
||||||
return cls._retrieve_from_feishu(question, scopes)
|
return cls._retrieve_from_feishu(question, scopes, config)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _retrieve_from_search(
|
def _retrieve_from_search(
|
||||||
@@ -300,15 +308,19 @@ class FeishuKnowledgeService:
|
|||||||
raise ExternalServiceError(f"飞书搜索接口调用失败:{last_error}", provider="feishu-search")
|
raise ExternalServiceError(f"飞书搜索接口调用失败:{last_error}", provider="feishu-search")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _retrieve_from_feishu(cls, question: str, scopes: list[KnowledgeScope]) -> list[RetrievedChunk]:
|
def _retrieve_from_feishu(
|
||||||
|
cls,
|
||||||
|
question: str,
|
||||||
|
scopes: list[KnowledgeScope],
|
||||||
|
config: FeishuRetrievalConfig,
|
||||||
|
) -> list[RetrievedChunk]:
|
||||||
"""从飞书知识库检索文档内容"""
|
"""从飞书知识库检索文档内容"""
|
||||||
from app.services.rag_service import RetrievedChunk
|
from app.services.rag_service import RetrievedChunk
|
||||||
|
|
||||||
settings = get_settings()
|
if not config.app_id or not config.app_secret:
|
||||||
if not settings.feishu_app_id or not settings.feishu_app_secret:
|
|
||||||
raise ExternalServiceError(
|
raise ExternalServiceError(
|
||||||
"未配置飞书应用凭证,无法读取飞书知识库。请配置 FEISHU_APP_ID/FEISHU_APP_SECRET,"
|
"未配置飞书应用凭证,无法读取飞书知识库。请配置 FEISHU_APP_ID/FEISHU_APP_SECRET,"
|
||||||
"或在系统配置中填写可用的飞书搜索接口地址。",
|
"或在系统设置中填写飞书 AppID/AppSecret。",
|
||||||
provider="feishu",
|
provider="feishu",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -318,7 +330,7 @@ class FeishuKnowledgeService:
|
|||||||
|
|
||||||
for scope in scopes:
|
for scope in scopes:
|
||||||
try:
|
try:
|
||||||
documents = cls._load_documents(scope)
|
documents = cls._load_documents(scope, config)
|
||||||
except ExternalServiceError as exc:
|
except ExternalServiceError as exc:
|
||||||
load_errors.append(f"{scope.name}:{exc}")
|
load_errors.append(f"{scope.name}:{exc}")
|
||||||
continue
|
continue
|
||||||
@@ -350,7 +362,11 @@ class FeishuKnowledgeService:
|
|||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _load_documents(cls, scope: KnowledgeScope) -> list[tuple[str, str, str | None]]:
|
def _load_documents(
|
||||||
|
cls,
|
||||||
|
scope: KnowledgeScope,
|
||||||
|
config: FeishuRetrievalConfig,
|
||||||
|
) -> list[tuple[str, str, str | None]]:
|
||||||
"""加载指定知识库 scope 下的所有文档"""
|
"""加载指定知识库 scope 下的所有文档"""
|
||||||
node_id = scope.feishu_node_id
|
node_id = scope.feishu_node_id
|
||||||
base_url = f"https://zcn7hk6n047c.feishu.cn"
|
base_url = f"https://zcn7hk6n047c.feishu.cn"
|
||||||
@@ -360,11 +376,11 @@ class FeishuKnowledgeService:
|
|||||||
|
|
||||||
# 1. 判断 node 类型:先查 wiki 节点信息
|
# 1. 判断 node 类型:先查 wiki 节点信息
|
||||||
try:
|
try:
|
||||||
node_info = cls._get_node_info_with_cache(node_id)
|
node_info = cls._get_node_info_with_cache(node_id, config)
|
||||||
except ExternalServiceError:
|
except ExternalServiceError:
|
||||||
# 可能是普通 docx token,直接尝试读取
|
# 可能是普通 docx token,直接尝试读取
|
||||||
try:
|
try:
|
||||||
content = cls._get_document_content_with_cache(node_id)
|
content = cls._get_document_content_with_cache(node_id, config)
|
||||||
title = node_id
|
title = node_id
|
||||||
return [(title, content, f"{base_url}/docx/{node_id}")]
|
return [(title, content, f"{base_url}/docx/{node_id}")]
|
||||||
except ExternalServiceError:
|
except ExternalServiceError:
|
||||||
@@ -377,12 +393,12 @@ class FeishuKnowledgeService:
|
|||||||
|
|
||||||
# 如果节点本身就是一个文档
|
# 如果节点本身就是一个文档
|
||||||
if obj_type == "docx":
|
if obj_type == "docx":
|
||||||
content = cls._get_document_content_with_cache(obj_token)
|
content = cls._get_document_content_with_cache(obj_token, config)
|
||||||
source_url = f"{base_url}/wiki/{node_id}"
|
source_url = f"{base_url}/wiki/{node_id}"
|
||||||
return [(title, content, source_url)]
|
return [(title, content, source_url)]
|
||||||
|
|
||||||
# 如果节点是文件夹,获取子节点
|
# 如果节点是文件夹,获取子节点
|
||||||
children = cls._get_wiki_children_with_cache(node_id)
|
children = cls._get_wiki_children_with_cache(node_id, config)
|
||||||
|
|
||||||
documents = []
|
documents = []
|
||||||
for child in children:
|
for child in children:
|
||||||
@@ -393,7 +409,7 @@ class FeishuKnowledgeService:
|
|||||||
|
|
||||||
if child_obj_type == "docx" and child_obj_token:
|
if child_obj_type == "docx" and child_obj_token:
|
||||||
try:
|
try:
|
||||||
content = cls._get_document_content_with_cache(child_obj_token)
|
content = cls._get_document_content_with_cache(child_obj_token, config)
|
||||||
source_url = f"{base_url}/wiki/{child_node_token}"
|
source_url = f"{base_url}/wiki/{child_node_token}"
|
||||||
documents.append((child_title, content, source_url))
|
documents.append((child_title, content, source_url))
|
||||||
except ExternalServiceError:
|
except ExternalServiceError:
|
||||||
@@ -402,7 +418,7 @@ class FeishuKnowledgeService:
|
|||||||
# 也读取节点本身的文档(如果有)
|
# 也读取节点本身的文档(如果有)
|
||||||
if obj_type == "docx" and obj_token:
|
if obj_type == "docx" and obj_token:
|
||||||
try:
|
try:
|
||||||
content = cls._get_document_content_with_cache(obj_token)
|
content = cls._get_document_content_with_cache(obj_token, config)
|
||||||
source_url = f"{base_url}/wiki/{node_id}"
|
source_url = f"{base_url}/wiki/{node_id}"
|
||||||
documents.insert(0, (title, content, source_url))
|
documents.insert(0, (title, content, source_url))
|
||||||
except ExternalServiceError:
|
except ExternalServiceError:
|
||||||
@@ -411,9 +427,9 @@ class FeishuKnowledgeService:
|
|||||||
return documents
|
return documents
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_node_info_with_cache(cls, node_token: str) -> dict:
|
def _get_node_info_with_cache(cls, node_token: str, config: FeishuRetrievalConfig) -> dict:
|
||||||
"""带缓存获取节点信息"""
|
"""带缓存获取节点信息"""
|
||||||
cache_key = f"node_info:{node_token}"
|
cache_key = f"{config.app_id}:node_info:{node_token}"
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if cache_key in cls._content_cache:
|
if cache_key in cls._content_cache:
|
||||||
@@ -421,28 +437,29 @@ class FeishuKnowledgeService:
|
|||||||
if now - ts < cls._CACHE_TTL:
|
if now - ts < cls._CACHE_TTL:
|
||||||
return eval(cached_data) # noqa: S307
|
return eval(cached_data) # noqa: S307
|
||||||
|
|
||||||
info = _get_wiki_node_info(node_token)
|
info = _get_wiki_node_info(node_token, config)
|
||||||
cls._content_cache[cache_key] = (str(info), now)
|
cls._content_cache[cache_key] = (str(info), now)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_document_content_with_cache(cls, doc_token: str) -> str:
|
def _get_document_content_with_cache(cls, doc_token: str, config: FeishuRetrievalConfig) -> str:
|
||||||
"""带缓存读取文档内容"""
|
"""带缓存读取文档内容"""
|
||||||
|
cache_key = f"{config.app_id}:doc:{doc_token}"
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if doc_token in cls._content_cache:
|
if cache_key in cls._content_cache:
|
||||||
content, ts = cls._content_cache[doc_token]
|
content, ts = cls._content_cache[cache_key]
|
||||||
if now - ts < cls._CACHE_TTL:
|
if now - ts < cls._CACHE_TTL:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
content = _get_docx_content(doc_token)
|
content = _get_docx_content(doc_token, config)
|
||||||
cls._content_cache[doc_token] = (content, now)
|
cls._content_cache[cache_key] = (content, now)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_wiki_children_with_cache(cls, node_token: str) -> list[dict]:
|
def _get_wiki_children_with_cache(cls, node_token: str, config: FeishuRetrievalConfig) -> list[dict]:
|
||||||
"""带缓存获取子节点"""
|
"""带缓存获取子节点"""
|
||||||
cache_key = f"children:{node_token}"
|
cache_key = f"{config.app_id}:children:{node_token}"
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if cache_key in cls._content_cache:
|
if cache_key in cls._content_cache:
|
||||||
@@ -450,7 +467,7 @@ class FeishuKnowledgeService:
|
|||||||
if now - ts < cls._CACHE_TTL:
|
if now - ts < cls._CACHE_TTL:
|
||||||
return eval(cached_data) # noqa: S307
|
return eval(cached_data) # noqa: S307
|
||||||
|
|
||||||
children = _get_wiki_children(node_token)
|
children = _get_wiki_children(node_token, config)
|
||||||
cls._content_cache[cache_key] = (str(children), now)
|
cls._content_cache[cache_key] = (str(children), now)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
@@ -513,11 +530,17 @@ def _feishu_retrieval_config(db: Session | None) -> FeishuRetrievalConfig:
|
|||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
return FeishuRetrievalConfig(
|
return FeishuRetrievalConfig(
|
||||||
search_url=_config_text(db, "feishu_search_url", settings.feishu_search_url),
|
search_url=_config_text(db, "feishu_search_url", settings.feishu_search_url),
|
||||||
|
app_id=_config_text(db, "feishu_app_id", settings.feishu_app_id),
|
||||||
|
app_secret=_config_text(db, "feishu_app_secret", settings.feishu_app_secret),
|
||||||
timeout_seconds=_config_int(db, "feishu_timeout_seconds", settings.feishu_timeout_seconds, minimum=1, maximum=120),
|
timeout_seconds=_config_int(db, "feishu_timeout_seconds", settings.feishu_timeout_seconds, minimum=1, maximum=120),
|
||||||
retry_count=_config_int(db, "feishu_retry_count", settings.feishu_retry_count, minimum=0, maximum=10),
|
retry_count=_config_int(db, "feishu_retry_count", settings.feishu_retry_count, minimum=0, maximum=10),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _token_cache_key(app_id: str, app_secret: str) -> str:
|
||||||
|
return hashlib.sha256(f"{app_id}:{app_secret}".encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _config_text(db: Session | None, key: str, default: str) -> str:
|
def _config_text(db: Session | None, key: str, default: str) -> str:
|
||||||
value = _config_value(db, key)
|
value = _config_value(db, key)
|
||||||
return value.strip() if value is not None else default
|
return value.strip() if value is not None else default
|
||||||
|
|||||||
Reference in New Issue
Block a user