Merge branch 'refs/heads/release/v0.2.8' into fix/features_028

This commit is contained in:
Timebomb2018
2026-03-19 17:58:17 +08:00
11 changed files with 110 additions and 37 deletions

View File

@@ -172,7 +172,7 @@ def _retrieve_for_knowledge(
return results, chat_model, embedding_model return results, chat_model, embedding_model
# Folder 类型:递归处理子知识库 # Folder 类型:递归处理子知识库
if db_knowledge.type == knowledge_model.KnowledgeType.Folder: if db_knowledge.type == knowledge_model.KnowledgeType.FOLDER:
children = knowledge_repository.get_knowledges_by_parent_id(db=db, parent_id=db_knowledge.id) children = knowledge_repository.get_knowledges_by_parent_id(db=db, parent_id=db_knowledge.id)
for child in children: for child in children:
if not (child and child.chunk_num > 0 and child.status == 1): if not (child and child.chunk_num > 0 and child.status == 1):

View File

@@ -636,30 +636,33 @@ class WorkflowService:
final_messages = result.get("messages", [])[init_message_length:] final_messages = result.get("messages", [])[init_message_length:]
human_message = "" human_message = ""
assistant_message = "" assistant_message = ""
human_meta = {
"files": []
}
for message in final_messages: for message in final_messages:
if message["role"] == "user": if message["role"] == "user":
if isinstance(message["content"], str): if isinstance(message["content"], str):
human_message += message["content"] human_message += message["content"]
elif isinstance(message["content"], list): elif isinstance(message["content"], list):
for file in message["content"]: for file in message["content"]:
if file.get("type") == FileType.IMAGE: human_meta["files"].append({
human_message += f"![image]({file.get('url', '')})" "type": file.get("type"),
else: "url": file.get("url")
human_message += f"[{file.get('type')}]({file.get('url', '')})" })
if message["role"] == "assistant": if message["role"] == "assistant":
assistant_message = message["content"] assistant_message = message["content"]
self.conversation_service.add_message( self.conversation_service.add_message(
conversation_id=conversation_id_uuid, conversation_id=conversation_id_uuid,
role="user", role="user",
content=human_message, content=human_message,
meta_data=None meta_data=human_meta
) )
self.conversation_service.add_message( self.conversation_service.add_message(
message_id=message_id, message_id=message_id,
conversation_id=conversation_id_uuid, conversation_id=conversation_id_uuid,
role="assistant", role="assistant",
content=assistant_message, content=assistant_message,
meta_data={"usage": token_usage} meta_data={"usage": token_usage, "audio_url": None}
) )
self.update_execution_status( self.update_execution_status(
execution.execution_id, execution.execution_id,

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-02 15:01:59 * @Date: 2026-02-02 15:01:59
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-17 15:35:34 * @Last Modified time: 2026-03-19 13:41:26
*/ */
/** /**
@@ -42,7 +42,8 @@ const ButtonCheckbox: FC<ButtonCheckboxProps> = ({
icon, icon,
checkedIcon, checkedIcon,
children, children,
cicle = false cicle = false,
disabled,
}) => { }) => {
// Listen to value changes and trigger side effects via onValueChange callback // Listen to value changes and trigger side effects via onValueChange callback
useEffect(() => { useEffect(() => {
@@ -70,6 +71,7 @@ const ButtonCheckbox: FC<ButtonCheckboxProps> = ({
"rb:bg-[rgba(21,94,239,0.06)] rb:border-[rgba(21,94,239,0.25)] rb:hover:bg-[rgba(21,94,239,0.06)] rb:text-[#155EEF]": checked, "rb:bg-[rgba(21,94,239,0.06)] rb:border-[rgba(21,94,239,0.25)] rb:hover:bg-[rgba(21,94,239,0.06)] rb:text-[#155EEF]": checked,
// Unchecked state: gray border and dark text // Unchecked state: gray border and dark text
"rb:border-[#DFE4ED] rb:text-[#212332]": !checked, "rb:border-[#DFE4ED] rb:text-[#212332]": !checked,
"rb:opacity-65 rb:cursor-not-allowed!": disabled
})} })}
onClick={handleChange} onClick={handleChange}
> >

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2025-12-10 16:46:17 * @Date: 2025-12-10 16:46:17
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 10:37:01 * @Last Modified time: 2026-03-19 13:38:20
*/ */
import { type FC, useRef, useEffect, useState } from 'react' import { type FC, useRef, useEffect, useState } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
@@ -118,7 +118,7 @@ const ChatContent: FC<ChatContentProps> = ({
{labelFormat(item)} {labelFormat(item)}
</div> </div>
} }
{item.meta_data?.files && item.meta_data?.files.length > 0 && <Flex vertical align="end"> {item.meta_data?.files && item.meta_data?.files.length > 0 && <Flex gap={8} vertical align="end">
{item.meta_data?.files?.map((file) => { {item.meta_data?.files?.map((file) => {
if (file.type.includes('image')) { if (file.type.includes('image')) {
return ( return (

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2025-12-10 16:46:14 * @Date: 2025-12-10 16:46:14
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-06 13:36:20 * @Last Modified time: 2026-03-19 16:05:56
*/ */
import { type FC, useEffect, useMemo } from 'react' import { type FC, useEffect, useMemo } from 'react'
import { Flex, Input, Form } from 'antd' import { Flex, Input, Form } from 'antd'
@@ -109,15 +109,20 @@ const ChatInput: FC<ChatInputProps> = ({
} }
return ( return (
<div key={file.url || file.uid} className="rb:w-45 rb:text-[12px] rb:gap-2.5 rb:flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5"> <div key={file.url || file.uid} className="rb:w-45 rb:text-[12px] rb:gap-2.5 rb:flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5">
{(file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document')) && <div {file.type.includes('pdf')
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/word_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/word.svg')]" ? <div
></div>} className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/pdf_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/pdf.svg')]"
{(file.type.includes('pdf')) && <div ></div>
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/pdf_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/pdf.svg')]" : (file.type.includes('excel') || file.type.includes('spreadsheetml.sheet') || file.type.includes('csv'))
></div>} ? <div
{(file.type.includes('excel') || file.type.includes('spreadsheetml.sheet') || file.type.includes('csv')) && <div className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/excel_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/excel.svg')]"
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/excel_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/excel.svg')]" ></div>
></div>} : (file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document'))
? <div
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/word_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/word.svg')]"
></div>
: null
}
<div className="rb:flex-1 rb:w-32.5"> <div className="rb:flex-1 rb:w-32.5">
<div className="rb:leading-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.name}</div> <div className="rb:leading-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.name}</div>
<div className="rb:leading-3.5 rb:mt-0.5 rb:text-[#5B6167] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.type} · {file.size}</div> <div className="rb:leading-3.5 rb:mt-0.5 rb:text-[#5B6167] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.type} · {file.size}</div>

View File

@@ -1564,6 +1564,7 @@ export const en = {
summary: 'Summary', summary: 'Summary',
core_entities: 'Core Entities', core_entities: 'Core Entities',
communityDetailEmptyDesc: 'Click on a community in the chart on the left to view details', communityDetailEmptyDesc: 'Click on a community in the chart on the left to view details',
communityLoadingTip: 'Generating community graph',
}, },
space: { space: {
createSpace: 'Create Space', createSpace: 'Create Space',

View File

@@ -1562,6 +1562,7 @@ export const zh = {
summary: '摘要', summary: '摘要',
core_entities: '核心实体', core_entities: '核心实体',
communityDetailEmptyDesc: '点击左侧图表中的社区查看详情', communityDetailEmptyDesc: '点击左侧图表中的社区查看详情',
communityLoadingTip: '社区图谱生成中',
}, },
space: { space: {
createSpace: '创建空间', createSpace: '创建空间',

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:27:52 * @Date: 2026-02-03 16:27:52
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-18 21:25:23 * @Last Modified time: 2026-03-19 17:13:54
*/ */
import { type FC, useRef, useMemo, useCallback } from 'react'; import { type FC, useRef, useMemo, useCallback } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
@@ -212,7 +212,7 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
className={styles.tabs} className={styles.tabs}
/> />
</div> </div>
{application?.type === 'workflow' && source !== 'sharing' {application?.type === 'workflow' && source !== 'sharing' && activeTab === 'arrangement'
? <div className="rb:h-8 rb:flex rb:items-center rb:justify-end rb:gap-2.5"> ? <div className="rb:h-8 rb:flex rb:items-center rb:justify-end rb:gap-2.5">
<FeaturesConfig source={application?.type} value={features as FeaturesConfigForm} refresh={handleSaveFeaturesConfig} /> <FeaturesConfig source={application?.type} value={features as FeaturesConfigForm} refresh={handleSaveFeaturesConfig} />
<Button onClick={clear}>{t('workflow.clear')}</Button> <Button onClick={clear}>{t('workflow.clear')}</Button>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-05 * @Date: 2026-03-05
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 09:59:42 * @Last Modified time: 2026-03-19 15:18:20
*/ */
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd'; import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd';
@@ -27,22 +27,43 @@ const fileTypeOptions = [
{ {
type: 'document', type: 'document',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/txt.svg')]"></div>, icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/txt.svg')]"></div>,
formats: 'TXT, PDF, DOC, DOCX, XLSX, CSV, JSON', formats: [
"pdf",
"docx",
"doc",
"xlsx",
"xls",
"txt",
"csv",
"json",
"md",
],
}, },
{ {
type: 'image', type: 'image',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/image.svg')]"></div>, icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/image.svg')]"></div>,
formats: 'JPG, JPEG, PNG, GIF, WEBP', formats: [
"png",
"jpg",
"jpeg"
],
}, },
{ {
type: 'audio', type: 'audio',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/audio.svg')]"></div>, icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/audio.svg')]"></div>,
formats: 'MP3, M4A, WAV, OGG, FLAC', formats: [
"mp3",
"wav",
"m4a",
],
}, },
{ {
type: 'video', type: 'video',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/video.svg')]"></div>, icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/video.svg')]"></div>,
formats: 'MP4, MOV, AVI, WEBM', formats: [
"mp4",
"mov",
],
}, },
]; ];
@@ -50,16 +71,38 @@ const defaultValues: FileUpload = {
enabled: false, enabled: false,
image_enabled: false, image_enabled: false,
image_max_size_mb: 20, image_max_size_mb: 20,
image_allowed_extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp'], image_allowed_extensions: [
"png",
"jpg",
"jpeg"
],
audio_enabled: false, audio_enabled: false,
audio_max_size_mb: 50, audio_max_size_mb: 50,
audio_allowed_extensions: ['mp3', 'wav', 'm4a', 'ogg', 'flac'], audio_allowed_extensions: [
"mp3",
"wav",
"m4a",
"ogg",
"flac"
],
document_enabled: false, document_enabled: false,
document_max_size_mb: 100, document_max_size_mb: 100,
document_allowed_extensions: ['pdf', 'docx', 'xlsx', 'txt', 'csv', 'json'], document_allowed_extensions: [
"pdf",
"docx",
"xlsx",
"txt",
"csv",
"json"
],
video_enabled: false, video_enabled: false,
video_max_size_mb: 500, video_max_size_mb: 100,
video_allowed_extensions: ['mp4', 'mov', 'avi', 'webm'], video_allowed_extensions: [
"mp4",
"mov",
"avi",
"webm"
],
max_file_count: 5, max_file_count: 5,
allowed_transfer_methods: 'both' allowed_transfer_methods: 'both'
} }
@@ -127,7 +170,7 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mb-1">{t('application.maxCount')}</div> <div className="rb:text-[12px] rb:text-[#5B6167] rb:mb-1">{t('application.maxCount')}</div>
<Form.Item label={t('application.maxCount')} name="max_file_count"> <Form.Item label={t('application.maxCount')} name="max_file_count">
<InputNumber min={1} max={100} precision={0} className="rb:w-full!" placeholder={t('common.pleaseEnter')} /> <InputNumber min={1} max={20} precision={0} className="rb:w-full!" placeholder={t('common.pleaseEnter')} />
</Form.Item> </Form.Item>
<Form.Item label={t('application.supportedTypes')}> <Form.Item label={t('application.supportedTypes')}>
@@ -149,7 +192,7 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
<Flex align="center" justify="space-between"> <Flex align="center" justify="space-between">
<Flex vertical> <Flex vertical>
<div className="rb:font-medium">{t(`application.${option.type}`)}</div> <div className="rb:font-medium">{t(`application.${option.type}`)}</div>
<div className="rb:text-[12px] rb:text-[#5B6167]">{option.formats}</div> <div className="rb:text-[12px] rb:text-[#5B6167]">{option.formats.map(item => item.toUpperCase()).join(', ')}</div>
</Flex> </Flex>
<Form.Item name={enabledKey} valuePropName="checked" noStyle> <Form.Item name={enabledKey} valuePropName="checked" noStyle>
<Switch /> <Switch />
@@ -161,7 +204,7 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
<Flex align="center" gap={12} className="rb:mt-3! rb:pt-3! rb:border-t rb:border-[#DFE4ED]"> <Flex align="center" gap={12} className="rb:mt-3! rb:pt-3! rb:border-t rb:border-[#DFE4ED]">
<div>{t('application.singleMaxSize')}: </div> <div>{t('application.singleMaxSize')}: </div>
<Form.Item name={sizeKey} noStyle> <Form.Item name={sizeKey} noStyle>
<InputNumber min={1} max={500} suffix="MB" className="rb:flex-1" /> <InputNumber min={1} max={100} suffix="MB" className="rb:flex-1" />
</Form.Item> </Form.Item>
<Form.Item name={`${option.type}_allowed_extensions`} hidden /> <Form.Item name={`${option.type}_allowed_extensions`} hidden />
</Flex> </Flex>

View File

@@ -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-03-18 20:54:00 * @Last Modified time: 2026-03-19 12:30:41
*/ */
/** /**
* Conversation Page * Conversation Page
@@ -63,6 +63,7 @@ const Conversation: FC = () => {
const [isHasMemory, setIsHasMemory] = useState(false) const [isHasMemory, setIsHasMemory] = useState(false)
const [memory, setMemory] = useState(true) const [memory, setMemory] = useState(true)
const [features, setFeatures] = useState<FeaturesConfigForm>({} as FeaturesConfigForm) const [features, setFeatures] = useState<FeaturesConfigForm>({} as FeaturesConfigForm)
const [config, setConfig] = useState<Record<string, any>>({})
useEffect(() => { useEffect(() => {
const shareToken = localStorage.getItem(`shareToken_${token}`) const shareToken = localStorage.getItem(`shareToken_${token}`)
@@ -88,6 +89,7 @@ const Conversation: FC = () => {
.then(res => { .then(res => {
const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory?: boolean; } const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory?: boolean; }
toolbarRef.current?.setVariables(response.variables || []) toolbarRef.current?.setVariables(response.variables || [])
setConfig(response)
setFeatures(response.features) setFeatures(response.features)
setIsHasMemory((response.app_type === 'workflow' && response.memory) || (response.app_type !== 'workflow')) setIsHasMemory((response.app_type === 'workflow' && response.memory) || (response.app_type !== 'workflow'))
}) })
@@ -284,6 +286,7 @@ const Conversation: FC = () => {
} }
const handleChangeMemory = (value: boolean) => { const handleChangeMemory = (value: boolean) => {
if (config.app_type === 'workflow') return;
modal.confirm({ modal.confirm({
title: value ? t('memoryConversation.memoryTipTitle') : t('memoryConversation.memoryCancelTipTitle'), title: value ? t('memoryConversation.memoryTipTitle') : t('memoryConversation.memoryCancelTipTitle'),
okText: t('common.confirm'), okText: t('common.confirm'),
@@ -388,6 +391,7 @@ const Conversation: FC = () => {
icon={MemoryFunctionIcon} icon={MemoryFunctionIcon}
checkedIcon={MemoryFunctionCheckedIcon} checkedIcon={MemoryFunctionCheckedIcon}
checked={memory} checked={memory}
disabled={config.app_type === 'workflow'}
onChange={handleChangeMemory} onChange={handleChangeMemory}
> >
{t('memoryConversation.memory')} {t('memoryConversation.memory')}

View File

@@ -1,6 +1,8 @@
import React, { useState, type FC, useEffect } from 'react' import React, { useState, type FC, useEffect } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Spin, Flex } from 'antd';
import type { CommunityD3Node, CommunityGraphData, RawCommunityGraphData, RawCommunityNode } from '@/components/D3Graph/types' import type { CommunityD3Node, CommunityGraphData, RawCommunityGraphData, RawCommunityNode } from '@/components/D3Graph/types'
import { buildCommunityGraphData } from '@/components/D3Graph/utils' import { buildCommunityGraphData } from '@/components/D3Graph/utils'
import CommunityGraph from '@/components/D3Graph/CommunityGraph' import CommunityGraph from '@/components/D3Graph/CommunityGraph'
@@ -40,14 +42,17 @@ const NodeTooltip: FC<{ node: CommunityD3Node }> = ({ node }) => {
const CommunityNetwork: FC<{ onSelectCommunity?: (node: RawCommunityNode) => void }> = ({ onSelectCommunity }) => { const CommunityNetwork: FC<{ onSelectCommunity?: (node: RawCommunityNode) => void }> = ({ onSelectCommunity }) => {
const { id } = useParams() const { id } = useParams()
const { t } = useTranslation()
const [graphData, setGraphData] = useState<CommunityGraphData | null>(null) const [graphData, setGraphData] = useState<CommunityGraphData | null>(null)
const [empty, setEmpty] = useState(false) const [empty, setEmpty] = useState(false)
const [loading, setLoading] = useState(false)
useEffect(() => { useEffect(() => {
if (!id) return if (!id) return
const controller = new AbortController() const controller = new AbortController()
setEmpty(false) setEmpty(false)
setGraphData(null) setGraphData(null)
setLoading(true)
getMemoryCommunityGraph(id, { signal: controller.signal }).then(res => { getMemoryCommunityGraph(id, { signal: controller.signal }).then(res => {
const raw = res as RawCommunityGraphData const raw = res as RawCommunityGraphData
if (!raw.nodes?.length) { setEmpty(true); return } if (!raw.nodes?.length) { setEmpty(true); return }
@@ -55,9 +60,18 @@ const CommunityNetwork: FC<{ onSelectCommunity?: (node: RawCommunityNode) => voi
if (!built) { setEmpty(true); return } if (!built) { setEmpty(true); return }
setGraphData(built) setGraphData(built)
}).catch((e) => { if (e?.code !== 'ERR_CANCELED') setEmpty(true) }) }).catch((e) => { if (e?.code !== 'ERR_CANCELED') setEmpty(true) })
.finally(() => setLoading(false))
return () => controller.abort() return () => controller.abort()
}, [id]) }, [id])
if (loading) {
return <Flex align="center" justify="center" className="rb:w-full rb:h-full">
<Spin tip={t('userMemory.communityLoadingTip')} size="large">
<div className="rb:w-64 rb:h-64" />
</Spin>
</Flex>
}
return ( return (
<CommunityGraph <CommunityGraph
data={graphData} data={graphData}