Merge branch 'main' of github.com:parambharat/dify into tracing-weave

# Conflicts:
#	api/core/ops/ops_trace_manager.py
#	api/factories/file_factory.py
#	web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx
#	web/app/activate/page.tsx
#	web/app/components/base/chat/utils.ts
#	web/app/components/workflow/constants.ts
#	web/app/components/workflow/utils.ts
#	web/app/forgot-password/page.tsx
#	web/app/init/page.tsx
#	web/app/install/page.tsx
#	web/app/reset-password/layout.tsx
#	web/app/reset-password/page.tsx
#	web/app/signin/layout.tsx
#	web/models/app.ts
#	web/public/embed.min.js
pull/14262/head
Bharat Ramanathan 1 year ago
commit a93ee24fbe

@ -19,6 +19,8 @@ class PassportResource(Resource):
def get(self):
system_features = FeatureService.get_system_features()
app_code = request.headers.get("X-App-Code")
user_id = request.args.get("user_id")
if app_code is None:
raise Unauthorized("X-App-Code header is missing.")
@ -36,6 +38,24 @@ class PassportResource(Resource):
if not app_model or app_model.status != "normal" or not app_model.enable_site:
raise NotFound()
if user_id:
end_user = (
db.session.query(EndUser).filter(EndUser.app_id == app_model.id, EndUser.session_id == user_id).first()
)
if end_user:
pass
else:
end_user = EndUser(
tenant_id=app_model.tenant_id,
app_id=app_model.id,
type="browser",
is_anonymous=True,
session_id=user_id,
)
db.session.add(end_user)
db.session.commit()
else:
end_user = EndUser(
tenant_id=app_model.tenant_id,
app_id=app_model.id,
@ -43,7 +63,6 @@ class PassportResource(Resource):
is_anonymous=True,
session_id=generate_session_id(),
)
db.session.add(end_user)
db.session.commit()

@ -12,11 +12,14 @@ class CotAgentOutputParser:
def handle_react_stream_output(
cls, llm_response: Generator[LLMResultChunk, None, None], usage_dict: dict
) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]:
def parse_action(json_str):
try:
action = json.loads(json_str, strict=False)
def parse_action(action) -> Union[str, AgentScratchpadUnit.Action]:
action_name = None
action_input = None
if isinstance(action, str):
try:
action = json.loads(action, strict=False)
except json.JSONDecodeError:
return action or ""
# cohere always returns a list
if isinstance(action, list) and len(action) == 1:
@ -34,17 +37,20 @@ class CotAgentOutputParser:
action_input=action_input,
)
else:
return json_str or ""
except:
return json_str or ""
return json.dumps(action)
def extra_json_from_code_block(code_block) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]:
code_blocks = re.findall(r"```(.*?)```", code_block, re.DOTALL)
if not code_blocks:
return
for block in code_blocks:
def extra_json_from_code_block(code_block) -> list[Union[list, dict]]:
blocks = re.findall(r"```[json]*\s*([\[{].*[]}])\s*```", code_block, re.DOTALL | re.IGNORECASE)
if not blocks:
return []
try:
json_blocks = []
for block in blocks:
json_text = re.sub(r"^[a-zA-Z]+\n", "", block.strip(), flags=re.MULTILINE)
yield parse_action(json_text)
json_blocks.append(json.loads(json_text, strict=False))
return json_blocks
except:
return []
code_block_cache = ""
code_block_delimiter_count = 0
@ -78,7 +84,7 @@ class CotAgentOutputParser:
delta = response_content[index : index + steps]
yield_delta = False
if delta == "`":
if not in_json and delta == "`":
last_character = delta
code_block_cache += delta
code_block_delimiter_count += 1
@ -159,8 +165,14 @@ class CotAgentOutputParser:
if code_block_delimiter_count == 3:
if in_code_block:
last_character = delta
yield from extra_json_from_code_block(code_block_cache)
action_json_list = extra_json_from_code_block(code_block_cache)
if action_json_list:
for action_json in action_json_list:
yield parse_action(action_json)
code_block_cache = ""
else:
index += steps
continue
in_code_block = not in_code_block
code_block_delimiter_count = 0

@ -88,26 +88,6 @@ class OpikConfig(BaseTracingConfig):
return v
class WeaveConfig(BaseTracingConfig):
"""
Model class for Weave tracing config.
"""
api_key: str
entity: str | None = None
project: str
endpoint: str = "https://trace.wandb.ai"
@field_validator("endpoint")
@classmethod
def set_value(cls, v, info: ValidationInfo):
if v is None or v == "":
v = "https://trace.wandb.ai"
if not v.startswith("https://"):
raise ValueError("endpoint must start with https://")
return v
class WeaveConfig(BaseTracingConfig):
"""

@ -19,7 +19,6 @@ from core.ops.entities.config_entity import (
LangfuseConfig,
LangSmithConfig,
OpikConfig,
WeaveConfig,
TracingProviderEnum,
WeaveConfig,
)
@ -36,7 +35,6 @@ from core.ops.entities.trace_entity import (
)
from core.ops.langfuse_trace.langfuse_trace import LangFuseDataTrace
from core.ops.langsmith_trace.langsmith_trace import LangSmithDataTrace
from core.ops.weave_trace.weave_trace import WeaveDataTrace
from core.ops.opik_trace.opik_trace import OpikDataTrace
from core.ops.utils import get_message_data
from core.ops.weave_trace.weave_trace import WeaveDataTrace
@ -48,8 +46,6 @@ from tasks.ops_trace_task import process_trace_tasks
def build_opik_trace_instance(config: OpikConfig):
from core.ops.opik_trace.opik_trace import OpikDataTrace
return OpikDataTrace(config)

@ -134,8 +134,9 @@ def _build_from_local_file(
if row is None:
raise ValueError("Invalid upload file")
file_type = FileType(mapping.get("type", "custom"))
file_type = _standardize_file_type(file_type, extension="." + row.extension, mime_type=row.mime_type)
file_type = _standardize_file_type(extension="." + row.extension, mime_type=row.mime_type)
if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File(
id=mapping.get("id"),
@ -173,10 +174,9 @@ def _build_from_remote_url(
if upload_file is None:
raise ValueError("Invalid upload file")
file_type = FileType(mapping.get("type", "custom"))
file_type = _standardize_file_type(
file_type, extension="." + upload_file.extension, mime_type=upload_file.mime_type
)
file_type = _standardize_file_type(extension="." + upload_file.extension, mime_type=upload_file.mime_type)
if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File(
id=mapping.get("id"),
@ -198,8 +198,9 @@ def _build_from_remote_url(
mime_type, filename, file_size = _get_remote_file_info(url)
extension = mimetypes.guess_extension(mime_type) or ("." + filename.split(".")[-1] if "." in filename else ".bin")
file_type = FileType(mapping.get("type", "custom"))
file_type = _standardize_file_type(file_type, extension=extension, mime_type=mime_type)
file_type = _standardize_file_type(extension=extension, mime_type=mime_type)
if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File(
id=mapping.get("id"),
@ -250,8 +251,10 @@ def _build_from_tool_file(
raise ValueError(f"ToolFile {mapping.get('tool_file_id')} not found")
extension = "." + tool_file.file_key.split(".")[-1] if "." in tool_file.file_key else ".bin"
file_type = FileType(mapping.get("type", "custom"))
file_type = _standardize_file_type(file_type, extension=extension, mime_type=tool_file.mimetype)
file_type = _standardize_file_type(extension=extension, mime_type=tool_file.mimetype)
if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File(
id=mapping.get("id"),
@ -302,12 +305,10 @@ def _is_file_valid_with_config(
return True
def _standardize_file_type(file_type: FileType, /, *, extension: str = "", mime_type: str = "") -> FileType:
def _standardize_file_type(*, extension: str = "", mime_type: str = "") -> FileType:
"""
If custom type, try to guess the file type by extension and mime_type.
Infer the possible actual type of the file based on the extension and mime_type
"""
if file_type != FileType.CUSTOM:
return FileType(file_type)
guessed_type = None
if extension:
guessed_type = _get_file_type_by_extension(extension)

@ -0,0 +1,70 @@
import json
from collections.abc import Generator
from core.agent.entities import AgentScratchpadUnit
from core.agent.output_parser.cot_output_parser import CotAgentOutputParser
from core.model_runtime.entities.llm_entities import AssistantPromptMessage, LLMResultChunk, LLMResultChunkDelta
def mock_llm_response(text) -> Generator[LLMResultChunk, None, None]:
for i in range(len(text)):
yield LLMResultChunk(
model="model",
prompt_messages=[],
delta=LLMResultChunkDelta(index=0, message=AssistantPromptMessage(content=text[i], tool_calls=[])),
)
def test_cot_output_parser():
test_cases = [
{
"input": 'Through: abc\nAction: ```{"action": "Final Answer", "action_input": "```echarts\n {}\n```"}```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# code block with json
{
"input": 'Through: abc\nAction: ```json\n{"action": "Final Answer", "action_input": "```echarts\n {'
'}\n```"}```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# code block with JSON
{
"input": 'Through: abc\nAction: ```JSON\n{"action": "Final Answer", "action_input": "```echarts\n {'
'}\n```"}```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# list
{
"input": 'Through: abc\nAction: ```[{"action": "Final Answer", "action_input": "```echarts\n {}\n```"}]```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# no code block
{
"input": 'Through: abc\nAction: {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# no code block and json
{"input": "Through: abc\nAction: efg", "action": {}, "output": "Through: abc\n efg"},
]
parser = CotAgentOutputParser()
usage_dict = {}
for test_case in test_cases:
# mock llm_response as a generator by text
llm_response: Generator[LLMResultChunk, None, None] = mock_llm_response(test_case["input"])
results = parser.handle_react_stream_output(llm_response, usage_dict)
output = ""
for result in results:
if isinstance(result, str):
output += result
elif isinstance(result, AgentScratchpadUnit.Action):
if test_case["action"]:
assert result.to_dict() == test_case["action"]
output += json.dumps(result.to_dict())
if test_case["output"]:
assert output == test_case["output"]

@ -1,25 +1,12 @@
import React from 'react'
import Header from '../signin/_header'
import style from '../signin/page.module.css'
import ActivateForm from './activateForm'
import cn from '@/utils/classnames'
const Activate = () => {
return (
<div className={cn(
'bg-background-body',
style.background,
'flex min-h-screen w-full',
'sm:p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
cn(
'flex w-full shrink-0 flex-col rounded-2xl bg-background-section-burn shadow',
'space-between',
)
}>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
<Header />
<ActivateForm />
<div className='px-8 py-6 text-sm font-normal text-text-tertiary'>

@ -44,7 +44,10 @@ const OPTION_MAP = {
: ''}${IS_CE_EDITION
? `,
baseUrl: '${url}'`
: ''}
: ''},
systemVariables: {
// user_id: 'YOU CAN DEFINE USER ID HERE',
},
}
</script>
<script

@ -3,12 +3,17 @@ import type { IChatItem } from './chat/type'
import type { ChatItem, ChatItemInTree } from './types'
async function decodeBase64AndDecompress(base64String: string) {
try {
const binaryString = atob(base64String)
const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0))
const decompressedStream = new Response(compressedUint8Array).body?.pipeThrough(new DecompressionStream('gzip'))
const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer()
return new TextDecoder().decode(decompressedArrayBuffer)
}
catch {
return undefined
}
}
async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
const urlParams = new URLSearchParams(window.location.search)
@ -16,12 +21,26 @@ async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
const entriesArray = Array.from(urlParams.entries())
await Promise.all(
entriesArray.map(async ([key, value]) => {
if (!key.startsWith('sys.'))
inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
}),
)
return inputs
}
async function getProcessedSystemVariablesFromUrlParams(): Promise<Record<string, any>> {
const urlParams = new URLSearchParams(window.location.search)
const systemVariables: Record<string, any> = {}
const entriesArray = Array.from(urlParams.entries())
await Promise.all(
entriesArray.map(async ([key, value]) => {
if (key.startsWith('sys.'))
systemVariables[key.slice(4)] = await decodeBase64AndDecompress(decodeURIComponent(value))
}),
)
return systemVariables
}
function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean {
return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement
}
@ -166,6 +185,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch
export {
getProcessedInputsFromUrlParams,
getProcessedSystemVariablesFromUrlParams,
isValidGeneratedAnswer,
getLastAnswer,
buildChatItemTree,

@ -1,5 +1,6 @@
import { CONVERSATION_ID_INFO } from '../base/chat/constants'
import { fetchAccessToken } from '@/service/share'
import { getProcessedSystemVariablesFromUrlParams } from '../base/chat/utils'
export const checkOrSetAccessToken = async () => {
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
@ -12,7 +13,8 @@ export const checkOrSetAccessToken = async () => {
}
if (!accessTokenJson[sharedToken]) {
const res = await fetchAccessToken(sharedToken)
const sysUserId = (await getProcessedSystemVariablesFromUrlParams()).user_id
const res = await fetchAccessToken(sharedToken, sysUserId)
accessTokenJson[sharedToken] = res.access_token
localStorage.setItem('token', JSON.stringify(accessTokenJson))
}

@ -416,6 +416,10 @@ export const LOOP_PADDING = {
left: 16,
}
export const NODE_LAYOUT_HORIZONTAL_PADDING = 60
export const NODE_LAYOUT_VERTICAL_PADDING = 60
export const NODE_LAYOUT_MIN_DISTANCE = 100
let maxParallelLimit = 10
if (process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT && process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT !== '')

@ -8,12 +8,15 @@ import produce from 'immer'
import { useStore, useWorkflowStore } from '../store'
import {
CUSTOM_NODE, DSL_EXPORT_CHECK,
NODE_LAYOUT_HORIZONTAL_PADDING,
NODE_LAYOUT_VERTICAL_PADDING,
WORKFLOW_DATA_UPDATE,
} from '../constants'
import type { Node, WorkflowDataUpdater } from '../types'
import { ControlMode } from '../types'
import { BlockEnum, ControlMode } from '../types'
import {
getLayoutByDagre,
getLayoutForChildNodes,
initialEdges,
initialNodes,
} from '../utils'
@ -98,10 +101,81 @@ export const useWorkflowOrganize = () => {
} = store.getState()
const { setViewport } = reactflow
const nodes = getNodes()
const layout = getLayoutByDagre(nodes, edges)
const rankMap = {} as Record<string, Node>
nodes.forEach((node) => {
const loopAndIterationNodes = nodes.filter(
node => (node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration)
&& !node.parentId
&& node.type === CUSTOM_NODE,
)
const childLayoutsMap: Record<string, any> = {}
loopAndIterationNodes.forEach((node) => {
childLayoutsMap[node.id] = getLayoutForChildNodes(node.id, nodes, edges)
})
const containerSizeChanges: Record<string, { width: number, height: number }> = {}
loopAndIterationNodes.forEach((parentNode) => {
const childLayout = childLayoutsMap[parentNode.id]
if (!childLayout) return
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
let hasChildren = false
const childNodes = nodes.filter(node => node.parentId === parentNode.id)
childNodes.forEach((node) => {
if (childLayout.node(node.id)) {
hasChildren = true
const childNodeWithPosition = childLayout.node(node.id)
const nodeX = childNodeWithPosition.x - node.width! / 2
const nodeY = childNodeWithPosition.y - node.height! / 2
minX = Math.min(minX, nodeX)
minY = Math.min(minY, nodeY)
maxX = Math.max(maxX, nodeX + node.width!)
maxY = Math.max(maxY, nodeY + node.height!)
}
})
if (hasChildren) {
const requiredWidth = maxX - minX + NODE_LAYOUT_HORIZONTAL_PADDING * 2
const requiredHeight = maxY - minY + NODE_LAYOUT_VERTICAL_PADDING * 2
containerSizeChanges[parentNode.id] = {
width: Math.max(parentNode.width || 0, requiredWidth),
height: Math.max(parentNode.height || 0, requiredHeight),
}
}
})
const nodesWithUpdatedSizes = produce(nodes, (draft) => {
draft.forEach((node) => {
if ((node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration)
&& containerSizeChanges[node.id]) {
node.width = containerSizeChanges[node.id].width
node.height = containerSizeChanges[node.id].height
if (node.data.type === BlockEnum.Loop) {
node.data.width = containerSizeChanges[node.id].width
node.data.height = containerSizeChanges[node.id].height
}
else if (node.data.type === BlockEnum.Iteration) {
node.data.width = containerSizeChanges[node.id].width
node.data.height = containerSizeChanges[node.id].height
}
}
})
})
const layout = getLayoutByDagre(nodesWithUpdatedSizes, edges)
const rankMap = {} as Record<string, Node>
nodesWithUpdatedSizes.forEach((node) => {
if (!node.parentId && node.type === CUSTOM_NODE) {
const rank = layout.node(node.id).rank!
@ -115,7 +189,7 @@ export const useWorkflowOrganize = () => {
}
})
const newNodes = produce(nodes, (draft) => {
const newNodes = produce(nodesWithUpdatedSizes, (draft) => {
draft.forEach((node) => {
if (!node.parentId && node.type === CUSTOM_NODE) {
const nodeWithPosition = layout.node(node.id)
@ -126,7 +200,40 @@ export const useWorkflowOrganize = () => {
}
}
})
loopAndIterationNodes.forEach((parentNode) => {
const childLayout = childLayoutsMap[parentNode.id]
if (!childLayout) return
const childNodes = draft.filter(node => node.parentId === parentNode.id)
let minX = Infinity
let minY = Infinity
childNodes.forEach((node) => {
if (childLayout.node(node.id)) {
const childNodeWithPosition = childLayout.node(node.id)
const nodeX = childNodeWithPosition.x - node.width! / 2
const nodeY = childNodeWithPosition.y - node.height! / 2
minX = Math.min(minX, nodeX)
minY = Math.min(minY, nodeY)
}
})
childNodes.forEach((node) => {
if (childLayout.node(node.id)) {
const childNodeWithPosition = childLayout.node(node.id)
node.position = {
x: NODE_LAYOUT_HORIZONTAL_PADDING + (childNodeWithPosition.x - node.width! / 2 - minX),
y: NODE_LAYOUT_VERTICAL_PADDING + (childNodeWithPosition.y - node.height! / 2 - minY),
}
}
})
})
})
setNodes(newNodes)
const zoom = 0.7
setViewport({
@ -139,6 +246,7 @@ export const useWorkflowOrganize = () => {
handleSyncWorkflowDraft()
})
}, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
return {
handleLayout,
}

@ -32,6 +32,9 @@ import {
ITERATION_NODE_Z_INDEX,
LOOP_CHILDREN_Z_INDEX,
LOOP_NODE_Z_INDEX,
NODE_LAYOUT_HORIZONTAL_PADDING,
NODE_LAYOUT_MIN_DISTANCE,
NODE_LAYOUT_VERTICAL_PADDING,
NODE_WIDTH_X_OFFSET,
START_INITIAL_POSITION,
} from './constants'
@ -461,12 +464,141 @@ export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => {
height: node.height!,
})
})
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target)
})
dagre.layout(dagreGraph)
return dagreGraph
}
export const getLayoutForChildNodes = (parentNodeId: string, originNodes: Node[], originEdges: Edge[]) => {
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
const nodes = cloneDeep(originNodes).filter(node => node.parentId === parentNodeId)
const edges = cloneDeep(originEdges).filter(edge =>
(edge.data?.isInIteration && edge.data?.iteration_id === parentNodeId)
|| (edge.data?.isInLoop && edge.data?.loop_id === parentNodeId),
)
const startNode = nodes.find(node =>
node.type === CUSTOM_ITERATION_START_NODE
|| node.type === CUSTOM_LOOP_START_NODE
|| node.data?.type === BlockEnum.LoopStart
|| node.data?.type === BlockEnum.IterationStart,
)
if (!startNode) {
dagreGraph.setGraph({
rankdir: 'LR',
align: 'UL',
nodesep: 40,
ranksep: 60,
marginx: NODE_LAYOUT_HORIZONTAL_PADDING,
marginy: NODE_LAYOUT_VERTICAL_PADDING,
})
nodes.forEach((node) => {
dagreGraph.setNode(node.id, {
width: node.width || 244,
height: node.height || 100,
})
})
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target)
})
dagre.layout(dagreGraph)
return dagreGraph
}
const startNodeOutEdges = edges.filter(edge => edge.source === startNode.id)
const firstConnectedNodes = startNodeOutEdges.map(edge =>
nodes.find(node => node.id === edge.target),
).filter(Boolean) as Node[]
const nonStartNodes = nodes.filter(node => node.id !== startNode.id)
const nonStartEdges = edges.filter(edge => edge.source !== startNode.id && edge.target !== startNode.id)
dagreGraph.setGraph({
rankdir: 'LR',
align: 'UL',
nodesep: 40,
ranksep: 60,
marginx: NODE_LAYOUT_HORIZONTAL_PADDING / 2,
marginy: NODE_LAYOUT_VERTICAL_PADDING / 2,
})
nonStartNodes.forEach((node) => {
dagreGraph.setNode(node.id, {
width: node.width || 244,
height: node.height || 100,
})
})
nonStartEdges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target)
})
dagre.layout(dagreGraph)
const startNodeSize = {
width: startNode.width || 44,
height: startNode.height || 48,
}
const startNodeX = NODE_LAYOUT_HORIZONTAL_PADDING / 1.5
let startNodeY = 100
let minFirstLayerX = Infinity
let avgFirstLayerY = 0
let firstLayerCount = 0
if (firstConnectedNodes.length > 0) {
firstConnectedNodes.forEach((node) => {
if (dagreGraph.node(node.id)) {
const nodePos = dagreGraph.node(node.id)
avgFirstLayerY += nodePos.y
firstLayerCount++
minFirstLayerX = Math.min(minFirstLayerX, nodePos.x - nodePos.width / 2)
}
})
if (firstLayerCount > 0) {
avgFirstLayerY /= firstLayerCount
startNodeY = avgFirstLayerY
}
const minRequiredX = startNodeX + startNodeSize.width + NODE_LAYOUT_MIN_DISTANCE
if (minFirstLayerX < minRequiredX) {
const shiftX = minRequiredX - minFirstLayerX
nonStartNodes.forEach((node) => {
if (dagreGraph.node(node.id)) {
const nodePos = dagreGraph.node(node.id)
dagreGraph.setNode(node.id, {
x: nodePos.x + shiftX,
y: nodePos.y,
width: nodePos.width,
height: nodePos.height,
})
}
})
}
}
dagreGraph.setNode(startNode.id, {
x: startNodeX + startNodeSize.width / 2,
y: startNodeY,
width: startNodeSize.width,
height: startNodeSize.height,
})
startNodeOutEdges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target)
})
return dagreGraph
}

@ -1,9 +1,8 @@
'use client'
import React from 'react'
import classNames from 'classnames'
import cn from 'classnames'
import { useSearchParams } from 'next/navigation'
import Header from '../signin/_header'
import style from '../signin/page.module.css'
import ForgotPasswordForm from './ForgotPasswordForm'
import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm'
@ -12,20 +11,8 @@ const ForgotPassword = () => {
const token = searchParams.get('token')
return (
<div className={classNames(
'bg-background-body',
style.background,
'flex w-full min-h-screen',
'p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
classNames(
'flex w-full flex-col bg-background-section-burn shadow rounded-2xl shrink-0',
'md:w-[608px] space-between',
)
}>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
<Header />
{token ? <ChangePasswordForm /> : <ForgotPasswordForm />}
<div className='px-8 py-6 text-sm font-normal text-text-tertiary'>

@ -1,24 +1,11 @@
import React from 'react'
import style from '../signin/page.module.css'
import InitPasswordPopup from './InitPasswordPopup'
import cn from '@/utils/classnames'
const Install = () => {
return (
<div className={cn(
'bg-background-body',
style.background,
'flex min-h-screen w-full',
'p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
cn(
'flex w-full shrink-0 flex-col rounded-2xl bg-background-section-burn shadow',
'space-between',
)
}>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
<div className="m-auto block w-96">
<InitPasswordPopup />
</div>

@ -1,25 +1,12 @@
import React from 'react'
import Header from '../signin/_header'
import style from '../signin/page.module.css'
import InstallForm from './installForm'
import classNames from '@/utils/classnames'
import cn from '@/utils/classnames'
const Install = () => {
return (
<div className={classNames(
'bg-background-body',
style.background,
'flex w-full min-h-screen',
'p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
classNames(
'flex w-full flex-col bg-background-section-burn shadow rounded-2xl shrink-0',
'md:w-[608px] space-between',
)
}>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
<Header />
<InstallForm />
<div className='px-8 py-6 text-sm font-normal text-text-tertiary'>

@ -1,24 +1,11 @@
import Header from '../signin/_header'
import style from '../signin/page.module.css'
import cn from '@/utils/classnames'
export default async function SignInLayout({ children }: any) {
return <>
<div className={cn(
'bg-background-body',
style.background,
'flex min-h-screen w-full',
'sm:p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
cn(
'flex w-full shrink-0 flex-col rounded-2xl bg-background-section-burn shadow',
'space-between',
)
}>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
<Header />
<div className={
cn(

@ -91,8 +91,8 @@ export default function CheckCode() {
<div className='py-2'>
<div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div>
</div>
<Link href={`/signin?${searchParams.toString()}`} className='flex h-9 items-center justify-center text-text-tertiary'>
<div className='bg-background-default-dimm inline-block rounded-full p-1'>
<Link href={`/signin?${searchParams.toString()}`} className='flex h-9 items-center justify-center text-text-tertiary hover:text-text-primary'>
<div className='inline-block rounded-full bg-background-default-dimmed p-1'>
<RiArrowLeftLine size={12} />
</div>
<span className='system-xs-regular ml-2'>{t('login.backToLogin')}</span>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

@ -1,32 +1,13 @@
import Header from './_header'
import style from './page.module.css'
import cn from '@/utils/classnames'
export default async function SignInLayout({ children }: any) {
return <>
<div className={cn(
'bg-background-body',
style.background,
'flex min-h-screen w-full',
'sm:p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
cn(
'flex w-full shrink-0 flex-col rounded-2xl bg-background-section-burn shadow',
'space-between',
)
}>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
<Header />
<div className={
cn(
'flex w-full grow flex-col items-center justify-center',
'px-6',
'md:px-[108px]',
)
}>
<div className={cn('flex w-full grow flex-col items-center justify-center px-6 md:px-[108px]')}>
<div className='flex flex-col md:w-[400px]'>
{children}
</div>

@ -5,8 +5,3 @@
.googleIcon {
background: center/contain url('./assets/google.svg');
}
.background {
background-image: url('./assets/background.png');
background-size: cover;
}

@ -50,7 +50,21 @@
return compressedInputs;
}
const params = new URLSearchParams(await getCompressedInputsFromConfig());
async function getCompressedSystemVariablesFromConfig() {
const systemVariables = config?.systemVariables || {};
const compressedSystemVariables = {};
await Promise.all(
Object.entries(systemVariables).map(async ([key, value]) => {
compressedSystemVariables[`sys.${key}`] = await compressAndEncodeBase64(value);
})
);
return compressedSystemVariables;
}
const params = new URLSearchParams({
...await getCompressedInputsFromConfig(),
...await getCompressedSystemVariablesFromConfig()
});
const baseUrl =
config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`;

@ -1,8 +1,8 @@
(()=>{let t="difyChatbotConfig",a="dify-chatbot-bubble-button",c="dify-chatbot-bubble-window",p=window[t],h={open:`<svg id="openIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
(()=>{let t="difyChatbotConfig",a="dify-chatbot-bubble-button",c="dify-chatbot-bubble-window",p=window[t],u={open:`<svg id="openIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.7586 2L16.2412 2C17.0462 1.99999 17.7105 1.99998 18.2517 2.04419C18.8138 2.09012 19.3305 2.18868 19.8159 2.43598C20.5685 2.81947 21.1804 3.43139 21.5639 4.18404C21.8112 4.66937 21.9098 5.18608 21.9557 5.74818C21.9999 6.28937 21.9999 6.95373 21.9999 7.7587L22 14.1376C22.0004 14.933 22.0007 15.5236 21.8636 16.0353C21.4937 17.4156 20.4155 18.4938 19.0352 18.8637C18.7277 18.9461 18.3917 18.9789 17.9999 18.9918L17.9999 20.371C18 20.6062 18 20.846 17.9822 21.0425C17.9651 21.2305 17.9199 21.5852 17.6722 21.8955C17.3872 22.2525 16.9551 22.4602 16.4983 22.4597C16.1013 22.4593 15.7961 22.273 15.6386 22.1689C15.474 22.06 15.2868 21.9102 15.1031 21.7632L12.69 19.8327C12.1714 19.4178 12.0174 19.3007 11.8575 19.219C11.697 19.137 11.5262 19.0771 11.3496 19.0408C11.1737 19.0047 10.9803 19 10.3162 19H7.75858C6.95362 19 6.28927 19 5.74808 18.9558C5.18598 18.9099 4.66928 18.8113 4.18394 18.564C3.43129 18.1805 2.81937 17.5686 2.43588 16.816C2.18859 16.3306 2.09002 15.8139 2.0441 15.2518C1.99988 14.7106 1.99989 14.0463 1.9999 13.2413V7.75868C1.99989 6.95372 1.99988 6.28936 2.0441 5.74818C2.09002 5.18608 2.18859 4.66937 2.43588 4.18404C2.81937 3.43139 3.43129 2.81947 4.18394 2.43598C4.66928 2.18868 5.18598 2.09012 5.74808 2.04419C6.28927 1.99998 6.95364 1.99999 7.7586 2ZM10.5073 7.5C10.5073 6.67157 9.83575 6 9.00732 6C8.1789 6 7.50732 6.67157 7.50732 7.5C7.50732 8.32843 8.1789 9 9.00732 9C9.83575 9 10.5073 8.32843 10.5073 7.5ZM16.6073 11.7001C16.1669 11.3697 15.5426 11.4577 15.2105 11.8959C15.1488 11.9746 15.081 12.0486 15.0119 12.1207C14.8646 12.2744 14.6432 12.4829 14.3566 12.6913C13.7796 13.111 12.9818 13.5001 12.0073 13.5001C11.0328 13.5001 10.235 13.111 9.65799 12.6913C9.37138 12.4829 9.15004 12.2744 9.00274 12.1207C8.93366 12.0486 8.86581 11.9745 8.80418 11.8959C8.472 11.4577 7.84775 11.3697 7.40732 11.7001C6.96549 12.0314 6.87595 12.6582 7.20732 13.1001C7.20479 13.0968 7.21072 13.1043 7.22094 13.1171C7.24532 13.1478 7.29407 13.2091 7.31068 13.2289C7.36932 13.2987 7.45232 13.3934 7.55877 13.5045C7.77084 13.7258 8.08075 14.0172 8.48165 14.3088C9.27958 14.8891 10.4818 15.5001 12.0073 15.5001C13.5328 15.5001 14.735 14.8891 15.533 14.3088C15.9339 14.0172 16.2438 13.7258 16.4559 13.5045C16.5623 13.3934 16.6453 13.2987 16.704 13.2289C16.7333 13.1939 16.7567 13.165 16.7739 13.1432C17.1193 12.6969 17.0729 12.0493 16.6073 11.7001ZM15.0073 6C15.8358 6 16.5073 6.67157 16.5073 7.5C16.5073 8.32843 15.8358 9 15.0073 9C14.1789 9 13.5073 8.32843 13.5073 7.5C13.5073 6.67157 14.1789 6 15.0073 6Z" fill="white"/>
</svg>`,close:`<svg id="closeIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 18L6 6M6 18L18 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`};async function e(){if(p&&p.token){var e=new URLSearchParams(await(async()=>{var e=p?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=(e=t,e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e),await btoa(String.fromCharCode(...e)))})),n})());let t=`${p.baseUrl||`https://${p.isDev?"dev.":""}udify.app`}/chatbot/${p.token}?`+e;e=o();function o(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=c,e.src=t,e.style.cssText=`
</svg>`};async function e(){if(p&&p.token){var e=new URLSearchParams({...await(async()=>{var e=p?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await o(t)})),n})(),...await(async()=>{var e=p?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await o(t)})),n})()});let t=`${p.baseUrl||`https://${p.isDev?"dev.":""}udify.app`}/chatbot/${p.token}?`+e;e=i();async function o(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function i(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=c,e.src=t,e.style.cssText=`
position: absolute;
display: flex;
flex-direction: column;
@ -18,7 +18,7 @@
z-index: 2147483640;
overflow: hidden;
user-select: none;
`,e}function i(){var e,t;window.innerWidth<=640||(e=document.getElementById(c),t=document.getElementById(a),e&&t&&((t=t.getBoundingClientRect()).top-5>e.clientHeight?(e.style.bottom="0px",e.style.top="unset"):(e.style.bottom="unset",e.style.top="0px"),t.right>e.clientWidth?(e.style.right="0",e.style.left="unset"):(e.style.right="unset",e.style.left=0)))}function n(){let n=document.createElement("div");Object.entries(p.containerProps||{}).forEach(([e,t])=>{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=a;var e=document.createElement("style");document.head.appendChild(e),e.sheet.insertRule(`
`,e}function r(){var e,t;window.innerWidth<=640||(e=document.getElementById(c),t=document.getElementById(a),e&&t&&((t=t.getBoundingClientRect()).top-5>e.clientHeight?(e.style.bottom="0px",e.style.top="unset"):(e.style.bottom="unset",e.style.top="0px"),t.right>e.clientWidth?(e.style.right="0",e.style.left="unset"):(e.style.right="unset",e.style.left=0)))}function n(){let n=document.createElement("div");Object.entries(p.containerProps||{}).forEach(([e,t])=>{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=a;var e=document.createElement("style");document.head.appendChild(e),e.sheet.insertRule(`
#${n.id} {
position: fixed;
bottom: var(--${n.id}-bottom, 1rem);
@ -33,4 +33,4 @@
cursor: pointer;
z-index: 2147483647;
}
`);let t=document.createElement("div");if(t.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",t.innerHTML=h.open,n.appendChild(t),document.body.appendChild(n),n.addEventListener("click",function(){var e=document.getElementById(c);e?(e.style.display="none"===e.style.display?"block":"none",t.innerHTML="none"===e.style.display?h.open:h.close,"none"===e.style.display?document.removeEventListener("keydown",d):document.addEventListener("keydown",d),i()):(n.prepend(o()),i(),this.title="Exit (ESC)",t.innerHTML=h.close,document.addEventListener("keydown",d))}),p.draggable){var s=n;var l=p.dragAxis||"both";let i=!1,d,r;s.addEventListener("mousedown",function(e){i=!0,d=e.clientX-s.offsetLeft,r=e.clientY-s.offsetTop}),document.addEventListener("mousemove",function(e){var t,n,o;i&&(s.style.transition="none",s.style.cursor="grabbing",(t=document.getElementById(c))&&(t.style.display="none",s.querySelector("div").innerHTML=h.open),t=e.clientX-d,e=window.innerHeight-e.clientY-r,o=s.getBoundingClientRect(),n=window.innerWidth-o.width,o=window.innerHeight-o.height,"x"!==l&&"both"!==l||s.style.setProperty(`--${a}-left`,Math.max(0,Math.min(t,n))+"px"),"y"!==l&&"both"!==l||s.style.setProperty(`--${a}-bottom`,Math.max(0,Math.min(e,o))+"px"))}),document.addEventListener("mouseup",function(){i=!1,s.style.transition="",s.style.cursor="pointer"})}}e.style.display="none",document.body.appendChild(e),2048<t.length&&console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"),document.getElementById(a)||n()}else console.error(t+" is empty or token is not provided")}function d(e){var t;"Escape"===e.key&&(e=document.getElementById(c),t=document.getElementById(a),e)&&"none"!==e.style.display&&(e.style.display="none",t.querySelector("div").innerHTML=h.open)}document.addEventListener("keydown",d),p?.dynamicScript?e():document.body.onload=e})();
`);let t=document.createElement("div");if(t.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",t.innerHTML=u.open,n.appendChild(t),document.body.appendChild(n),n.addEventListener("click",function(){var e=document.getElementById(c);e?(e.style.display="none"===e.style.display?"block":"none",t.innerHTML="none"===e.style.display?u.open:u.close,"none"===e.style.display?document.removeEventListener("keydown",d):document.addEventListener("keydown",d),r()):(n.prepend(i()),r(),this.title="Exit (ESC)",t.innerHTML=u.close,document.addEventListener("keydown",d))}),p.draggable){var s=n;var l=p.dragAxis||"both";let i=!1,r,d;s.addEventListener("mousedown",function(e){i=!0,r=e.clientX-s.offsetLeft,d=e.clientY-s.offsetTop}),document.addEventListener("mousemove",function(e){var t,n,o;i&&(s.style.transition="none",s.style.cursor="grabbing",(t=document.getElementById(c))&&(t.style.display="none",s.querySelector("div").innerHTML=u.open),t=e.clientX-r,e=window.innerHeight-e.clientY-d,o=s.getBoundingClientRect(),n=window.innerWidth-o.width,o=window.innerHeight-o.height,"x"!==l&&"both"!==l||s.style.setProperty(`--${a}-left`,Math.max(0,Math.min(t,n))+"px"),"y"!==l&&"both"!==l||s.style.setProperty(`--${a}-bottom`,Math.max(0,Math.min(e,o))+"px"))}),document.addEventListener("mouseup",function(){i=!1,s.style.transition="",s.style.cursor="pointer"})}}e.style.display="none",document.body.appendChild(e),2048<t.length&&console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"),document.getElementById(a)||n()}else console.error(t+" is empty or token is not provided")}function d(e){var t;"Escape"===e.key&&(e=document.getElementById(c),t=document.getElementById(a),e)&&"none"!==e.style.display&&(e.style.display="none",t.querySelector("div").innerHTML=u.open)}document.addEventListener("keydown",d),p?.dynamicScript?e():document.body.onload=e})();

@ -262,8 +262,9 @@ export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { c
return (getAction('post', !isPublicAPI))(url, { body, header }, { needAllResponseContent: true })
}
export const fetchAccessToken = async (appCode: string) => {
export const fetchAccessToken = async (appCode: string, userId?: string) => {
const headers = new Headers()
headers.append('X-App-Code', appCode)
return get('/passport', { headers }) as Promise<{ access_token: string }>
const url = userId ? `/passport?user_id=${encodeURIComponent(userId)}` : '/passport'
return get(url, { headers }) as Promise<{ access_token: string }>
}

Loading…
Cancel
Save