mirror of
https://github.com/langgenius/dify.git
synced 2026-02-06 08:08:57 +00:00
Compare commits
10 Commits
deploy/age
...
2-6-type-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4321e24a1 | ||
|
|
23b6f33bd3 | ||
|
|
a297b06aac | ||
|
|
f95322ef9c | ||
|
|
cef8058a8f | ||
|
|
5a1a3bb859 | ||
|
|
3371fa7861 | ||
|
|
97ecde5389 | ||
|
|
5b22d5026b | ||
|
|
7d34faaf74 |
@@ -102,8 +102,6 @@ forbidden_modules =
|
||||
core.trigger
|
||||
core.variables
|
||||
ignore_imports =
|
||||
core.workflow.nodes.agent.agent_node -> core.db.session_factory
|
||||
core.workflow.nodes.agent.agent_node -> models.tools
|
||||
core.workflow.nodes.loop.loop_node -> core.app.workflow.node_factory
|
||||
core.workflow.graph_engine.command_channels.redis_channel -> extensions.ext_redis
|
||||
core.workflow.workflow_entry -> core.app.workflow.layers.observability
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Generator, Mapping, Sequence
|
||||
from typing import TYPE_CHECKING, Any, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from packaging.version import Version
|
||||
from pydantic import ValidationError
|
||||
@@ -11,7 +11,6 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from core.agent.entities import AgentToolEntity
|
||||
from core.agent.plugin_entities import AgentStrategyParameter
|
||||
from core.db.session_factory import session_factory
|
||||
from core.file import File, FileTransferMethod
|
||||
from core.memory.token_buffer_memory import TokenBufferMemory
|
||||
from core.model_manager import ModelInstance, ModelManager
|
||||
@@ -50,12 +49,6 @@ from factories import file_factory
|
||||
from factories.agent_factory import get_plugin_agent_strategy
|
||||
from models import ToolFile
|
||||
from models.model import Conversation
|
||||
from models.tools import (
|
||||
ApiToolProvider,
|
||||
BuiltinToolProvider,
|
||||
MCPToolProvider,
|
||||
WorkflowToolProvider,
|
||||
)
|
||||
from services.tools.builtin_tools_manage_service import BuiltinToolManageService
|
||||
|
||||
from .exc import (
|
||||
@@ -266,7 +259,7 @@ class AgentNode(Node[AgentNodeData]):
|
||||
value = cast(list[dict[str, Any]], value)
|
||||
tool_value = []
|
||||
for tool in value:
|
||||
provider_type = self._infer_tool_provider_type(tool, self.tenant_id)
|
||||
provider_type = ToolProviderType(tool.get("type", ToolProviderType.BUILT_IN))
|
||||
setting_params = tool.get("settings", {})
|
||||
parameters = tool.get("parameters", {})
|
||||
manual_input_params = [key for key, value in parameters.items() if value is not None]
|
||||
@@ -755,34 +748,3 @@ class AgentNode(Node[AgentNodeData]):
|
||||
llm_usage=llm_usage,
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _infer_tool_provider_type(tool_config: dict[str, Any], tenant_id: str) -> ToolProviderType:
|
||||
provider_type_str = tool_config.get("type")
|
||||
if provider_type_str:
|
||||
return ToolProviderType(provider_type_str)
|
||||
|
||||
provider_id = tool_config.get("provider_name")
|
||||
if not provider_id:
|
||||
return ToolProviderType.BUILT_IN
|
||||
|
||||
with session_factory.create_session() as session:
|
||||
provider_map: dict[
|
||||
type[Union[WorkflowToolProvider, MCPToolProvider, ApiToolProvider, BuiltinToolProvider]],
|
||||
ToolProviderType,
|
||||
] = {
|
||||
WorkflowToolProvider: ToolProviderType.WORKFLOW,
|
||||
MCPToolProvider: ToolProviderType.MCP,
|
||||
ApiToolProvider: ToolProviderType.API,
|
||||
BuiltinToolProvider: ToolProviderType.BUILT_IN,
|
||||
}
|
||||
|
||||
for provider_model, provider_type in provider_map.items():
|
||||
stmt = select(provider_model).where(
|
||||
provider_model.id == provider_id,
|
||||
provider_model.tenant_id == tenant_id,
|
||||
)
|
||||
if session.scalar(stmt):
|
||||
return provider_type
|
||||
|
||||
raise AgentNodeError(f"Tool provider with ID '{provider_id}' not found.")
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from core.workflow.nodes.agent.agent_node import AgentNode
|
||||
|
||||
|
||||
class TestInferToolProviderType:
|
||||
"""Test cases for AgentNode._infer_tool_provider_type method."""
|
||||
|
||||
def test_infer_type_from_config_workflow(self):
|
||||
"""Test inferring workflow provider type from config."""
|
||||
tool_config = {
|
||||
"type": "workflow",
|
||||
"provider_name": "workflow-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.WORKFLOW
|
||||
|
||||
def test_infer_type_from_config_builtin(self):
|
||||
"""Test inferring builtin provider type from config."""
|
||||
tool_config = {
|
||||
"type": "builtin",
|
||||
"provider_name": "builtin-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.BUILT_IN
|
||||
|
||||
def test_infer_type_from_config_api(self):
|
||||
"""Test inferring API provider type from config."""
|
||||
tool_config = {
|
||||
"type": "api",
|
||||
"provider_name": "api-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.API
|
||||
|
||||
def test_infer_type_from_config_mcp(self):
|
||||
"""Test inferring MCP provider type from config."""
|
||||
tool_config = {
|
||||
"type": "mcp",
|
||||
"provider_name": "mcp-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.MCP
|
||||
|
||||
def test_infer_type_invalid_config_value_raises_error(self):
|
||||
"""Test that invalid type value in config raises ValueError."""
|
||||
tool_config = {
|
||||
"type": "invalid-type",
|
||||
"provider_name": "workflow-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
def test_infer_workflow_type_from_database(self):
|
||||
"""Test inferring workflow provider type from database."""
|
||||
tool_config = {
|
||||
"provider_name": "workflow-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
|
||||
mock_session = MagicMock()
|
||||
mock_create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# First query (WorkflowToolProvider) returns a result
|
||||
mock_session.scalar.return_value = True
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.WORKFLOW
|
||||
# Should only query once (after finding WorkflowToolProvider)
|
||||
assert mock_session.scalar.call_count == 1
|
||||
|
||||
def test_infer_mcp_type_from_database(self):
|
||||
"""Test inferring MCP provider type from database."""
|
||||
tool_config = {
|
||||
"provider_name": "mcp-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
|
||||
mock_session = MagicMock()
|
||||
mock_create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# First query (WorkflowToolProvider) returns None
|
||||
# Second query (MCPToolProvider) returns a result
|
||||
mock_session.scalar.side_effect = [None, True]
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.MCP
|
||||
assert mock_session.scalar.call_count == 2
|
||||
|
||||
def test_infer_api_type_from_database(self):
|
||||
"""Test inferring API provider type from database."""
|
||||
tool_config = {
|
||||
"provider_name": "api-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
|
||||
mock_session = MagicMock()
|
||||
mock_create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# First query (WorkflowToolProvider) returns None
|
||||
# Second query (MCPToolProvider) returns None
|
||||
# Third query (ApiToolProvider) returns a result
|
||||
mock_session.scalar.side_effect = [None, None, True]
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.API
|
||||
assert mock_session.scalar.call_count == 3
|
||||
|
||||
def test_infer_builtin_type_from_database(self):
|
||||
"""Test inferring builtin provider type from database."""
|
||||
tool_config = {
|
||||
"provider_name": "builtin-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
|
||||
mock_session = MagicMock()
|
||||
mock_create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# First three queries return None
|
||||
# Fourth query (BuiltinToolProvider) returns a result
|
||||
mock_session.scalar.side_effect = [None, None, None, True]
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.BUILT_IN
|
||||
assert mock_session.scalar.call_count == 4
|
||||
|
||||
def test_infer_type_default_when_not_found(self):
|
||||
"""Test raising AgentNodeError when provider is not found in database."""
|
||||
tool_config = {
|
||||
"provider_name": "unknown-provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
|
||||
mock_session = MagicMock()
|
||||
mock_create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# All queries return None
|
||||
mock_session.scalar.return_value = None
|
||||
|
||||
# Current implementation raises AgentNodeError when provider not found
|
||||
from core.workflow.nodes.agent.exc import AgentNodeError
|
||||
|
||||
with pytest.raises(AgentNodeError, match="Tool provider with ID 'unknown-provider-id' not found"):
|
||||
AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
def test_infer_type_default_when_no_provider_name(self):
|
||||
"""Test defaulting to BUILT_IN when provider_name is missing."""
|
||||
tool_config = {}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
|
||||
assert result == ToolProviderType.BUILT_IN
|
||||
|
||||
def test_infer_type_database_exception_propagates(self):
|
||||
"""Test that database exception propagates (current implementation doesn't catch it)."""
|
||||
tool_config = {
|
||||
"provider_name": "provider-id",
|
||||
}
|
||||
tenant_id = "test-tenant"
|
||||
|
||||
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
|
||||
mock_session = MagicMock()
|
||||
mock_create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# Database query raises exception
|
||||
mock_session.scalar.side_effect = Exception("Database error")
|
||||
|
||||
# Current implementation doesn't catch exceptions, so it propagates
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
AgentNode._infer_tool_provider_type(tool_config, tenant_id)
|
||||
@@ -1,261 +0,0 @@
|
||||
/**
|
||||
* MAX_PARALLEL_LIMIT Configuration Bug Test
|
||||
*
|
||||
* This test reproduces and verifies the fix for issue #23083:
|
||||
* MAX_PARALLEL_LIMIT environment variable does not take effect in iteration panel
|
||||
*/
|
||||
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
|
||||
// Mock environment variables before importing constants
|
||||
const originalEnv = process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT
|
||||
|
||||
// Test with different environment values
|
||||
function setupEnvironment(value?: string) {
|
||||
if (value)
|
||||
process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = value
|
||||
else
|
||||
delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT
|
||||
|
||||
// Clear module cache to force re-evaluation
|
||||
vi.resetModules()
|
||||
}
|
||||
|
||||
function restoreEnvironment() {
|
||||
if (originalEnv)
|
||||
process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = originalEnv
|
||||
else
|
||||
delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT
|
||||
|
||||
vi.resetModules()
|
||||
}
|
||||
|
||||
// Mock i18next with proper implementation
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
if (key.includes('MaxParallelismTitle'))
|
||||
return 'Max Parallelism'
|
||||
if (key.includes('MaxParallelismDesc'))
|
||||
return 'Maximum number of parallel executions'
|
||||
if (key.includes('parallelMode'))
|
||||
return 'Parallel Mode'
|
||||
if (key.includes('parallelPanelDesc'))
|
||||
return 'Enable parallel execution'
|
||||
if (key.includes('errorResponseMethod'))
|
||||
return 'Error Response Method'
|
||||
return key
|
||||
},
|
||||
}),
|
||||
initReactI18next: {
|
||||
type: '3rdParty',
|
||||
init: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock i18next module completely to prevent initialization issues
|
||||
vi.mock('i18next', () => ({
|
||||
use: vi.fn().mockReturnThis(),
|
||||
init: vi.fn().mockReturnThis(),
|
||||
t: vi.fn(key => key),
|
||||
isInitialized: true,
|
||||
}))
|
||||
|
||||
// Mock the useConfig hook
|
||||
vi.mock('@/app/components/workflow/nodes/iteration/use-config', () => ({
|
||||
default: () => ({
|
||||
inputs: {
|
||||
is_parallel: true,
|
||||
parallel_nums: 5,
|
||||
error_handle_mode: 'terminated',
|
||||
},
|
||||
changeParallel: vi.fn(),
|
||||
changeParallelNums: vi.fn(),
|
||||
changeErrorHandleMode: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock other components
|
||||
vi.mock('@/app/components/workflow/nodes/_base/components/variable/var-reference-picker', () => ({
|
||||
default: function MockVarReferencePicker() {
|
||||
return <div data-testid="var-reference-picker">VarReferencePicker</div>
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/nodes/_base/components/split', () => ({
|
||||
default: function MockSplit() {
|
||||
return <div data-testid="split">Split</div>
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/nodes/_base/components/field', () => ({
|
||||
default: function MockField({ title, children }: { title: string, children: React.ReactNode }) {
|
||||
return (
|
||||
<div data-testid="field">
|
||||
<label>{title}</label>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
const getParallelControls = () => ({
|
||||
numberInput: screen.getByRole('spinbutton'),
|
||||
slider: screen.getByRole('slider'),
|
||||
})
|
||||
|
||||
describe('MAX_PARALLEL_LIMIT Configuration Bug', () => {
|
||||
const mockNodeData = {
|
||||
id: 'test-iteration-node',
|
||||
type: 'iteration' as const,
|
||||
data: {
|
||||
title: 'Test Iteration',
|
||||
desc: 'Test iteration node',
|
||||
iterator_selector: ['test'],
|
||||
output_selector: ['output'],
|
||||
is_parallel: true,
|
||||
parallel_nums: 5,
|
||||
error_handle_mode: 'terminated' as const,
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
restoreEnvironment()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
restoreEnvironment()
|
||||
})
|
||||
|
||||
describe('Environment Variable Parsing', () => {
|
||||
it('should parse MAX_PARALLEL_LIMIT from NEXT_PUBLIC_MAX_PARALLEL_LIMIT environment variable', async () => {
|
||||
setupEnvironment('25')
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(25)
|
||||
})
|
||||
|
||||
it('should fallback to default when environment variable is not set', async () => {
|
||||
setupEnvironment() // No environment variable
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(10)
|
||||
})
|
||||
|
||||
it('should handle invalid environment variable values', async () => {
|
||||
setupEnvironment('invalid')
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
|
||||
// Should fall back to default when parsing fails
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(10)
|
||||
})
|
||||
|
||||
it('should handle empty environment variable', async () => {
|
||||
setupEnvironment('')
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
|
||||
// Should fall back to default when empty
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(10)
|
||||
})
|
||||
|
||||
// Edge cases for boundary values
|
||||
it('should clamp MAX_PARALLEL_LIMIT to MIN when env is 0 or negative', async () => {
|
||||
setupEnvironment('0')
|
||||
let { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(10) // Falls back to default
|
||||
|
||||
setupEnvironment('-5')
|
||||
;({ MAX_PARALLEL_LIMIT } = await import('@/config'))
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(10) // Falls back to default
|
||||
})
|
||||
|
||||
it('should handle float numbers by parseInt behavior', async () => {
|
||||
setupEnvironment('12.7')
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
// parseInt truncates to integer
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(12)
|
||||
})
|
||||
})
|
||||
|
||||
describe('UI Component Integration (Main Fix Verification)', () => {
|
||||
it('should render iteration panel with environment-configured max value', async () => {
|
||||
// Set environment variable to a different value
|
||||
setupEnvironment('30')
|
||||
|
||||
// Import Panel after setting environment
|
||||
const Panel = await import('@/app/components/workflow/nodes/iteration/panel').then(mod => mod.default)
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
|
||||
render(
|
||||
<Panel
|
||||
id="test-node"
|
||||
// @ts-expect-error key type mismatch
|
||||
data={mockNodeData.data}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Behavior-focused assertion: UI max should equal MAX_PARALLEL_LIMIT
|
||||
const { numberInput, slider } = getParallelControls()
|
||||
expect(numberInput).toHaveAttribute('max', String(MAX_PARALLEL_LIMIT))
|
||||
expect(slider).toHaveAttribute('aria-valuemax', String(MAX_PARALLEL_LIMIT))
|
||||
|
||||
// Verify the actual values
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(30)
|
||||
expect(numberInput.getAttribute('max')).toBe('30')
|
||||
expect(slider.getAttribute('aria-valuemax')).toBe('30')
|
||||
})
|
||||
|
||||
it('should maintain UI consistency with different environment values', async () => {
|
||||
setupEnvironment('15')
|
||||
const Panel = await import('@/app/components/workflow/nodes/iteration/panel').then(mod => mod.default)
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
|
||||
render(
|
||||
<Panel
|
||||
id="test-node"
|
||||
// @ts-expect-error key type mismatch
|
||||
data={mockNodeData.data}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Both input and slider should use the same max value from MAX_PARALLEL_LIMIT
|
||||
const { numberInput, slider } = getParallelControls()
|
||||
|
||||
expect(numberInput.getAttribute('max')).toBe(slider.getAttribute('aria-valuemax'))
|
||||
expect(numberInput.getAttribute('max')).toBe(String(MAX_PARALLEL_LIMIT))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Legacy Constant Verification (For Transition Period)', () => {
|
||||
// Marked as transition/deprecation tests
|
||||
it('should maintain MAX_ITERATION_PARALLEL_NUM for backward compatibility', async () => {
|
||||
const { MAX_ITERATION_PARALLEL_NUM } = await import('@/app/components/workflow/constants')
|
||||
expect(typeof MAX_ITERATION_PARALLEL_NUM).toBe('number')
|
||||
expect(MAX_ITERATION_PARALLEL_NUM).toBe(10) // Hardcoded legacy value
|
||||
})
|
||||
|
||||
it('should demonstrate MAX_PARALLEL_LIMIT vs legacy constant difference', async () => {
|
||||
setupEnvironment('50')
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
const { MAX_ITERATION_PARALLEL_NUM } = await import('@/app/components/workflow/constants')
|
||||
|
||||
// MAX_PARALLEL_LIMIT is configurable, MAX_ITERATION_PARALLEL_NUM is not
|
||||
expect(MAX_PARALLEL_LIMIT).toBe(50)
|
||||
expect(MAX_ITERATION_PARALLEL_NUM).toBe(10)
|
||||
expect(MAX_PARALLEL_LIMIT).not.toBe(MAX_ITERATION_PARALLEL_NUM)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Constants Validation', () => {
|
||||
it('should validate that required constants exist and have correct types', async () => {
|
||||
const { MAX_PARALLEL_LIMIT } = await import('@/config')
|
||||
const { MIN_ITERATION_PARALLEL_NUM } = await import('@/app/components/workflow/constants')
|
||||
expect(typeof MAX_PARALLEL_LIMIT).toBe('number')
|
||||
expect(typeof MIN_ITERATION_PARALLEL_NUM).toBe('number')
|
||||
expect(MAX_PARALLEL_LIMIT).toBeGreaterThanOrEqual(MIN_ITERATION_PARALLEL_NUM)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -109,6 +109,7 @@ const AgentTools: FC = () => {
|
||||
tool_parameters: paramsWithDefaultValue,
|
||||
notAuthor: !tool.is_team_authorization,
|
||||
enabled: true,
|
||||
type: tool.provider_type as CollectionType,
|
||||
}
|
||||
}
|
||||
const handleSelectTool = (tool: ToolDefaultValue) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RemixiconComponentType } from '@remixicon/react'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
export const InputTypeEnum = z.enum([
|
||||
'text-input',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ZodNumber, ZodSchema, ZodString } from 'zod'
|
||||
import type { BaseConfiguration } from './types'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import { BaseFieldType } from './types'
|
||||
|
||||
export const generateZodSchema = (fields: BaseConfiguration[]) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
const ContactMethod = z.union([
|
||||
z.literal('email'),
|
||||
@@ -22,10 +22,10 @@ export const UserSchema = z.object({
|
||||
.min(3, 'Surname must be at least 3 characters long')
|
||||
.regex(/^[A-Z]/, 'Surname must start with a capital letter'),
|
||||
isAcceptingTerms: z.boolean().refine(val => val, {
|
||||
message: 'You must accept the terms and conditions',
|
||||
error: 'You must accept the terms and conditions',
|
||||
}),
|
||||
contact: z.object({
|
||||
email: z.string().email('Invalid email address'),
|
||||
email: z.email('Invalid email address'),
|
||||
phone: z.string().optional(),
|
||||
preferredContactMethod: ContactMethod,
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ZodSchema, ZodString } from 'zod'
|
||||
import type { InputFieldConfiguration } from './types'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import { SupportedFileTypes, TransferMethod } from '@/app/components/rag-pipeline/components/panel/input-field/editor/form/schema'
|
||||
import { InputFieldType } from './types'
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { env } from '@/env'
|
||||
import ParamItem from '.'
|
||||
|
||||
type Props = {
|
||||
@@ -12,7 +13,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const maxTopK = (() => {
|
||||
const configValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-top-k-max-value') || '', 10)
|
||||
const configValue = Number.parseInt(env.NEXT_PUBLIC_TOP_K_MAX_VALUE || '', 10)
|
||||
if (configValue && !isNaN(configValue))
|
||||
return configValue
|
||||
return 10
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import withValidation from '.'
|
||||
|
||||
describe('withValidation HOC', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import withValidation from '.'
|
||||
|
||||
// Sample components to wrap with validation
|
||||
@@ -65,7 +65,7 @@ const ProductCard = ({ name, price, category, inStock }: ProductCardProps) => {
|
||||
// Create validated versions
|
||||
const userSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
email: z.string().email('Invalid email'),
|
||||
email: z.email('Invalid email'),
|
||||
age: z.number().min(0).max(150),
|
||||
})
|
||||
|
||||
@@ -371,7 +371,7 @@ export const ConfigurationValidation: Story = {
|
||||
)
|
||||
|
||||
const configSchema = z.object({
|
||||
apiUrl: z.string().url('Must be valid URL'),
|
||||
apiUrl: z.url('Must be valid URL'),
|
||||
timeout: z.number().min(0).max(30000),
|
||||
retries: z.number().min(0).max(5),
|
||||
debug: z.boolean(),
|
||||
@@ -430,7 +430,7 @@ export const UsageDocumentation: Story = {
|
||||
<div>
|
||||
<h4 className="mb-2 text-sm font-semibold text-gray-900">Usage Example</h4>
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-xs text-gray-100">
|
||||
{`import { z } from 'zod'
|
||||
{`import * as z from 'zod'
|
||||
import withValidation from './withValidation'
|
||||
|
||||
// Define your component
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { env } from '@/env'
|
||||
|
||||
const TextLabel: FC<PropsWithChildren> = (props) => {
|
||||
return <label className="text-xs font-semibold leading-none text-text-secondary">{props.children}</label>
|
||||
@@ -46,7 +47,7 @@ export const DelimiterInput: FC<InputProps & { tooltip?: string }> = (props) =>
|
||||
}
|
||||
|
||||
export const MaxLengthInput: FC<InputNumberProps> = (props) => {
|
||||
const maxValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-indexing-max-segmentation-tokens-length') || '4000', 10)
|
||||
const maxValue = Number.parseInt(env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH || '4000', 10)
|
||||
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ParentMode, PreProcessingRule, ProcessRule, Rules, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { env } from '@/env'
|
||||
import { ChunkingMode, ProcessMode } from '@/models/datasets'
|
||||
import escape from './escape'
|
||||
import unescape from './unescape'
|
||||
@@ -9,7 +10,7 @@ export const DEFAULT_SEGMENT_IDENTIFIER = '\\n\\n'
|
||||
export const DEFAULT_MAXIMUM_CHUNK_LENGTH = 1024
|
||||
export const DEFAULT_OVERLAP = 50
|
||||
export const MAXIMUM_CHUNK_TOKEN_LENGTH = Number.parseInt(
|
||||
globalThis.document?.body?.getAttribute('data-public-indexing-max-segmentation-tokens-length') || '4000',
|
||||
env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH || '4000',
|
||||
10,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Actions from './actions'
|
||||
@@ -53,7 +53,7 @@ const createFailingSchema = () => {
|
||||
issues: [{ path: ['field1'], message: 'is required' }],
|
||||
},
|
||||
}),
|
||||
} as unknown as z.ZodSchema
|
||||
} as unknown as z.ZodType
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { env } from '@/env'
|
||||
import { useLogout } from '@/service/use-common'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import AccountAbout from '../account-about'
|
||||
@@ -178,7 +179,7 @@ export default function AppSelector() {
|
||||
</Link>
|
||||
</MenuItem>
|
||||
{
|
||||
document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
|
||||
env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && (
|
||||
<MenuItem>
|
||||
<div
|
||||
className={cn(itemClassName, 'justify-between', 'data-[active]:bg-state-base-hover')}
|
||||
|
||||
@@ -129,6 +129,7 @@ export const useToolSelectorState = ({
|
||||
extra: {
|
||||
description: tool.tool_description,
|
||||
},
|
||||
type: tool.provider_type,
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { SerwistProvider } from '@serwist/turbopack/react'
|
||||
import { useEffect } from 'react'
|
||||
import { IS_DEV } from '@/config'
|
||||
import { env } from '@/env'
|
||||
import { isClient } from '@/utils/client'
|
||||
|
||||
export function PWAProvider({ children }: { children: React.ReactNode }) {
|
||||
@@ -10,7 +11,7 @@ export function PWAProvider({ children }: { children: React.ReactNode }) {
|
||||
return <DisabledPWAProvider>{children}</DisabledPWAProvider>
|
||||
}
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''
|
||||
const basePath = env.NEXT_PUBLIC_BASE_PATH || ''
|
||||
const swUrl = `${basePath}/serwist/sw.js`
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { SchemaOptions } from './types'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import { InputTypeEnum } from '@/app/components/base/form/components/field/input-type-select/types'
|
||||
import { MAX_VAR_KEY_LENGTH } from '@/config'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
@@ -41,49 +41,47 @@ export const createInputFieldSchema = (type: PipelineInputVarType, t: TFunction,
|
||||
tooltips: z.string().optional(),
|
||||
})
|
||||
if (type === PipelineInputVarType.textInput || type === PipelineInputVarType.paragraph) {
|
||||
return z.object({
|
||||
return z.looseObject({
|
||||
maxLength: z.number().min(1).max(TEXT_MAX_LENGTH),
|
||||
default: z.string().optional(),
|
||||
}).merge(commonSchema).passthrough()
|
||||
}).extend(commonSchema.shape)
|
||||
}
|
||||
if (type === PipelineInputVarType.number) {
|
||||
return z.object({
|
||||
return z.looseObject({
|
||||
default: z.number().optional(),
|
||||
unit: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
}).merge(commonSchema).passthrough()
|
||||
}).extend(commonSchema.shape)
|
||||
}
|
||||
if (type === PipelineInputVarType.select) {
|
||||
return z.object({
|
||||
options: z.array(z.string()).nonempty({
|
||||
message: t('variableConfig.errorMsg.atLeastOneOption', { ns: 'appDebug' }),
|
||||
}).refine(
|
||||
return z.looseObject({
|
||||
options: z.tuple([z.string()], z.string()).refine(
|
||||
arr => new Set(arr).size === arr.length,
|
||||
{
|
||||
message: t('variableConfig.errorMsg.optionRepeat', { ns: 'appDebug' }),
|
||||
},
|
||||
),
|
||||
default: z.string().optional(),
|
||||
}).merge(commonSchema).passthrough()
|
||||
}).extend(commonSchema.shape)
|
||||
}
|
||||
if (type === PipelineInputVarType.singleFile) {
|
||||
return z.object({
|
||||
return z.looseObject({
|
||||
allowedFileUploadMethods: z.array(TransferMethod),
|
||||
allowedTypesAndExtensions: z.object({
|
||||
allowedTypesAndExtensions: z.looseObject({
|
||||
allowedFileExtensions: z.array(z.string()).optional(),
|
||||
allowedFileTypes: z.array(SupportedFileTypes),
|
||||
}),
|
||||
}).merge(commonSchema).passthrough()
|
||||
}).extend(commonSchema.shape)
|
||||
}
|
||||
if (type === PipelineInputVarType.multiFiles) {
|
||||
return z.object({
|
||||
return z.looseObject({
|
||||
allowedFileUploadMethods: z.array(TransferMethod),
|
||||
allowedTypesAndExtensions: z.object({
|
||||
allowedTypesAndExtensions: z.looseObject({
|
||||
allowedFileExtensions: z.array(z.string()).optional(),
|
||||
allowedFileTypes: z.array(SupportedFileTypes),
|
||||
}),
|
||||
maxLength: z.number().min(1).max(maxFileUploadLimit),
|
||||
}).merge(commonSchema).passthrough()
|
||||
}).extend(commonSchema.shape)
|
||||
}
|
||||
return commonSchema.passthrough()
|
||||
return z.looseObject(commonSchema.shape)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@ import * as Sentry from '@sentry/react'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { IS_DEV } from '@/config'
|
||||
import { env } from '@/env'
|
||||
|
||||
const SentryInitializer = ({
|
||||
children,
|
||||
}: { children: React.ReactElement }) => {
|
||||
useEffect(() => {
|
||||
const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn')
|
||||
if (!IS_DEV && SENTRY_DSN) {
|
||||
const sentryDsn = env.NEXT_PUBLIC_SENTRY_DSN
|
||||
if (!IS_DEV && sentryDsn) {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
dsn: sentryDsn,
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration(),
|
||||
|
||||
@@ -87,6 +87,7 @@ export type ToolValue = {
|
||||
enabled?: boolean
|
||||
extra?: { description?: string } & Record<string, unknown>
|
||||
credential_id?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export type DataSourceItem = {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { env } from '@/env'
|
||||
|
||||
export type TopKAndScoreThresholdProps = {
|
||||
topK: number
|
||||
@@ -16,7 +17,7 @@ export type TopKAndScoreThresholdProps = {
|
||||
}
|
||||
|
||||
const maxTopK = (() => {
|
||||
const configValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-top-k-max-value') || '', 10)
|
||||
const configValue = Number.parseInt(env.NEXT_PUBLIC_TOP_K_MAX_VALUE || '', 10)
|
||||
if (configValue && !isNaN(configValue))
|
||||
return configValue
|
||||
return 10
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ValidationError } from 'jsonschema'
|
||||
import type { ArrayItems, Field, LLMNodeType } from './types'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import { draft07Validator, forbidBooleanProperties } from '@/utils/validators'
|
||||
import { ArrayType, Type } from './types'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
const arrayStringSchemaParttern = z.array(z.string())
|
||||
const arrayNumberSchemaParttern = z.array(z.number())
|
||||
@@ -7,7 +7,7 @@ const arrayNumberSchemaParttern = z.array(z.number())
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
|
||||
type Literal = z.infer<typeof literalSchema>
|
||||
type Json = Literal | { [key: string]: Json } | Json[]
|
||||
const jsonSchema: z.ZodType<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]))
|
||||
const jsonSchema: z.ZodType<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(z.string(), jsonSchema)]))
|
||||
const arrayJsonSchema: z.ZodType<Json[]> = z.lazy(() => z.array(jsonSchema))
|
||||
|
||||
export const validateJSONSchema = (schema: any, type: string) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { formContext, useAppForm } from '@/app/components/base/form'
|
||||
import { zodSubmitValidator } from '@/app/components/base/form/utils/zod-submit-validator'
|
||||
@@ -22,10 +22,10 @@ import Input from '../components/base/input'
|
||||
import Loading from '../components/base/loading'
|
||||
|
||||
const accountFormSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: 'error.emailInValid' })
|
||||
.email('error.emailInValid'),
|
||||
email: z.email('error.emailInValid')
|
||||
.min(1, {
|
||||
error: 'error.emailInValid',
|
||||
}),
|
||||
})
|
||||
|
||||
const ForgotPasswordForm = () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { z } from 'zod'
|
||||
import * as z from 'zod'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { formContext, useAppForm } from '@/app/components/base/form'
|
||||
import { zodSubmitValidator } from '@/app/components/base/form/utils/zod-submit-validator'
|
||||
@@ -22,13 +22,15 @@ import { encryptPassword as encodePassword } from '@/utils/encryption'
|
||||
import Loading from '../components/base/loading'
|
||||
|
||||
const accountFormSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: 'error.emailInValid' })
|
||||
.email('error.emailInValid'),
|
||||
name: z.string().min(1, { message: 'error.nameEmpty' }),
|
||||
email: z.email('error.emailInValid')
|
||||
.min(1, {
|
||||
error: 'error.emailInValid',
|
||||
}),
|
||||
name: z.string().min(1, {
|
||||
error: 'error.nameEmpty',
|
||||
}),
|
||||
password: z.string().min(8, {
|
||||
message: 'error.passwordLengthInValid',
|
||||
error: 'error.passwordLengthInValid',
|
||||
}).regex(validPassword, 'error.passwordInvalid'),
|
||||
})
|
||||
|
||||
@@ -197,7 +199,7 @@ const InstallForm = () => {
|
||||
</div>
|
||||
|
||||
<div className={cn('mt-1 text-xs text-text-secondary', {
|
||||
'text-red-400 !text-sm': passwordErrors && passwordErrors.length > 0,
|
||||
'!text-sm text-red-400': passwordErrors && passwordErrors.length > 0,
|
||||
})}
|
||||
>
|
||||
{t('error.passwordInvalid', { ns: 'login' })}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Instrument_Serif } from 'next/font/google'
|
||||
import { NuqsAdapter } from 'nuqs/adapters/next/app'
|
||||
import GlobalPublicStoreProvider from '@/context/global-public-context'
|
||||
import { TanstackQueryInitializer } from '@/context/query-client'
|
||||
import { clientEnvKeys, env, getDatasetAttrKeyFromEnvKey } from '@/env'
|
||||
import { getLocaleOnServer } from '@/i18n-config/server'
|
||||
import { DatasetAttr } from '@/types/feature'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { ToastProvider } from './components/base/toast'
|
||||
import BrowserInitializer from './components/browser-initializer'
|
||||
@@ -39,40 +39,7 @@ const LocaleLayout = async ({
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const locale = await getLocaleOnServer()
|
||||
|
||||
const datasetMap: Record<DatasetAttr, string | undefined> = {
|
||||
[DatasetAttr.DATA_API_PREFIX]: process.env.NEXT_PUBLIC_API_PREFIX,
|
||||
[DatasetAttr.DATA_PUBLIC_API_PREFIX]: process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX,
|
||||
[DatasetAttr.DATA_MARKETPLACE_API_PREFIX]: process.env.NEXT_PUBLIC_MARKETPLACE_API_PREFIX,
|
||||
[DatasetAttr.DATA_MARKETPLACE_URL_PREFIX]: process.env.NEXT_PUBLIC_MARKETPLACE_URL_PREFIX,
|
||||
[DatasetAttr.DATA_PUBLIC_EDITION]: process.env.NEXT_PUBLIC_EDITION,
|
||||
[DatasetAttr.DATA_PUBLIC_AMPLITUDE_API_KEY]: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY,
|
||||
[DatasetAttr.DATA_PUBLIC_COOKIE_DOMAIN]: process.env.NEXT_PUBLIC_COOKIE_DOMAIN,
|
||||
[DatasetAttr.DATA_PUBLIC_SUPPORT_MAIL_LOGIN]: process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN,
|
||||
[DatasetAttr.DATA_PUBLIC_SENTRY_DSN]: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
[DatasetAttr.DATA_PUBLIC_MAINTENANCE_NOTICE]: process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE,
|
||||
[DatasetAttr.DATA_PUBLIC_SITE_ABOUT]: process.env.NEXT_PUBLIC_SITE_ABOUT,
|
||||
[DatasetAttr.DATA_PUBLIC_TEXT_GENERATION_TIMEOUT_MS]: process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS,
|
||||
[DatasetAttr.DATA_PUBLIC_MAX_TOOLS_NUM]: process.env.NEXT_PUBLIC_MAX_TOOLS_NUM,
|
||||
[DatasetAttr.DATA_PUBLIC_MAX_PARALLEL_LIMIT]: process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT,
|
||||
[DatasetAttr.DATA_PUBLIC_TOP_K_MAX_VALUE]: process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE,
|
||||
[DatasetAttr.DATA_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH]: process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH,
|
||||
[DatasetAttr.DATA_PUBLIC_LOOP_NODE_MAX_COUNT]: process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT,
|
||||
[DatasetAttr.DATA_PUBLIC_MAX_ITERATIONS_NUM]: process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM,
|
||||
[DatasetAttr.DATA_PUBLIC_MAX_TREE_DEPTH]: process.env.NEXT_PUBLIC_MAX_TREE_DEPTH,
|
||||
[DatasetAttr.DATA_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME]: process.env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME,
|
||||
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER,
|
||||
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
|
||||
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
|
||||
[DatasetAttr.DATA_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX]: process.env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
|
||||
[DatasetAttr.NEXT_PUBLIC_ZENDESK_WIDGET_KEY]: process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY,
|
||||
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT,
|
||||
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION,
|
||||
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL,
|
||||
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID,
|
||||
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN,
|
||||
[DatasetAttr.DATA_PUBLIC_BATCH_CONCURRENCY]: process.env.NEXT_PUBLIC_BATCH_CONCURRENCY,
|
||||
}
|
||||
const datasetMap = Object.fromEntries(clientEnvKeys.map(envKey => [getDatasetAttrKeyFromEnvKey(envKey), env[envKey]]))
|
||||
|
||||
return (
|
||||
<html lang={locale ?? 'en'} className={cn('h-full', instrumentSerif.variable)} suppressHydrationWarning>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createSerwistRoute } from '@serwist/turbopack'
|
||||
import { env } from '@/env'
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''
|
||||
const basePath = env.NEXT_PUBLIC_BASE_PATH || ''
|
||||
|
||||
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } = createSerwistRoute({
|
||||
swSrc: 'app/sw.ts',
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { env } from '@/env'
|
||||
import { PromptRole } from '@/models/debug'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { AgentStrategy } from '@/types/app'
|
||||
import { DatasetAttr } from '@/types/feature'
|
||||
import pkg from '../package.json'
|
||||
|
||||
const getBooleanConfig = (
|
||||
envVar: string | undefined,
|
||||
dataAttrKey: DatasetAttr,
|
||||
defaultValue: boolean = true,
|
||||
) => {
|
||||
if (envVar !== undefined && envVar !== '')
|
||||
return envVar === 'true'
|
||||
const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey)
|
||||
if (attrValue !== undefined && attrValue !== '')
|
||||
return attrValue === 'true'
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
const getNumberConfig = (
|
||||
envVar: string | undefined,
|
||||
dataAttrKey: DatasetAttr,
|
||||
defaultValue: number,
|
||||
) => {
|
||||
if (envVar) {
|
||||
@@ -29,54 +24,37 @@ const getNumberConfig = (
|
||||
if (!Number.isNaN(parsed) && parsed > 0)
|
||||
return parsed
|
||||
}
|
||||
|
||||
const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey)
|
||||
if (attrValue) {
|
||||
const parsed = Number.parseInt(attrValue)
|
||||
if (!Number.isNaN(parsed) && parsed > 0)
|
||||
return parsed
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
const getStringConfig = (
|
||||
envVar: string | undefined,
|
||||
dataAttrKey: DatasetAttr,
|
||||
defaultValue: string,
|
||||
) => {
|
||||
if (envVar)
|
||||
return envVar
|
||||
|
||||
const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey)
|
||||
if (attrValue)
|
||||
return attrValue
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
export const API_PREFIX = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_API_PREFIX,
|
||||
DatasetAttr.DATA_API_PREFIX,
|
||||
env.NEXT_PUBLIC_API_PREFIX,
|
||||
'http://localhost:5001/console/api',
|
||||
)
|
||||
export const PUBLIC_API_PREFIX = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX,
|
||||
DatasetAttr.DATA_PUBLIC_API_PREFIX,
|
||||
env.NEXT_PUBLIC_PUBLIC_API_PREFIX,
|
||||
'http://localhost:5001/api',
|
||||
)
|
||||
export const MARKETPLACE_API_PREFIX = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_MARKETPLACE_API_PREFIX,
|
||||
DatasetAttr.DATA_MARKETPLACE_API_PREFIX,
|
||||
env.NEXT_PUBLIC_MARKETPLACE_API_PREFIX,
|
||||
'http://localhost:5002/api',
|
||||
)
|
||||
export const MARKETPLACE_URL_PREFIX = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_MARKETPLACE_URL_PREFIX,
|
||||
DatasetAttr.DATA_MARKETPLACE_URL_PREFIX,
|
||||
env.NEXT_PUBLIC_MARKETPLACE_URL_PREFIX,
|
||||
'',
|
||||
)
|
||||
|
||||
const EDITION = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_EDITION,
|
||||
DatasetAttr.DATA_PUBLIC_EDITION,
|
||||
env.NEXT_PUBLIC_EDITION,
|
||||
'SELF_HOSTED',
|
||||
)
|
||||
|
||||
@@ -84,17 +62,16 @@ export const IS_CE_EDITION = EDITION === 'SELF_HOSTED'
|
||||
export const IS_CLOUD_EDITION = EDITION === 'CLOUD'
|
||||
|
||||
export const AMPLITUDE_API_KEY = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY,
|
||||
DatasetAttr.DATA_PUBLIC_AMPLITUDE_API_KEY,
|
||||
env.NEXT_PUBLIC_AMPLITUDE_API_KEY,
|
||||
'',
|
||||
)
|
||||
|
||||
export const IS_DEV = process.env.NODE_ENV === 'development'
|
||||
export const IS_PROD = process.env.NODE_ENV === 'production'
|
||||
export const IS_DEV = env.NODE_ENV === 'development'
|
||||
export const IS_PROD = env.NODE_ENV === 'production'
|
||||
|
||||
export const SUPPORT_MAIL_LOGIN = !!(
|
||||
process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN
|
||||
|| globalThis.document?.body?.getAttribute('data-public-support-mail-login')
|
||||
export const SUPPORT_MAIL_LOGIN = getBooleanConfig(
|
||||
env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN,
|
||||
false,
|
||||
)
|
||||
|
||||
export const TONE_LIST = [
|
||||
@@ -161,14 +138,12 @@ export const getMaxToken = (modelId: string) => {
|
||||
export const LOCALE_COOKIE_NAME = 'locale'
|
||||
|
||||
const COOKIE_DOMAIN = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_COOKIE_DOMAIN,
|
||||
DatasetAttr.DATA_PUBLIC_COOKIE_DOMAIN,
|
||||
env.NEXT_PUBLIC_COOKIE_DOMAIN,
|
||||
'',
|
||||
).trim()
|
||||
|
||||
export const BATCH_CONCURRENCY = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_BATCH_CONCURRENCY,
|
||||
DatasetAttr.DATA_PUBLIC_BATCH_CONCURRENCY,
|
||||
env.NEXT_PUBLIC_BATCH_CONCURRENCY,
|
||||
5, // default
|
||||
)
|
||||
|
||||
@@ -342,10 +317,10 @@ export const VAR_REGEX
|
||||
export const resetReg = () => (VAR_REGEX.lastIndex = 0)
|
||||
|
||||
export const DISABLE_UPLOAD_IMAGE_AS_ICON
|
||||
= process.env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON === 'true'
|
||||
= env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON === 'true'
|
||||
|
||||
export const GITHUB_ACCESS_TOKEN
|
||||
= process.env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN || ''
|
||||
= env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN || ''
|
||||
|
||||
export const SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS = '.difypkg,.difybndl'
|
||||
export const FULL_DOC_PREVIEW_LENGTH = 50
|
||||
@@ -353,59 +328,48 @@ export const FULL_DOC_PREVIEW_LENGTH = 50
|
||||
export const JSON_SCHEMA_MAX_DEPTH = 10
|
||||
|
||||
export const MAX_TOOLS_NUM = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_MAX_TOOLS_NUM,
|
||||
DatasetAttr.DATA_PUBLIC_MAX_TOOLS_NUM,
|
||||
env.NEXT_PUBLIC_MAX_TOOLS_NUM,
|
||||
10,
|
||||
)
|
||||
export const MAX_PARALLEL_LIMIT = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT,
|
||||
DatasetAttr.DATA_PUBLIC_MAX_PARALLEL_LIMIT,
|
||||
env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT,
|
||||
10,
|
||||
)
|
||||
export const TEXT_GENERATION_TIMEOUT_MS = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS,
|
||||
DatasetAttr.DATA_PUBLIC_TEXT_GENERATION_TIMEOUT_MS,
|
||||
env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS,
|
||||
60000,
|
||||
)
|
||||
export const LOOP_NODE_MAX_COUNT = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT,
|
||||
DatasetAttr.DATA_PUBLIC_LOOP_NODE_MAX_COUNT,
|
||||
env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT,
|
||||
100,
|
||||
)
|
||||
export const MAX_ITERATIONS_NUM = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM,
|
||||
DatasetAttr.DATA_PUBLIC_MAX_ITERATIONS_NUM,
|
||||
env.NEXT_PUBLIC_MAX_ITERATIONS_NUM,
|
||||
99,
|
||||
)
|
||||
export const MAX_TREE_DEPTH = getNumberConfig(
|
||||
process.env.NEXT_PUBLIC_MAX_TREE_DEPTH,
|
||||
DatasetAttr.DATA_PUBLIC_MAX_TREE_DEPTH,
|
||||
env.NEXT_PUBLIC_MAX_TREE_DEPTH,
|
||||
50,
|
||||
)
|
||||
|
||||
export const ALLOW_UNSAFE_DATA_SCHEME = getBooleanConfig(
|
||||
process.env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME,
|
||||
DatasetAttr.DATA_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME,
|
||||
env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME,
|
||||
false,
|
||||
)
|
||||
export const ENABLE_WEBSITE_JINAREADER = getBooleanConfig(
|
||||
process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER,
|
||||
DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER,
|
||||
env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER,
|
||||
true,
|
||||
)
|
||||
export const ENABLE_WEBSITE_FIRECRAWL = getBooleanConfig(
|
||||
process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
|
||||
DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
|
||||
env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
|
||||
true,
|
||||
)
|
||||
export const ENABLE_WEBSITE_WATERCRAWL = getBooleanConfig(
|
||||
process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
|
||||
DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
|
||||
env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
|
||||
false,
|
||||
)
|
||||
export const ENABLE_SINGLE_DOLLAR_LATEX = getBooleanConfig(
|
||||
process.env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
|
||||
DatasetAttr.DATA_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
|
||||
env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -414,40 +378,34 @@ export const VALUE_SELECTOR_DELIMITER = '@@@'
|
||||
export const validPassword = /^(?=.*[a-z])(?=.*\d)\S{8,}$/i
|
||||
|
||||
export const ZENDESK_WIDGET_KEY = getStringConfig(
|
||||
process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY,
|
||||
DatasetAttr.NEXT_PUBLIC_ZENDESK_WIDGET_KEY,
|
||||
env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY,
|
||||
'',
|
||||
)
|
||||
export const ZENDESK_FIELD_IDS = {
|
||||
ENVIRONMENT: getStringConfig(
|
||||
process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT,
|
||||
DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT,
|
||||
env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT,
|
||||
'',
|
||||
),
|
||||
VERSION: getStringConfig(
|
||||
process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION,
|
||||
DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION,
|
||||
env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION,
|
||||
'',
|
||||
),
|
||||
EMAIL: getStringConfig(
|
||||
process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL,
|
||||
DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL,
|
||||
env.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL,
|
||||
'',
|
||||
),
|
||||
WORKSPACE_ID: getStringConfig(
|
||||
process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID,
|
||||
DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID,
|
||||
env.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID,
|
||||
'',
|
||||
),
|
||||
PLAN: getStringConfig(
|
||||
process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN,
|
||||
DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN,
|
||||
env.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN,
|
||||
'',
|
||||
),
|
||||
}
|
||||
export const APP_VERSION = pkg.version
|
||||
|
||||
export const IS_MARKETPLACE = globalThis.document?.body?.getAttribute('data-is-marketplace') === 'true'
|
||||
export const IS_MARKETPLACE = env.NEXT_PUBLIC_IS_MARKETPLACE === 'true'
|
||||
|
||||
export const RAG_PIPELINE_PREVIEW_CHUNK_NUM = 20
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { setUserId, setUserProperties } from '@/app/components/base/amplitude'
|
||||
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
|
||||
import MaintenanceNotice from '@/app/components/header/maintenance-notice'
|
||||
import { ZENDESK_FIELD_IDS } from '@/config'
|
||||
import { env } from '@/env'
|
||||
import {
|
||||
useCurrentWorkspace,
|
||||
useLangGeniusVersion,
|
||||
@@ -204,7 +205,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full flex-col overflow-y-auto">
|
||||
{globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && <MaintenanceNotice />}
|
||||
{env.NEXT_PUBLIC_MAINTENANCE_NOTICE && <MaintenanceNotice />}
|
||||
<div className="relative flex grow flex-col overflow-y-auto overflow-x-hidden bg-background-body">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
144
web/env.ts
Normal file
144
web/env.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { createEnv } from '@t3-oss/env-nextjs'
|
||||
import { kebabCase, replace } from 'string-ts'
|
||||
import * as z from 'zod'
|
||||
import { isServer } from './utils/client'
|
||||
|
||||
const optionalString = z.string().optional()
|
||||
const CLIENT_ENV_PREFIX = 'NEXT_PUBLIC_' as const
|
||||
type ClientSchema = Record<`${typeof CLIENT_ENV_PREFIX}${string}`, z.ZodType>
|
||||
|
||||
const clientSchema = {
|
||||
NEXT_PUBLIC_ALLOW_EMBED: optionalString,
|
||||
NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME: optionalString,
|
||||
NEXT_PUBLIC_AMPLITUDE_API_KEY: optionalString,
|
||||
NEXT_PUBLIC_API_PREFIX: optionalString,
|
||||
NEXT_PUBLIC_BASE_PATH: optionalString,
|
||||
NEXT_PUBLIC_BATCH_CONCURRENCY: optionalString,
|
||||
NEXT_PUBLIC_COOKIE_DOMAIN: optionalString,
|
||||
NEXT_PUBLIC_CSP_WHITELIST: optionalString,
|
||||
NEXT_PUBLIC_DEPLOY_ENV: optionalString,
|
||||
NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON: optionalString,
|
||||
NEXT_PUBLIC_EDITION: optionalString,
|
||||
NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX: optionalString,
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL: optionalString,
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER: optionalString,
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL: optionalString,
|
||||
NEXT_PUBLIC_GITHUB_ACCESS_TOKEN: optionalString,
|
||||
NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: optionalString,
|
||||
NEXT_PUBLIC_IS_MARKETPLACE: optionalString,
|
||||
NEXT_PUBLIC_LOOP_NODE_MAX_COUNT: optionalString,
|
||||
NEXT_PUBLIC_MAINTENANCE_NOTICE: optionalString,
|
||||
NEXT_PUBLIC_MARKETPLACE_API_PREFIX: optionalString,
|
||||
NEXT_PUBLIC_MARKETPLACE_URL_PREFIX: optionalString,
|
||||
NEXT_PUBLIC_MAX_ITERATIONS_NUM: optionalString,
|
||||
NEXT_PUBLIC_MAX_PARALLEL_LIMIT: optionalString,
|
||||
NEXT_PUBLIC_MAX_TOOLS_NUM: optionalString,
|
||||
NEXT_PUBLIC_MAX_TREE_DEPTH: optionalString,
|
||||
NEXT_PUBLIC_PUBLIC_API_PREFIX: optionalString,
|
||||
NEXT_PUBLIC_SENTRY_DSN: optionalString,
|
||||
NEXT_PUBLIC_SITE_ABOUT: optionalString,
|
||||
NEXT_PUBLIC_SUPPORT_MAIL_LOGIN: optionalString,
|
||||
NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS: optionalString,
|
||||
NEXT_PUBLIC_TOP_K_MAX_VALUE: optionalString,
|
||||
NEXT_PUBLIC_UPLOAD_IMAGE_AS_ICON: optionalString,
|
||||
NEXT_PUBLIC_WEB_PREFIX: optionalString,
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL: optionalString,
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT: optionalString,
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN: optionalString,
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION: optionalString,
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID: optionalString,
|
||||
NEXT_PUBLIC_ZENDESK_WIDGET_KEY: optionalString,
|
||||
} satisfies ClientSchema
|
||||
|
||||
export type ClientEnvKey = keyof typeof clientSchema
|
||||
|
||||
type SnakeToKebabCase<S extends string> = S extends `${infer Head}_${infer Tail}`
|
||||
? `${Lowercase<Head>}-${SnakeToKebabCase<Tail>}`
|
||||
: Lowercase<S>
|
||||
type DatasetAttrKeyFromEnvKey<K extends ClientEnvKey> = K extends `${typeof CLIENT_ENV_PREFIX}${infer Suffix}`
|
||||
? `data-${SnakeToKebabCase<Suffix>}`
|
||||
: never
|
||||
|
||||
export type ClientDatasetAttrKey = DatasetAttrKeyFromEnvKey<ClientEnvKey>
|
||||
|
||||
export const clientEnvKeys = Object.keys(clientSchema) as ReadonlyArray<ClientEnvKey>
|
||||
|
||||
const getClientEnvSuffix = (key: ClientEnvKey) => kebabCase(replace(key, CLIENT_ENV_PREFIX, ''))
|
||||
|
||||
export const getDatasetAttrKeyFromEnvKey = <K extends ClientEnvKey>(key: K) => {
|
||||
const suffix = getClientEnvSuffix(key)
|
||||
return `data-${suffix}` as const
|
||||
}
|
||||
|
||||
const getRuntimeEnvFromBody = <K extends ClientEnvKey>(key: K) => {
|
||||
if (typeof window === 'undefined')
|
||||
return undefined
|
||||
|
||||
const body = globalThis.document?.body
|
||||
if (!body)
|
||||
return undefined
|
||||
|
||||
const value = body.getAttribute(getDatasetAttrKeyFromEnvKey(key))
|
||||
if (value !== null && value !== '')
|
||||
return value
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
ANALYZE: optionalString,
|
||||
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: optionalString,
|
||||
NEXT_TELEMETRY_DISABLED: optionalString,
|
||||
PORT: optionalString,
|
||||
TEXT_GENERATION_TIMEOUT_MS: optionalString,
|
||||
},
|
||||
shared: {
|
||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||
},
|
||||
client: clientSchema,
|
||||
experimental__runtimeEnv: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NEXT_PUBLIC_ALLOW_EMBED: isServer ? process.env.NEXT_PUBLIC_ALLOW_EMBED : getRuntimeEnvFromBody('NEXT_PUBLIC_ALLOW_EMBED'),
|
||||
NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME: isServer ? process.env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME : getRuntimeEnvFromBody('NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME'),
|
||||
NEXT_PUBLIC_AMPLITUDE_API_KEY: isServer ? process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY : getRuntimeEnvFromBody('NEXT_PUBLIC_AMPLITUDE_API_KEY'),
|
||||
NEXT_PUBLIC_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_API_PREFIX : getRuntimeEnvFromBody('NEXT_PUBLIC_API_PREFIX'),
|
||||
NEXT_PUBLIC_BASE_PATH: isServer ? process.env.NEXT_PUBLIC_BASE_PATH : getRuntimeEnvFromBody('NEXT_PUBLIC_BASE_PATH'),
|
||||
NEXT_PUBLIC_BATCH_CONCURRENCY: isServer ? process.env.NEXT_PUBLIC_BATCH_CONCURRENCY : getRuntimeEnvFromBody('NEXT_PUBLIC_BATCH_CONCURRENCY'),
|
||||
NEXT_PUBLIC_COOKIE_DOMAIN: isServer ? process.env.NEXT_PUBLIC_COOKIE_DOMAIN : getRuntimeEnvFromBody('NEXT_PUBLIC_COOKIE_DOMAIN'),
|
||||
NEXT_PUBLIC_CSP_WHITELIST: isServer ? process.env.NEXT_PUBLIC_CSP_WHITELIST : getRuntimeEnvFromBody('NEXT_PUBLIC_CSP_WHITELIST'),
|
||||
NEXT_PUBLIC_DEPLOY_ENV: isServer ? process.env.NEXT_PUBLIC_DEPLOY_ENV : getRuntimeEnvFromBody('NEXT_PUBLIC_DEPLOY_ENV'),
|
||||
NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON: isServer ? process.env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON : getRuntimeEnvFromBody('NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON'),
|
||||
NEXT_PUBLIC_EDITION: isServer ? process.env.NEXT_PUBLIC_EDITION : getRuntimeEnvFromBody('NEXT_PUBLIC_EDITION'),
|
||||
NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX: isServer ? process.env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX : getRuntimeEnvFromBody('NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX'),
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL : getRuntimeEnvFromBody('NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL'),
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER : getRuntimeEnvFromBody('NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER'),
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL : getRuntimeEnvFromBody('NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL'),
|
||||
NEXT_PUBLIC_GITHUB_ACCESS_TOKEN: isServer ? process.env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN : getRuntimeEnvFromBody('NEXT_PUBLIC_GITHUB_ACCESS_TOKEN'),
|
||||
NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: isServer ? process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH : getRuntimeEnvFromBody('NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH'),
|
||||
NEXT_PUBLIC_IS_MARKETPLACE: isServer ? process.env.NEXT_PUBLIC_IS_MARKETPLACE : getRuntimeEnvFromBody('NEXT_PUBLIC_IS_MARKETPLACE'),
|
||||
NEXT_PUBLIC_LOOP_NODE_MAX_COUNT: isServer ? process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT : getRuntimeEnvFromBody('NEXT_PUBLIC_LOOP_NODE_MAX_COUNT'),
|
||||
NEXT_PUBLIC_MAINTENANCE_NOTICE: isServer ? process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE : getRuntimeEnvFromBody('NEXT_PUBLIC_MAINTENANCE_NOTICE'),
|
||||
NEXT_PUBLIC_MARKETPLACE_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_MARKETPLACE_API_PREFIX : getRuntimeEnvFromBody('NEXT_PUBLIC_MARKETPLACE_API_PREFIX'),
|
||||
NEXT_PUBLIC_MARKETPLACE_URL_PREFIX: isServer ? process.env.NEXT_PUBLIC_MARKETPLACE_URL_PREFIX : getRuntimeEnvFromBody('NEXT_PUBLIC_MARKETPLACE_URL_PREFIX'),
|
||||
NEXT_PUBLIC_MAX_ITERATIONS_NUM: isServer ? process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM : getRuntimeEnvFromBody('NEXT_PUBLIC_MAX_ITERATIONS_NUM'),
|
||||
NEXT_PUBLIC_MAX_PARALLEL_LIMIT: isServer ? process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT : getRuntimeEnvFromBody('NEXT_PUBLIC_MAX_PARALLEL_LIMIT'),
|
||||
NEXT_PUBLIC_MAX_TOOLS_NUM: isServer ? process.env.NEXT_PUBLIC_MAX_TOOLS_NUM : getRuntimeEnvFromBody('NEXT_PUBLIC_MAX_TOOLS_NUM'),
|
||||
NEXT_PUBLIC_MAX_TREE_DEPTH: isServer ? process.env.NEXT_PUBLIC_MAX_TREE_DEPTH : getRuntimeEnvFromBody('NEXT_PUBLIC_MAX_TREE_DEPTH'),
|
||||
NEXT_PUBLIC_PUBLIC_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX : getRuntimeEnvFromBody('NEXT_PUBLIC_PUBLIC_API_PREFIX'),
|
||||
NEXT_PUBLIC_SENTRY_DSN: isServer ? process.env.NEXT_PUBLIC_SENTRY_DSN : getRuntimeEnvFromBody('NEXT_PUBLIC_SENTRY_DSN'),
|
||||
NEXT_PUBLIC_SITE_ABOUT: isServer ? process.env.NEXT_PUBLIC_SITE_ABOUT : getRuntimeEnvFromBody('NEXT_PUBLIC_SITE_ABOUT'),
|
||||
NEXT_PUBLIC_SUPPORT_MAIL_LOGIN: isServer ? process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN : getRuntimeEnvFromBody('NEXT_PUBLIC_SUPPORT_MAIL_LOGIN'),
|
||||
NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS: isServer ? process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS : getRuntimeEnvFromBody('NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS'),
|
||||
NEXT_PUBLIC_TOP_K_MAX_VALUE: isServer ? process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE : getRuntimeEnvFromBody('NEXT_PUBLIC_TOP_K_MAX_VALUE'),
|
||||
NEXT_PUBLIC_UPLOAD_IMAGE_AS_ICON: isServer ? process.env.NEXT_PUBLIC_UPLOAD_IMAGE_AS_ICON : getRuntimeEnvFromBody('NEXT_PUBLIC_UPLOAD_IMAGE_AS_ICON'),
|
||||
NEXT_PUBLIC_WEB_PREFIX: isServer ? process.env.NEXT_PUBLIC_WEB_PREFIX : getRuntimeEnvFromBody('NEXT_PUBLIC_WEB_PREFIX'),
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL: isServer ? process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL : getRuntimeEnvFromBody('NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL'),
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT: isServer ? process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT : getRuntimeEnvFromBody('NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT'),
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN: isServer ? process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN : getRuntimeEnvFromBody('NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN'),
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION: isServer ? process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION : getRuntimeEnvFromBody('NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION'),
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID: isServer ? process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID : getRuntimeEnvFromBody('NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID'),
|
||||
NEXT_PUBLIC_ZENDESK_WIDGET_KEY: isServer ? process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY : getRuntimeEnvFromBody('NEXT_PUBLIC_ZENDESK_WIDGET_KEY'),
|
||||
},
|
||||
emptyStringAsUndefined: true,
|
||||
})
|
||||
@@ -4912,11 +4912,6 @@
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/install/installForm.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/reset-password/layout.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { NextConfig } from 'next'
|
||||
import process from 'node:process'
|
||||
import withBundleAnalyzerInit from '@next/bundle-analyzer'
|
||||
import createMDX from '@next/mdx'
|
||||
import { codeInspectorPlugin } from 'code-inspector-plugin'
|
||||
import { env } from './env'
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
const isDev = env.NODE_ENV === 'development'
|
||||
const withMDX = createMDX({
|
||||
extension: /\.mdx?$/,
|
||||
options: {
|
||||
@@ -18,19 +18,19 @@ const withMDX = createMDX({
|
||||
},
|
||||
})
|
||||
const withBundleAnalyzer = withBundleAnalyzerInit({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
enabled: env.ANALYZE === 'true',
|
||||
})
|
||||
|
||||
// the default url to prevent parse url error when running jest
|
||||
const hasSetWebPrefix = process.env.NEXT_PUBLIC_WEB_PREFIX
|
||||
const port = process.env.PORT || 3000
|
||||
const hasSetWebPrefix = env.NEXT_PUBLIC_WEB_PREFIX
|
||||
const port = env.PORT || '3000'
|
||||
const locImageURLs = !hasSetWebPrefix ? [new URL(`http://localhost:${port}/**`), new URL(`http://127.0.0.1:${port}/**`)] : []
|
||||
const remoteImageURLs = ([hasSetWebPrefix ? new URL(`${process.env.NEXT_PUBLIC_WEB_PREFIX}/**`) : '', ...locImageURLs].filter(item => !!item)) as URL[]
|
||||
const remoteImageURLs = ([hasSetWebPrefix ? new URL(`${env.NEXT_PUBLIC_WEB_PREFIX}/**`) : '', ...locImageURLs].filter(item => !!item)) as URL[]
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||
basePath: env.NEXT_PUBLIC_BASE_PATH || '',
|
||||
serverExternalPackages: ['esbuild'],
|
||||
transpilePackages: ['echarts', 'zrender'],
|
||||
transpilePackages: ['@t3-oss/env-core', '@t3-oss/env-nextjs', 'echarts', 'zrender'],
|
||||
turbopack: {
|
||||
rules: codeInspectorPlugin({
|
||||
bundler: 'turbopack',
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"@remixicon/react": "4.7.0",
|
||||
"@sentry/react": "8.55.0",
|
||||
"@svgdotjs/svg.js": "3.2.5",
|
||||
"@t3-oss/env-nextjs": "0.13.10",
|
||||
"@tailwindcss/typography": "0.5.19",
|
||||
"@tanstack/react-form": "1.23.7",
|
||||
"@tanstack/react-query": "5.90.5",
|
||||
@@ -159,7 +160,7 @@
|
||||
"ufo": "1.6.3",
|
||||
"use-context-selector": "2.0.0",
|
||||
"uuid": "10.0.0",
|
||||
"zod": "3.25.76",
|
||||
"zod": "4.3.6",
|
||||
"zundo": "2.3.0",
|
||||
"zustand": "5.0.9"
|
||||
},
|
||||
|
||||
70
web/pnpm-lock.yaml
generated
70
web/pnpm-lock.yaml
generated
@@ -125,6 +125,9 @@ importers:
|
||||
'@svgdotjs/svg.js':
|
||||
specifier: 3.2.5
|
||||
version: 3.2.5
|
||||
'@t3-oss/env-nextjs':
|
||||
specifier: 0.13.10
|
||||
version: 0.13.10(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)
|
||||
'@tailwindcss/typography':
|
||||
specifier: 0.5.19
|
||||
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))
|
||||
@@ -357,8 +360,8 @@ importers:
|
||||
specifier: 10.0.0
|
||||
version: 10.0.0
|
||||
zod:
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
specifier: 4.3.6
|
||||
version: 4.3.6
|
||||
zundo:
|
||||
specifier: 2.3.0
|
||||
version: 2.3.0(zustand@5.0.9(@types/react@19.2.9)(immer@11.1.0)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)))
|
||||
@@ -2817,6 +2820,40 @@ packages:
|
||||
'@swc/types@0.1.25':
|
||||
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
|
||||
|
||||
'@t3-oss/env-core@0.13.10':
|
||||
resolution: {integrity: sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g==}
|
||||
peerDependencies:
|
||||
arktype: ^2.1.0
|
||||
typescript: '>=5.0.0'
|
||||
valibot: ^1.0.0-beta.7 || ^1.0.0
|
||||
zod: ^3.24.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
arktype:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
valibot:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@t3-oss/env-nextjs@0.13.10':
|
||||
resolution: {integrity: sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ==}
|
||||
peerDependencies:
|
||||
arktype: ^2.1.0
|
||||
typescript: '>=5.0.0'
|
||||
valibot: ^1.0.0-beta.7 || ^1.0.0
|
||||
zod: ^3.24.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
arktype:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
valibot:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/typography@0.5.19':
|
||||
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
|
||||
peerDependencies:
|
||||
@@ -7538,9 +7575,6 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
zod@4.3.6:
|
||||
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
||||
|
||||
@@ -8239,7 +8273,7 @@ snapshots:
|
||||
eslint: 9.39.2(jiti@1.21.7)
|
||||
ts-pattern: 5.9.0
|
||||
typescript: 5.9.3
|
||||
zod: 3.25.76
|
||||
zod: 4.3.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -9954,6 +9988,20 @@ snapshots:
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
|
||||
'@t3-oss/env-core@0.13.10(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)':
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
valibot: 1.2.0(typescript@5.9.3)
|
||||
zod: 4.3.6
|
||||
|
||||
'@t3-oss/env-nextjs@0.13.10(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@t3-oss/env-core': 0.13.10(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
valibot: 1.2.0(typescript@5.9.3)
|
||||
zod: 4.3.6
|
||||
|
||||
'@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
postcss-selector-parser: 6.0.10
|
||||
@@ -11948,8 +11996,8 @@ snapshots:
|
||||
'@babel/parser': 7.28.6
|
||||
eslint: 9.39.2(jiti@1.21.7)
|
||||
hermes-parser: 0.25.1
|
||||
zod: 3.25.76
|
||||
zod-validation-error: 4.0.2(zod@3.25.76)
|
||||
zod: 4.3.6
|
||||
zod-validation-error: 4.0.2(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -15595,11 +15643,9 @@ snapshots:
|
||||
|
||||
zen-observable@0.8.15: {}
|
||||
|
||||
zod-validation-error@4.0.2(zod@3.25.76):
|
||||
zod-validation-error@4.0.2(zod@4.3.6):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod@3.25.76: {}
|
||||
zod: 4.3.6
|
||||
|
||||
zod@4.3.6: {}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { env } from '@/env'
|
||||
|
||||
const NECESSARY_DOMAIN = '*.sentry.io http://localhost:* http://127.0.0.1:* https://analytics.google.com googletagmanager.com *.googletagmanager.com https://www.google-analytics.com https://api.github.com https://api2.amplitude.com *.amplitude.com'
|
||||
|
||||
const wrapResponseWithXFrameOptions = (response: NextResponse, pathname: string) => {
|
||||
// prevent clickjacking: https://owasp.org/www-community/attacks/Clickjacking
|
||||
// Chatbot page should be allowed to be embedded in iframe. It's a feature
|
||||
if (process.env.NEXT_PUBLIC_ALLOW_EMBED !== 'true' && !pathname.startsWith('/chat') && !pathname.startsWith('/workflow') && !pathname.startsWith('/completion') && !pathname.startsWith('/webapp-signin'))
|
||||
if (env.NEXT_PUBLIC_ALLOW_EMBED !== 'true' && !pathname.startsWith('/chat') && !pathname.startsWith('/workflow') && !pathname.startsWith('/completion') && !pathname.startsWith('/webapp-signin'))
|
||||
response.headers.set('X-Frame-Options', 'DENY')
|
||||
|
||||
return response
|
||||
@@ -21,11 +22,11 @@ export function proxy(request: NextRequest) {
|
||||
},
|
||||
})
|
||||
|
||||
const isWhiteListEnabled = !!process.env.NEXT_PUBLIC_CSP_WHITELIST && process.env.NODE_ENV === 'production'
|
||||
const isWhiteListEnabled = !!env.NEXT_PUBLIC_CSP_WHITELIST && env.NODE_ENV === 'production'
|
||||
if (!isWhiteListEnabled)
|
||||
return wrapResponseWithXFrameOptions(response, pathname)
|
||||
|
||||
const whiteList = `${process.env.NEXT_PUBLIC_CSP_WHITELIST} ${NECESSARY_DOMAIN}`
|
||||
const whiteList = `${env.NEXT_PUBLIC_CSP_WHITELIST} ${NECESSARY_DOMAIN}`
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
|
||||
const csp = `'nonce-${nonce}'`
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const loadGetBaseURL = async (isClientValue: boolean) => {
|
||||
vi.resetModules()
|
||||
vi.doMock('@/utils/client', () => ({ isClient: isClientValue }))
|
||||
vi.doMock('@/utils/client', () => ({ isClient: isClientValue, isServer: !isClientValue }))
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
// eslint-disable-next-line next/no-assign-module-variable
|
||||
const module = await import('./client')
|
||||
|
||||
@@ -107,37 +107,3 @@ export const defaultSystemFeatures: SystemFeatures = {
|
||||
enable_trial_app: false,
|
||||
enable_explore_banner: false,
|
||||
}
|
||||
|
||||
export enum DatasetAttr {
|
||||
DATA_API_PREFIX = 'data-api-prefix',
|
||||
DATA_PUBLIC_API_PREFIX = 'data-public-api-prefix',
|
||||
DATA_MARKETPLACE_API_PREFIX = 'data-marketplace-api-prefix',
|
||||
DATA_MARKETPLACE_URL_PREFIX = 'data-marketplace-url-prefix',
|
||||
DATA_PUBLIC_EDITION = 'data-public-edition',
|
||||
DATA_PUBLIC_AMPLITUDE_API_KEY = 'data-public-amplitude-api-key',
|
||||
DATA_PUBLIC_COOKIE_DOMAIN = 'data-public-cookie-domain',
|
||||
DATA_PUBLIC_SUPPORT_MAIL_LOGIN = 'data-public-support-mail-login',
|
||||
DATA_PUBLIC_SENTRY_DSN = 'data-public-sentry-dsn',
|
||||
DATA_PUBLIC_MAINTENANCE_NOTICE = 'data-public-maintenance-notice',
|
||||
DATA_PUBLIC_SITE_ABOUT = 'data-public-site-about',
|
||||
DATA_PUBLIC_TEXT_GENERATION_TIMEOUT_MS = 'data-public-text-generation-timeout-ms',
|
||||
DATA_PUBLIC_MAX_TOOLS_NUM = 'data-public-max-tools-num',
|
||||
DATA_PUBLIC_MAX_PARALLEL_LIMIT = 'data-public-max-parallel-limit',
|
||||
DATA_PUBLIC_TOP_K_MAX_VALUE = 'data-public-top-k-max-value',
|
||||
DATA_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH = 'data-public-indexing-max-segmentation-tokens-length',
|
||||
DATA_PUBLIC_LOOP_NODE_MAX_COUNT = 'data-public-loop-node-max-count',
|
||||
DATA_PUBLIC_MAX_ITERATIONS_NUM = 'data-public-max-iterations-num',
|
||||
DATA_PUBLIC_MAX_TREE_DEPTH = 'data-public-max-tree-depth',
|
||||
DATA_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME = 'data-public-allow-unsafe-data-scheme',
|
||||
DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER = 'data-public-enable-website-jinareader',
|
||||
DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL = 'data-public-enable-website-firecrawl',
|
||||
DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL = 'data-public-enable-website-watercrawl',
|
||||
DATA_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX = 'data-public-enable-single-dollar-latex',
|
||||
NEXT_PUBLIC_ZENDESK_WIDGET_KEY = 'next-public-zendesk-widget-key',
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT = 'next-public-zendesk-field-id-environment',
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION = 'next-public-zendesk-field-id-version',
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL = 'next-public-zendesk-field-id-email',
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID = 'next-public-zendesk-field-id-workspace-id',
|
||||
NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN = 'next-public-zendesk-field-id-plan',
|
||||
DATA_PUBLIC_BATCH_CONCURRENCY = 'data-public-batch-concurrency',
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/app/components/base/prompt-editor/constants'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { getMaxVarNameLength, MARKETPLACE_URL_PREFIX, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW } from '@/config'
|
||||
import { env } from '@/env'
|
||||
|
||||
const otherAllowedRegex = /^\w+$/
|
||||
|
||||
@@ -129,7 +130,7 @@ export const getVars = (value: string) => {
|
||||
|
||||
// Set the value of basePath
|
||||
// example: /dify
|
||||
export const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''
|
||||
export const basePath = env.NEXT_PUBLIC_BASE_PATH || ''
|
||||
|
||||
export function getMarketplaceUrl(path: string, params?: Record<string, string | undefined>) {
|
||||
const searchParams = new URLSearchParams({ source: encodeURIComponent(window.location.origin) })
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import { z, ZodError } from 'zod'
|
||||
|
||||
describe('Zod Features', () => {
|
||||
it('should support string', () => {
|
||||
const stringSchema = z.string()
|
||||
const numberLikeStringSchema = z.coerce.string() // 12 would be converted to '12'
|
||||
const stringSchemaWithError = z.string({
|
||||
required_error: 'Name is required',
|
||||
invalid_type_error: 'Invalid name type, expected string',
|
||||
})
|
||||
|
||||
const urlSchema = z.string().url()
|
||||
const uuidSchema = z.string().uuid()
|
||||
|
||||
expect(stringSchema.parse('hello')).toBe('hello')
|
||||
expect(() => stringSchema.parse(12)).toThrow()
|
||||
expect(numberLikeStringSchema.parse('12')).toBe('12')
|
||||
expect(numberLikeStringSchema.parse(12)).toBe('12')
|
||||
expect(() => stringSchemaWithError.parse(undefined)).toThrow('Name is required')
|
||||
expect(() => stringSchemaWithError.parse(12)).toThrow('Invalid name type, expected string')
|
||||
|
||||
expect(urlSchema.parse('https://dify.ai')).toBe('https://dify.ai')
|
||||
expect(uuidSchema.parse('123e4567-e89b-12d3-a456-426614174000')).toBe('123e4567-e89b-12d3-a456-426614174000')
|
||||
})
|
||||
|
||||
it('should support enum', () => {
|
||||
enum JobStatus {
|
||||
waiting = 'waiting',
|
||||
processing = 'processing',
|
||||
completed = 'completed',
|
||||
}
|
||||
expect(z.nativeEnum(JobStatus).parse(JobStatus.waiting)).toBe(JobStatus.waiting)
|
||||
expect(z.nativeEnum(JobStatus).parse('completed')).toBe('completed')
|
||||
expect(() => z.nativeEnum(JobStatus).parse('invalid')).toThrow()
|
||||
})
|
||||
|
||||
it('should support number', () => {
|
||||
const numberSchema = z.number()
|
||||
const numberWithMin = z.number().gt(0) // alias min
|
||||
const numberWithMinEqual = z.number().gte(0)
|
||||
const numberWithMax = z.number().lt(100) // alias max
|
||||
|
||||
expect(numberSchema.parse(123)).toBe(123)
|
||||
expect(numberWithMin.parse(50)).toBe(50)
|
||||
expect(numberWithMinEqual.parse(0)).toBe(0)
|
||||
expect(() => numberWithMin.parse(-1)).toThrow()
|
||||
expect(numberWithMax.parse(50)).toBe(50)
|
||||
expect(() => numberWithMax.parse(101)).toThrow()
|
||||
})
|
||||
|
||||
it('should support boolean', () => {
|
||||
const booleanSchema = z.boolean()
|
||||
expect(booleanSchema.parse(true)).toBe(true)
|
||||
expect(booleanSchema.parse(false)).toBe(false)
|
||||
expect(() => booleanSchema.parse('true')).toThrow()
|
||||
})
|
||||
|
||||
it('should support date', () => {
|
||||
const dateSchema = z.date()
|
||||
expect(dateSchema.parse(new Date('2023-01-01'))).toEqual(new Date('2023-01-01'))
|
||||
})
|
||||
|
||||
it('should support object', () => {
|
||||
const userSchema = z.object({
|
||||
id: z.union([z.string(), z.number()]),
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
age: z.number().min(0).max(120).optional(),
|
||||
})
|
||||
|
||||
type User = z.infer<typeof userSchema>
|
||||
|
||||
const validUser: User = {
|
||||
id: 1,
|
||||
name: 'John',
|
||||
email: 'john@example.com',
|
||||
age: 30,
|
||||
}
|
||||
|
||||
expect(userSchema.parse(validUser)).toEqual(validUser)
|
||||
})
|
||||
|
||||
it('should support object optional field', () => {
|
||||
const userSchema = z.object({
|
||||
name: z.string(),
|
||||
optionalField: z.optional(z.string()),
|
||||
})
|
||||
type User = z.infer<typeof userSchema>
|
||||
|
||||
const user: User = {
|
||||
name: 'John',
|
||||
}
|
||||
const userWithOptionalField: User = {
|
||||
name: 'John',
|
||||
optionalField: 'optional',
|
||||
}
|
||||
expect(userSchema.safeParse(user).success).toEqual(true)
|
||||
expect(userSchema.safeParse(userWithOptionalField).success).toEqual(true)
|
||||
})
|
||||
|
||||
it('should support object intersection', () => {
|
||||
const Person = z.object({
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
const Employee = z.object({
|
||||
role: z.string(),
|
||||
})
|
||||
|
||||
const EmployedPerson = z.intersection(Person, Employee)
|
||||
const validEmployedPerson = {
|
||||
name: 'John',
|
||||
role: 'Developer',
|
||||
}
|
||||
expect(EmployedPerson.parse(validEmployedPerson)).toEqual(validEmployedPerson)
|
||||
})
|
||||
|
||||
it('should support record', () => {
|
||||
const recordSchema = z.record(z.string(), z.number())
|
||||
const validRecord = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
}
|
||||
expect(recordSchema.parse(validRecord)).toEqual(validRecord)
|
||||
})
|
||||
|
||||
it('should support array', () => {
|
||||
const numbersSchema = z.array(z.number())
|
||||
const stringArraySchema = z.string().array()
|
||||
|
||||
expect(numbersSchema.parse([1, 2, 3])).toEqual([1, 2, 3])
|
||||
expect(stringArraySchema.parse(['a', 'b', 'c'])).toEqual(['a', 'b', 'c'])
|
||||
})
|
||||
|
||||
it('should support promise', async () => {
|
||||
const promiseSchema = z.promise(z.string())
|
||||
const validPromise = Promise.resolve('success')
|
||||
|
||||
await expect(promiseSchema.parse(validPromise)).resolves.toBe('success')
|
||||
})
|
||||
|
||||
it('should support unions', () => {
|
||||
const unionSchema = z.union([z.string(), z.number()])
|
||||
|
||||
expect(unionSchema.parse('success')).toBe('success')
|
||||
expect(unionSchema.parse(404)).toBe(404)
|
||||
})
|
||||
|
||||
it('should support functions', () => {
|
||||
const functionSchema = z.function().args(z.string(), z.number(), z.optional(z.string())).returns(z.number())
|
||||
const validFunction = (name: string, age: number, _optional?: string): number => {
|
||||
return age
|
||||
}
|
||||
expect(functionSchema.safeParse(validFunction).success).toEqual(true)
|
||||
})
|
||||
|
||||
it('should support undefined, null, any, and void', () => {
|
||||
const undefinedSchema = z.undefined()
|
||||
const nullSchema = z.null()
|
||||
const anySchema = z.any()
|
||||
|
||||
expect(undefinedSchema.parse(undefined)).toBeUndefined()
|
||||
expect(nullSchema.parse(null)).toBeNull()
|
||||
expect(anySchema.parse('anything')).toBe('anything')
|
||||
expect(anySchema.parse(3)).toBe(3)
|
||||
})
|
||||
|
||||
it('should safeParse would not throw', () => {
|
||||
expect(z.string().safeParse('abc').success).toBe(true)
|
||||
expect(z.string().safeParse(123).success).toBe(false)
|
||||
expect(z.string().safeParse(123).error).toBeInstanceOf(ZodError)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user