mirror of https://github.com/langgenius/dify.git
Merge branch 'main' into feat/tool-plugin-oauth
This commit is contained in:
commit
a58e99c671
|
|
@ -5,7 +5,7 @@ from typing import cast
|
|||
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
from flask_restful import Resource, fields, marshal, marshal_with, reqparse
|
||||
from flask_restful import Resource, marshal, marshal_with, reqparse
|
||||
from sqlalchemy import asc, desc, select
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
|
|
@ -239,12 +239,10 @@ class DatasetDocumentListApi(Resource):
|
|||
|
||||
return response
|
||||
|
||||
documents_and_batch_fields = {"documents": fields.List(fields.Nested(document_fields)), "batch": fields.String}
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@marshal_with(documents_and_batch_fields)
|
||||
@marshal_with(dataset_and_document_fields)
|
||||
@cloud_edition_billing_resource_check("vector_space")
|
||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||
def post(self, dataset_id):
|
||||
|
|
@ -290,6 +288,8 @@ class DatasetDocumentListApi(Resource):
|
|||
|
||||
try:
|
||||
documents, batch = DocumentService.save_document_with_dataset_id(dataset, knowledge_config, current_user)
|
||||
dataset = DatasetService.get_dataset(dataset_id)
|
||||
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ class AgentNode(ToolNode):
|
|||
_node_data_cls = AgentNodeData # type: ignore
|
||||
_node_type = NodeType.AGENT
|
||||
|
||||
@classmethod
|
||||
def version(cls) -> str:
|
||||
return "1"
|
||||
|
||||
def _run(self) -> Generator:
|
||||
"""
|
||||
Run the agent node
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ def _extract_text_from_excel(file_content: bytes) -> str:
|
|||
df = df.applymap(lambda x: " ".join(str(x).splitlines()) if isinstance(x, str) else x) # type: ignore
|
||||
|
||||
# Combine multi-line text in column names into a single line
|
||||
df.columns = pd.Index([" ".join(col.splitlines()) for col in df.columns])
|
||||
df.columns = pd.Index([" ".join(str(col).splitlines()) for col in df.columns])
|
||||
|
||||
# Manually construct the Markdown table
|
||||
markdown_table += _construct_markdown_table(df) + "\n\n"
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ class KnowledgeRetrievalNode(LLMNode):
|
|||
_node_data_cls = KnowledgeRetrievalNodeData # type: ignore
|
||||
_node_type = NodeType.KNOWLEDGE_RETRIEVAL
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
return "1"
|
||||
|
||||
def _run(self) -> NodeRunResult: # type: ignore
|
||||
node_data = cast(KnowledgeRetrievalNodeData, self.node_data)
|
||||
# extract variables
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ class QuestionClassifierNode(LLMNode):
|
|||
_node_data_cls = QuestionClassifierNodeData # type: ignore
|
||||
_node_type = NodeType.QUESTION_CLASSIFIER
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
return "1"
|
||||
|
||||
def _run(self):
|
||||
node_data = cast(QuestionClassifierNodeData, self.node_data)
|
||||
variable_pool = self.graph_runtime_state.variable_pool
|
||||
|
|
|
|||
|
|
@ -832,7 +832,12 @@ class Conversation(Base):
|
|||
|
||||
@property
|
||||
def first_message(self):
|
||||
return db.session.query(Message).filter(Message.conversation_id == self.id).first()
|
||||
return (
|
||||
db.session.query(Message)
|
||||
.filter(Message.conversation_id == self.id)
|
||||
.order_by(Message.created_at.asc())
|
||||
.first()
|
||||
)
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
from core.workflow.nodes.base.node import BaseNode
|
||||
from core.workflow.nodes.enums import NodeType
|
||||
|
||||
# Ensures that all node classes are imported.
|
||||
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
|
||||
|
||||
_ = NODE_TYPE_CLASSES_MAPPING
|
||||
|
||||
|
||||
def _get_all_subclasses(root: type[BaseNode]) -> list[type[BaseNode]]:
|
||||
subclasses = []
|
||||
queue = [root]
|
||||
while queue:
|
||||
cls = queue.pop()
|
||||
|
||||
subclasses.extend(cls.__subclasses__())
|
||||
queue.extend(cls.__subclasses__())
|
||||
|
||||
return subclasses
|
||||
|
||||
|
||||
def test_ensure_subclasses_of_base_node_has_node_type_and_version_method_defined():
|
||||
classes = _get_all_subclasses(BaseNode) # type: ignore
|
||||
type_version_set: set[tuple[NodeType, str]] = set()
|
||||
|
||||
for cls in classes:
|
||||
# Validate that 'version' is directly defined in the class (not inherited) by checking the class's __dict__
|
||||
assert "version" in cls.__dict__, f"class {cls} should have version method defined (NOT INHERITED.)"
|
||||
node_type = cls._node_type
|
||||
node_version = cls.version()
|
||||
|
||||
assert isinstance(cls._node_type, NodeType)
|
||||
assert isinstance(node_version, str)
|
||||
node_type_and_version = (node_type, node_version)
|
||||
assert node_type_and_version not in type_version_set
|
||||
type_version_set.add(node_type_and_version)
|
||||
|
|
@ -342,3 +342,26 @@ def test_extract_text_from_excel_all_sheets_fail(mock_excel_file):
|
|||
assert result == ""
|
||||
|
||||
assert mock_excel_instance.parse.call_count == 2
|
||||
|
||||
|
||||
@patch("pandas.ExcelFile")
|
||||
def test_extract_text_from_excel_numeric_type_column(mock_excel_file):
|
||||
"""Test extracting text from Excel file with numeric column names."""
|
||||
|
||||
# Test numeric type column
|
||||
data = {1: ["Test"], 1.1: ["Test"]}
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
# Mock ExcelFile
|
||||
mock_excel_instance = Mock()
|
||||
mock_excel_instance.sheet_names = ["Sheet1"]
|
||||
mock_excel_instance.parse.return_value = df
|
||||
mock_excel_file.return_value = mock_excel_instance
|
||||
|
||||
file_content = b"fake_excel_content"
|
||||
result = _extract_text_from_excel(file_content)
|
||||
|
||||
expected_manual = "| 1.0 | 1.1 |\n| --- | --- |\n| Test | Test |\n\n"
|
||||
|
||||
assert expected_manual == result
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat
|
|||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { noop } from 'lodash-es'
|
||||
import PromptLogModal from '../../base/prompt-log-modal'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
|
@ -190,11 +191,13 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|||
const { userProfile: { timezone } } = useAppContext()
|
||||
const { formatTime } = useTimestamp()
|
||||
const { onClose, appDetail } = useContext(DrawerContext)
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||
currentLogItem: state.currentLogItem,
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
showPromptLogModal: state.showPromptLogModal,
|
||||
setShowPromptLogModal: state.setShowPromptLogModal,
|
||||
currentLogModalActiveTab: state.currentLogModalActiveTab,
|
||||
})))
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -516,6 +519,16 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|||
defaultTab={currentLogModalActiveTab}
|
||||
/>
|
||||
)}
|
||||
{!isChatMode && showPromptLogModal && (
|
||||
<PromptLogModal
|
||||
width={width}
|
||||
currentLogItem={currentLogItem}
|
||||
onCancel={() => {
|
||||
setCurrentLogItem()
|
||||
setShowPromptLogModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||
appId: params.appId as string,
|
||||
messageId: messageId!,
|
||||
})
|
||||
const logItem = {
|
||||
const logItem = Array.isArray(data.message) ? {
|
||||
...data,
|
||||
log: [
|
||||
...data.message,
|
||||
|
|
@ -185,6 +185,11 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||
]
|
||||
: []),
|
||||
],
|
||||
} : {
|
||||
...data,
|
||||
log: [typeof data.message === 'string' ? {
|
||||
text: data.message,
|
||||
} : data.message],
|
||||
}
|
||||
setCurrentLogItem(logItem)
|
||||
setShowPromptLogModal(true)
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ const ChatVariableModal = ({
|
|||
{type === ChatVarType.String && (
|
||||
// Input will remove \n\r, so use Textarea just like description area
|
||||
<textarea
|
||||
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
||||
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
||||
value={value}
|
||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@ const DebugAndPreview = () => {
|
|||
|
||||
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||
const [panelWidth, setPanelWidth] = useState(400)
|
||||
const panelWidth = useStore(s => s.previewPanelWidth)
|
||||
const setPanelWidth = useStore(s => s.setPreviewPanelWidth)
|
||||
const handleResize = useCallback((width: number) => {
|
||||
localStorage.setItem('debug-and-preview-panel-width', `${width}`)
|
||||
setPanelWidth(width)
|
||||
}, [setPanelWidth])
|
||||
const maxPanelWidth = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export type LayoutSliceShape = {
|
|||
setRightPanelWidth: (width: number) => void
|
||||
nodePanelWidth: number
|
||||
setNodePanelWidth: (width: number) => void
|
||||
previewPanelWidth: number
|
||||
setPreviewPanelWidth: (width: number) => void
|
||||
otherPanelWidth: number
|
||||
setOtherPanelWidth: (width: number) => void
|
||||
bottomPanelWidth: number // min-width = 400px; default-width = auto || 480px;
|
||||
|
|
@ -31,6 +33,8 @@ export const createLayoutSlice: StateCreator<LayoutSliceShape> = set => ({
|
|||
setRightPanelWidth: width => set(() => ({ rightPanelWidth: width })),
|
||||
nodePanelWidth: localStorage.getItem('workflow-node-panel-width') ? Number.parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 400,
|
||||
setNodePanelWidth: width => set(() => ({ nodePanelWidth: width })),
|
||||
previewPanelWidth: localStorage.getItem('debug-and-preview-panel-width') ? Number.parseFloat(localStorage.getItem('debug-and-preview-panel-width')!) : 400,
|
||||
setPreviewPanelWidth: width => set(() => ({ previewPanelWidth: width })),
|
||||
otherPanelWidth: 400,
|
||||
setOtherPanelWidth: width => set(() => ({ otherPanelWidth: width })),
|
||||
bottomPanelWidth: 480,
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export type Block = {
|
|||
|
||||
export type NodeDefault<T> = {
|
||||
defaultValue: Partial<T>
|
||||
defaultRunInputData: Record<string, any>
|
||||
defaultRunInputData?: Record<string, any>
|
||||
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
|
||||
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
|
||||
checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string }
|
||||
|
|
|
|||
|
|
@ -273,3 +273,5 @@ export const MAX_TREE_DEPTH = getNumberConfig(process.env.NEXT_PUBLIC_MAX_TREE_D
|
|||
export const ENABLE_WEBSITE_JINAREADER = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER, DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER, true)
|
||||
export const ENABLE_WEBSITE_FIRECRAWL = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL, DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL, true)
|
||||
export const ENABLE_WEBSITE_WATERCRAWL = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL, DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL, false)
|
||||
|
||||
export const VALUE_SELECTOR_DELIMITER = '@@@'
|
||||
|
|
|
|||
Loading…
Reference in New Issue