Release/v0.2.4 (#397)
* Fix/bug en zh (#389) * [fix]The log retains genuine alerts and errors, while filtering out unnecessary noise. * [fix]Scenario English and Chinese, emotion specifications * [fix]Change the "no data" scenario from 0.0 to None * [fix]The emotional health indicators, emotional advice, and emotional distribution analysis are all linked together. * [fix]The emotional health indicators, emotional advice, and emotional distribution analysis are all linked together. * [fix]Separate expected errors from unexpected errors * [changes]Translation of emotion labels, and the list of hosts arranged in the order of creation * [changes]Translation of emotion labels, and the list of hosts arranged in the order of creation * feat(web): improve knowledge base form validation and parser config handling - Refactor form validation logic to support tab-specific field validation in edit mode - Add conditional validation for knowledge graph fields when editing existing knowledge base - Preserve all existing parser_config fields when merging graphrag configuration - Skip third-party authentication check when editing on knowledge graph tab - Update form value retrieval to include disabled fields using getFieldsValue(true) - Improve comments to clarify parser_config field preservation and validation behavior - This change enables users to edit knowledge graph settings without re-validating all basic configuration fields * fix(web): improve infinite scroll handling in knowledge base list - Add auto-load detection when initial data doesn't fill viewport to prevent empty scrollbar - Implement scroll height check to automatically load more data if content is insufficient - Fix hasMore condition to prevent premature loader hiding - Update loader visibility to only show when data exists and is actively loading - Refine end message display to show only when all data is loaded and no more items available - Resolves issue where knowledge base list shows no scrollbar on initial load with limited items * fix(web): FileUpload bugfix * fix(web): change skill search key * Fix/bug en zh (#391) * [fix]The log retains genuine alerts and errors, while filtering out unnecessary noise. * [fix]Scenario English and Chinese, emotion specifications * [fix]Change the "no data" scenario from 0.0 to None * [fix]The emotional health indicators, emotional advice, and emotional distribution analysis are all linked together. * [fix]The emotional health indicators, emotional advice, and emotional distribution analysis are all linked together. * [fix]Separate expected errors from unexpected errors * [changes]Translation of emotion labels, and the list of hosts arranged in the order of creation * [changes]Translation of emotion labels, and the list of hosts arranged in the order of creation * [fix]The mainframe engineering supports Chinese verification. * [fix]The mainframe engineering supports Chinese verification. * fix(web): update en * fix(web): file upload bugfix * fix(web): memory-write node hide message config --------- Co-authored-by: 乐力齐 <162269739+lanceyq@users.noreply.github.com> Co-authored-by: yujiangping <yujiangping@taofen8.com> Co-authored-by: zhaoying <yzhao96@best-inc.com> Co-authored-by: yingzhao <zhaoyingyz@126.com>
This commit is contained in:
@@ -74,7 +74,7 @@ class OntologyClass(BaseModel):
|
|||||||
"""Validate that the class name follows PascalCase convention.
|
"""Validate that the class name follows PascalCase convention.
|
||||||
|
|
||||||
PascalCase rules:
|
PascalCase rules:
|
||||||
- Must start with an uppercase letter
|
- Must start with an uppercase letter (for English) or any character (for Chinese/Unicode)
|
||||||
- Cannot contain spaces
|
- Cannot contain spaces
|
||||||
- Should not contain special characters except underscores
|
- Should not contain special characters except underscores
|
||||||
|
|
||||||
@@ -90,7 +90,10 @@ class OntologyClass(BaseModel):
|
|||||||
if not v:
|
if not v:
|
||||||
raise ValueError("Class name cannot be empty")
|
raise ValueError("Class name cannot be empty")
|
||||||
|
|
||||||
if not v[0].isupper():
|
# For Chinese/Unicode characters, skip the uppercase check
|
||||||
|
# Only check uppercase for ASCII letters
|
||||||
|
first_char = v[0]
|
||||||
|
if first_char.isascii() and first_char.isalpha() and not first_char.isupper():
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Class name '{v}' must start with an uppercase letter (PascalCase)"
|
f"Class name '{v}' must start with an uppercase letter (PascalCase)"
|
||||||
)
|
)
|
||||||
@@ -100,11 +103,11 @@ class OntologyClass(BaseModel):
|
|||||||
f"Class name '{v}' cannot contain spaces (PascalCase)"
|
f"Class name '{v}' cannot contain spaces (PascalCase)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for invalid characters (allow alphanumeric and underscore only)
|
# Check for invalid characters (allow alphanumeric, underscore, and Unicode characters)
|
||||||
if not all(c.isalnum() or c == '_' for c in v):
|
if not all(c.isalnum() or c == '_' or ord(c) > 127 for c in v):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Class name '{v}' contains invalid characters. "
|
f"Class name '{v}' contains invalid characters. "
|
||||||
"Only alphanumeric characters and underscores are allowed"
|
"Only alphanumeric characters, underscores, and Unicode characters are allowed"
|
||||||
)
|
)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -88,8 +88,10 @@ class OntologyValidator:
|
|||||||
logger.warning(f"Validation failed: {error_msg}")
|
logger.warning(f"Validation failed: {error_msg}")
|
||||||
return False, error_msg
|
return False, error_msg
|
||||||
|
|
||||||
# Check if starts with uppercase letter
|
# Check if starts with uppercase letter (only for ASCII letters)
|
||||||
if not name[0].isupper():
|
# For Chinese/Unicode characters, skip this check
|
||||||
|
first_char = name[0]
|
||||||
|
if first_char.isascii() and first_char.isalpha() and not first_char.isupper():
|
||||||
error_msg = f"Class name '{name}' must start with an uppercase letter (PascalCase)"
|
error_msg = f"Class name '{name}' must start with an uppercase letter (PascalCase)"
|
||||||
logger.warning(f"Validation failed: {error_msg}")
|
logger.warning(f"Validation failed: {error_msg}")
|
||||||
return False, error_msg
|
return False, error_msg
|
||||||
@@ -100,9 +102,9 @@ class OntologyValidator:
|
|||||||
logger.warning(f"Validation failed: {error_msg}")
|
logger.warning(f"Validation failed: {error_msg}")
|
||||||
return False, error_msg
|
return False, error_msg
|
||||||
|
|
||||||
# Check for invalid characters (only alphanumeric and underscore allowed)
|
# Check for invalid characters (allow alphanumeric, underscore, and Unicode characters)
|
||||||
if not re.match(r'^[A-Za-z0-9_]+$', name):
|
if not re.match(r'^[A-Za-z0-9_\u4e00-\u9fff]+$', name):
|
||||||
error_msg = f"Class name '{name}' contains invalid characters. Only alphanumeric characters and underscores are allowed"
|
error_msg = f"Class name '{name}' contains invalid characters. Only alphanumeric characters, underscores, and Chinese characters are allowed"
|
||||||
logger.warning(f"Validation failed: {error_msg}")
|
logger.warning(f"Validation failed: {error_msg}")
|
||||||
return False, error_msg
|
return False, error_msg
|
||||||
|
|
||||||
|
|||||||
@@ -124,17 +124,17 @@ class EmotionAnalyticsService:
|
|||||||
# 将查询结果转换为字典,方便查找
|
# 将查询结果转换为字典,方便查找
|
||||||
tags_dict = {tag["emotion_type"]: tag for tag in tags}
|
tags_dict = {tag["emotion_type"]: tag for tag in tags}
|
||||||
|
|
||||||
# 补全缺失的情绪维度,并翻译 emotion_type
|
# 补全缺失的情绪维度,直接使用英文枚举key(前端自行翻译)
|
||||||
complete_tags = []
|
complete_tags = []
|
||||||
for emotion in all_emotion_types:
|
for emotion in all_emotion_types:
|
||||||
if emotion in tags_dict:
|
if emotion in tags_dict:
|
||||||
tag = tags_dict[emotion].copy()
|
tag = tags_dict[emotion].copy()
|
||||||
tag["emotion_type"] = self._translate_emotion_type(emotion, language)
|
tag["emotion_type"] = emotion
|
||||||
complete_tags.append(tag)
|
complete_tags.append(tag)
|
||||||
else:
|
else:
|
||||||
# 如果该情绪类型不存在,添加默认值
|
# 如果该情绪类型不存在,添加默认值
|
||||||
complete_tags.append({
|
complete_tags.append({
|
||||||
"emotion_type": self._translate_emotion_type(emotion, language),
|
"emotion_type": emotion,
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"percentage": 0.0,
|
"percentage": 0.0,
|
||||||
"avg_intensity": 0.0
|
"avg_intensity": 0.0
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ def get_workspace_end_users(
|
|||||||
) -> List[EndUser]:
|
) -> List[EndUser]:
|
||||||
"""获取工作空间的所有宿主(优化版本:减少数据库查询次数)
|
"""获取工作空间的所有宿主(优化版本:减少数据库查询次数)
|
||||||
|
|
||||||
返回结果按 updated_at 从新到旧排序(NULL 值排在最后)
|
返回结果按 created_at 从新到旧排序(NULL 值排在最后)
|
||||||
"""
|
"""
|
||||||
business_logger.info(f"获取工作空间宿主列表: workspace_id={workspace_id}, 操作者: {current_user.username}")
|
business_logger.info(f"获取工作空间宿主列表: workspace_id={workspace_id}, 操作者: {current_user.username}")
|
||||||
|
|
||||||
@@ -71,13 +71,13 @@ def get_workspace_end_users(
|
|||||||
app_ids = [app.id for app in apps_orm]
|
app_ids = [app.id for app in apps_orm]
|
||||||
|
|
||||||
# 批量查询所有 end_users(一次查询而非循环查询)
|
# 批量查询所有 end_users(一次查询而非循环查询)
|
||||||
# 按 updated_at 降序排序,NULL 值排在最后;id 作为次级排序键保证确定性
|
# 按 created_at 降序排序,NULL 值排在最后;id 作为次级排序键保证确定性
|
||||||
from app.models.end_user_model import EndUser as EndUserModel
|
from app.models.end_user_model import EndUser as EndUserModel
|
||||||
from sqlalchemy import desc, nullslast
|
from sqlalchemy import desc, nullslast
|
||||||
end_users_orm = db.query(EndUserModel).filter(
|
end_users_orm = db.query(EndUserModel).filter(
|
||||||
EndUserModel.app_id.in_(app_ids)
|
EndUserModel.app_id.in_(app_ids)
|
||||||
).order_by(
|
).order_by(
|
||||||
nullslast(desc(EndUserModel.updated_at)),
|
nullslast(desc(EndUserModel.created_at)),
|
||||||
desc(EndUserModel.id)
|
desc(EndUserModel.id)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
|||||||
@@ -1289,6 +1289,7 @@ export const en = {
|
|||||||
priority: 'Structured Integration',
|
priority: 'Structured Integration',
|
||||||
addTool: 'Add Tool',
|
addTool: 'Add Tool',
|
||||||
tool: 'Tool',
|
tool: 'Tool',
|
||||||
|
variableConfig: 'Variable Configuration',
|
||||||
|
|
||||||
statistics: 'Data Statistics',
|
statistics: 'Data Statistics',
|
||||||
daily_conversations: 'Daily Conversations',
|
daily_conversations: 'Daily Conversations',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:29:21
|
* @Date: 2026-02-03 16:29:21
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-09 16:56:27
|
* @Last Modified time: 2026-02-10 18:46:40
|
||||||
*/
|
*/
|
||||||
import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@@ -480,9 +480,11 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
{t('application.debuggingAndPreview')}
|
{t('application.debuggingAndPreview')}
|
||||||
|
|
||||||
<Space size={10}>
|
<Space size={10}>
|
||||||
<Button type="primary" ghost onClick={handleOpenVariableConfig}>
|
{chatVariables.length > 0 &&
|
||||||
{t('application.variableConfig')}
|
<Button type="primary" ghost onClick={handleOpenVariableConfig}>
|
||||||
</Button>
|
{t('application.variableConfig')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
<Button type="primary" ghost onClick={handleAddModel}>
|
<Button type="primary" ghost onClick={handleAddModel}>
|
||||||
+ {t('application.addModel')}
|
+ {t('application.addModel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:27:39
|
* @Date: 2026-02-03 16:27:39
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-10 12:18:23
|
* @Last Modified time: 2026-02-10 17:40:15
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Chat debugging component for application testing
|
* Chat debugging component for application testing
|
||||||
@@ -366,10 +366,8 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
const handleMessageChange = (message: string) => {
|
const handleMessageChange = (message: string) => {
|
||||||
setMessage(message)
|
setMessage(message)
|
||||||
}
|
}
|
||||||
const [update, setUpdate] = useState(false)
|
|
||||||
const fileChange = (file?: any) => {
|
const fileChange = (file?: any) => {
|
||||||
setFileList([...fileList, file])
|
setFileList([...fileList, file])
|
||||||
setUpdate(prev => !prev)
|
|
||||||
}
|
}
|
||||||
// const handleRecordingComplete = async (file: any) => {
|
// const handleRecordingComplete = async (file: any) => {
|
||||||
// console.log('file', file)
|
// console.log('file', file)
|
||||||
@@ -456,29 +454,27 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
onChange={handleMessageChange}
|
onChange={handleMessageChange}
|
||||||
>
|
>
|
||||||
<Flex justify="space-between" className="rb:flex-1">
|
<Flex justify="space-between" className="rb:flex-1">
|
||||||
<Flex gap={8} align="center">
|
<Flex gap={8} align="center">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
{ key: 'define', label: t('memoryConversation.addRemoteFile') },
|
{ key: 'define', label: t('memoryConversation.addRemoteFile') },
|
||||||
{
|
{
|
||||||
key: 'upload', label: (
|
key: 'upload', label: (
|
||||||
<UploadFiles
|
<UploadFiles
|
||||||
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
||||||
onChange={fileChange}
|
onChange={fileChange}
|
||||||
fileList={[]}
|
/>
|
||||||
update={update}
|
)
|
||||||
/>
|
},
|
||||||
)
|
],
|
||||||
},
|
onClick: handleShowUpload
|
||||||
],
|
}}
|
||||||
onClick: handleShowUpload
|
>
|
||||||
}}
|
<div
|
||||||
>
|
className="rb:size-6 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/link.svg')] rb:hover:bg-[url('@/assets/images/conversation/link_hover.svg')]"
|
||||||
<div
|
></div>
|
||||||
className="rb:size-6 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/link.svg')] rb:hover:bg-[url('@/assets/images/conversation/link_hover.svg')]"
|
</Dropdown>
|
||||||
></div>
|
|
||||||
</Dropdown>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* <Flex align="center">
|
{/* <Flex align="center">
|
||||||
<AudioRecorder onRecordingComplete={handleRecordingComplete} />
|
<AudioRecorder onRecordingComplete={handleRecordingComplete} />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-05 10:45:08
|
* @Date: 2026-02-05 10:45:08
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-05 10:45:08
|
* @Last Modified time: 2026-02-10 17:59:37
|
||||||
*/
|
*/
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { Space, List, Flex, Tooltip } from 'antd';
|
import { Space, List, Flex, Tooltip } from 'antd';
|
||||||
@@ -31,7 +31,7 @@ interface SkillModalProps {
|
|||||||
*
|
*
|
||||||
* A modal dialog for selecting skills from a searchable list.
|
* A modal dialog for selecting skills from a searchable list.
|
||||||
* Features:
|
* Features:
|
||||||
* - Search functionality to filter skills by keywords
|
* - Search functionality to filter skills by search
|
||||||
* - Grid layout displaying skill cards with icons and descriptions
|
* - Grid layout displaying skill cards with icons and descriptions
|
||||||
* - Multi-select capability with visual feedback
|
* - Multi-select capability with visual feedback
|
||||||
* - Excludes already selected skills from the list
|
* - Excludes already selected skills from the list
|
||||||
@@ -49,7 +49,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [list, setList] = useState<Skill[]>([])
|
const [list, setList] = useState<Skill[]>([])
|
||||||
const [filterList, setFilterList] = useState<Skill[]>([])
|
const [filterList, setFilterList] = useState<Skill[]>([])
|
||||||
const [query, setQuery] = useState<{keywords?: string}>({})
|
const [query, setQuery] = useState<{search?: string}>({})
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||||
const [selectedRows, setSelectedRows] = useState<Skill[]>([])
|
const [selectedRows, setSelectedRows] = useState<Skill[]>([])
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
|
|||||||
if (visible) {
|
if (visible) {
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
}, [query.keywords, visible])
|
}, [query.search, visible])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the skill list from API with current search parameters
|
* Fetches the skill list from API with current search parameters
|
||||||
@@ -123,7 +123,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
|
|||||||
* @param value - Search keyword
|
* @param value - Search keyword
|
||||||
*/
|
*/
|
||||||
const handleSearch = (value?: string) => {
|
const handleSearch = (value?: string) => {
|
||||||
setQuery({keywords: value})
|
setQuery({search: value})
|
||||||
setSelectedIds([])
|
setSelectedIds([])
|
||||||
setSelectedRows([])
|
setSelectedRows([])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-06 21:09:42
|
* @Date: 2026-02-06 21:09:42
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-09 16:41:31
|
* @Last Modified time: 2026-02-11 11:32:48
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* File Upload Component
|
* File Upload Component
|
||||||
@@ -55,8 +55,6 @@ interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
|
|||||||
maxCount?: number;
|
maxCount?: number;
|
||||||
/** Custom file removal callback */
|
/** Custom file removal callback */
|
||||||
onRemove?: (file: UploadFile) => boolean | void | Promise<boolean | void>;
|
onRemove?: (file: UploadFile) => boolean | void | Promise<boolean | void>;
|
||||||
/** Trigger to reset file list */
|
|
||||||
update?: boolean;
|
|
||||||
}
|
}
|
||||||
// Mapping of file extensions to MIME types
|
// Mapping of file extensions to MIME types
|
||||||
const ALL_FILE_TYPE: {
|
const ALL_FILE_TYPE: {
|
||||||
@@ -109,7 +107,6 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
isAutoUpload = true,
|
isAutoUpload = true,
|
||||||
maxCount = 1,
|
maxCount = 1,
|
||||||
onRemove: customOnRemove,
|
onRemove: customOnRemove,
|
||||||
update,
|
|
||||||
requestConfig,
|
requestConfig,
|
||||||
...props
|
...props
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -118,11 +115,6 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
const [fileList, setFileList] = useState<UploadFile[]>(propFileList);
|
const [fileList, setFileList] = useState<UploadFile[]>(propFileList);
|
||||||
const [accept, setAccept] = useState<string | undefined>();
|
const [accept, setAccept] = useState<string | undefined>();
|
||||||
|
|
||||||
// Reset file list when update prop changes
|
|
||||||
useEffect(() => {
|
|
||||||
setFileList([])
|
|
||||||
}, [update])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates file type and size before upload
|
* Validates file type and size before upload
|
||||||
* @returns Upload.LIST_IGNORE to prevent upload, or true to proceed
|
* @returns Upload.LIST_IGNORE to prevent upload, or true to proceed
|
||||||
@@ -185,11 +177,10 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
/**
|
/**
|
||||||
* Handles upload state changes
|
* Handles upload state changes
|
||||||
*/
|
*/
|
||||||
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList, event }) => {
|
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
|
||||||
console.log('event', event)
|
|
||||||
setFileList(newFileList);
|
setFileList(newFileList);
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(maxCount === 1 ? newFileList[0] : newFileList);
|
onChange(maxCount === 1 ? newFileList[newFileList.length - 1] : newFileList);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:58:03
|
* @Date: 2026-02-03 16:58:03
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-09 20:20:01
|
* @Last Modified time: 2026-02-10 17:41:05
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Conversation Page
|
* Conversation Page
|
||||||
@@ -254,10 +254,8 @@ const Conversation: FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const [update, setUpdate] = useState(false)
|
|
||||||
const fileChange = (file?: any) => {
|
const fileChange = (file?: any) => {
|
||||||
form.setFieldValue('files', [...(queryValues.files || []), file])
|
form.setFieldValue('files', [...(queryValues.files || []), file])
|
||||||
setUpdate(prev => !prev)
|
|
||||||
}
|
}
|
||||||
// const handleRecordingComplete = async (file: any) => {
|
// const handleRecordingComplete = async (file: any) => {
|
||||||
// console.log('file', file)
|
// console.log('file', file)
|
||||||
@@ -353,8 +351,6 @@ const Conversation: FC = () => {
|
|||||||
action={shareFileUploadUrlWithoutApiPrefix}
|
action={shareFileUploadUrlWithoutApiPrefix}
|
||||||
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
||||||
onChange={fileChange}
|
onChange={fileChange}
|
||||||
fileList={[]}
|
|
||||||
update={update}
|
|
||||||
requestConfig={{
|
requestConfig={{
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
|
|||||||
@@ -221,9 +221,9 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
|||||||
status: record.status,
|
status: record.status,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process parser_config data, set default values if not present
|
// Process parser_config data, preserve all existing fields and merge graphrag config
|
||||||
baseValues.parser_config = {
|
baseValues.parser_config = {
|
||||||
...record.parser_config,
|
...record.parser_config, // Preserve all existing parser_config fields (yuque_user_id, yuque_token, feishu_app_id, etc.)
|
||||||
graphrag: {
|
graphrag: {
|
||||||
use_graphrag: false,
|
use_graphrag: false,
|
||||||
scene_name: '',
|
scene_name: '',
|
||||||
@@ -362,12 +362,32 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
|||||||
// Actual save logic
|
// Actual save logic
|
||||||
const performSave = async () => {
|
const performSave = async () => {
|
||||||
try {
|
try {
|
||||||
await form.validateFields();
|
// Get fields to validate based on current tab and edit mode
|
||||||
setLoading(true);
|
let fieldsToValidate: any[] | undefined = undefined;
|
||||||
const formValues = form.getFieldsValue();
|
|
||||||
|
|
||||||
// Check Third-party authentication before saving
|
// If in edit mode and on knowledge graph tab, only validate knowledge graph fields
|
||||||
if (formValues.type === 'Third-party' || currentType === 'Third-party') {
|
if (datasets?.id && activeTab === 'knowledgeGraph') {
|
||||||
|
fieldsToValidate = [
|
||||||
|
['parser_config', 'graphrag', 'use_graphrag'],
|
||||||
|
['parser_config', 'graphrag', 'scene_name'],
|
||||||
|
['parser_config', 'graphrag', 'entity_types'],
|
||||||
|
['parser_config', 'graphrag', 'method'],
|
||||||
|
['parser_config', 'graphrag', 'resolution'],
|
||||||
|
['parser_config', 'graphrag', 'community'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate only specified fields or all fields
|
||||||
|
await form.validateFields(fieldsToValidate);
|
||||||
|
setLoading(true);
|
||||||
|
// Get all field values including disabled fields
|
||||||
|
const formValues = form.getFieldsValue(true);
|
||||||
|
|
||||||
|
// Only check Third-party authentication when creating new or explicitly on basic config tab
|
||||||
|
// Skip authentication check when editing and on knowledge graph tab
|
||||||
|
const shouldCheckAuth = !datasets?.id || activeTab === 'basic';
|
||||||
|
|
||||||
|
if (shouldCheckAuth && (formValues.type === 'Third-party' || currentType === 'Third-party')) {
|
||||||
const platform = formValues.parser_config?._third_party_platform || thirdPartyPlatform;
|
const platform = formValues.parser_config?._third_party_platform || thirdPartyPlatform;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -334,6 +334,18 @@ const KnowledgeBaseManagement: FC = () => {
|
|||||||
setHasMore(hasNext);
|
setHasMore(hasNext);
|
||||||
|
|
||||||
buildModelMenus(list, isLoadMore);
|
buildModelMenus(list, isLoadMore);
|
||||||
|
|
||||||
|
// 首次加载后,检查是否需要自动加载更多(解决无滚动条问题)
|
||||||
|
if (!isLoadMore && hasNext) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const scrollDiv = document.getElementById('scrollableDiv');
|
||||||
|
if (scrollDiv && scrollDiv.scrollHeight <= scrollDiv.clientHeight) {
|
||||||
|
console.log('No scrollbar detected, auto-loading more data');
|
||||||
|
setPage(2);
|
||||||
|
fetchData(2, true);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch knowledge base list:', error);
|
console.error('Failed to fetch knowledge base list:', error);
|
||||||
if (!isLoadMore) {
|
if (!isLoadMore) {
|
||||||
@@ -532,10 +544,10 @@ const KnowledgeBaseManagement: FC = () => {
|
|||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
dataLength={data.length}
|
dataLength={data.length}
|
||||||
next={loadMore}
|
next={loadMore}
|
||||||
hasMore={hasMore && !loading}
|
hasMore={hasMore}
|
||||||
loader={<div className="rb:text-center rb:py-4">{t('common.loading')}</div>}
|
loader={loading && data.length > 0 ? <div className="rb:text-center rb:py-4">{t('common.loading')}</div> : null}
|
||||||
endMessage={
|
endMessage={
|
||||||
data.length > 0 ? (
|
data.length > 0 && !hasMore ? (
|
||||||
<div className="rb:text-center rb:py-4 rb:text-gray-400">
|
<div className="rb:text-center rb:py-4 rb:text-gray-400">
|
||||||
{t('common.noMoreData')}
|
{t('common.noMoreData')}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-06 21:10:56
|
* @Date: 2026-02-06 21:10:56
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-10 12:17:41
|
* @Last Modified time: 2026-02-10 17:41:24
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Workflow Chat Component
|
* Workflow Chat Component
|
||||||
@@ -343,13 +343,11 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
|||||||
const handleMessageChange = (message: string) => {
|
const handleMessageChange = (message: string) => {
|
||||||
setMessage(message)
|
setMessage(message)
|
||||||
}
|
}
|
||||||
const [update, setUpdate] = useState(false)
|
|
||||||
/**
|
/**
|
||||||
* Handles file upload from local device
|
* Handles file upload from local device
|
||||||
*/
|
*/
|
||||||
const fileChange = (file?: any) => {
|
const fileChange = (file?: any) => {
|
||||||
setFileList([...fileList, file])
|
setFileList([...fileList, file])
|
||||||
setUpdate(prev => !prev)
|
|
||||||
}
|
}
|
||||||
// const handleRecordingComplete = async (file: any) => {
|
// const handleRecordingComplete = async (file: any) => {
|
||||||
// console.log('file', file)
|
// console.log('file', file)
|
||||||
@@ -517,8 +515,6 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
|||||||
<UploadFiles
|
<UploadFiles
|
||||||
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
||||||
onChange={fileChange}
|
onChange={fileChange}
|
||||||
fileList={[]}
|
|
||||||
update={update}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:39:59
|
* @Date: 2026-02-03 15:39:59
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-09 19:56:42
|
* @Last Modified time: 2026-02-11 12:07:06
|
||||||
*/
|
*/
|
||||||
import { type FC, useEffect, useState, useMemo } from "react";
|
import { type FC, useEffect, useState, useMemo } from "react";
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@@ -627,6 +627,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
}
|
}
|
||||||
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
|
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
|
||||||
className={key === 'parallel_count' ? 'rb:-mt-3! rb:leading-3.5!' : ''}
|
className={key === 'parallel_count' ? 'rb:-mt-3! rb:leading-3.5!' : ''}
|
||||||
|
hidden={Boolean(config.hidden)}
|
||||||
>
|
>
|
||||||
{config.type === 'input'
|
{config.type === 'input'
|
||||||
? <Input placeholder={t('common.pleaseEnter')} />
|
? <Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:06:18
|
* @Date: 2026-02-03 15:06:18
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-09 20:08:03
|
* @Last Modified time: 2026-02-11 12:07:20
|
||||||
*/
|
*/
|
||||||
import LoopNode from './components/Nodes/LoopNode';
|
import LoopNode from './components/Nodes/LoopNode';
|
||||||
import NormalNode from './components/Nodes/NormalNode';
|
import NormalNode from './components/Nodes/NormalNode';
|
||||||
@@ -240,7 +240,8 @@ export const nodeLibrary: NodeLibrary[] = [
|
|||||||
config: {
|
config: {
|
||||||
message: {
|
message: {
|
||||||
type: 'editor',
|
type: 'editor',
|
||||||
isArray: false
|
isArray: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
type: 'messageEditor',
|
type: 'messageEditor',
|
||||||
|
|||||||
Reference in New Issue
Block a user