Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
14cfb310
Commit
14cfb310
authored
Feb 24, 2024
by
JzoNg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
app creation
parent
f607a334
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
299 additions
and
22 deletions
+299
-22
NewAppCard.tsx
web/app/(commonLayout)/apps/NewAppCard.tsx
+8
-5
appForm.tsx
web/app/components/app/create-app-dialog/appForm.tsx
+170
-0
index.tsx
web/app/components/app/create-app-dialog/index.tsx
+42
-0
newAppDialog.tsx
web/app/components/app/create-app-dialog/newAppDialog.tsx
+57
-0
index.tsx
web/app/components/app/create-from-dsl-modal/index.tsx
+2
-2
app.ts
web/i18n/en-US/app.ts
+9
-7
app.ts
web/i18n/zh-Hans/app.ts
+9
-6
apps.ts
web/service/apps.ts
+2
-2
No files found.
web/app/(commonLayout)/apps/NewAppCard.tsx
View file @
14cfb310
...
...
@@ -2,7 +2,7 @@
import
{
forwardRef
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
NewAppDialog
from
'./NewAppD
ialog'
import
CreateAppDialog
from
'@/app/components/app/create-app-d
ialog'
import
CreateFromDSLModal
from
'@/app/components/app/create-from-dsl-modal'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
Plus
}
from
'@/app/components/base/icons/src/vender/line/general'
...
...
@@ -17,7 +17,7 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
const
{
t
}
=
useTranslation
()
const
{
onPlanInfoChanged
}
=
useProviderContext
()
const
[
showNewAppDialog
,
setShowNewAppDialog
]
=
useState
(
fals
e
)
const
[
showNewAppDialog
,
setShowNewAppDialog
]
=
useState
(
tru
e
)
const
[
showCreateFromDSLModal
,
setShowCreateFromDSLModal
]
=
useState
(
false
)
return
(
<
a
...
...
@@ -50,12 +50,15 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
onSuccess
()
}
}
/>
<
NewAppDialog
show=
{
showNewAppDialog
}
onSuccess=
{
()
=>
{
<
CreateAppDialog
show=
{
showNewAppDialog
}
onClose=
{
()
=>
setShowNewAppDialog
(
false
)
}
onSuccess=
{
()
=>
{
onPlanInfoChanged
()
if
(
onSuccess
)
onSuccess
()
}
}
onClose=
{
()
=>
setShowNewAppDialog
(
false
)
}
/>
}
}
/>
</
a
>
)
})
...
...
web/app/components/app/create-app-dialog/appForm.tsx
0 → 100644
View file @
14cfb310
'use client'
import
type
{
MouseEventHandler
}
from
'react'
import
{
useCallback
,
useRef
,
useState
}
from
'react'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
{
useRouter
}
from
'next/navigation'
import
{
useContext
,
useContextSelector
}
from
'use-context-selector'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
ToastContext
}
from
'@/app/components/base/toast'
import
AppsContext
,
{
useAppContext
}
from
'@/context/app-context'
import
type
{
AppMode
}
from
'@/types/app'
import
{
createApp
}
from
'@/service/apps'
import
Button
from
'@/app/components/base/button'
import
AppIcon
from
'@/app/components/base/app-icon'
import
EmojiPicker
from
'@/app/components/base/emoji-picker'
import
AppsFull
from
'@/app/components/billing/apps-full-in-dialog'
import
{
BubbleText
}
from
'@/app/components/base/icons/src/vender/solid/education'
import
{
CuteRobote
}
from
'@/app/components/base/icons/src/vender/solid/communication'
import
{
Route
}
from
'@/app/components/base/icons/src/vender/line/mapsAndTravel'
export
type
AppFormProps
=
{
onConfirm
:
()
=>
void
onHide
:
()
=>
void
}
const
AppForm
=
({
onConfirm
,
onHide
,
}:
AppFormProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
router
=
useRouter
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
mutateApps
=
useContextSelector
(
AppsContext
,
state
=>
state
.
mutateApps
)
const
[
appMode
,
setAppMode
]
=
useState
<
AppMode
>
(
'chat'
)
const
[
emoji
,
setEmoji
]
=
useState
({
icon
:
'🤖'
,
icon_background
:
'#FFEAD5'
})
const
[
showEmojiPicker
,
setShowEmojiPicker
]
=
useState
(
false
)
const
[
name
,
setName
]
=
useState
(
''
)
const
[
description
,
setDescription
]
=
useState
(
''
)
const
{
plan
,
enableBilling
}
=
useProviderContext
()
const
isAppsFull
=
(
enableBilling
&&
plan
.
usage
.
buildApps
>=
plan
.
total
.
buildApps
)
const
{
isCurrentWorkspaceManager
}
=
useAppContext
()
const
isCreatingRef
=
useRef
(
false
)
const
onCreate
:
MouseEventHandler
=
useCallback
(
async
()
=>
{
if
(
!
name
.
trim
())
{
notify
({
type
:
'error'
,
message
:
t
(
'app.newApp.nameNotEmpty'
)
})
return
}
if
(
isCreatingRef
.
current
)
return
isCreatingRef
.
current
=
true
try
{
const
app
=
await
createApp
({
mode
:
appMode
,
name
,
icon
:
emoji
.
icon
,
icon_background
:
emoji
.
icon_background
,
description
,
})
notify
({
type
:
'success'
,
message
:
t
(
'app.newApp.appCreated'
)
})
onConfirm
()
onHide
()
mutateApps
()
router
.
push
(
`/app/
${
app
.
id
}
/
${
isCurrentWorkspaceManager
?
'configuration'
:
'overview'
}
`
)
}
catch
(
e
)
{
notify
({
type
:
'error'
,
message
:
t
(
'app.newApp.appCreateFailed'
)
})
}
isCreatingRef
.
current
=
false
},
[
name
,
notify
,
t
,
appMode
,
emoji
.
icon
,
emoji
.
icon_background
,
description
,
onConfirm
,
onHide
,
mutateApps
,
router
,
isCurrentWorkspaceManager
])
return
(
<>
{
/* app type */
}
<
div
className=
'pt-2 px-8'
>
<
div
className=
'py-2 text-sm leading-[20px] font-medium text-gray-900'
>
{
t
(
'app.newApp.captionAppType'
)
}
</
div
>
<
div
>
<
div
className=
{
cn
(
'relative mb-2 p-3 pl-[56px] min-h-[84px] bg-gray-25 rounded-xl border-[1.5px] border-gray-100 cursor-pointer hover:border-[#d1e0ff]'
,
appMode
===
'chat'
&&
'border-primary-400 hover:border-primary-400'
,
)
}
onClick=
{
()
=>
setAppMode
(
'chat'
)
}
>
<
div
className=
'absolute top-3 left-3 w-8 h-8 p-2 bg-indigo-50 rounded-lg'
>
<
BubbleText
className=
'w-4 h-4 text-indigo-600'
/>
</
div
>
<
div
className=
'mb-1 text-sm leading-[20px] font-semibold text-gray-800'
>
{
t
(
'app.types.chatbot'
)
}
</
div
>
<
div
className=
'text-xs leading-[18px] text-gray-500'
>
{
t
(
'app.newApp.chatbotDescription'
)
}
</
div
>
</
div
>
<
div
className=
{
cn
(
'relative mb-2 p-3 pl-[56px] min-h-[84px] bg-gray-25 rounded-xl border-[1.5px] border-gray-100 cursor-pointer hover:border-[#d1e0ff]'
,
appMode
===
'agent'
&&
'border-primary-400 hover:border-primary-400'
,
)
}
onClick=
{
()
=>
setAppMode
(
'agent'
)
}
>
<
div
className=
'absolute top-3 left-3 w-8 h-8 p-2 bg-indigo-50 rounded-lg'
>
<
CuteRobote
className=
'w-4 h-4 text-indigo-600'
/>
</
div
>
<
div
className=
'mb-1 text-sm leading-[20px] font-semibold text-gray-800'
>
{
t
(
'app.types.agent'
)
}
</
div
>
<
div
className=
'text-xs leading-[18px] text-gray-500'
>
{
t
(
'app.newApp.agentDescription'
)
}
</
div
>
</
div
>
<
div
className=
{
cn
(
'relative mb-2 p-3 pl-[56px] min-h-[84px] bg-gray-25 rounded-xl border-[1.5px] border-gray-100 cursor-pointer hover:border-[#d1e0ff]'
,
appMode
===
'workflow'
&&
'border-primary-400 hover:border-primary-400'
,
)
}
onClick=
{
()
=>
setAppMode
(
'workflow'
)
}
>
<
div
className=
'absolute top-3 left-3 w-8 h-8 p-2 bg-indigo-50 rounded-lg'
>
<
Route
className=
'w-4 h-4 text-indigo-600'
/>
</
div
>
<
div
className=
'mb-1 text-sm leading-[20px] font-semibold text-gray-800'
>
{
t
(
'app.types.workflow'
)
}
</
div
>
<
div
className=
'text-xs leading-[18px] text-gray-500'
>
{
t
(
'app.newApp.workflowDescription'
)
}
</
div
>
</
div
>
</
div
>
</
div
>
{
/* icon & name */
}
<
div
className=
'pt-2 px-8'
>
<
div
className=
'py-2 text-sm font-medium leading-[20px] text-gray-900'
>
{
t
(
'app.newApp.captionName'
)
}
</
div
>
<
div
className=
'flex items-center justify-between space-x-3'
>
<
AppIcon
size=
'large'
onClick=
{
()
=>
{
setShowEmojiPicker
(
true
)
}
}
className=
'cursor-pointer'
icon=
{
emoji
.
icon
}
background=
{
emoji
.
icon_background
}
/>
<
input
value=
{
name
}
onChange=
{
e
=>
setName
(
e
.
target
.
value
)
}
placeholder=
{
t
(
'app.newApp.appNamePlaceholder'
)
||
''
}
className=
'grow h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg border border-transparent outline-none appearance-none caret-primary-600 placeholder:text-gray-400 hover:bg-gray-50 hover:border hover:border-gray-300 focus:bg-gray-50 focus:border focus:border-gray-300 focus:shadow-xs'
/>
</
div
>
{
showEmojiPicker
&&
<
EmojiPicker
onSelect=
{
(
icon
,
icon_background
)
=>
{
setEmoji
({
icon
,
icon_background
})
setShowEmojiPicker
(
false
)
}
}
onClose=
{
()
=>
{
setEmoji
({
icon
:
'🤖'
,
icon_background
:
'#FFEAD5'
})
setShowEmojiPicker
(
false
)
}
}
/>
}
</
div
>
{
/* description */
}
<
div
className=
'pt-2 px-8'
>
<
div
className=
'py-2 text-sm font-medium leading-[20px] text-gray-900'
>
{
t
(
'app.newApp.captionDescription'
)
}
</
div
>
<
textarea
className=
'w-full h-10 px-3 py-2 text-sm font-normal bg-gray-100 rounded-lg border border-transparent outline-none appearance-none caret-primary-600 placeholder:text-gray-400 hover:bg-gray-50 hover:border hover:border-gray-300 focus:bg-gray-50 focus:border focus:border-gray-300 focus:shadow-xs h-[80px] resize-none'
placeholder=
{
t
(
'app.newApp.appDescriptionPlaceholder'
)
||
''
}
value=
{
description
}
onChange=
{
e
=>
setDescription
(
e
.
target
.
value
)
}
/>
</
div
>
{
isAppsFull
&&
(
<
div
className=
'px-8 py-2'
>
<
AppsFull
loc=
'app-create'
/>
</
div
>
)
}
<
div
className=
'px-8 py-6 flex justify-end'
>
<
Button
className=
'mr-2 text-gray-700 text-sm font-medium'
onClick=
{
onHide
}
>
{
t
(
'app.newApp.Cancel'
)
}
</
Button
>
<
Button
className=
'text-sm font-medium'
disabled=
{
isAppsFull
||
!
name
}
type=
"primary"
onClick=
{
onCreate
}
>
{
t
(
'app.newApp.Create'
)
}
</
Button
>
</
div
>
</>
)
}
export
default
AppForm
web/app/components/app/create-app-dialog/index.tsx
0 → 100644
View file @
14cfb310
'use client'
import
{
useTranslation
}
from
'react-i18next'
import
NewAppDialog
from
'./newAppDialog'
import
AppForm
from
'./appForm'
import
{
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
type
CreateAppDialogProps
=
{
show
:
boolean
onSuccess
:
()
=>
void
onClose
:
()
=>
void
}
const
CreateAppDialog
=
({
show
,
onSuccess
,
onClose
}:
CreateAppDialogProps
)
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
NewAppDialog
className=
'flex'
show=
{
show
}
onClose=
{
()
=>
{}
}
>
{
/* blank form */
}
<
div
className=
'shrink-0 max-w-[480px] h-full bg-white overflow-y-auto'
>
{
/* Heading */
}
<
div
className=
'sticky top-0 pl-8 pr-6 pt-6 pb-3 rounded-ss-xl text-xl leading-[30px] font-semibold text-gray-900'
>
{
t
(
'app.newApp.startFromBlank'
)
}
</
div
>
{
/* app form */
}
<
AppForm
onHide=
{
onClose
}
onConfirm=
{
onSuccess
}
/>
</
div
>
{
/* template list */
}
<
div
className=
'grow bg-gray-100'
>
<
div
className=
'sticky top-0 pl-8 pr-6 pt-6 pb-3 rounded-se-xl text-xl leading-[30px] font-semibold text-gray-900'
>
{
t
(
'app.newApp.startFromTemplate'
)
}
</
div
>
</
div
>
<
div
className=
'absolute top-6 left-[464px] w-8 h-8 p-1 bg-white border-2 border-gray-200 rounded-2xl text-xs leading-[20px] font-medium text-gray-500 cursor-default'
>
OR
</
div
>
<
div
className=
'absolute right-6 top-6 p-2 cursor-pointer'
onClick=
{
onClose
}
>
<
XClose
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
</
NewAppDialog
>
)
}
export
default
CreateAppDialog
web/app/components/app/create-app-dialog/newAppDialog.tsx
0 → 100644
View file @
14cfb310
import
{
Fragment
,
useCallback
}
from
'react'
import
type
{
ReactNode
}
from
'react'
import
{
Dialog
,
Transition
}
from
'@headlessui/react'
import
cn
from
'classnames'
type
DialogProps
=
{
className
?:
string
children
:
ReactNode
show
:
boolean
onClose
?:
()
=>
void
}
const
NewAppDialog
=
({
className
,
children
,
show
,
onClose
,
}:
DialogProps
)
=>
{
const
close
=
useCallback
(()
=>
onClose
?.(),
[
onClose
])
return
(
<
Transition
appear
show=
{
show
}
as=
{
Fragment
}
>
<
Dialog
as=
"div"
className=
"relative z-40"
onClose=
{
close
}
>
<
Transition
.
Child
as=
{
Fragment
}
enter=
"ease-out duration-300"
enterFrom=
"opacity-0"
enterTo=
"opacity-100"
leave=
"ease-in duration-200"
leaveFrom=
"opacity-100"
leaveTo=
"opacity-0"
>
<
div
className=
"fixed inset-0 bg-black bg-opacity-25"
/>
</
Transition
.
Child
>
<
div
className=
"fixed inset-0"
>
<
div
className=
"flex flex-col items-center justify-center min-h-full pt-[56px]"
>
<
Transition
.
Child
as=
{
Fragment
}
enter=
"ease-out duration-300"
enterFrom=
"opacity-0 scale-95"
enterTo=
"opacity-100 scale-100"
leave=
"ease-in duration-200"
leaveFrom=
"opacity-100 scale-100"
leaveTo=
"opacity-0 scale-95"
>
<
Dialog
.
Panel
className=
{
cn
(
'grow relative w-full h-[calc(100vh-56px)] p-0 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-t-xl'
,
className
)
}
>
{
children
}
</
Dialog
.
Panel
>
</
Transition
.
Child
>
</
div
>
</
div
>
</
Dialog
>
</
Transition
>
)
}
export
default
NewAppDialog
web/app/components/app/create-from-dsl-modal/index.tsx
View file @
14cfb310
...
...
@@ -139,7 +139,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProp
isShow=
{
show
}
onClose=
{
()
=>
{}
}
>
<
div
className=
'relative pb-2 text-xl font-medium leading-[30px] text-gray-900'
>
Create from DSL file
</
div
>
<
div
className=
'relative pb-2 text-xl font-medium leading-[30px] text-gray-900'
>
{
t
(
'app.createFromConfigFile'
)
}
</
div
>
<
div
className=
'absolute right-4 top-4 p-2 cursor-pointer'
onClick=
{
onClose
}
>
<
XClose
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
...
...
@@ -186,7 +186,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProp
</
div
>
)
}
</
div
>
{
isAppsFull
&&
<
AppsFull
loc=
'app-create'
/>
}
{
isAppsFull
&&
<
AppsFull
loc=
'app-create
-dsl
'
/>
}
<
div
className=
'pt-6 flex justify-end'
>
<
Button
className=
'mr-2 text-gray-700 text-sm font-medium'
onClick=
{
onClose
}
>
{
t
(
'app.newApp.Cancel'
)
}
</
Button
>
<
Button
className=
'text-sm font-medium'
disabled=
{
isAppsFull
||
!
currentFile
}
type=
"primary"
onClick=
{
onCreate
}
>
{
t
(
'app.newApp.Create'
)
}
</
Button
>
...
...
web/i18n/en-US/app.ts
View file @
14cfb310
...
...
@@ -6,10 +6,6 @@ const translation = {
agent
:
'Agent'
,
workflow
:
'Workflow'
,
},
modes
:
{
completion
:
'Text Generator'
,
chat
:
'Basic Assistant'
,
},
duplicate
:
'Duplicate'
,
duplicateTitle
:
'Duplicate App'
,
export
:
'Export DSL'
,
...
...
@@ -23,11 +19,17 @@ const translation = {
communityIntro
:
'Discuss with team members, contributors and developers on different channels.'
,
roadmap
:
'See our roadmap'
,
appNamePlaceholder
:
'Please enter the name of the app'
,
newApp
:
{
start
ToCreate
:
'Let
\'
s start with your new
app'
,
captionName
:
'App icon & nam
e'
,
start
FromBlank
:
'Start from a blank
app'
,
startFromTemplate
:
'Start from a templat
e'
,
captionAppType
:
'What type of app do you want to create?'
,
chatbotDescription
:
'Build a chat-based assistant using a Large Language Model'
,
agentDescription
:
'Build an intelligent Agent which can autonomously choose tools to complete the tasks'
,
workflowDescription
:
'Description text here'
,
captionName
:
'App icon & name'
,
appNamePlaceholder
:
'Give your app a name'
,
captionDescription
:
'Description'
,
appDescriptionPlaceholder
:
'Enter the description of the app'
,
previewDemo
:
'Preview demo'
,
chatApp
:
'Assistant'
,
chatAppIntro
:
...
...
web/i18n/zh-Hans/app.ts
View file @
14cfb310
...
...
@@ -6,10 +6,6 @@ const translation = {
agent
:
'Agent'
,
workflow
:
'工作流'
,
},
modes
:
{
completion
:
'文本生成型'
,
chat
:
'基础助手'
,
},
duplicate
:
'复制'
,
duplicateTitle
:
'复制应用'
,
export
:
'导出 DSL'
,
...
...
@@ -24,9 +20,16 @@ const translation = {
roadmap
:
'产品路线图'
,
appNamePlaceholder
:
'请输入应用名称'
,
newApp
:
{
start
ToCreate
:
'开始创建一个
新应用'
,
captionName
:
'图标 & 名称
'
,
start
FromBlank
:
'开始创建
新应用'
,
startFromTemplate
:
'从应用模版创建
'
,
captionAppType
:
'想要哪种应用类型?'
,
chatbotDescription
:
'使用大型语言模型构建基于聊天的助手'
,
agentDescription
:
'构建一个智能Agent,可以自主选择工具来完成任务'
,
workflowDescription
:
'Description text here'
,
captionName
:
'图标 & 名称'
,
appNamePlaceholder
:
'给你的应用起个名字'
,
captionDescription
:
'描述'
,
appDescriptionPlaceholder
:
'输入应用的描述'
,
previewDemo
:
'预览 Demo'
,
chatApp
:
'助手'
,
chatAppIntro
:
...
...
web/service/apps.ts
View file @
14cfb310
...
...
@@ -16,8 +16,8 @@ export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> =
return
get
<
AppTemplatesResponse
>
(
url
)
}
export
const
createApp
:
Fetcher
<
AppDetailResponse
,
{
name
:
string
;
icon
:
string
;
icon_background
:
string
;
mode
:
AppMode
;
config
?:
ModelConfig
}
>
=
({
name
,
icon
,
icon_background
,
mode
,
config
})
=>
{
return
post
<
AppDetailResponse
>
(
'apps'
,
{
body
:
{
name
,
icon
,
icon_background
,
mode
,
model_config
:
config
}
})
export
const
createApp
:
Fetcher
<
AppDetailResponse
,
{
name
:
string
;
icon
:
string
;
icon_background
:
string
;
mode
:
AppMode
;
description
?:
string
;
config
?:
ModelConfig
}
>
=
({
name
,
icon
,
icon_background
,
mode
,
description
,
config
})
=>
{
return
post
<
AppDetailResponse
>
(
'apps'
,
{
body
:
{
name
,
icon
,
icon_background
,
mode
,
description
,
model_config
:
config
}
})
}
export
const
deleteApp
:
Fetcher
<
CommonResponse
,
string
>
=
(
appID
)
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment