1.添加移动端用户的检测工具类,移动端用户只能访问对话页面。
2.评查点列表添加文档属性类型字段。 3.优化dify的对话侧边栏的显示效果。 4.评查点规则添加使用文档属性类型的输入框。添加多实体开关的操作开关。
This commit is contained in:
@@ -665,13 +665,11 @@ export default function Chat() {
|
||||
|
||||
return (
|
||||
<Layout style={{ height: '100%', display: 'flex', flexDirection: 'row', position: 'relative' }}>
|
||||
{/* 移动端遮罩层 */}
|
||||
{!sidebarCollapsed && isMobile && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 z-[999]"
|
||||
onClick={handleSidebarToggle}
|
||||
/>
|
||||
)}
|
||||
{/* 移动端遮罩层 - 点击可收起侧边栏 */}
|
||||
<div
|
||||
className={`chat-sidebar-overlay ${!sidebarCollapsed && isMobile ? 'visible' : ''}`}
|
||||
onClick={handleSidebarToggle}
|
||||
/>
|
||||
|
||||
{/* ChatSidebar 隐藏时显示的展开按钮 */}
|
||||
{sidebarCollapsed && (
|
||||
|
||||
@@ -82,10 +82,11 @@ export const SourcesPanel = React.memo(({ resources }: { resources: RetrieverRes
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
placement="topLeft"
|
||||
autoAdjustOverflow={false}
|
||||
placement="top"
|
||||
autoAdjustOverflow={true}
|
||||
color="rgba(0, 104, 74, 0.92)"
|
||||
classNames={{ root: 'source-tooltip-overlay' }}
|
||||
overlayStyle={{ maxWidth: 'calc(100vw - 32px)' }}
|
||||
>
|
||||
<div className="source-item">
|
||||
<span className="source-item-number">{resource.position}</span>
|
||||
|
||||
@@ -9,6 +9,7 @@ interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
userRole?: UserRole;
|
||||
frontendJWT?: string;
|
||||
isMobile?: boolean; // 是否为移动端设备(服务端通过 User-Agent 检测)
|
||||
}
|
||||
|
||||
// 添加一个接口表示路由handle可能包含的属性
|
||||
@@ -23,7 +24,7 @@ interface Match {
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '' }: LayoutProps) {
|
||||
export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '', isMobile = false }: LayoutProps) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [effectiveUserRole, setEffectiveUserRole] = useState<UserRole>(userRole);
|
||||
const [effectiveFrontendJWT, setEffectiveFrontendJWT] = useState<string>(frontendJWT);
|
||||
@@ -32,10 +33,12 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
|
||||
|
||||
// 检查当前路径是否应该隐藏侧边栏
|
||||
const noLayoutPaths = ['/login', '/'];
|
||||
const shouldHideSidebar = noLayoutPaths.includes(location.pathname);
|
||||
// 移动端设备强制隐藏侧边栏
|
||||
const shouldHideSidebar = isMobile || noLayoutPaths.includes(location.pathname);
|
||||
|
||||
// 检查当前路由是否应该隐藏默认面包屑
|
||||
const shouldHideBreadcrumb = shouldHideSidebar || matches.some(match =>
|
||||
// 移动端设备强制隐藏面包屑(避免显示首页链接)
|
||||
const shouldHideBreadcrumb = isMobile || shouldHideSidebar || matches.some(match =>
|
||||
match.handle && match.handle.hideBreadcrumb === true
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { EvaluationPoint } from '~/models/evaluation_points';
|
||||
import type { EvaluationPointGroup } from '~/models/evaluation_point_groups';
|
||||
import { getRulesList, getRuleTypes, type RuleType } from '~/api/evaluation_points/rules';
|
||||
import { getRulesList, getRuleTypes, getAttributeTypes, type RuleType, type AttributeTypeOption } from '~/api/evaluation_points/rules';
|
||||
|
||||
interface BasicInfoProps {
|
||||
onChange?: (data: Record<string, unknown>) => void;
|
||||
@@ -101,6 +101,11 @@ export function BasicInfo({
|
||||
const [filteredRuleTypes, setFilteredRuleTypes] = useState<RuleType[]>([]);
|
||||
const [ruleTypesLoading, setRuleTypesLoading] = useState(false);
|
||||
|
||||
// 适用文档属性类型相关状态
|
||||
const [attributeTypeOptions, setAttributeTypeOptions] = useState<AttributeTypeOption[]>([]);
|
||||
const [attributeTypesLoading, setAttributeTypesLoading] = useState(false);
|
||||
const [isCustomAttributeType, setIsCustomAttributeType] = useState(false); // 是否为自定义输入模式
|
||||
|
||||
// 从 Session Storage 获取 documentTypeIds 并调用 API 获取评查点类型
|
||||
useEffect(() => {
|
||||
const fetchRuleTypes = async () => {
|
||||
@@ -140,6 +145,43 @@ export function BasicInfo({
|
||||
fetchRuleTypes();
|
||||
}, [frontendJWT]);
|
||||
|
||||
// 获取适用文档属性类型选项
|
||||
useEffect(() => {
|
||||
const fetchAttributeTypes = async () => {
|
||||
try {
|
||||
setAttributeTypesLoading(true);
|
||||
const response = await getAttributeTypes(frontendJWT);
|
||||
|
||||
if (response.data) {
|
||||
setAttributeTypeOptions(response.data);
|
||||
} else {
|
||||
// 使用默认选项
|
||||
setAttributeTypeOptions([{ code: 'ALL', label: '通用' }]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取适用属性类型失败:', error);
|
||||
setAttributeTypeOptions([{ code: 'ALL', label: '通用' }]);
|
||||
} finally {
|
||||
setAttributeTypesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAttributeTypes();
|
||||
}, [frontendJWT]);
|
||||
|
||||
// 检查初始数据中的 document_attribute_type 是否为自定义值
|
||||
useEffect(() => {
|
||||
if (formData.document_attribute_type) {
|
||||
const isInOptions = attributeTypeOptions.some(
|
||||
opt => opt.code === formData.document_attribute_type
|
||||
);
|
||||
// 如果当前值不在选项列表中,则切换到自定义模式
|
||||
if (!isInOptions && attributeTypeOptions.length > 0) {
|
||||
setIsCustomAttributeType(true);
|
||||
}
|
||||
}
|
||||
}, [formData.document_attribute_type, attributeTypeOptions]);
|
||||
|
||||
// 根据选择的评查点类型筛选可用的规则组
|
||||
const filteredRuleGroups = evaluationPointGroups.filter(group =>
|
||||
formData.evaluation_point_groups_pid &&
|
||||
@@ -268,6 +310,14 @@ export function BasicInfo({
|
||||
newData.evaluation_point_groups_id = null; // 清空规则组选择
|
||||
}
|
||||
break;
|
||||
case 'document-attribute-type':
|
||||
// 处理适用文档属性类型选择(下拉框模式)
|
||||
newData.document_attribute_type = value || 'ALL';
|
||||
break;
|
||||
case 'document-attribute-type-custom':
|
||||
// 处理适用文档属性类型输入(自定义模式)
|
||||
newData.document_attribute_type = value;
|
||||
break;
|
||||
}
|
||||
|
||||
setFormData(newData);
|
||||
@@ -320,18 +370,19 @@ export function BasicInfo({
|
||||
}
|
||||
}, [formData.references_laws?.articles]);
|
||||
|
||||
// 检查是否需要自动展开描述区域
|
||||
// 检查是否需要自动展开描述区域(仅在初始数据加载时执行一次)
|
||||
useEffect(() => {
|
||||
// 如果描述或法律依据相关字段有值,则自动展开
|
||||
// 如果初始数据中描述或法律依据相关字段有值,则自动展开
|
||||
if (
|
||||
formData.description ||
|
||||
formData.references_laws?.name ||
|
||||
(formData.references_laws?.articles && formData.references_laws.articles.length > 0) ||
|
||||
formData.references_laws?.content
|
||||
initialData?.description ||
|
||||
initialData?.references_laws?.name ||
|
||||
(initialData?.references_laws?.articles && initialData.references_laws.articles.length > 0) ||
|
||||
initialData?.references_laws?.content
|
||||
) {
|
||||
setIsDescExpanded(true);
|
||||
}
|
||||
}, [formData]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// 注释掉自动选择规则组的逻辑,避免无限循环
|
||||
// 原因:此 useEffect 依赖 onChange 和 filteredRuleGroups,每次渲染都可能触发
|
||||
@@ -461,8 +512,8 @@ export function BasicInfo({
|
||||
</div>
|
||||
<div>
|
||||
<label className="form-label" htmlFor="is-enabled">是否启用</label>
|
||||
<select
|
||||
id="is-enabled"
|
||||
<select
|
||||
id="is-enabled"
|
||||
className="form-select"
|
||||
value={formData.is_enabled ? 'true' : 'false'}
|
||||
onChange={handleInputChange}
|
||||
@@ -472,11 +523,83 @@ export function BasicInfo({
|
||||
</select>
|
||||
<div className="form-tip">创建后是否立即启用此评查点</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="form-label" htmlFor="document-attribute-type">
|
||||
适用文档属性类型
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{isCustomAttributeType ? (
|
||||
// 自定义输入模式
|
||||
<input
|
||||
type="text"
|
||||
id="document-attribute-type-custom"
|
||||
className="form-input flex-1"
|
||||
placeholder="请输入自定义属性类型"
|
||||
value={formData.document_attribute_type || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
) : (
|
||||
// 下拉选择模式
|
||||
<select
|
||||
id="document-attribute-type"
|
||||
className="form-select flex-1"
|
||||
value={formData.document_attribute_type || 'ALL'}
|
||||
onChange={handleInputChange}
|
||||
disabled={attributeTypesLoading}
|
||||
>
|
||||
{attributeTypesLoading ? (
|
||||
<option value="">加载中...</option>
|
||||
) : (
|
||||
attributeTypeOptions.map(option => (
|
||||
<option key={option.code} value={option.code}>
|
||||
{option.label}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={`px-3 py-2 text-sm rounded border transition-colors ${
|
||||
isCustomAttributeType
|
||||
? 'bg-[#00684a] text-white border-[#00684a] hover:bg-[#005a3f]'
|
||||
: 'bg-white text-gray-600 border-gray-300 hover:border-[#00684a] hover:text-[#00684a]'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsCustomAttributeType(!isCustomAttributeType);
|
||||
// 切换到自定义模式时,如果当前值是预设值,清空它让用户输入
|
||||
// 切换回下拉模式时,如果当前值不在选项中,设置为默认值 ALL
|
||||
if (!isCustomAttributeType) {
|
||||
// 切换到自定义模式
|
||||
} else {
|
||||
// 切换回下拉模式
|
||||
const currentValue = formData.document_attribute_type;
|
||||
const isInOptions = attributeTypeOptions.some(opt => opt.code === currentValue);
|
||||
if (!isInOptions) {
|
||||
const newData = { ...formData, document_attribute_type: 'ALL' };
|
||||
setFormData(newData);
|
||||
if (onChange) {
|
||||
onChange(newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={isCustomAttributeType ? '切换到下拉选择' : '切换到自定义输入'}
|
||||
>
|
||||
<i className={`ri-${isCustomAttributeType ? 'list-check' : 'edit-line'}`}></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-tip">
|
||||
{isCustomAttributeType
|
||||
? '输入自定义的文档属性类型,点击右侧按钮可切换回下拉选择'
|
||||
: '选择评查点适用的文档属性类型,点击右侧按钮可自定义输入'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<div
|
||||
className={`flex justify-between items-center cursor-pointer ${isDescExpanded ? 'expanded' : ''}`}
|
||||
className={`flex items-center cursor-pointer ${isDescExpanded ? 'expanded' : ''}`}
|
||||
onClick={handleToggleDescription}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
@@ -487,7 +610,7 @@ export function BasicInfo({
|
||||
role="button"
|
||||
>
|
||||
<label className="form-label mb-0" htmlFor="description-section">评查点描述与法律依据</label>
|
||||
<i className={`ri-arrow-${isDescExpanded ? 'up' : 'down'}-s-line text-lg expand-icon`}></i>
|
||||
<i className={`${isDescExpanded ? 'ri-arrow-drop-up-line' : 'ri-arrow-drop-down-line'} text-lg expand-icon ml-2`}></i>
|
||||
</div>
|
||||
|
||||
<div className={`mt-2 ${isDescExpanded ? '' : 'hidden'}`} id="description-section">
|
||||
|
||||
@@ -48,10 +48,19 @@ export function ExtractionSettings({
|
||||
vlmFieldTypeOptions = EVALUATION_OPTIONS.vlmFieldTypeOptions,
|
||||
}: ExtractionSettingsProps) {
|
||||
|
||||
// 多实体抽取开关状态
|
||||
const [multiEntityEnabled, setMultiEntityEnabled] = useState<boolean>(
|
||||
initialData?.extraction_config?.multi_entity?.enabled ?? false
|
||||
);
|
||||
|
||||
// 核心数据状态
|
||||
const [formData, setFormData] = useState<EvaluationPoint>({
|
||||
// 字段配置
|
||||
extraction_config: {
|
||||
multi_entity: initialData?.extraction_config?.multi_entity ?? {
|
||||
enabled: false,
|
||||
expand_mode: 'awareness',
|
||||
},
|
||||
llm: initialData?.extraction_config?.llm ?? {
|
||||
fields: [],
|
||||
prompt_setting: {
|
||||
@@ -488,6 +497,10 @@ export function ExtractionSettings({
|
||||
const updatedFormData = {
|
||||
...formData,
|
||||
extraction_config: {
|
||||
multi_entity: {
|
||||
enabled: multiEntityEnabled,
|
||||
expand_mode: 'awareness' as const
|
||||
},
|
||||
llm: {
|
||||
fields: fields.llm,
|
||||
prompt_setting: {
|
||||
@@ -562,12 +575,46 @@ export function ExtractionSettings({
|
||||
}
|
||||
};
|
||||
|
||||
// 处理多实体抽取开关变化
|
||||
const handleMultiEntityToggle = () => {
|
||||
const newValue = !multiEntityEnabled;
|
||||
setMultiEntityEnabled(newValue);
|
||||
setHasPendingChanges(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ant-card">
|
||||
<div className="ant-card-header">
|
||||
<h3>抽取设置</h3>
|
||||
</div>
|
||||
<div className="ant-card-body">
|
||||
{/* 多实体抽取开关 */}
|
||||
<div className="mb-6 p-3 bg-gray-50 rounded-md border border-gray-200 w-[40%]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<i className="ri-group-line text-lg mr-2 text-gray-600"></i>
|
||||
<div>
|
||||
<span className="font-medium text-gray-800">多实体抽取</span>
|
||||
<span className="text-xs text-gray-500 ml-3">启用后,系统将按实体展开字段进行抽取(AI感知模式)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="relative inline-flex items-center cursor-pointer ml-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={multiEntityEnabled}
|
||||
onChange={handleMultiEntityToggle}
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-green-700/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-700"></div>
|
||||
<span className="ml-2 text-sm font-medium text-gray-700">
|
||||
{multiEntityEnabled ? '已启用' : '已禁用'}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<div className="tab-nav mb-4" id="extraction-method-tabs">
|
||||
<button
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Button } from '~/components/ui/Button';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
onSave?: () => void;
|
||||
@@ -18,13 +20,14 @@ export function PageHeader({
|
||||
<h1 className="text-xl font-medium text-gray-800">{title}</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
{showBackButton && (
|
||||
<button
|
||||
type="button"
|
||||
className="ant-btn ant-btn-default"
|
||||
<Button
|
||||
type="default"
|
||||
className=" focus:!ring-gray-300"
|
||||
onClick={onBack}
|
||||
icon="ri-arrow-left-line"
|
||||
>
|
||||
<i className="ri-arrow-left-line mr-1"></i> 返回
|
||||
</button>
|
||||
返回
|
||||
</Button>
|
||||
)}
|
||||
{showSaveButton && (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user