merge main

This commit is contained in:
zxhlyh 2025-05-15 10:14:40 +08:00
commit 11977596c9
323 changed files with 9307 additions and 156 deletions

View File

@ -476,6 +476,7 @@ LOGIN_LOCKOUT_DURATION=86400
ENABLE_OTEL=false
OTLP_BASE_ENDPOINT=http://localhost:4318
OTLP_API_KEY=
OTEL_EXPORTER_OTLP_PROTOCOL=
OTEL_EXPORTER_TYPE=otlp
OTEL_SAMPLING_RATE=0.1
OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000

View File

@ -27,6 +27,11 @@ class OTelConfig(BaseSettings):
default="otlp",
)
OTEL_EXPORTER_OTLP_PROTOCOL: str = Field(
description="OTLP exporter protocol ('grpc' or 'http')",
default="http",
)
OTEL_SAMPLING_RATE: float = Field(default=0.1, description="Sampling rate for traces (0.0 to 1.0)")
OTEL_BATCH_EXPORT_SCHEDULE_DELAY: int = Field(

View File

@ -1,5 +1,7 @@
from flask_restful import fields
from libs.helper import AppIconUrlField
parameters__system_parameters = {
"image_file_size_limit": fields.Integer,
"video_file_size_limit": fields.Integer,
@ -22,3 +24,20 @@ parameters_fields = {
"file_upload": fields.Raw,
"system_parameters": fields.Nested(parameters__system_parameters),
}
site_fields = {
"title": fields.String,
"chat_color_theme": fields.String,
"chat_color_theme_inverted": fields.Boolean,
"icon_type": fields.String,
"icon": fields.String,
"icon_background": fields.String,
"icon_url": AppIconUrlField,
"description": fields.String,
"copyright": fields.String,
"privacy_policy": fields.String,
"custom_disclaimer": fields.String,
"default_language": fields.String,
"show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
}

View File

@ -6,6 +6,6 @@ bp = Blueprint("service_api", __name__, url_prefix="/v1")
api = ExternalApi(bp)
from . import index
from .app import annotation, app, audio, completion, conversation, file, message, workflow
from .app import annotation, app, audio, completion, conversation, file, message, site, workflow
from .dataset import dataset, document, hit_testing, metadata, segment, upload_file
from .workspace import models

View File

@ -0,0 +1,30 @@
from flask_restful import Resource, marshal_with
from werkzeug.exceptions import Forbidden
from controllers.common import fields
from controllers.service_api import api
from controllers.service_api.wraps import validate_app_token
from extensions.ext_database import db
from models.account import TenantStatus
from models.model import App, Site
class AppSiteApi(Resource):
"""Resource for app sites."""
@validate_app_token
@marshal_with(fields.site_fields)
def get(self, app_model: App):
"""Retrieve app site info."""
site = db.session.query(Site).filter(Site.app_id == app_model.id).first()
if not site:
raise Forbidden()
if app_model.tenant.status == TenantStatus.ARCHIVE:
raise Forbidden()
return site
api.add_resource(AppSiteApi, "/site")

View File

@ -313,7 +313,7 @@ class DatasetApi(DatasetApiResource):
try:
if DatasetService.delete_dataset(dataset_id_str, current_user):
DatasetPermissionService.clear_partial_member_list(dataset_id_str)
return {"result": "success"}, 204
return 204
else:
raise NotFound("Dataset not found.")
except services.errors.dataset.DatasetInUseError:

View File

@ -323,7 +323,7 @@ class DocumentDeleteApi(DatasetApiResource):
except services.errors.document.DocumentIndexingError:
raise DocumentIndexingError("Cannot delete document during indexing.")
return {"result": "success"}, 204
return 204
class DocumentListApi(DatasetApiResource):

View File

@ -159,7 +159,7 @@ class DatasetSegmentApi(DatasetApiResource):
if not segment:
raise NotFound("Segment not found.")
SegmentService.delete_segment(segment, document, dataset)
return {"result": "success"}, 204
return 204
@cloud_edition_billing_resource_check("vector_space", "dataset")
def post(self, tenant_id, dataset_id, document_id, segment_id):
@ -344,7 +344,7 @@ class DatasetChildChunkApi(DatasetApiResource):
except ChildChunkDeleteIndexServiceError as e:
raise ChildChunkDeleteIndexError(str(e))
return {"result": "success"}, 204
return 204
@cloud_edition_billing_resource_check("vector_space", "dataset")
@cloud_edition_billing_knowledge_limit_check("add_segment", "dataset")

View File

@ -264,6 +264,7 @@ class KnowledgeRetrievalNode(LLMNode):
"data_source_type": "external",
"retriever_from": "workflow",
"score": item.metadata.get("score"),
"doc_metadata": item.metadata,
},
"title": item.metadata.get("title"),
"content": item.page_content,

View File

@ -39,6 +39,10 @@ def init_app(app: DifyApp):
handlers=log_handlers,
force=True,
)
# Apply RequestIdFormatter to all handlers
apply_request_id_formatter()
# Disable propagation for noisy loggers to avoid duplicate logs
logging.getLogger("sqlalchemy.engine").propagate = False
log_tz = dify_config.LOG_TZ
@ -74,3 +78,16 @@ class RequestIdFilter(logging.Filter):
def filter(self, record):
record.req_id = get_request_id() if flask.has_request_context() else ""
return True
class RequestIdFormatter(logging.Formatter):
def format(self, record):
if not hasattr(record, "req_id"):
record.req_id = ""
return super().format(record)
def apply_request_id_formatter():
for handler in logging.root.handlers:
if handler.formatter:
handler.formatter = RequestIdFormatter(dify_config.LOG_FORMAT, dify_config.LOG_DATEFORMAT)

View File

@ -114,8 +114,10 @@ def init_app(app: DifyApp):
pass
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter as GRPCMetricExporter
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCSpanExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as HTTPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPSpanExporter
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
@ -158,19 +160,32 @@ def init_app(app: DifyApp):
sampler = ParentBasedTraceIdRatio(dify_config.OTEL_SAMPLING_RATE)
provider = TracerProvider(resource=resource, sampler=sampler)
set_tracer_provider(provider)
exporter: Union[OTLPSpanExporter, ConsoleSpanExporter]
metric_exporter: Union[OTLPMetricExporter, ConsoleMetricExporter]
exporter: Union[GRPCSpanExporter, HTTPSpanExporter, ConsoleSpanExporter]
metric_exporter: Union[GRPCMetricExporter, HTTPMetricExporter, ConsoleMetricExporter]
protocol = (dify_config.OTEL_EXPORTER_OTLP_PROTOCOL or "").lower()
if dify_config.OTEL_EXPORTER_TYPE == "otlp":
exporter = OTLPSpanExporter(
endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/traces",
headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"},
)
metric_exporter = OTLPMetricExporter(
endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/metrics",
headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"},
)
if protocol == "grpc":
exporter = GRPCSpanExporter(
endpoint=dify_config.OTLP_BASE_ENDPOINT,
# Header field names must consist of lowercase letters, check RFC7540
headers=(("authorization", f"Bearer {dify_config.OTLP_API_KEY}"),),
insecure=True,
)
metric_exporter = GRPCMetricExporter(
endpoint=dify_config.OTLP_BASE_ENDPOINT,
headers=(("authorization", f"Bearer {dify_config.OTLP_API_KEY}"),),
insecure=True,
)
else:
exporter = HTTPSpanExporter(
endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/traces",
headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"},
)
metric_exporter = HTTPMetricExporter(
endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/metrics",
headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"},
)
else:
# Fallback to console exporter
exporter = ConsoleSpanExporter()
metric_exporter = ConsoleMetricExporter()

View File

@ -0,0 +1,33 @@
"""add index for workflow_conversation_variables.conversation_id
Revision ID: d28f2004b072
Revises: 6a9f914f656c
Create Date: 2025-05-14 14:03:36.713828
"""
from alembic import op
import models as models
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd28f2004b072'
down_revision = '6a9f914f656c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op:
batch_op.create_index(batch_op.f('workflow_conversation_variables_conversation_id_idx'), ['conversation_id'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('workflow_conversation_variables_conversation_id_idx'))
# ### end Alembic commands ###

View File

@ -770,7 +770,7 @@ class ConversationVariable(Base):
__tablename__ = "workflow_conversation_variables"
id: Mapped[str] = mapped_column(StringUUID, primary_key=True)
conversation_id: Mapped[str] = mapped_column(StringUUID, nullable=False, primary_key=True)
conversation_id: Mapped[str] = mapped_column(StringUUID, nullable=False, primary_key=True, index=True)
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False, index=True)
data = mapped_column(db.Text, nullable=False)
created_at = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp(), index=True)

10
dev/start-api Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
set -x
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
cd "$SCRIPT_DIR/.."
uv --directory api run \
flask run --host 0.0.0.0 --port=5001 --debug

11
dev/start-worker Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
set -x
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
cd "$SCRIPT_DIR/.."
uv --directory api run \
celery -A app.celery worker \
-P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion

View File

@ -1081,6 +1081,7 @@ PLUGIN_TENCENT_COS_REGION=
ENABLE_OTEL=false
OTLP_BASE_ENDPOINT=http://localhost:4318
OTLP_API_KEY=
OTEL_EXPORTER_OTLP_PROTOCOL=
OTEL_EXPORTER_TYPE=otlp
OTEL_SAMPLING_RATE=0.1
OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000

View File

@ -478,6 +478,7 @@ x-shared-env: &shared-api-worker-env
ENABLE_OTEL: ${ENABLE_OTEL:-false}
OTLP_BASE_ENDPOINT: ${OTLP_BASE_ENDPOINT:-http://localhost:4318}
OTLP_API_KEY: ${OTLP_API_KEY:-}
OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-}
OTEL_EXPORTER_TYPE: ${OTEL_EXPORTER_TYPE:-otlp}
OTEL_SAMPLING_RATE: ${OTEL_SAMPLING_RATE:-0.1}
OTEL_BATCH_EXPORT_SCHEDULE_DELAY: ${OTEL_BATCH_EXPORT_SCHEDULE_DELAY:-5000}

View File

@ -262,7 +262,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
{
currentTab === CreateFromDSLModalTab.FROM_URL && (
<div>
<div className='system-md-semibold leading6 mb-1'>DSL URL</div>
<div className='system-md-semibold mb-1 text-text-secondary'>DSL URL</div>
<Input
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
value={dslUrlValue}

View File

@ -3,6 +3,7 @@ import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import {
RiDeleteBinLine,
RiUploadCloud2Line,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
@ -10,8 +11,7 @@ import { formatFileSize } from '@/utils/format'
import cn from '@/utils/classnames'
import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files'
import { ToastContext } from '@/app/components/base/toast'
import { UploadCloud01 } from '@/app/components/base/icons/src/vender/line/general'
import Button from '@/app/components/base/button'
import ActionButton from '@/app/components/base/action-button'
export type Props = {
file: File | undefined
@ -102,19 +102,19 @@ const Uploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('flex h-12 items-center rounded-xl border border-dashed border-gray-200 bg-gray-50 text-sm font-normal', dragging && 'border border-[#B2CCFF] bg-[#F5F8FF]')}>
<div className={cn('flex h-12 items-center rounded-[10px] border border-dashed border-components-dropzone-border bg-components-dropzone-bg text-sm font-normal', dragging && 'border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}>
<div className='flex w-full items-center justify-center space-x-2'>
<UploadCloud01 className='mr-2 h-6 w-6' />
<div className='text-gray-500'>
<RiUploadCloud2Line className='h-6 w-6 text-text-tertiary' />
<div className='text-text-tertiary'>
{t('datasetCreation.stepOne.uploader.button')}
<span className='cursor-pointer pl-1 text-[#155eef]' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span>
<span className='cursor-pointer pl-1 text-text-accent' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span>
</div>
</div>
{dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />}
</div>
)}
{file && (
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', 'hover:border-[#B2CCFF] hover:bg-[#F5F8FF]')}>
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', ' hover:bg-components-panel-on-panel-item-bg-hover')}>
<div className='flex items-center justify-center p-3'>
<YamlIcon className="h-6 w-6 shrink-0" />
</div>
@ -126,12 +126,10 @@ const Uploader: FC<Props> = ({
<span>{formatFileSize(file.size)}</span>
</div>
</div>
<div className='hidden items-center group-hover:flex'>
<Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
<div className='mx-2 h-4 w-px bg-gray-200' />
<div className='cursor-pointer p-2' onClick={removeFile}>
<div className='hidden items-center pr-3 group-hover:flex'>
<ActionButton onClick={removeFile}>
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
</div>
</ActionButton>
</div>
</div>
)}

View File

@ -5,6 +5,8 @@ import type {
import {
memo,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import type { ChatItem } from '../types'
@ -52,6 +54,8 @@ const Question: FC<QuestionProps> = ({
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState(content)
const [contentWidth, setContentWidth] = useState(0)
const contentRef = useRef<HTMLDivElement>(null)
const handleEdit = useCallback(() => {
setIsEditing(true)
@ -75,14 +79,31 @@ const Question: FC<QuestionProps> = ({
item.nextSibling && switchSibling?.(item.nextSibling)
}, [switchSibling, item.prevSibling, item.nextSibling])
const getContentWidth = () => {
if (contentRef.current)
setContentWidth(contentRef.current?.clientWidth)
}
useEffect(() => {
if (!contentRef.current)
return
const resizeObserver = new ResizeObserver(() => {
getContentWidth()
})
resizeObserver.observe(contentRef.current)
return () => {
resizeObserver.disconnect()
}
}, [])
return (
<div className='mb-2 flex justify-end pl-14 last:mb-0'>
<div className={cn('group relative mr-4 flex max-w-full items-start', isEditing && 'flex-1')}>
<div className='mb-2 flex justify-end last:mb-0'>
<div className={cn('group relative mr-4 flex max-w-full items-start pl-14', isEditing && 'flex-1')}>
<div className={cn('mr-2 gap-1', isEditing ? 'hidden' : 'flex')}>
<div className="
absolutegap-0.5 hidden rounded-[10px] border-[0.5px] border-components-actionbar-border
bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex
">
<div
className="absolute hidden gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex"
style={{ right: contentWidth + 8 }}
>
<ActionButton onClick={() => {
copy(content)
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
@ -95,6 +116,7 @@ const Question: FC<QuestionProps> = ({
</div>
</div>
<div
ref={contentRef}
className='w-full rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900'
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
>

View File

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon L">
<g id="Vector">
<path d="M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z" fill="#354052"/>
<path d="M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z" fill="#354052"/>
<path d="M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z" fill="#354052"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 579 B

View File

@ -0,0 +1,62 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon L"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Vector"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
}
]
},
"name": "Collapse"
}

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Collapse.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Collapse'
export default Icon

View File

@ -1,5 +1,6 @@
export { default as AlignLeft } from './AlignLeft'
export { default as BezierCurve03 } from './BezierCurve03'
export { default as Collapse } from './Collapse'
export { default as Colors } from './Colors'
export { default as ImageIndentLeft } from './ImageIndentLeft'
export { default as LeftIndent02 } from './LeftIndent02'

View File

@ -0,0 +1,97 @@
'use client'
import { useState } from 'react'
import {
RiCheckLine,
RiComputerLine,
RiMoonLine,
RiSunLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useTheme } from 'next-themes'
import ActionButton from '@/app/components/base/action-button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
export type Theme = 'light' | 'dark' | 'system'
export default function ThemeSelector() {
const { t } = useTranslation()
const { theme, setTheme } = useTheme()
const [open, setOpen] = useState(false)
const handleThemeChange = (newTheme: Theme) => {
setTheme(newTheme)
setOpen(false)
}
const getCurrentIcon = () => {
switch (theme) {
case 'light': return <RiSunLine className='h-4 w-4 text-text-tertiary' />
case 'dark': return <RiMoonLine className='h-4 w-4 text-text-tertiary' />
default: return <RiComputerLine className='h-4 w-4 text-text-tertiary' />
}
}
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{ mainAxis: 6 }}
>
<PortalToFollowElemTrigger
onClick={() => setOpen(!open)}
>
<ActionButton
className={`h-8 w-8 p-[6px] ${open && 'bg-state-base-hover'}`}
>
{getCurrentIcon()}
</ActionButton>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<div className='flex w-[144px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<button
className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleThemeChange('light')}
>
<RiSunLine className='h-4 w-4 text-text-tertiary' />
<div className='flex grow items-center justify-start px-1'>
<span className='system-md-regular'>{t('common.theme.light')}</span>
</div>
{theme === 'light' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'>
<RiCheckLine className='h-4 w-4 text-text-accent' />
</div>}
</button>
<button
className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleThemeChange('dark')}
>
<RiMoonLine className='h-4 w-4 text-text-tertiary' />
<div className='flex grow items-center justify-start px-1'>
<span className='system-md-regular'>{t('common.theme.dark')}</span>
</div>
{theme === 'dark' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'>
<RiCheckLine className='h-4 w-4 text-text-accent' />
</div>}
</button>
<button
className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleThemeChange('system')}
>
<RiComputerLine className='h-4 w-4 text-text-tertiary' />
<div className='flex grow items-center justify-start px-1'>
<span className='system-md-regular'>{t('common.theme.auto')}</span>
</div>
{theme === 'system' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'>
<RiCheckLine className='h-4 w-4 text-text-accent' />
</div>}
</button>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}

View File

@ -0,0 +1,58 @@
'use client'
import {
RiComputerLine,
RiMoonLine,
RiSunLine,
} from '@remixicon/react'
import { useTheme } from 'next-themes'
import cn from '@/utils/classnames'
export type Theme = 'light' | 'dark' | 'system'
export default function ThemeSwitcher() {
const { theme, setTheme } = useTheme()
const handleThemeChange = (newTheme: Theme) => {
setTheme(newTheme)
}
return (
<div className='flex items-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5'>
<div
className={cn(
'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
theme === 'system' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only',
)}
onClick={() => handleThemeChange('system')}
>
<div className='p-0.5'>
<RiComputerLine className='h-4 w-4' />
</div>
</div>
<div className={cn('h-[14px] w-px bg-transparent', theme === 'dark' && 'bg-divider-regular')}></div>
<div
className={cn(
'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
theme === 'light' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only',
)}
onClick={() => handleThemeChange('light')}
>
<div className='p-0.5'>
<RiSunLine className='h-4 w-4' />
</div>
</div>
<div className={cn('h-[14px] w-px bg-transparent', theme === 'system' && 'bg-divider-regular')}></div>
<div
className={cn(
'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
theme === 'dark' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only',
)}
onClick={() => handleThemeChange('dark')}
>
<div className='p-0.5'>
<RiMoonLine className='h-4 w-4' />
</div>
</div>
</div>
)
}

View File

@ -121,7 +121,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
</button>
)}
</div>
<article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'dark:prose-invert')}>
<article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'prose-invert')}>
{Template}
</article>
</div>

View File

@ -121,7 +121,7 @@ const Doc = ({ appDetail }: IDocProps) => {
</button>
)}
</div>
<article className={cn('prose-xl prose', theme === Theme.dark && 'dark:prose-invert')} >
<article className={cn('prose-xl prose', theme === Theme.dark && 'prose-invert')} >
{(appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') && (
(() => {
switch (locale) {

View File

@ -2,20 +2,18 @@
import React, { useEffect, useState } from 'react'
import copy from 'copy-to-clipboard'
import { t } from 'i18next'
import s from './style.module.css'
import Tooltip from '@/app/components/base/tooltip'
import CopyFeedback from '@/app/components/base/copy-feedback'
type IInputCopyProps = {
value?: string
className?: string
readOnly?: boolean
children?: React.ReactNode
}
const InputCopy = ({
value = '',
className,
readOnly = true,
children,
}: IInputCopyProps) => {
const [isCopied, setIsCopied] = useState(false)
@ -45,23 +43,12 @@ const InputCopy = ({
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
position='bottom'
>
{value}
<span className='text-text-secondary'>{value}</span>
</Tooltip>
</div>
</div>
<div className="h-4 shrink-0 border bg-divider-regular" />
<Tooltip
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
position='bottom'
>
<div className="shrink-0 px-0.5">
<div className={`box-border flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={() => {
copy(value)
setIsCopied(true)
}}>
</div>
</div>
</Tooltip>
<div className="h-4 w-px shrink-0 bg-divider-regular" />
<div className='mx-1'><CopyFeedback content={value} /></div>
</div>
</div>
)

View File

@ -647,3 +647,62 @@ The text generation application offers non-session support and is ideal for tran
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `chat_color_theme` (string) Chat color theme, in hex format
- `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL.
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
- `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -645,3 +645,62 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### レスポンス
- `title` (string) WebApp名
- `chat_color_theme` (string) チャットの色テーマ、16進数形式
- `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
- `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -507,7 +507,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
</Col>
</Row>
---
<Heading
@ -612,6 +611,64 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
- `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
method='GET'

View File

@ -1305,6 +1305,64 @@ Chat applications support session persistence, allowing previous chat history to
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `chat_color_theme` (string) Chat color theme, in hex format
- `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
- `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
method='GET'

View File

@ -1303,3 +1303,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### 応答
- `title` (string) WebApp名
- `chat_color_theme` (string) チャットの色テーマ、16進数形式
- `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
- `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -1329,7 +1329,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
- `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'

View File

@ -1341,6 +1341,64 @@ Chat applications support session persistence, allowing previous chat history to
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `chat_color_theme` (string) Chat color theme, in hex format
- `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
- `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
method='GET'

View File

@ -1330,3 +1330,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### 応答
- `title` (string) WebApp名
- `chat_color_theme` (string) チャットの色テーマ、16進数形式
- `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
- `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -1332,3 +1332,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
- `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -737,3 +737,56 @@ Workflow applications offers non-session support and is ideal for translation, a
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL.
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -740,3 +740,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
———
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### 応答
- `title` (string) WebApp名
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -727,3 +727,57 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -31,8 +31,8 @@ const Category: FC<ICategoryProps> = ({
const isAllCategories = !list.includes(value as AppCategory) || value === allCategoriesEn
const itemClassName = (isSelected: boolean) => cn(
'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-gray-700 hover:bg-gray-200',
isSelected && 'border-gray-200 bg-white text-primary-600 shadow-xs hover:bg-white',
'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-text-tertiary hover:bg-components-main-nav-nav-button-bg-active',
isSelected && 'border-components-main-nav-nav-button-border bg-components-main-nav-nav-button-bg-active text-components-main-nav-nav-button-text-active shadow-xs',
)
return (
@ -50,7 +50,7 @@ const Category: FC<ICategoryProps> = ({
className={itemClassName(name === value)}
onClick={() => onChange(name)}
>
{categoryI18n[name] ? t(`explore.category.${name}`) : name}
{(categoryI18n as any)[name] ? t(`explore.category.${name}`) : name}
</div>
))}
</div>

View File

@ -14,6 +14,7 @@ import {
RiMap2Line,
RiSettings3Line,
RiStarLine,
RiTShirt2Line,
} from '@remixicon/react'
import Link from 'next/link'
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
@ -25,6 +26,7 @@ import Compliance from './compliance'
import PremiumBadge from '@/app/components/base/premium-badge'
import { useGetDocLanguage } from '@/context/i18n'
import Avatar from '@/app/components/base/avatar'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { logout } from '@/service/common'
import AppContext, { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
@ -82,8 +84,8 @@ export default function AppSelector() {
<MenuItems
className="
absolute right-0 mt-1.5 w-60 max-w-80
origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur
shadow-lg focus:outline-none
origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur shadow-lg
backdrop-blur-sm focus:outline-none
"
>
<MenuItem disabled>
@ -186,6 +188,15 @@ export default function AppSelector() {
)
}
</div>
<MenuItem disabled>
<div className='p-1'>
<div className={cn(itemClassName, 'hover:bg-transparent')}>
<RiTShirt2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.theme.theme')}</div>
<ThemeSwitcher/>
</div>
</div>
</MenuItem>
<MenuItem>
<div className='p-1' onClick={() => handleLogout()}>
<div

View File

@ -1,4 +1,5 @@
import { useCallback, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import {
@ -29,6 +30,7 @@ const InstallFromMarketplace = ({
searchText,
}: InstallFromMarketplaceProps) => {
const { t } = useTranslation()
const { theme } = useTheme()
const [collapse, setCollapse] = useState(false)
const locale = getLocaleOnClient()
const {
@ -53,7 +55,7 @@ const InstallFromMarketplace = ({
</div>
<div className='mb-2 flex items-center pt-2'>
<span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='system-sm-medium inline-flex items-center text-text-accent'>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}${theme ? `?theme=${theme}` : ''}`} className='system-sm-medium inline-flex items-center text-text-accent'>
{t('plugin.marketplace.difyMarketplace')}
<RiArrowRightUpLine className='h-4 w-4' />
</Link>

View File

@ -1,4 +1,5 @@
'use client'
import { useTheme } from 'next-themes'
import { RiArrowRightUpLine } from '@remixicon/react'
import { getPluginLinkInMarketplace } from '../utils'
import Card from '@/app/components/plugins/card'
@ -22,6 +23,7 @@ const CardWrapper = ({
locale,
}: CardWrapperProps) => {
const { t } = useMixedTranslation(locale)
const { theme } = useTheme()
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
@ -54,7 +56,7 @@ const CardWrapper = ({
>
{t('plugin.detailPanel.operation.install')}
</Button>
<a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
<a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
<Button
className='w-full gap-0.5'
>

View File

@ -1,4 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import {
@ -49,6 +50,7 @@ const DetailHeader = ({
onUpdate,
}: Props) => {
const { t } = useTranslation()
const { theme } = useTheme()
const locale = useGetLanguage()
const { checkForUpdates, fetchReleases } = useGitHubReleases()
const { setShowUpdatePluginModal } = useModalContext()
@ -85,9 +87,9 @@ const DetailHeader = ({
if (isFromGitHub)
return `https://github.com/${meta!.repo}`
if (isFromMarketplace)
return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`
return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`
return ''
}, [author, isFromGitHub, isFromMarketplace, meta, name])
}, [author, isFromGitHub, isFromMarketplace, meta, name, theme])
const [isShowUpdateModal, {
setTrue: showUpdateModal,

View File

@ -183,7 +183,7 @@ const ReasoningConfigForm: React.FC<Props> = ({
<>
{isString && (
<Input
className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'rounded-lg border px-3 py-[6px]')}
className={cn(inputsIsFocus[variable] ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'rounded-lg border px-3 py-[6px]')}
value={varInput?.value as string || ''}
onChange={handleMixedTypeChange(variable)}
nodesOutputVars={nodeOutputVars}

View File

@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import React, { useMemo } from 'react'
import { useTheme } from 'next-themes'
import {
RiArrowRightUpLine,
RiBugLine,
@ -38,6 +39,7 @@ const PluginItem: FC<Props> = ({
plugin,
}) => {
const { t } = useTranslation()
const { theme } = useTheme()
const { categoriesMap } = useSingleCategories()
const currentPluginID = usePluginPageContext(v => v.currentPluginID)
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
@ -164,7 +166,7 @@ const PluginItem: FC<Props> = ({
}
{source === PluginSource.marketplace
&& <>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`} target='_blank' className='flex items-center gap-0.5'>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'>
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('plugin.from')} <span className='text-text-secondary'>marketplace</span></div>
<RiArrowRightUpLine className='h-3 w-3 text-text-tertiary' />
</a>

View File

@ -1,6 +1,7 @@
'use client'
import React from 'react'
import type { FC } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { RiArrowRightUpLine } from '@remixicon/react'
import Badge from '../base/badge'
@ -28,6 +29,7 @@ const ProviderCard: FC<Props> = ({
}) => {
const getValueFromI18nObject = useRenderI18nObject()
const { t } = useTranslation()
const { theme } = useTheme()
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
@ -74,7 +76,7 @@ const ProviderCard: FC<Props> = ({
className='grow'
variant='secondary'
>
<a href={`${getPluginLinkInMarketplace(payload)}?language=${locale}`} target='_blank' className='flex items-center gap-0.5'>
<a href={`${getPluginLinkInMarketplace(payload)}?language=${locale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'>
{t('plugin.detailPanel.operation.detail')}
<RiArrowRightUpLine className='h-4 w-4' />
</a>

View File

@ -12,6 +12,8 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Divider from '@/app/components/base/divider'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import InfoModal from './info-modal'
import type { SiteInfo } from '@/models/share'
import cn from '@/utils/classnames'
@ -59,6 +61,13 @@ const MenuDropdown: FC<Props> = ({
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-50'>
<div className='w-[224px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
<div className='p-1'>
<div className={cn('system-md-regular flex cursor-pointer items-center rounded-lg py-1.5 pl-3 pr-2 text-text-secondary')}>
<div className='grow'>{t('common.theme.theme')}</div>
<ThemeSwitcher/>
</div>
</div>
<Divider type='horizontal' className='my-0' />
<div className='p-1'>
{data?.privacy_policy && (
<a href={data.privacy_policy} target='_blank' className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>

View File

@ -2,6 +2,7 @@ import {
useEffect,
useRef,
} from 'react'
import { useTheme } from 'next-themes'
import {
RiArrowRightUpLine,
RiArrowUpDoubleLine,
@ -25,7 +26,7 @@ const Marketplace = ({
}: MarketplaceProps) => {
const locale = getLocaleOnClient()
const { t } = useTranslation()
const { theme } = useTheme()
const {
isLoading,
marketplaceCollections,
@ -83,7 +84,7 @@ const Marketplace = ({
</span>
{t('common.operation.in')}
<a
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}`}
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}${theme ? `&theme=${theme}` : ''}`}
className='system-sm-medium ml-1 flex items-center text-text-accent'
target='_blank'
>

View File

@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { RiMoreFill } from '@remixicon/react'
import ActionButton from '@/app/components/base/action-button'
@ -31,6 +32,7 @@ const OperationDropdown: FC<Props> = ({
version,
}) => {
const { t } = useTranslation()
const { theme } = useTheme()
const openRef = useRef(open)
const setOpen = useCallback((v: boolean) => {
onOpenChange(v)
@ -78,7 +80,7 @@ const OperationDropdown: FC<Props> = ({
<PortalToFollowElemContent className='z-[9999]'>
<div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>

View File

@ -140,6 +140,16 @@ const WorkflowChecklist = ({
</div>
)
}
{
node.varErrorMessage?.map((errorMessage: string) => (
<div className='rounded-b-lg bg-gray-25 px-3 py-2'>
<div className='flex text-xs leading-[18px] text-gray-500'>
<AlertTriangle className='mr-2 mt-[3px] h-3 w-3 text-[#F79009]' />
{errorMessage}
</div>
</div>
))
}
</div>
</div>
))

View File

@ -15,6 +15,7 @@ import { useStore } from '../store'
import {
getToolCheckParams,
getValidTreeNodes,
transformStartNodeVariables,
} from '../utils'
import {
CUSTOM_NODE,
@ -45,6 +46,9 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const { data: strategyProviders } = useStrategyProviders()
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
const chatVarList = useStore(s => s.conversationVariables)
const environmentVariables = useStore(s => s.environmentVariables)
const getCheckData = useCallback((data: CommonNodeType<{}>) => {
let checkData = data
if (data.type === BlockEnum.KnowledgeRetrieval) {
@ -64,7 +68,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const needWarningNodes = useMemo(() => {
const list = []
const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges)
const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true)
const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables)
const errMessageMap = new Map()
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
@ -110,8 +117,32 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
toolIcon,
unConnected: !validNodes.find(n => n.id === node.id),
errorMessage,
varErrorMessage: [],
})
}
errMessageMap.set(node.id, list[list.length - 1])
if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) {
const { errorMessage: varErrorMessages } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t)
if (varErrorMessages?.length) {
const errMessage = errMessageMap.get(node.id)
if (errMessage) {
errMessage.varErrorMessage = varErrorMessages
}
else {
list.push({
id: node.id,
type: node.data.type,
title: node.data.title,
toolIcon,
unConnected: !validNodes.find(n => n.id === node.id),
errorMessage: '',
varErrorMessage: varErrorMessages,
})
errMessageMap.set(node.id, list[list.length - 1])
}
}
}
}
}
@ -133,8 +164,13 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
})
}
for (let i = 0; i < validNodes.length; i++) {
const node = validNodes[i]
delete node._parentOutputVarMap
}
return list
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData])
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData, chatVarList, environmentVariables])
return needWarningNodes
}
@ -153,6 +189,9 @@ export const useChecklistBeforePublish = () => {
const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail)
const updateTime = useRef(0)
const chatVarList = useStore(s => s.conversationVariables)
const environmentVariables = useStore(s => s.environmentVariables)
const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => {
let checkData = data
if (data.type === BlockEnum.KnowledgeRetrieval) {
@ -183,12 +222,15 @@ export const useChecklistBeforePublish = () => {
const {
validNodes,
maxDepth,
} = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges)
} = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true)
if (maxDepth > MAX_TREE_DEPTH) {
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) })
return false
}
const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables)
// Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed
const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => {
@ -239,6 +281,14 @@ export const useChecklistBeforePublish = () => {
notify({ type: 'error', message: `[${node.data.title}] ${t('workflow.common.needConnectTip')}` })
return false
}
if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) {
const { errorMessage: varErrorMessage } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t)
if (varErrorMessage?.length) {
notify({ type: 'error', message: `[${node.data.title}] ${varErrorMessage[0]}` })
return false
}
}
}
if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) {
@ -252,7 +302,7 @@ export const useChecklistBeforePublish = () => {
}
return true
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData])
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, chatVarList, environmentVariables])
return {
handleCheckBeforePublish,

View File

@ -164,7 +164,7 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val
return res
}
const formatItem = (
export const formatItem = (
item: any,
isChatMode: boolean,
filterVar: (payload: Var, selector: ValueSelector) => boolean,

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByText } from '../../utils/workflow'
import type { AnswerNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -24,6 +25,19 @@ const nodeDefault: NodeDefault<AnswerNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: AnswerNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const answer_warnings = getNotExistVariablesByText(payload.answer || '', varMap)
if (answer_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.answer.answer')} ${t('workflow.common.referenceVar')}${answer_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: [...answer_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import { type AssignerNodeType, WriteMode } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -45,6 +46,23 @@ const nodeDefault: NodeDefault<AssignerNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: AssignerNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr: string[] = []
const variables_warnings = getNotExistVariablesByArray(payload.items.map(item => item.variable_selector ?? []) ?? [], varMap)
if (variables_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.assigner.assignedVariable')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`)
const value_warnings = getNotExistVariablesByArray(payload.items.map(item => item.value ?? []) ?? [], varMap)
if (value_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.assigner.setVariable')} ${t('workflow.common.referenceVar')}${value_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: [...variables_warnings, ...value_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import { CodeLanguage, type CodeNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -34,7 +35,20 @@ const nodeDefault: NodeDefault<CodeNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: CodeNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const variables_selector = payload.variables.map(v => v.value_selector)
const variables_selector_warnings = getNotExistVariablesByArray(variables_selector, varMap)
if (variables_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.code.inputVars')} ${t('workflow.common.referenceVar')}${variables_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: variables_selector_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import type { DocExtractorNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -29,6 +30,19 @@ const nodeDefault: NodeDefault<DocExtractorNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: DocExtractorNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr: string[] = []
const variables_warnings = getNotExistVariablesByArray([payload.variable_selector], varMap)
if (variables_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.docExtractor.inputVar')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: variables_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -52,7 +52,7 @@ const Node: FC<NodeProps<EndNodeType>> = ({
isChatMode,
})
return (
<div key={index} className='flex h-6 items-center justify-between space-x-1 rounded-md bg-components-badge-white-to-dark px-1 text-xs font-normal text-text-secondary'>
<div key={index} className='flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'>
<div className='flex items-center text-xs font-medium text-text-tertiary'>
{!isEnv && !isChatVar && (
<>

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow'
import { AuthorizationType, BodyType, Method } from './types'
import type { BodyPayload, HttpNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
@ -44,8 +45,8 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.api') })
if (!errorMessages
&& payload.body.type === BodyType.binary
&& ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0)
&& payload.body.type === BodyType.binary
&& ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0)
)
errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.binaryFileVariable') })
@ -54,6 +55,53 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: HttpNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr: string[] = []
const url_warnings = getNotExistVariablesByText(payload.url, varMap)
if (url_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.http.api')} ${t('workflow.common.referenceVar')}${url_warnings.join('、')}${t('workflow.common.noExist')}`)
const headers_warnings = getNotExistVariablesByText(payload.headers, varMap)
if (headers_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.http.headers')} ${t('workflow.common.referenceVar')}${headers_warnings.join('、')}${t('workflow.common.noExist')}`)
const params_warnings = getNotExistVariablesByText(payload.params, varMap)
if (params_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.http.params')} ${t('workflow.common.referenceVar')}${params_warnings.join('、')}${t('workflow.common.noExist')}`)
const body_warnings: string[] = []
if ([BodyType.binary].includes(payload.body.type)) {
const body_data = payload.body.data as BodyPayload
body_data.forEach((item) => {
const key_warnings = getNotExistVariablesByText(item.key || '', varMap)
if (key_warnings.length)
body_warnings.push(...key_warnings)
const warnings = getNotExistVariablesByArray([item.file || []], varMap)
if (warnings.length)
body_warnings.push(...warnings)
})
}
else {
const body_data = payload.body.data as BodyPayload
body_data.forEach((item) => {
const key_warnings = getNotExistVariablesByText(item.key || '', varMap)
if (key_warnings.length)
body_warnings.push(...key_warnings)
const value_warnings = getNotExistVariablesByText(item.value || '', varMap)
if (value_warnings.length)
body_warnings.push(...value_warnings)
})
}
if (body_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.http.body')} ${t('workflow.common.referenceVar')}${body_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: [...url_warnings, ...headers_warnings, ...params_warnings, ...body_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,6 @@
import type { Var } from '../../types'
import type { NodeDefault } from '../../types'
import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow'
import { type IfElseNodeType, LogicalOperator } from './types'
import { isEmptyRelatedOperator } from './utils'
import { genNodeMetaData } from '@/app/components/workflow/utils'
@ -74,6 +76,41 @@ const nodeDefault: NodeDefault<IfElseNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: IfElseNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const condition_variable_selector_warnings: string[] = []
const condition_value_warnings: string[] = []
payload.cases.forEach((caseItem) => {
caseItem.conditions.forEach((condition) => {
if (!condition.variable_selector)
return
const selector_warnings = getNotExistVariablesByArray([condition.variable_selector], varMap)
if (selector_warnings.length)
condition_variable_selector_warnings.push(...selector_warnings)
const value_warnings = Array.isArray(condition.value) ? getNotExistVariablesByArray([condition.value], varMap) : getNotExistVariablesByText(condition.value, varMap)
if (value_warnings.length)
condition_value_warnings.push(...value_warnings)
condition.sub_variable_condition?.conditions.forEach((subCondition) => {
const sub_variable_value_warnings = Array.isArray(subCondition.value) ? getNotExistVariablesByArray([subCondition.value], varMap) : getNotExistVariablesByText(subCondition.value, varMap)
if (sub_variable_value_warnings.length)
condition_value_warnings.push(...sub_variable_value_warnings)
})
})
})
if (condition_variable_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.ifElse.condition')} ${t('workflow.common.referenceVar')}${condition_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
if (condition_value_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.ifElse.enterValue')} ${t('workflow.common.referenceVar')}${condition_value_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: condition_variable_selector_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,8 +1,8 @@
import { ErrorHandleMode } from '../../types'
import type { NodeDefault } from '../../types'
import { BlockEnum, ErrorHandleMode } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import type { IterationNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
const i18nPrefix = 'workflow'
@ -49,6 +49,19 @@ const nodeDefault: NodeDefault<IterationNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: IterationNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr: string[] = []
const iterator_selector_warnings = getNotExistVariablesByArray([payload.iterator_selector], varMap)
if (iterator_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.iteration.input')} ${t('workflow.common.referenceVar')}${iterator_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: iterator_selector_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import type { KnowledgeRetrievalNodeType } from './types'
import { checkoutRerankModelConfigedInRetrievalSettings } from './utils'
import { DATASET_DEFAULT } from '@/config'
@ -47,6 +48,19 @@ const nodeDefault: NodeDefault<KnowledgeRetrievalNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: KnowledgeRetrievalNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const query_variable_selector_warnings = getNotExistVariablesByArray([payload.query_variable_selector], varMap)
if (query_variable_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.knowledgeRetrieval.queryVariable')} ${t('workflow.common.referenceVar')}${query_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: [...query_variable_selector_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,9 +1,9 @@
import { VarType } from '../../types'
import type { NodeDefault } from '../../types'
import { BlockEnum, VarType } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import { comparisonOperatorNotRequireValue } from '../if-else/utils'
import { type ListFilterNodeType, OrderBy } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
const i18nPrefix = 'workflow.errorMsg'
@ -58,6 +58,18 @@ const nodeDefault: NodeDefault<ListFilterNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: ListFilterNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const variable_warnings = getNotExistVariablesByArray([payload.variable], varMap)
if (variable_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.listFilter.inputVar')} ${t('workflow.common.referenceVar')}${variable_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: variable_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -97,16 +97,14 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
{inputs.extract_by?.enabled
? (
<div className='flex items-center justify-between'>
{hasSubVariable && (
<div className='mr-2 grow'>
<ExtractInput
value={inputs.extract_by.serial as string}
onChange={handleExtractsChange}
readOnly={readOnly}
nodeId={id}
/>
</div>
)}
<div className='mr-2 grow'>
<ExtractInput
value={inputs.extract_by.serial as string}
onChange={handleExtractsChange}
readOnly={readOnly}
nodeId={id}
/>
</div>
</div>
)
: null}

View File

@ -1,8 +1,9 @@
import { EditionType } from '../../types'
import type { Var } from '../../types'
import { BlockEnum, EditionType, VarType } from '../../types'
import { type NodeDefault, type PromptItem, PromptRole } from '../../types'
import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow'
import type { LLMNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
const i18nPrefix = 'workflow.errorMsg'
@ -82,6 +83,37 @@ const nodeDefault: NodeDefault<LLMNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: LLMNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const prompt_templates_warnings: string[] = []
if (payload.context?.enabled && payload.context?.variable_selector?.length) {
const context_variable_selector_warnings = getNotExistVariablesByArray([payload.context.variable_selector], varMap)
if (context_variable_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.llm.context')} ${t('workflow.common.referenceVar')}${context_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
}
const prompt_templates = Array.isArray(payload.prompt_template) ? payload.prompt_template : [payload.prompt_template] as PromptItem[]
prompt_templates.forEach((v) => {
prompt_templates_warnings.push(...getNotExistVariablesByText(v.text, { context: { variable: 'context', type: VarType.string }, ...varMap }))
})
if (prompt_templates_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.llm.prompt')} ${t('workflow.common.referenceVar')}${prompt_templates_warnings.join('、')}${t('workflow.common.noExist')}`)
const memory_query_prompt_template_warnings = getNotExistVariablesByText(payload.memory?.query_prompt_template || '', varMap)
if (memory_query_prompt_template_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.common.memories.title')}USER ${t('workflow.common.referenceVar')}${memory_query_prompt_template_warnings.join('、')}${t('workflow.common.noExist')}`)
if (payload.vision?.enabled && payload.vision?.configs?.variable_selector?.length) {
const vision_variable_selector_warnings = getNotExistVariablesByArray([payload.vision.configs.variable_selector], varMap)
if (vision_variable_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
}
return {
isValid: true,
warning_vars: [...prompt_templates_warnings, ...memory_query_prompt_template_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow'
import { type ParameterExtractorNodeType, ReasoningModeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -61,6 +62,30 @@ const nodeDefault: NodeDefault<ParameterExtractorNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: ParameterExtractorNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr: string[] = []
const variables_warnings = getNotExistVariablesByArray([payload.query], varMap)
if (variables_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.parameterExtractor.inputVar')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`)
let vision_variable_warnings: string[] = []
if (payload.vision?.configs?.variable_selector?.length) {
vision_variable_warnings = getNotExistVariablesByArray([payload.vision.configs.variable_selector], varMap)
if (vision_variable_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_warnings.join('、')}${t('workflow.common.noExist')}`)
}
const instruction_warnings = getNotExistVariablesByText(payload.instruction, varMap)
if (instruction_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.parameterExtractor.instruction')} ${t('workflow.common.referenceVar')}${instruction_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: [...variables_warnings, ...vision_variable_warnings, ...instruction_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow'
import type { QuestionClassifierNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -68,6 +69,30 @@ const nodeDefault: NodeDefault<QuestionClassifierNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: QuestionClassifierNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const query_variable_selector_warnings = getNotExistVariablesByArray([payload.query_variable_selector], varMap)
if (query_variable_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.questionClassifiers.inputVars')} ${t('workflow.common.referenceVar')}${query_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
let vision_variable_selector_warnings: string[] = []
if (payload.vision?.configs?.variable_selector?.length) {
vision_variable_selector_warnings = getNotExistVariablesByArray([payload.vision?.configs?.variable_selector], varMap)
if (vision_variable_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
}
const instruction_warnings: string[] = getNotExistVariablesByText(payload.instruction, varMap)
if (instruction_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.questionClassifiers.advancedSetting')}-${t('workflow.nodes.questionClassifiers.instruction')} ${t('workflow.common.referenceVar')}${instruction_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: [...query_variable_selector_warnings, ...vision_variable_selector_warnings, ...instruction_warnings],
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,5 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, Var } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import type { TemplateTransformNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -32,6 +33,19 @@ const nodeDefault: NodeDefault<TemplateTransformNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: TemplateTransformNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const variables_selector = payload.variables.map(v => v.value_selector)
const variables_selector_warnings = getNotExistVariablesByArray(variables_selector, varMap)
if (variables_selector_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.templateTransform.inputVars')} ${t('workflow.common.referenceVar')}${variables_selector_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -179,7 +179,7 @@ const InputVarList: FC<Props> = ({
</div>
{isString && (
<Input
className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'rounded-lg border px-3 py-[6px]')}
className={cn(inputsIsFocus[variable] ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'rounded-lg border px-3 py-[6px]')}
value={varInput?.value as string || ''}
onChange={handleMixedTypeChange(variable)}
readOnly={readOnly}

View File

@ -1,8 +1,9 @@
import type { NodeDefault } from '../../types'
import type { ToolNodeType } from './types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
import type { NodeDefault, Var } from '../../types'
import type { ToolNodeType } from './types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow'
const i18nPrefix = 'workflow.errorMsg'
@ -59,6 +60,35 @@ const nodeDefault: NodeDefault<ToolNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: ToolNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr = []
const tool_parametersMap = payload.tool_parameters
const tool_parameters_array = Object.values(tool_parametersMap)
const tool_parameters_warnings: string[] = []
tool_parameters_array?.forEach((item) => {
if (!item.value)
return
if (Array.isArray(item.value)) {
const warnings = getNotExistVariablesByArray([item.value], varMap)
if (warnings.length)
tool_parameters_warnings.push(...warnings)
return
}
if (typeof item.value === 'string') {
const warnings = getNotExistVariablesByText(item.value, varMap)
if (warnings.length)
tool_parameters_warnings.push(...warnings)
}
})
if (tool_parameters_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.tool.inputVars')} ${t('workflow.common.referenceVar')}${tool_parameters_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: tool_parameters_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -1,4 +1,6 @@
import type { Var } from '../../types'
import { type NodeDefault, VarType } from '../../types'
import { getNotExistVariablesByArray } from '../../utils/workflow'
import type { VariableAssignerNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
@ -51,6 +53,18 @@ const nodeDefault: NodeDefault<VariableAssignerNodeType> = {
errorMessage: errorMessages,
}
},
checkVarValid(payload: VariableAssignerNodeType, varMap: Record<string, Var>, t: any) {
const errorMessageArr: string[] = []
const variables_warnings = getNotExistVariablesByArray(payload.variables ?? [], varMap)
if (variables_warnings.length)
errorMessageArr.push(`${t('workflow.nodes.variableAssigner.title')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`)
return {
isValid: true,
warning_vars: variables_warnings,
errorMessage: errorMessageArr,
}
},
}
export default nodeDefault

View File

@ -37,8 +37,13 @@ export type WorkflowSliceShape = {
export const createWorkflowSlice: StateCreator<WorkflowSliceShape> = set => ({
workflowRunningData: undefined,
setWorkflowRunningData: workflowRunningData => set(() => ({ workflowRunningData })),
clipboardElements: [],
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
clipboardElements: (() => {
const storedElements = localStorage.getItem('clipboard_elements')
return storedElements ? JSON.parse(storedElements) : []
})(),
setClipboardElements: (clipboardElements) => {
localStorage.setItem('clipboard_elements', JSON.stringify(clipboardElements))
},
selection: null,
setSelection: selection => set(() => ({ selection })),
bundleNodeSize: null,

View File

@ -303,6 +303,7 @@ export type NodeDefault<T = {}> = {
}
defaultValue: Partial<T>
checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string }
checkVarValid?: (payload: T, varMap: Record<string, Var>, t: any,) => { isValid: boolean; errorMessage?: string[] }
}
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void

View File

@ -5,6 +5,18 @@ import type {
} from '@/app/components/workflow/types'
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
jest.mock('ky', () => ({
__esModule: true,
default: {
create: jest.fn(),
},
}))
jest.mock('lodash-es/groupBy', () => ({
__esModule: true,
default: jest.fn(),
}))
describe('preprocessNodesAndEdges', () => {
it('process nodes without iteration node or loop node should return origin nodes and edges.', () => {
const nodes = [

View File

@ -10,14 +10,20 @@ import {
uniqBy,
} from 'lodash-es'
import type {
ConversationVariable,
Edge,
EnvironmentVariable,
Node,
Var,
} from '../types'
import {
BlockEnum,
} from '../types'
import type { IterationNodeType } from '../nodes/iteration/types'
import type { LoopNodeType } from '../nodes/loop/types'
import { VAR_REGEX_TEXT } from '@/config'
import { formatItem } from '../nodes/_base/components/variable/utils'
import type { StructuredOutput } from '../nodes/llm/types'
export const canRunBySingle = (nodeType: BlockEnum) => {
return nodeType === BlockEnum.LLM
@ -86,7 +92,17 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo
return nodesConnectedSourceOrTargetHandleIdsMap
}
export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
function getParentOutputVarMap(item: Var, path: string, varMap: Record<string, Var>) {
if (!item.children || (Array.isArray(item.children) && !item.children.length) || ((item.children as StructuredOutput).schema))
return
(item.children as Var[]).forEach((child) => {
const newPath = `${path}.${child.variable}`
varMap[newPath] = child
getParentOutputVarMap(child, newPath, varMap)
})
}
export const getValidTreeNodes = (nodes: Node[], edges: Edge[], isCollectVar?: boolean) => {
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
if (!startNode) {
@ -109,6 +125,19 @@ export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
outgoers.forEach((outgoer) => {
list.push(outgoer)
if (isCollectVar) {
const nodeObj = formatItem(root, false, () => true)
const varMap = {} as Record<string, Var>
nodeObj.vars.forEach((item) => {
if (item.variable.startsWith('sys.'))
return
const newPath = `${nodeObj.nodeId}.${item.variable}`
varMap[newPath] = item
getParentOutputVarMap(item, newPath, varMap)
})
outgoer._parentOutputVarMap = { ...(root._parentOutputVarMap ?? {}), ...varMap }
}
if (outgoer.data.type === BlockEnum.Iteration)
list.push(...nodes.filter(node => node.parentId === outgoer.id))
if (outgoer.data.type === BlockEnum.Loop)
@ -327,3 +356,48 @@ export const getParallelInfo = (nodes: Node[], edges: Edge[], parentNodeId?: str
export const hasErrorHandleNode = (nodeType?: BlockEnum) => {
return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code
}
export const transformStartNodeVariables = (chatVarList: ConversationVariable[], environmentVariables: EnvironmentVariable[]) => {
const variablesMap: Record<string, ConversationVariable | EnvironmentVariable> = {}
chatVarList.forEach((variable) => {
variablesMap[`conversation.${variable.name}`] = variable
})
environmentVariables.forEach((variable) => {
variablesMap[`env.${variable.name}`] = variable
})
return variablesMap
}
export const getNotExistVariablesByText = (text: string, varMap: Record<string, Var>) => {
const var_warnings: string[] = []
text?.replace(VAR_REGEX_TEXT, (str, id_name) => {
if (id_name.startsWith('sys.'))
return str
if (varMap[id_name])
return str
const arr = id_name.split('.')
arr.shift()
var_warnings.push(arr.join('.'))
return str
})
return var_warnings
}
export const getNotExistVariablesByArray = (array: string[][], varMap: Record<string, Var>) => {
if (!array.length)
return []
const var_warnings: string[] = []
array.forEach((item) => {
if (!item.length)
return
if (['sys'].includes(item[0]))
return
const var_warning = varMap[item.join('.')]
if (var_warning)
return
const arr = [...item]
arr.shift()
var_warnings.push(arr.join('.'))
})
return var_warnings
}

View File

@ -65,8 +65,7 @@ const LocaleLayout = async ({
<TanstackQueryIniter>
<ThemeProvider
attribute='data-theme'
forcedTheme='light'
defaultTheme='light' // TODO: change to 'system' when dark mode ready
defaultTheme='system'
enableSystem
disableTransitionOnChange
>

View File

@ -2,6 +2,8 @@
import React from 'react'
import { useContext } from 'use-context-selector'
import Select from '@/app/components/base/select/locale'
import ThemeSelector from '@/app/components/base/theme-selector'
import Divider from '@/app/components/base/divider'
import { languages } from '@/i18n/language'
import type { Locale } from '@/i18n'
import I18n from '@/context/i18n'
@ -10,17 +12,22 @@ import LogoSite from '@/app/components/base/logo/logo-site'
const Header = () => {
const { locale, setLocaleOnClient } = useContext(I18n)
return <div className='flex w-full items-center justify-between p-6'>
<LogoSite />
<Select
value={locale}
items={languages.filter(item => item.supported)}
onChange={(value) => {
setLocaleOnClient(value as Locale)
}}
/>
</div>
return (
<div className='flex w-full items-center justify-between p-6'>
<LogoSite />
<div className='flex items-center gap-1'>
<Select
value={locale}
items={languages.filter(item => item.supported)}
onChange={(value) => {
setLocaleOnClient(value as Locale)
}}
/>
<Divider type='vertical' className='mx-0 ml-2 h-4' />
<ThemeSelector />
</div>
</div>
)
}
export default Header

View File

@ -282,6 +282,8 @@ Thought: {{agent_scratchpad}}
export const VAR_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi
export const VAR_REGEX_TEXT = /\{\{#([a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*)#\}\}/gi
export const resetReg = () => VAR_REGEX.lastIndex = 0
export let textGenerationTimeoutMs = 60000

View File

@ -43,7 +43,7 @@ async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) {
targetObject[key] = translation
}
catch {
console.error(`Error translating ${sourceObj[key]}(${key}) to ${toLanguage}`)
console.error(`Error translating "${sourceObj[key]}" to ${toLanguage}. Key: ${key}`)
}
}
}
@ -59,6 +59,14 @@ async function autoGenTrans(fileName, toGenLanguage) {
const toGenLanguageFilePath = path.join(__dirname, toGenLanguage, `${fileName}.ts`)
// eslint-disable-next-line sonarjs/code-eval
const fullKeyContent = eval(transpile(fs.readFileSync(fullKeyFilePath, 'utf8')))
// if toGenLanguageFilePath is not exist, create it
if (!fs.existsSync(toGenLanguageFilePath)) {
fs.writeFileSync(toGenLanguageFilePath, `const translation = {
}
export default translation
`)
}
// To keep object format and format it for magicast to work: const translation = { ... } => export default {...}
const readContent = await loadFile(toGenLanguageFilePath)
const { code: toGenContent } = generateCode(readContent)

View File

@ -27,9 +27,14 @@ async function getKeysFromLanuage(language) {
// console.log(camelCaseFileName)
const content = fs.readFileSync(filePath, 'utf8')
// eslint-disable-next-line sonarjs/code-eval
const translation = eval(transpile(content))
const translationObj = eval(transpile(content))
// console.log(translation)
const keys = Object.keys(translation)
if(!translationObj || typeof translationObj !== 'object') {
console.error(`Error parsing file: ${filePath}`)
reject(new Error(`Error parsing file: ${filePath}`))
return
}
const keys = Object.keys(translationObj)
const nestedKeys = []
const iterateKeys = (obj, prefix = '') => {
for (const key in obj) {
@ -39,7 +44,7 @@ async function getKeysFromLanuage(language) {
iterateKeys(obj[key], nestedKey)
}
}
iterateKeys(translation)
iterateKeys(translationObj)
allKeys = [...keys, ...nestedKeys].map(
key => `${camelCaseFileName}.${key}`,

View File

@ -161,6 +161,10 @@ const translation = {
description: 'Opik ist eine Open-Source-Plattform zum Bewerten, Testen und Überwachen von LLM-Anwendungen.',
title: 'Opik',
},
weave: {
title: 'Weben',
description: 'Weave ist eine Open-Source-Plattform zur Bewertung, Testung und Überwachung von LLM-Anwendungen.',
},
},
answerIcon: {
descriptionInExplore: 'Gibt an, ob das WebApp-Symbol zum Ersetzen 🤖 in Explore verwendet werden soll',
@ -201,6 +205,17 @@ const translation = {
label: 'APP',
noParams: 'Keine Parameter erforderlich',
},
structOutput: {
required: 'Erforderlich',
structured: 'Strukturiert',
structuredTip: 'Strukturierte Ausgaben ist eine Funktion, die sicherstellt, dass das Modell immer Antworten generiert, die Ihrem bereitgestellten JSON-Schema entsprechen.',
modelNotSupportedTip: 'Das aktuelle Modell unterstützt diese Funktion nicht und wird automatisch auf Eingabeinjektion heruntergestuft.',
modelNotSupported: 'Modell nicht unterstützt',
configure: 'Konfigurieren',
notConfiguredTip: 'Die strukturierte Ausgabe wurde bisher nicht konfiguriert.',
moreFillTip: 'Maximal 10 Ebenen der Verschachtelung anzeigen',
LLMResponse: 'LLM-Antwort',
},
}
export default translation

View File

@ -69,6 +69,7 @@ const translation = {
messageRequest: {
title: 'Nachrichtenguthaben',
tooltip: 'Nachrichtenaufrufkontingente für verschiedene Tarife unter Verwendung von OpenAI-Modellen (außer gpt4).Nachrichten über dem Limit verwenden Ihren OpenAI-API-Schlüssel.',
titlePerMonth: '{{count,number}} Nachrichten/Monat',
},
annotatedResponse: {
title: 'Kontingentgrenzen für Annotationen',
@ -77,27 +78,94 @@ const translation = {
ragAPIRequestTooltip: 'Bezieht sich auf die Anzahl der API-Aufrufe, die nur die Wissensdatenbankverarbeitungsfähigkeiten von Dify aufrufen.',
receiptInfo: 'Nur der Teaminhaber und der Teamadministrator können abonnieren und Abrechnungsinformationen einsehen',
annotationQuota: 'Kontingent für Anmerkungen',
unlimitedApiRate: 'Keine API-Ratebeschränkung',
teamMember_other: '{{count,number}} Teammitglieder',
priceTip: 'pro Arbeitsbereich/',
teamWorkspace: '{{count,number}} Team Arbeitsplatz',
annualBilling: 'Jährliche Abrechnung',
self: 'Selbst gehostet',
freeTrialTipPrefix: 'Melden Sie sich an und erhalten Sie ein',
cloud: 'Cloud-Dienst',
apiRateLimitTooltip: 'Die API-Datenbeschränkung gilt für alle Anfragen, die über die Dify-API gemacht werden, einschließlich Textgenerierung, Chat-Konversationen, Workflow-Ausführungen und Dokumentenverarbeitung.',
getStarted: 'Loslegen',
apiRateLimitUnit: '{{count,number}}/Tag',
documentsTooltip: 'Vorgabe für die Anzahl der Dokumente, die aus der Wissensdatenquelle importiert werden.',
apiRateLimit: 'API-Datenlimit',
documents: '{{count,number}} Wissensdokumente',
comparePlanAndFeatures: 'Pläne und Funktionen vergleichen',
freeTrialTipSuffix: 'Keine Kreditkarte erforderlich',
freeTrialTip: 'kostenlose Testversion von 200 OpenAI-Anfragen.',
documentsRequestQuota: '{{count,number}}/min Wissensanforderungsratenlimit',
teamMember_one: '{{count,number}} Teammitglied',
documentsRequestQuotaTooltip: 'Gibt die Gesamtzahl der Aktionen an, die ein Arbeitsbereich pro Minute innerhalb der Wissensbasis ausführen kann, einschließlich der Erstellung, Löschung, Aktualisierung von Datensätzen, des Hochladens von Dokumenten, von Änderungen, der Archivierung und von Abfragen in der Wissensbasis. Diese Kennzahl wird verwendet, um die Leistung von Anfragen an die Wissensbasis zu bewerten. Wenn ein Sandbox-Nutzer beispielsweise in einer Minute 10 aufeinanderfolgende Testdurchläufe durchführt, wird sein Arbeitsbereich für die nächste Minute vorübergehend daran gehindert, die folgenden Aktionen auszuführen: Erstellung, Löschung, Aktualisierung von Datensätzen sowie das Hochladen oder Ändern von Dokumenten.',
},
plans: {
sandbox: {
name: 'Sandbox',
description: '200 mal GPT kostenlos testen',
includesTitle: 'Beinhaltet:',
for: 'Kostenlose Testversion der Kernfunktionen',
},
professional: {
name: 'Professionell',
description: 'Für Einzelpersonen und kleine Teams, um mehr Leistung erschwinglich freizuschalten.',
includesTitle: 'Alles im kostenlosen Tarif, plus:',
for: 'Für unabhängige Entwickler/kleine Teams',
},
team: {
name: 'Team',
description: 'Zusammenarbeiten ohne Grenzen und Top-Leistung genießen.',
includesTitle: 'Alles im Professionell-Tarif, plus:',
for: 'Für mittelgroße Teams',
},
enterprise: {
name: 'Unternehmen',
description: 'Erhalten Sie volle Fähigkeiten und Unterstützung für großangelegte, missionskritische Systeme.',
includesTitle: 'Alles im Team-Tarif, plus:',
features: {
2: 'Exklusive Unternehmensfunktionen',
8: 'Professioneller technischer Support',
6: 'Erweiterte Sicherheits- und Kontrollsysteme',
4: 'SSO',
0: 'Enterprise-Grade Skalierbare Bereitstellungslösungen',
3: 'Mehrere Arbeitsbereiche und Unternehmensverwaltung',
1: 'Kommerzielle Lizenzgenehmigung',
5: 'Verhandelte SLAs durch Dify-Partner',
7: 'Updates und Wartung von Dify offiziell',
},
btnText: 'Vertrieb kontaktieren',
price: 'Benutzerdefiniert',
priceTip: 'Jährliche Abrechnung nur',
for: 'Für große Teams',
},
community: {
features: {
2: 'Entspricht der Dify Open Source Lizenz',
1: 'Einzelner Arbeitsbereich',
0: 'Alle Kernfunktionen wurden im öffentlichen Repository veröffentlicht.',
},
description: 'Für Einzelbenutzer, kleine Teams oder nicht-kommerzielle Projekte',
for: 'Für Einzelbenutzer, kleine Teams oder nicht-kommerzielle Projekte',
btnText: 'Beginnen Sie mit der Gemeinschaft',
price: 'Kostenlos',
includesTitle: 'Kostenlose Funktionen:',
name: 'Gemeinschaft',
},
premium: {
features: {
2: 'WebApp-Logo und Branding-Anpassung',
0: 'Selbstverwaltete Zuverlässigkeit durch verschiedene Cloud-Anbieter',
3: 'Priorisierte E-Mail- und Chat-Unterstützung',
1: 'Einzelner Arbeitsbereich',
},
includesTitle: 'Alles aus der Community, plus:',
name: 'Premium',
priceTip: 'Basierend auf dem Cloud-Marktplatz',
for: 'Für mittelgroße Organisationen und Teams',
btnText: 'Jetzt Premium erhalten in',
comingSoon: 'Microsoft Azure- und Google Cloud-Support demnächst verfügbar',
description: 'Für mittelgroße Organisationen und Teams',
price: 'Skalierbar',
},
},
vectorSpace: {
@ -107,12 +175,26 @@ const translation = {
apps: {
fullTipLine1: 'Upgraden Sie Ihren Tarif, um',
fullTipLine2: 'mehr Apps zu bauen.',
contactUs: 'Kontaktieren Sie uns',
fullTip1: 'Upgrade, um mehr Apps zu erstellen',
fullTip2des: 'Es wird empfohlen, inaktive Anwendungen zu bereinigen, um Speicherplatz freizugeben, oder uns zu kontaktieren.',
fullTip1des: 'Sie haben das Limit für das Erstellen von Apps in diesem Plan erreicht.',
fullTip2: 'Limit erreicht',
},
annotatedResponse: {
fullTipLine1: 'Upgraden Sie Ihren Tarif, um',
fullTipLine2: 'mehr Konversationen zu annotieren.',
quotaTitle: 'Kontingent für Annotation-Antworten',
},
usagePage: {
buildApps: 'Apps erstellen',
annotationQuota: 'Annotierungsquote',
teamMembers: 'Teammitglieder',
documentsUploadQuota: 'Dokumenten-Upload-Quota',
vectorSpace: 'Wissensdatenbank',
vectorSpaceTooltip: 'Dokumente mit dem Hochqualitäts-Indexierungsmodus verbrauchen Ressourcen des Knowledge Data Storage. Wenn der Knowledge Data Storage die Grenze erreicht, werden keine neuen Dokumente hochgeladen.',
},
teamMembers: 'Teammitglieder',
}
export default translation

View File

@ -54,6 +54,10 @@ const translation = {
viewDetails: 'Details anzeigen',
in: 'in',
copied: 'Kopiert',
downloadFailed: 'Download fehlgeschlagen. Bitte versuchen Sie es später erneut.',
downloadSuccess: 'Download abgeschlossen.',
more: 'Mehr',
format: 'Format',
},
placeholder: {
input: 'Bitte eingeben',
@ -153,6 +157,9 @@ const translation = {
community: 'Gemeinschaft',
about: 'Über',
logout: 'Abmelden',
compliance: 'Einhaltung',
support: 'Unterstützung',
github: 'GitHub',
},
settings: {
accountGroup: 'KONTO',
@ -202,6 +209,9 @@ const translation = {
feedbackLabel: 'Sagen Sie uns, warum Sie Ihr Konto gelöscht haben?',
feedbackPlaceholder: 'Wahlfrei',
permanentlyDeleteButton: 'Konto dauerhaft löschen',
workspaceIcon: 'Arbeitsbereichssymbol',
workspaceName: 'Arbeitsbereichsname',
editWorkspaceInfo: 'Arbeitsbereichsinformationen bearbeiten',
},
members: {
team: 'Team',
@ -543,6 +553,7 @@ const translation = {
inputPlaceholder: 'Sprechen Sie mit dem Bot',
thought: 'Gedanke',
thinking: 'Denken...',
resend: 'Erneut senden',
},
promptEditor: {
placeholder: 'Schreiben Sie hier Ihr Aufforderungswort, geben Sie \'{\' ein, um eine Variable einzufügen, geben Sie \'/\' ein, um einen Aufforderungs-Inhaltsblock einzufügen',
@ -637,6 +648,25 @@ const translation = {
pagination: {
perPage: 'Artikel pro Seite',
},
theme: {
light: 'Licht',
theme: 'Thema',
dark: 'dunkel',
auto: 'System',
},
compliance: {
iso27001: 'ISO 27001:2022 Zertifizierung',
professionalUpgradeTooltip: 'Nur verfügbar mit einem Teamplan oder höher.',
gdpr: 'DSGVO DPA',
soc2Type2: 'SOC 2 Typ II Bericht',
soc2Type1: 'SOC 2 Typ I Bericht',
sandboxUpgradeTooltip: 'Nur verfügbar mit einem Professional- oder Teamplan.',
},
imageInput: {
dropImageHere: 'Laden Sie Ihr Bild hierher hoch oder',
browse: 'blättern',
supportedFormats: 'Unterstützt PNG, JPG, JPEG, WEBP und GIF',
},
}
export default translation

View File

@ -3,6 +3,8 @@ const translation = {
upgradeTip: {
prefix: 'Erweitere deinen Plan auf',
suffix: 'um deine Marke anzupassen.',
title: 'Upgrade deinen Plan',
des: 'Upgrade deinen Plan, um deine Marke anzupassen.',
},
webapp: {
title: 'WebApp Marke anpassen',

View File

@ -82,6 +82,14 @@ const translation = {
jinaReaderNotConfiguredDescription: 'Richten Sie Jina Reader ein, indem Sie Ihren kostenlosen API-Schlüssel für den Zugriff eingeben.',
useSitemapTooltip: 'Folgen Sie der Sitemap, um die Website zu crawlen. Ist dies nicht der Fall, crawlt Jina Reader iterativ basierend auf der Seitenrelevanz, sodass weniger, aber qualitativ hochwertigere Seiten angezeigt werden.',
jinaReaderDoc: 'Erfahre mehr über Jina Reader',
configureJinaReader: 'Jina Reader konfigurieren',
waterCrawlNotConfigured: 'Watercrawl ist nicht konfiguriert',
configureWatercrawl: 'Wasserkrabbe konfigurieren',
watercrawlDocLink: 'https://docs.dify.ai/de/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
watercrawlTitle: 'Webinhalt mit Watercrawl extrahieren',
watercrawlDoc: 'Wasserkriechen-Dokumente',
configureFirecrawl: 'Firecrawl konfigurieren',
waterCrawlNotConfiguredDescription: 'Konfigurieren Sie Watercrawl mit dem API-Schlüssel, um es zu verwenden.',
},
cancel: 'Abbrechen',
},
@ -200,6 +208,11 @@ const translation = {
title: 'Verbinden Sie sich mit anderen Datenquellen?',
description: 'Derzeit verfügt die Wissensdatenbank von Dify nur über begrenzte Datenquellen. Das Beitragen einer Datenquelle zur Dify-Wissensdatenbank ist eine fantastische Möglichkeit, die Flexibilität und Leistungsfähigkeit der Plattform für alle Benutzer zu verbessern. Unser Beitragsleitfaden erleichtert Ihnen den Einstieg. Bitte klicken Sie auf den untenstehenden Link, um mehr zu erfahren.',
},
watercrawl: {
configWatercrawl: 'Wasserkrabbe konfigurieren',
apiKeyPlaceholder: 'API-Schlüssel von watercrawl.dev',
getApiKeyLinkText: 'Holen Sie sich Ihren API-Schlüssel von watercrawl.dev',
},
}
export default translation

View File

@ -25,6 +25,7 @@ const translation = {
learnMore: 'Mehr erfahren',
description: ' über die Abrufmethode.',
longDescription: ' über die Abrufmethode, dies kann jederzeit in den Wissenseinstellungen geändert werden.',
method: 'Abrufmethode',
},
save: 'Speichern',
permissionsInvitedMembers: 'Teilweise Teammitglieder',

View File

@ -168,6 +168,54 @@ const translation = {
documentsDisabled: '{{num}} Dokumente deaktiviert - seit über 30 Tagen inaktiv',
allKnowledge: 'Alles Wissen',
allKnowledgeDescription: 'Wählen Sie diese Option aus, um das gesamte Wissen in diesem Arbeitsbereich anzuzeigen. Nur der Workspace-Besitzer kann das gesamte Wissen verwalten.',
metadata: {
createMetadata: {
namePlaceholder: 'Metadatenname hinzufügen',
back: 'Zurück',
title: 'Neue Metadaten',
name: 'Name',
type: 'Art',
},
checkName: {
empty: 'Der Metadatenname darf nicht leer sein.',
invalid: 'Der Metadatenname darf nur Kleinbuchstaben, Zahlen und Unterstriche enthalten und muss mit einem Kleinbuchstaben beginnen.',
},
batchEditMetadata: {
editMetadata: 'Metadaten bearbeiten',
multipleValue: 'Mehrwert',
applyToAllSelectDocument: 'Auf alle ausgewählten Dokumente anwenden',
applyToAllSelectDocumentTip: 'Erstellen Sie automatisch alle oben bearbeiteten und neuen Metadaten für alle ausgewählten Dokumente, andernfalls wird die Bearbeitung der Metadaten nur auf Dokumente angewendet, die bereits Metadaten enthalten.',
editDocumentsNum: 'Bearbeiten von {{num}} Dokumenten',
},
selectMetadata: {
manageAction: 'Verwalten',
search: 'Metadaten durchsuchen',
newAction: 'Neue Metadaten',
},
datasetMetadata: {
name: 'Name',
disabled: 'Deaktiviert',
description: 'Sie können alle Metadaten in diesem Wissen hier verwalten. Änderungen werden mit jedem Dokument synchronisiert.',
deleteContent: 'Bist du sicher, dass du die Metadaten "{{name}}" löschen möchtest?',
addMetaData: 'Metadaten hinzufügen',
deleteTitle: 'Bestätigen Sie das Löschen',
values: '{{num}} Werte',
builtIn: 'Eingebaut',
rename: 'Umbenennen',
builtInDescription: 'Integrierte Metadaten werden automatisch extrahiert und generiert. Sie müssen vor der Verwendung aktiviert werden und können nicht bearbeitet werden.',
namePlaceholder: 'Metadatenname',
},
documentMetadata: {
startLabeling: 'Labeling starten',
technicalParameters: 'Technische Parameter',
documentInformation: 'Dokumentinformationen',
metadataToolTip: 'Metadaten dienen als ein entscheidender Filter, der die Genauigkeit und Relevanz der Informationsbeschaffung verbessert. Sie können die Metadaten für dieses Dokument hier ändern und hinzufügen.',
},
chooseTime: 'Wählen Sie eine Zeit...',
metadata: 'Metadaten',
addMetadata: 'Metadaten hinzufügen',
},
embeddingModelNotAvailable: 'Das Einbettungsmodell ist nicht verfügbar.',
}
export default translation

View File

@ -0,0 +1,47 @@
const translation = {
toVerifiedTip: {
coupon: 'exklusiver 100% Gutschein',
end: 'für den Dify Professional Plan.',
front: 'Sie sind jetzt berechtigt, den Status „Bildung verifiziert“ zu erhalten. Bitte geben Sie unten Ihre Bildungsinformationen ein, um den Prozess abzuschließen und eine Zu erhalten.',
},
form: {
schoolName: {
placeholder: 'Geben Sie den offiziellen, unabgekürzten Namen Ihrer Schule ein.',
title: 'Ihr Schulname',
},
schoolRole: {
option: {
teacher: 'Lehrer',
administrator: 'Schuladministrator',
student: 'Schüler',
},
title: 'Ihre Schulrolle',
},
terms: {
desc: {
and: 'und',
privacyPolicy: 'Datenschutzrichtlinie',
termsOfService: 'Nutzungsbedingungen',
end: '. Durch die Einreichung:',
front: 'Ihre Informationen und die Nutzung des Status "Bildung bestätigt" unterliegen unseren',
},
option: {
inSchool: 'Ich bestätige, dass ich an der angegebenen Einrichtung eingeschrieben oder angestellt bin. Dify kann einen Nachweis über die Einschreibung/Anstellung anfordern. Wenn ich meine Berechtigung falsch darstelle, stimme ich zu, alle Gebühren zu zahlen, die aufgrund meines Bildungsstatus ursprünglich erlassen wurden.',
age: 'Ich bestätige, dass ich mindestens 18 Jahre alt bin.',
},
title: 'Allgemeine Geschäftsbedingungen',
},
},
toVerified: 'Bildung überprüfen lassen',
rejectTitle: 'Ihre Dify-Ausbildungsüberprüfung wurde abgelehnt.',
currentSigned: 'DERZEIT ANGEMELDET ALS',
submit: 'Einreichen',
submitError: 'Die Formularübermittlung ist fehlgeschlagen. Bitte versuchen Sie es später erneut.',
rejectContent: 'Leider sind Sie nicht für den Status "Education Verified" berechtigt und können daher den exklusiven 100%-Gutschein für den Dify Professional Plan nicht erhalten, wenn Sie diese E-Mail-Adresse verwenden.',
successContent: 'Wir haben einen 100% Rabattgutschein für den Dify Professional Plan auf Ihr Konto ausgestellt. Der Gutschein ist ein Jahr lang gültig, bitte nutzen Sie ihn innerhalb des Gültigkeitszeitraums.',
learn: 'Erfahren Sie, wie Sie Ihre Ausbildung überprüfen lassen.',
emailLabel: 'Ihre aktuelle E-Mail',
successTitle: 'Sie haben die Dify-Ausbildung verifiziert',
}
export default translation

View File

@ -37,6 +37,7 @@ const translation = {
HR: 'Personalwesen',
Agent: 'Agent',
Workflow: 'Arbeitsablauf',
Entertainment: 'Unterhaltung',
},
}

View File

@ -180,6 +180,8 @@ const translation = {
pluginsResult: '{{num}} Ergebnisse',
empower: 'Unterstützen Sie Ihre KI-Entwicklung',
and: 'und',
partnerTip: 'Von einem Dify-Partner verifiziert',
verifiedTip: 'Von Dify überprüft',
},
task: {
clearAll: 'Alle löschen',
@ -204,6 +206,10 @@ const translation = {
findMoreInMarketplace: 'Weitere Informationen finden Sie im Marketplace',
installPlugin: 'Plugin installieren',
installFrom: 'INSTALLIEREN VON',
metadata: {
title: 'Plugins',
},
difyVersionNotCompatible: 'Die aktuelle Dify-Version ist mit diesem Plugin nicht kompatibel, bitte aktualisieren Sie auf die erforderliche Mindestversion: {{minimalDifyVersion}}',
}
export default translation

View File

@ -32,6 +32,10 @@ const translation = {
temporarySystemIssue: 'Entschuldigung, vorübergehendes Systemproblem.',
expand: 'Erweitern',
collapse: 'Reduzieren',
chatSettingsTitle: 'Neues Chat-Setup',
newChatTip: 'Bereits in einem neuen Chat',
viewChatSettings: 'Chateinstellungen anzeigen',
chatFormTip: 'Chat-Einstellungen können nach Beginn des Chats nicht mehr geändert werden.',
},
generation: {
tabs: {
@ -70,6 +74,8 @@ const translation = {
moreThanMaxLengthLine: 'Zeile {{rowIndex}}: {{varName}} Wert darf nicht mehr als {{maxLength}} Zeichen sein',
atLeastOne: 'Bitte geben Sie mindestens eine Zeile in die hochgeladene Datei ein.',
},
executions: '{{num}} HINRICHTUNGEN',
execution: 'AUSFÜHRUNG',
},
}

View File

@ -1,3 +1,37 @@
const translation = {}
const translation = {
daysInWeek: {
Sat: 'Sat',
Fri: 'Freitag',
Thu: 'Donnerstag',
Tue: 'Tue',
Sun: 'Sonne',
Mon: 'Mon',
Wed: 'Mittwoch',
},
months: {
August: 'August',
March: 'März',
January: 'Januar',
June: 'Juni',
July: 'Juli',
November: 'November',
September: 'September',
April: 'April',
February: 'Februar',
May: 'Mai',
December: 'Dezember',
October: 'Oktober',
},
operation: {
pickDate: 'Datum auswählen',
ok: 'OK',
cancel: 'Stornieren',
now: 'Jetzt',
},
title: {
pickTime: 'Wähle Zeit',
},
defaultPlaceholder: 'Wähle eine Zeit...',
}
export default translation

View File

@ -106,6 +106,15 @@ const translation = {
addFailureBranch: 'Fail-Branch hinzufügen',
loadMore: 'Weitere Workflows laden',
noHistory: 'Keine Geschichte',
exportSVG: 'Als SVG exportieren',
noExist: 'Keine solche Variable',
versionHistory: 'Versionsverlauf',
publishUpdate: 'Update veröffentlichen',
referenceVar: 'Referenzvariable',
exportImage: 'Bild exportieren',
exportJPEG: 'Als JPEG exportieren',
exitVersions: 'Ausgangsversionen',
exportPNG: 'Als PNG exportieren',
},
env: {
envPanelTitle: 'Umgebungsvariablen',
@ -205,6 +214,7 @@ const translation = {
testRunIteration: 'Testlaufiteration',
back: 'Zurück',
iteration: 'Iteration',
loop: 'Schleife',
},
tabs: {
'searchBlock': 'Block suchen',
@ -243,6 +253,9 @@ const translation = {
'list-operator': 'List-Operator',
'document-extractor': 'Doc Extraktor',
'agent': 'Agent',
'loop': 'Schleife',
'loop-start': 'Schleifenbeginn',
'loop-end': 'Schleife beenden',
},
blocksAbout: {
'start': 'Definieren Sie die Anfangsparameter zum Starten eines Workflows',
@ -263,6 +276,8 @@ const translation = {
'list-operator': 'Wird verwendet, um Array-Inhalte zu filtern oder zu sortieren.',
'document-extractor': 'Wird verwendet, um hochgeladene Dokumente in Textinhalte zu analysieren, die für LLM leicht verständlich sind.',
'agent': 'Aufruf großer Sprachmodelle zur Beantwortung von Fragen oder zur Verarbeitung natürlicher Sprache',
'loop': 'Führen Sie eine Schleife aus, bis die Abschlussbedingungen erfüllt sind oder die maximalen Schleifenanzahl erreicht ist.',
'loop-end': 'Entspricht "break". Dieser Knoten hat keine Konfigurationselemente. Wenn der Schleifenrumpf diesen Knoten erreicht, wird die Schleife beendet.',
},
operator: {
zoomIn: 'Vergrößern',
@ -404,6 +419,34 @@ const translation = {
variable: 'Variable',
},
sysQueryInUser: 'sys.query in Benutzernachricht erforderlich',
jsonSchema: {
warningTips: {
saveSchema: 'Bitte beenden Sie die Bearbeitung des aktuellen Feldes, bevor Sie das Schema speichern.',
},
stringValidations: 'Stringvalidierungen',
addField: 'Feld hinzufügen',
generateJsonSchema: 'JSON-Schema generieren',
back: 'Zurück',
addChildField: 'Kindfeld hinzufügen',
generationTip: 'Sie können natürliche Sprache verwenden, um schnell ein JSON-Schema zu erstellen.',
title: 'Strukturiertes Ausgabeschema',
resetDefaults: 'Zurücksetzen',
showAdvancedOptions: 'Erweiterte Optionen anzeigen',
fieldNamePlaceholder: 'Feldname',
descriptionPlaceholder: 'Fügen Sie eine Beschreibung hinzu.',
resultTip: 'Hier ist das generierte Ergebnis. Wenn Sie nicht zufrieden sind, können Sie zurückgehen und Ihre Eingabeaufforderung ändern.',
generatedResult: 'Generiertes Ergebnis',
promptTooltip: 'Konvertiere die Textbeschreibung in eine standardisierte JSON-Schema-Struktur.',
promptPlaceholder: 'Beschreibe dein JSON-Schema...',
doc: 'Erfahren Sie mehr über strukturierten Output.',
required: 'erforderlich',
generate: 'Generieren',
apply: 'Bewerben',
import: 'Import aus JSON',
generating: 'Generiere JSON-Schema...',
instruction: 'Anleitung',
regenerate: 'Regenerieren',
},
},
knowledgeRetrieval: {
queryVariable: 'Abfragevariable',
@ -416,6 +459,33 @@ const translation = {
url: 'Segmentierte URL',
metadata: 'Weitere Metadaten',
},
metadata: {
options: {
disabled: {
title: 'Deaktiviert',
subTitle: 'Keine Aktivierung der Metadatfilterung',
},
automatic: {
desc: 'Automatisch Filterbedingungen für Metadaten basierend auf Abfragevariablen generieren.',
title: 'Automatisch',
subTitle: 'Automatisch Metadatenfilterbedingungen basierend auf der Benutzeranfrage generieren',
},
manual: {
title: 'Handbuch',
subTitle: 'Manuell Filterbedingungen für Metadaten hinzufügen',
},
},
panel: {
placeholder: 'Wert eingeben',
datePlaceholder: 'Wählen Sie eine Zeit...',
add: 'Bedingung hinzufügen',
title: 'Metadatenfilterbedingungen',
select: 'Wählen Sie eine Variable aus...',
conditions: 'Bedingungen',
search: 'Suchmetadaten',
},
title: 'Metadatenfilterung',
},
},
http: {
inputVars: 'Eingabevariablen',
@ -505,6 +575,8 @@ const translation = {
'all of': 'alle',
'exists': 'existiert',
'not in': 'nicht in',
'after': 'nach',
'before': 'vor',
},
enterValue: 'Wert eingeben',
addCondition: 'Bedingung hinzufügen',
@ -520,6 +592,7 @@ const translation = {
},
select: 'Auswählen',
addSubVariable: 'Untervariable',
condition: 'Bedingung',
},
variableAssigner: {
title: 'Variablen zuweisen',
@ -562,6 +635,8 @@ const translation = {
'extend': 'Ausdehnen',
'*=': '*=',
'overwrite': 'Überschreiben',
'remove-first': 'Erste entfernen',
'remove-last': 'Letzte entfernen',
},
'setParameter': 'Parameter setzen...',
'noVarTip': 'Klicken Sie auf die Schaltfläche "+", um Variablen hinzuzufügen',
@ -766,6 +841,38 @@ const translation = {
configureModel: 'Modell konfigurieren',
linkToPlugin: 'Link zu Plugins',
},
loop: {
ErrorMethod: {
removeAbnormalOutput: 'Abnormale Ausgaben entfernen',
continueOnError: 'Fortfahren bei Fehler',
operationTerminated: 'Beendet',
},
comma: ',',
loopNode: 'Schleifen-Knoten',
loop_other: '{{count}} Schleifen',
totalLoopCount: 'Gesamtanzahl der Schleifen: {{count}}',
deleteDesc: 'Das Löschen des Schleifen-Knotens entfernt alle untergeordneten Knoten.',
loopVariables: 'Schleifenvariablen',
loop_one: '{{count}} Schleife',
breakCondition: 'Schleifenbeendigungsbedingung',
setLoopVariables: 'Setze Variablen innerhalb des Schleifenbereichs',
breakConditionTip: 'Nur Variablen innerhalb von Schleifen mit Abbruchbedingungen und Konversationsvariablen können referenziert werden.',
loopMaxCountError: 'Bitte geben Sie eine gültige maximale Schleifenanzahl ein, die von 1 bis {{maxCount}} reicht.',
deleteTitle: 'Schleifen-Knoten löschen?',
currentLoop: 'Aktueller Loop',
loopMaxCount: 'Maximale Schleifenanzahl',
finalLoopVariables: 'Endgültige Schleifenvariablen',
exitConditionTip: 'Ein Schleifen-Knoten benötigt mindestens eine Ausgangsbedingung.',
errorResponseMethod: 'Fehlerantwortmethode',
initialLoopVariables: 'Ursprüngliche Schleifenvariablen',
variableName: 'Variablenname',
error_one: '{{count}} Fehler',
currentLoopCount: 'Aktuelle Schleifenanzahl: {{count}}',
inputMode: 'Eingabemodus',
error_other: '{{count}} Fehler',
output: 'Ausgabewert',
input: 'Eingabe',
},
},
tracing: {
stopBy: 'Gestoppt von {{user}}',
@ -777,6 +884,38 @@ const translation = {
noVarsForOperation: 'Es stehen keine Variablen für die Zuweisung mit der ausgewählten Operation zur Verfügung.',
assignedVarsDescription: 'Zugewiesene Variablen müssen beschreibbare Variablen sein, z. B.',
},
versionHistory: {
filter: {
all: 'Alle',
onlyShowNamedVersions: 'Nur benannte Versionen anzeigen',
onlyYours: 'Nur dein',
reset: 'Filter zurücksetzen',
empty: 'Kein passendes Versionsprotokoll gefunden.',
},
editField: {
releaseNotesLengthLimit: 'Die Versionshinweise dürfen {{limit}} Zeichen nicht überschreiten.',
titleLengthLimit: 'Der Titel darf {{limit}} Zeichen nicht überschreiten.',
releaseNotes: 'Versionshinweise',
title: 'Titel',
},
action: {
restoreFailure: 'Wiederherstellung der Version fehlgeschlagen',
updateSuccess: 'Version aktualisiert',
deleteSuccess: 'Version gelöscht',
deleteFailure: 'Version löschen fehlgeschlagen',
restoreSuccess: 'Version wiederhergestellt',
updateFailure: 'Aktualisierung der Version fehlgeschlagen',
},
latest: 'Neueste',
nameThisVersion: 'Nennen Sie diese Version',
currentDraft: 'Aktueller Entwurf',
releaseNotesPlaceholder: 'Beschreibe, was sich geändert hat.',
defaultName: 'Unbetitelte Version',
title: 'Versionen',
editVersionInfo: 'Versionsinformationen bearbeiten',
deletionTip: 'Die Löschung ist unumkehrbar, bitte bestätigen Sie.',
restorationTip: 'Nach der Wiederherstellung der Version wird der aktuelle Entwurf überschrieben.',
},
}
export default translation

View File

@ -1,4 +1,10 @@
const translation = {
theme: {
theme: 'Theme',
light: 'light',
dark: 'dark',
auto: 'system',
},
api: {
success: 'Success',
actionSuccess: 'Action succeeded',

View File

@ -113,6 +113,8 @@ const translation = {
addFailureBranch: 'Add Fail Branch',
loadMore: 'Load More',
noHistory: 'No History',
referenceVar: 'Reference Variable',
noExist: 'No such variable',
},
env: {
envPanelTitle: 'Environment Variables',
@ -599,6 +601,7 @@ const translation = {
selectVariable: 'Select variable...',
addSubVariable: 'Sub Variable',
select: 'Select',
condition: 'Condition',
},
variableAssigner: {
title: 'Assign variables',

View File

@ -159,6 +159,10 @@ const translation = {
description: 'Opik es una plataforma de código abierto para evaluar, probar y monitorear aplicaciones LLM.',
title: 'Opik',
},
weave: {
description: 'Weave es una plataforma de código abierto para evaluar, probar y monitorear aplicaciones de LLM.',
title: 'Tejer',
},
},
answerIcon: {
title: 'Usar el icono de la aplicación web para reemplazar 🤖',
@ -194,6 +198,16 @@ const translation = {
noParams: 'No se necesitan parámetros',
params: 'PARÁMETROS DE LA APLICACIÓN',
},
structOutput: {
notConfiguredTip: 'La salida estructurada aún no ha sido configurada.',
required: 'Requerido',
configure: 'Configurar',
LLMResponse: 'Respuesta del LLM',
moreFillTip: 'Mostrando un máximo de 10 niveles de anidación',
modelNotSupportedTip: 'El modelo actual no admite esta función y se degrada automáticamente a inyección de comandos.',
structuredTip: 'Las Salidas Estructuradas son una función que garantiza que el modelo siempre generará respuestas que se ajusten a su esquema JSON proporcionado.',
modelNotSupported: 'Modelo no soportado',
},
}
export default translation

View File

@ -70,6 +70,7 @@ const translation = {
messageRequest: {
title: 'Créditos de Mensajes',
tooltip: 'Cuotas de invocación de mensajes para varios planes utilizando modelos de OpenAI (excepto gpt4). Los mensajes que excedan el límite utilizarán tu clave API de OpenAI.',
titlePerMonth: '{{count,number}} mensajes/mes',
},
annotatedResponse: {
title: 'Límites de Cuota de Anotación',
@ -77,27 +78,94 @@ const translation = {
},
ragAPIRequestTooltip: 'Se refiere al número de llamadas API que invocan solo las capacidades de procesamiento de base de conocimientos de Dify.',
receiptInfo: 'Solo el propietario del equipo y el administrador del equipo pueden suscribirse y ver la información de facturación.',
priceTip: 'por espacio de trabajo/',
teamMember_one: '{{count, número}} Miembro del Equipo',
getStarted: 'Comenzar',
apiRateLimitUnit: '{{count, número}}/día',
freeTrialTipSuffix: 'No se requiere tarjeta de crédito',
unlimitedApiRate: 'Sin límite de tasa de API',
apiRateLimit: 'Límite de tasa de API',
documentsTooltip: 'Cuota sobre el número de documentos importados desde la Fuente de Datos del Conocimiento.',
comparePlanAndFeatures: 'Compara planes y características',
cloud: 'Servicio en la nube',
teamMember_other: '{{count,number}} Miembros del equipo',
annualBilling: 'Facturación Anual',
self: 'Autoalojado',
freeTrialTip: 'prueba gratuita de 200 llamadas de OpenAI.',
teamWorkspace: '{{count,number}} Espacio de Trabajo en Equipo',
documents: '{{count,number}} Documentos de Conocimiento',
documentsRequestQuota: '{{count,number}}/min Límite de tasa de solicitud de conocimiento',
freeTrialTipPrefix: 'Regístrate y obtén un',
apiRateLimitTooltip: 'El límite de tasa de la API se aplica a todas las solicitudes realizadas a través de la API de Dify, incluidos la generación de texto, las conversaciones de chat, las ejecuciones de flujo de trabajo y el procesamiento de documentos.',
documentsRequestQuotaTooltip: 'Especifica el número total de acciones que un espacio de trabajo puede realizar por minuto dentro de la base de conocimientos, incluyendo la creación, eliminación, actualización de conjuntos de datos, carga de documentos, modificaciones, archivo y consultas a la base de conocimientos. Esta métrica se utiliza para evaluar el rendimiento de las solicitudes a la base de conocimientos. Por ejemplo, si un usuario de Sandbox realiza 10 pruebas consecutivas en un minuto, su espacio de trabajo será temporalmente restringido de realizar las siguientes acciones durante el siguiente minuto: creación de conjuntos de datos, eliminación, actualizaciones y carga o modificaciones de documentos.',
},
plans: {
sandbox: {
name: 'Sandbox',
description: 'Prueba gratuita de 200 veces GPT',
includesTitle: 'Incluye:',
for: 'Prueba gratuita de capacidades básicas',
},
professional: {
name: 'Profesional',
description: 'Para individuos y pequeños equipos que desean desbloquear más poder de manera asequible.',
includesTitle: 'Todo en el plan gratuito, más:',
for: 'Para desarrolladores independientes/equipos pequeños',
},
team: {
name: 'Equipo',
description: 'Colabora sin límites y disfruta de un rendimiento de primera categoría.',
includesTitle: 'Todo en el plan Profesional, más:',
for: 'Para equipos de tamaño mediano',
},
enterprise: {
name: 'Empresa',
description: 'Obtén capacidades completas y soporte para sistemas críticos a gran escala.',
includesTitle: 'Todo en el plan Equipo, más:',
features: {
0: 'Soluciones de implementación escalables de nivel empresarial',
7: 'Actualizaciones y Mantenimiento por Dify Oficialmente',
8: 'Soporte Técnico Profesional',
3: 'Múltiples Espacios de Trabajo y Gestión Empresarial',
1: 'Autorización de Licencia Comercial',
2: 'Características Exclusivas de la Empresa',
5: 'SLA negociados por Dify Partners',
4: 'SSO',
6: 'Seguridad y Controles Avanzados',
},
btnText: 'Contactar ventas',
for: 'Para equipos de gran tamaño',
price: 'Personalizado',
priceTip: 'Facturación Anual Solo',
},
community: {
features: {
0: 'Todas las características principales se lanzaron bajo el repositorio público',
2: 'Cumple con la Licencia de Código Abierto de Dify',
1: 'Espacio de trabajo único',
},
includesTitle: 'Características gratuitas:',
for: 'Para usuarios individuales, pequeños equipos o proyectos no comerciales',
price: 'Gratis',
btnText: 'Comienza con la Comunidad',
name: 'Comunidad',
description: 'Para usuarios individuales, pequeños equipos o proyectos no comerciales',
},
premium: {
features: {
0: 'Confiabilidad autogestionada por varios proveedores de nube',
1: 'Espacio de trabajo único',
3: 'Soporte prioritario por correo electrónico y chat',
2: 'Personalización de logotipos y marcas de WebApp',
},
description: 'Para organizaciones y equipos de tamaño mediano',
comingSoon: 'Soporte de Microsoft Azure y Google Cloud disponible próximamente',
btnText: 'Obtén Premium en',
priceTip: 'Basado en el Mercado de la Nube',
price: 'Escalable',
includesTitle: 'Todo de Community, además:',
name: 'Premium',
for: 'Para organizaciones y equipos de tamaño mediano',
},
},
vectorSpace: {
@ -107,12 +175,26 @@ const translation = {
apps: {
fullTipLine1: 'Actualiza tu plan para',
fullTipLine2: 'crear más aplicaciones.',
fullTip1des: 'Has alcanzado el límite de aplicaciones de construcción en este plan',
fullTip2des: 'Se recomienda limpiar las aplicaciones inactivas para liberar espacio de uso, o contactarnos.',
fullTip1: 'Actualiza para crear más aplicaciones',
fullTip2: 'Límite de plan alcanzado',
contactUs: 'Contáctanos',
},
annotatedResponse: {
fullTipLine1: 'Actualiza tu plan para',
fullTipLine2: 'anotar más conversaciones.',
quotaTitle: 'Cuota de Respuesta Anotada',
},
usagePage: {
buildApps: 'Construir aplicaciones',
documentsUploadQuota: 'Cuota de carga de documentos',
vectorSpace: 'Almacenamiento de Datos de Conocimiento',
teamMembers: 'Miembros del equipo',
annotationQuota: 'Cuota de anotación',
vectorSpaceTooltip: 'Los documentos con el modo de indexación de alta calidad consumirán recursos de Almacenamiento de Datos de Conocimiento. Cuando el Almacenamiento de Datos de Conocimiento alcanza el límite, no se subirán nuevos documentos.',
},
teamMembers: 'Miembros del equipo',
}
export default translation

View File

@ -54,6 +54,10 @@ const translation = {
in: 'en',
viewDetails: 'Ver detalles',
copied: 'Copiado',
more: 'Más',
downloadSuccess: 'Descarga completada.',
downloadFailed: 'La descarga ha fallado. Por favor, inténtalo de nuevo más tarde.',
format: 'Formato',
},
errorMsg: {
fieldRequired: '{{field}} es requerido',
@ -157,6 +161,9 @@ const translation = {
community: 'Comunidad',
about: 'Acerca de',
logout: 'Cerrar sesión',
support: 'Apoyo',
compliance: 'Cumplimiento',
github: 'GitHub',
},
settings: {
accountGroup: 'CUENTA',
@ -206,6 +213,9 @@ const translation = {
feedbackTitle: 'Retroalimentación',
feedbackLabel: '¿Cuéntanos por qué eliminaste tu cuenta?',
feedbackPlaceholder: 'Opcional',
workspaceIcon: 'Icono de espacio de trabajo',
editWorkspaceInfo: 'Editar información del espacio de trabajo',
workspaceName: 'Nombre del espacio de trabajo',
},
members: {
team: 'Equipo',
@ -547,6 +557,7 @@ const translation = {
inputPlaceholder: 'Hablar con el bot',
thinking: 'Pensamiento...',
thought: 'Pensamiento',
resend: 'Reenviar',
},
promptEditor: {
placeholder: 'Escribe tu palabra de indicación aquí, ingresa \'{\' para insertar una variable, ingresa \'/\' para insertar un bloque de contenido de indicación',
@ -637,6 +648,24 @@ const translation = {
pagination: {
perPage: 'Elementos por página',
},
theme: {
auto: 'sistema',
light: 'luz',
theme: 'Tema',
},
compliance: {
iso27001: 'Certificación ISO 27001:2022',
gdpr: 'GDPR DPA',
soc2Type1: 'Informe SOC 2 Tipo I',
sandboxUpgradeTooltip: 'Solo disponible con un plan Profesional o de Equipo.',
professionalUpgradeTooltip: 'Solo disponible con un plan de equipo o superior.',
soc2Type2: 'Informe SOC 2 Tipo II',
},
imageInput: {
supportedFormats: 'Soporta PNG, JPG, JPEG, WEBP y GIF',
browse: 'navegar',
dropImageHere: 'Deja tu imagen aquí, o',
},
}
export default translation

Some files were not shown because too many files have changed in this diff Show More