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
4edaa95c
Commit
4edaa95c
authored
Mar 06, 2024
by
JzoNg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
app menu
parent
067e6b5a
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
367 additions
and
58 deletions
+367
-58
layout.tsx
...p/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx
+1
-14
AppCard.tsx
web/app/(commonLayout)/apps/AppCard.tsx
+13
-7
Apps.tsx
web/app/(commonLayout)/apps/Apps.tsx
+1
-2
app-info.tsx
web/app/components/app-sidebar/app-info.tsx
+301
-0
index.tsx
web/app/components/app-sidebar/index.tsx
+14
-8
index.tsx
web/app/components/explore/app-list/index.tsx
+3
-0
index.tsx
web/app/components/explore/create-app-modal/index.tsx
+19
-18
app.ts
web/i18n/en-US/app.ts
+4
-3
app.ts
web/i18n/zh-Hans/app.ts
+4
-3
explore.ts
web/models/explore.ts
+2
-2
apps.ts
web/service/apps.ts
+5
-1
No files found.
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx
View file @
4edaa95c
...
...
@@ -31,19 +31,6 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const
{
appDetail
,
setAppDetail
}
=
useStore
()
const
{
data
:
response
}
=
useSWR
(
detailParams
,
fetchAppDetail
)
const
appModeName
=
(()
=>
{
if
(
response
?.
mode
===
'chat'
||
response
?.
mode
===
'advanced-chat'
)
return
t
(
'app.types.chatbot'
)
if
(
response
?.
mode
===
'agent-chat'
)
return
t
(
'app.types.agent'
)
if
(
response
?.
mode
===
'completion'
)
return
t
(
'app.types.completion'
)
return
t
(
'app.types.workflow'
)
})()
const
navigation
=
useMemo
(()
=>
{
const
navs
=
[
...(
isCurrentWorkspaceManager
...
...
@@ -97,7 +84,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
return
(
<
div
className=
{
cn
(
s
.
app
,
'flex'
,
'overflow-hidden'
)
}
>
<
AppSideBar
title=
{
response
.
name
}
icon=
{
response
.
icon
}
icon_background=
{
response
.
icon_background
}
desc=
{
appModeNam
e
}
navigation=
{
navigation
}
/>
<
AppSideBar
title=
{
response
.
name
}
icon=
{
response
.
icon
}
icon_background=
{
response
.
icon_background
}
desc=
{
response
.
mod
e
}
navigation=
{
navigation
}
/>
<
div
className=
"bg-white grow overflow-hidden"
>
{
children
}
</
div
>
...
...
web/app/(commonLayout)/apps/AppCard.tsx
View file @
4edaa95c
...
...
@@ -122,6 +122,8 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
message
:
t
(
'app.newApp.appCreated'
),
})
localStorage
.
setItem
(
NEED_REFRESH_APP_LIST_KEY
,
'1'
)
mutateApps
()
onPlanInfoChanged
()
getRedirection
(
isCurrentWorkspaceManager
,
newApp
,
push
)
}
catch
(
e
)
{
...
...
@@ -175,13 +177,17 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
<
span
className=
{
s
.
actionName
}
>
{
t
(
'common.operation.settings'
)
}
</
span
>
</
button
>
<
Divider
className=
"!my-1"
/>
<
button
className=
{
s
.
actionItem
}
onClick=
{
onClickDuplicate
}
disabled=
{
detailState
.
loading
}
>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'app.duplicate'
)
}
</
span
>
</
button
>
<
button
className=
{
s
.
actionItem
}
onClick=
{
onClickExport
}
disabled=
{
detailState
.
loading
}
>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'app.export'
)
}
</
span
>
</
button
>
<
Divider
className=
"!my-1"
/>
{
app
.
mode
!==
'completion'
&&
(
<>
<
button
className=
{
s
.
actionItem
}
onClick=
{
onClickDuplicate
}
disabled=
{
detailState
.
loading
}
>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'app.duplicate'
)
}
</
span
>
</
button
>
<
button
className=
{
s
.
actionItem
}
onClick=
{
onClickExport
}
disabled=
{
detailState
.
loading
}
>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'app.export'
)
}
</
span
>
</
button
>
<
Divider
className=
"!my-1"
/>
</>
)
}
<
div
className=
{
cn
(
s
.
actionItem
,
s
.
deleteActionItem
,
'group'
)
}
onClick=
{
onClickDelete
}
...
...
web/app/(commonLayout)/apps/Apps.tsx
View file @
4edaa95c
...
...
@@ -49,11 +49,10 @@ const Apps = () => {
)
const
anchorRef
=
useRef
<
HTMLDivElement
>
(
null
)
// #TODO# query key ???
const
options
=
[
{
value
:
'all'
,
text
:
t
(
'app.types.all'
)
},
{
value
:
'chat'
,
text
:
t
(
'app.types.chatbot'
)
},
{
value
:
'agent
-chat
'
,
text
:
t
(
'app.types.agent'
)
},
{
value
:
'agent'
,
text
:
t
(
'app.types.agent'
)
},
{
value
:
'workflow'
,
text
:
t
(
'app.types.workflow'
)
},
]
...
...
web/app/components/app-sidebar/app-info.tsx
0 → 100644
View file @
4edaa95c
import
{
useTranslation
}
from
'react-i18next'
import
{
useRouter
}
from
'next/navigation'
import
{
useContext
,
useContextSelector
}
from
'use-context-selector'
import
cn
from
'classnames'
import
React
,
{
useCallback
,
useState
}
from
'react'
import
AppIcon
from
'../base/app-icon'
import
{
PortalToFollowElem
,
PortalToFollowElemContent
,
PortalToFollowElemTrigger
,
}
from
'@/app/components/base/portal-to-follow-elem'
import
{
ChevronDown
}
from
'@/app/components/base/icons/src/vender/line/arrows'
import
Divider
from
'@/app/components/base/divider'
import
Confirm
from
'@/app/components/base/confirm'
import
{
useStore
as
useAppStore
}
from
'@/app/components/app/store'
import
{
ToastContext
}
from
'@/app/components/base/toast'
import
AppsContext
from
'@/context/app-context'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
copyApp
,
deleteApp
,
exportAppConfig
,
updateAppInfo
}
from
'@/service/apps'
import
DuplicateAppModal
from
'@/app/components/app/duplicate-modal'
import
type
{
DuplicateAppModalProps
}
from
'@/app/components/app/duplicate-modal'
import
CreateAppModal
from
'@/app/components/explore/create-app-modal'
import
type
{
CreateAppModalProps
}
from
'@/app/components/explore/create-app-modal'
import
{
NEED_REFRESH_APP_LIST_KEY
}
from
'@/config'
import
{
getRedirection
}
from
'@/utils/app-redirection'
export
type
IAppInfoProps
=
{
expand
:
boolean
}
const
AppInfo
=
({
expand
}:
IAppInfoProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
{
replace
}
=
useRouter
()
const
{
onPlanInfoChanged
}
=
useProviderContext
()
const
appDetail
=
useAppStore
(
state
=>
state
.
appDetail
)
const
setAppDetail
=
useAppStore
(
state
=>
state
.
setAppDetail
)
const
[
open
,
setOpen
]
=
useState
(
false
)
const
[
showEditModal
,
setShowEditModal
]
=
useState
(
false
)
const
[
showDuplicateModal
,
setShowDuplicateModal
]
=
useState
(
false
)
const
[
showConfirmDelete
,
setShowConfirmDelete
]
=
useState
(
false
)
const
mutateApps
=
useContextSelector
(
AppsContext
,
state
=>
state
.
mutateApps
,
)
const
onEdit
:
CreateAppModalProps
[
'onConfirm'
]
=
useCallback
(
async
({
name
,
icon
,
icon_background
,
description
,
})
=>
{
if
(
!
appDetail
)
return
try
{
const
app
=
await
updateAppInfo
({
appID
:
appDetail
.
id
,
name
,
icon
,
icon_background
,
description
,
})
setShowEditModal
(
false
)
notify
({
type
:
'success'
,
message
:
t
(
'app.editDone'
),
})
console
.
log
(
app
.
description
)
setAppDetail
(
app
)
mutateApps
()
}
catch
(
e
)
{
notify
({
type
:
'error'
,
message
:
t
(
'app.editFailed'
)
})
}
},
[
appDetail
,
mutateApps
,
notify
,
setAppDetail
,
t
])
const
onCopy
:
DuplicateAppModalProps
[
'onConfirm'
]
=
async
({
name
,
icon
,
icon_background
})
=>
{
if
(
!
appDetail
)
return
try
{
const
newApp
=
await
copyApp
({
appID
:
appDetail
.
id
,
name
,
icon
,
icon_background
,
mode
:
appDetail
.
mode
,
})
setShowDuplicateModal
(
false
)
notify
({
type
:
'success'
,
message
:
t
(
'app.newApp.appCreated'
),
})
localStorage
.
setItem
(
NEED_REFRESH_APP_LIST_KEY
,
'1'
)
mutateApps
()
onPlanInfoChanged
()
getRedirection
(
true
,
newApp
,
replace
)
}
catch
(
e
)
{
notify
({
type
:
'error'
,
message
:
t
(
'app.newApp.appCreateFailed'
)
})
}
}
const
onExport
=
async
()
=>
{
if
(
!
appDetail
)
return
try
{
const
{
data
}
=
await
exportAppConfig
(
appDetail
.
id
)
const
a
=
document
.
createElement
(
'a'
)
const
file
=
new
Blob
([
data
],
{
type
:
'application/yaml'
})
a
.
href
=
URL
.
createObjectURL
(
file
)
a
.
download
=
`
${
appDetail
.
name
}
.yml`
a
.
click
()
}
catch
(
e
)
{
notify
({
type
:
'error'
,
message
:
t
(
'app.exportFailed'
)
})
}
}
const
onConfirmDelete
=
useCallback
(
async
()
=>
{
if
(
!
appDetail
)
return
try
{
await
deleteApp
(
appDetail
.
id
)
notify
({
type
:
'success'
,
message
:
t
(
'app.appDeleted'
)
})
mutateApps
()
onPlanInfoChanged
()
replace
(
'/apps'
)
}
catch
(
e
:
any
)
{
notify
({
type
:
'error'
,
message
:
`
${
t
(
'app.appDeleteFailed'
)}${
'message'
in
e
?
`:
${
e
.
message
}
`
:
''
}
`
,
})
}
setShowConfirmDelete
(
false
)
},
[
appDetail
,
mutateApps
,
notify
,
onPlanInfoChanged
,
replace
,
t
])
if
(
!
appDetail
)
return
null
return
(
<
PortalToFollowElem
open=
{
open
}
onOpenChange=
{
setOpen
}
placement=
'bottom-start'
offset=
{
4
}
>
<
div
className=
'relative'
>
<
PortalToFollowElemTrigger
onClick=
{
()
=>
setOpen
(
v
=>
!
v
)
}
className=
'block'
>
<
div
className=
'flex cursor-pointer'
>
<
div
className=
'shrink-0 mr-2'
>
<
AppIcon
icon=
{
appDetail
.
icon
}
background=
{
appDetail
.
icon_background
}
/>
</
div
>
{
expand
&&
(
<
div
className=
"grow w-0 pt-[2px]"
>
<
div
className=
'flex justify-between items-center text-sm leading-4 font-medium text-gray-900'
>
<
div
className=
'truncate'
title=
{
appDetail
.
name
}
>
{
appDetail
.
name
}
</
div
>
<
ChevronDown
className=
'shrink-0 ml-[2px] w-3 h-3 text-gray-500'
/>
</
div
>
<
div
className=
'flex items-center text-xs leading-[18px] font-medium text-gray-500 gap-1'
>
{
appDetail
.
mode
===
'advanced-chat'
&&
(
<>
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.chatbot'
).
toUpperCase
()
}
</
div
>
<
div
title=
{
t
(
'app.newApp.advanced'
)
||
''
}
className=
'px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.newApp.advanced'
).
toUpperCase
()
}
</
div
>
</>
)
}
{
appDetail
.
mode
===
'agent-chat'
&&
(
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.agent'
).
toUpperCase
()
}
</
div
>
)
}
{
appDetail
.
mode
===
'chat'
&&
(
<>
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.chatbot'
).
toUpperCase
()
}
</
div
>
<
div
title=
{
t
(
'app.newApp.basic'
)
||
''
}
className=
'px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
(
t
(
'app.newApp.basic'
).
toUpperCase
())
}
</
div
>
</>
)
}
{
appDetail
.
mode
===
'completion'
&&
(
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.completion'
).
toUpperCase
()
}
</
div
>
)
}
{
appDetail
.
mode
===
'workflow'
&&
(
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.workflow'
).
toUpperCase
()
}
</
div
>
)
}
</
div
>
</
div
>
)
}
</
div
>
</
PortalToFollowElemTrigger
>
<
PortalToFollowElemContent
className=
'z-[1002]'
>
<
div
className=
'w-[320px] bg-white rounded-2xl shadow-xl'
>
{
/* header */
}
<
div
className=
{
cn
(
'flex pl-4 pt-3 pr-3'
,
!
appDetail
.
description
&&
'pb-2'
)
}
>
<
div
className=
'shrink-0 mr-2'
>
<
AppIcon
icon=
{
appDetail
.
icon
}
background=
{
appDetail
.
icon_background
}
/>
</
div
>
<
div
className=
'grow w-0 pt-[2px]'
>
<
div
title=
{
appDetail
.
name
}
className=
'flex justify-between items-center text-sm leading-4 font-medium text-gray-900 truncate'
>
{
appDetail
.
name
}
</
div
>
<
div
className=
'flex items-center text-xs leading-[18px] font-medium text-gray-500 gap-1'
>
{
appDetail
.
mode
===
'advanced-chat'
&&
(
<>
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.chatbot'
).
toUpperCase
()
}
</
div
>
<
div
title=
{
t
(
'app.newApp.advanced'
)
||
''
}
className=
'px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.newApp.advanced'
).
toUpperCase
()
}
</
div
>
</>
)
}
{
appDetail
.
mode
===
'agent-chat'
&&
(
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.agent'
).
toUpperCase
()
}
</
div
>
)
}
{
appDetail
.
mode
===
'chat'
&&
(
<>
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.chatbot'
).
toUpperCase
()
}
</
div
>
<
div
title=
{
t
(
'app.newApp.basic'
)
||
''
}
className=
'px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
(
t
(
'app.newApp.basic'
).
toUpperCase
())
}
</
div
>
</>
)
}
{
appDetail
.
mode
===
'completion'
&&
(
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.completion'
).
toUpperCase
()
}
</
div
>
)
}
{
appDetail
.
mode
===
'workflow'
&&
(
<
div
className=
'shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'
>
{
t
(
'app.types.workflow'
).
toUpperCase
()
}
</
div
>
)
}
</
div
>
</
div
>
</
div
>
{
/* desscription */
}
{
appDetail
.
description
&&
(
<
div
className=
'px-4 py-2 text-gray-500 text-xs leading-[18px]'
>
{
appDetail
.
description
}
</
div
>
)
}
{
/* operations */
}
<
div
></
div
>
<
Divider
className=
"!my-1"
/>
<
div
className=
"w-full py-1"
>
<
div
className=
'h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
onClick=
{
()
=>
{
setOpen
(
false
)
setShowEditModal
(
true
)
}
}
>
<
span
className=
'text-gray-700 text-sm leading-5'
>
{
t
(
'app.editApp'
)
}
</
span
>
</
div
>
{
appDetail
.
mode
!==
'completion'
&&
(
<>
<
div
className=
'h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
onClick=
{
()
=>
{
setOpen
(
false
)
setShowDuplicateModal
(
true
)
}
}
>
<
span
className=
'text-gray-700 text-sm leading-5'
>
{
t
(
'app.duplicate'
)
}
</
span
>
</
div
>
<
div
className=
'h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
onClick=
{
onExport
}
>
<
span
className=
'text-gray-700 text-sm leading-5'
>
{
t
(
'app.export'
)
}
</
span
>
</
div
>
</>
)
}
<
Divider
className=
"!my-1"
/>
<
div
className=
'group h-9 py-2 px-3 mx-1 flex items-center hover:bg-red-50 rounded-lg cursor-pointer'
onClick=
{
()
=>
{
setOpen
(
false
)
setShowConfirmDelete
(
true
)
}
}
>
<
span
className=
'text-gray-700 text-sm leading-5 group-hover:text-red-500'
>
{
t
(
'common.operation.delete'
)
}
</
span
>
</
div
>
</
div
>
</
div
>
</
PortalToFollowElemContent
>
{
showEditModal
&&
(
<
CreateAppModal
isEditModal
appIcon=
{
appDetail
.
icon
}
appIconBackground=
{
appDetail
.
icon_background
}
appName=
{
appDetail
.
name
}
appDescription=
{
appDetail
.
description
}
show=
{
showEditModal
}
onConfirm=
{
onEdit
}
onHide=
{
()
=>
setShowEditModal
(
false
)
}
/>
)
}
{
showDuplicateModal
&&
(
<
DuplicateAppModal
appName=
{
appDetail
.
name
}
icon=
{
appDetail
.
icon
}
icon_background=
{
appDetail
.
icon_background
}
show=
{
showDuplicateModal
}
onConfirm=
{
onCopy
}
onHide=
{
()
=>
setShowDuplicateModal
(
false
)
}
/>
)
}
{
showConfirmDelete
&&
(
<
Confirm
title=
{
t
(
'app.deleteAppConfirmTitle'
)
}
content=
{
t
(
'app.deleteAppConfirmContent'
)
}
isShow=
{
showConfirmDelete
}
onClose=
{
()
=>
setShowConfirmDelete
(
false
)
}
onConfirm=
{
onConfirmDelete
}
onCancel=
{
()
=>
setShowConfirmDelete
(
false
)
}
/>
)
}
</
div
>
</
PortalToFollowElem
>
)
}
export
default
React
.
memo
(
AppInfo
)
web/app/components/app-sidebar/index.tsx
View file @
4edaa95c
...
...
@@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react'
import
NavLink
from
'./navLink'
import
type
{
NavIcon
}
from
'./navLink'
import
AppBasic
from
'./basic'
import
AppInfo
from
'./app-info'
import
useBreakpoints
,
{
MediaType
}
from
'@/hooks/use-breakpoints'
import
{
AlignLeft01
,
...
...
@@ -62,14 +63,19 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
${expand ? 'p-4' : 'p-2'}
`
}
>
<
AppBasic
mode=
{
modeState
}
iconType=
{
iconType
}
icon=
{
icon
}
icon_background=
{
icon_background
}
name=
{
title
}
type=
{
desc
}
/>
{
iconType
===
'app'
&&
(
<
AppInfo
expand=
{
expand
}
/>
)
}
{
iconType
!==
'app'
&&
(
<
AppBasic
mode=
{
modeState
}
iconType=
{
iconType
}
icon=
{
icon
}
icon_background=
{
icon_background
}
name=
{
title
}
type=
{
desc
}
/>
)
}
</
div
>
<
nav
className=
{
`
...
...
web/app/components/explore/app-list/index.tsx
View file @
4edaa95c
...
...
@@ -149,7 +149,10 @@ const Apps = ({
</
div
>
{
isShowCreateModal
&&
(
<
CreateAppModal
appIcon=
{
currApp
?.
app
.
icon
||
''
}
appIconBackground=
{
currApp
?.
app
.
icon_background
||
''
}
appName=
{
currApp
?.
app
.
name
||
''
}
appDescription=
{
currApp
?.
app
.
description
||
''
}
show=
{
isShowCreateModal
}
onConfirm=
{
onCreate
}
onHide=
{
()
=>
setIsShowCreateModal
(
false
)
}
...
...
web/app/components/explore/create-app-modal/index.tsx
View file @
4edaa95c
...
...
@@ -11,9 +11,12 @@ import AppsFull from '@/app/components/billing/apps-full-in-dialog'
import
{
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
export
type
CreateAppModalProps
=
{
appName
:
string
appDescription
?:
string
show
:
boolean
isEditModal
?:
boolean
appName
:
string
appDescription
:
string
appIcon
:
string
appIconBackground
:
string
onConfirm
:
(
info
:
{
name
:
string
icon
:
string
...
...
@@ -24,9 +27,12 @@ export type CreateAppModalProps = {
}
const
CreateAppModal
=
({
show
=
false
,
isEditModal
=
false
,
appIcon
,
appIconBackground
,
appName
,
appDescription
,
show
=
false
,
onConfirm
,
onHide
,
}:
CreateAppModalProps
)
=>
{
...
...
@@ -34,7 +40,7 @@ const CreateAppModal = ({
const
[
name
,
setName
]
=
React
.
useState
(
appName
)
const
[
showEmojiPicker
,
setShowEmojiPicker
]
=
useState
(
false
)
const
[
emoji
,
setEmoji
]
=
useState
({
icon
:
'🤖'
,
icon_background
:
'#FFEAD5'
})
const
[
emoji
,
setEmoji
]
=
useState
({
icon
:
appIcon
,
icon_background
:
appIconBackground
})
const
[
description
,
setDescription
]
=
useState
(
appDescription
||
''
)
const
{
plan
,
enableBilling
}
=
useProviderContext
()
...
...
@@ -64,7 +70,12 @@ const CreateAppModal = ({
<
div
className=
'absolute right-4 top-4 p-2 cursor-pointer'
onClick=
{
onHide
}
>
<
XClose
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
<
div
className=
'mb-9 font-semibold text-xl leading-[30px] text-gray-900'
>
{
t
(
'explore.appCustomize.title'
,
{
name
:
appName
})
}
</
div
>
{
isEditModal
&&
(
<
div
className=
'mb-9 font-semibold text-xl leading-[30px] text-gray-900'
>
{
t
(
'app.editAppTitle'
)
}
</
div
>
)
}
{
!
isEditModal
&&
(
<
div
className=
'mb-9 font-semibold text-xl leading-[30px] text-gray-900'
>
{
t
(
'explore.appCustomize.title'
,
{
name
:
appName
})
}
</
div
>
)
}
<
div
className=
'mb-9'
>
{
/* icon & name */
}
<
div
className=
'pt-2'
>
...
...
@@ -78,16 +89,6 @@ const CreateAppModal = ({
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'
>
...
...
@@ -99,10 +100,10 @@ const CreateAppModal = ({
onChange=
{
e
=>
setDescription
(
e
.
target
.
value
)
}
/>
</
div
>
{
isAppsFull
&&
<
AppsFull
loc=
'app-explore-create'
/>
}
{
!
isEditModal
&&
isAppsFull
&&
<
AppsFull
loc=
'app-explore-create'
/>
}
</
div
>
<
div
className=
'flex flex-row-reverse'
>
<
Button
disabled=
{
isAppsFull
}
className=
'w-24 ml-2'
type=
'primary'
onClick=
{
submit
}
>
{
t
(
'common.operation.creat
e'
)
}
</
Button
>
<
Button
disabled=
{
!
isEditModal
&&
isAppsFull
}
className=
'w-24 ml-2'
type=
'primary'
onClick=
{
submit
}
>
{
!
isEditModal
?
t
(
'common.operation.create'
)
:
t
(
'common.operation.sav
e'
)
}
</
Button
>
<
Button
className=
'w-24'
onClick=
{
onHide
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
</
div
>
</
Modal
>
...
...
@@ -112,7 +113,7 @@ const CreateAppModal = ({
setShowEmojiPicker
(
false
)
}
}
onClose=
{
()
=>
{
setEmoji
({
icon
:
'🤖'
,
icon_background
:
'#FFEAD5'
})
setEmoji
({
icon
:
appIcon
,
icon_background
:
appIconBackground
})
setShowEmojiPicker
(
false
)
}
}
/>
}
...
...
web/i18n/en-US/app.ts
View file @
4edaa95c
...
...
@@ -58,9 +58,10 @@ const translation = {
appCreated
:
'App created'
,
appCreateFailed
:
'Failed to create app'
,
},
editApp
:
{
startToEdit
:
'Edit App'
,
},
editApp
:
'Edit Info'
,
editAppTitle
:
'Edit App Info'
,
editDone
:
'App info updated'
,
editFailed
:
'Failed to update app info'
,
emoji
:
{
ok
:
'OK'
,
cancel
:
'Cancel'
,
...
...
web/i18n/zh-Hans/app.ts
View file @
4edaa95c
...
...
@@ -57,9 +57,10 @@ const translation = {
appCreated
:
'应用已创建'
,
appCreateFailed
:
'应用创建失败'
,
},
editApp
:
{
startToEdit
:
'编辑应用'
,
},
editApp
:
'编辑信息'
,
editAppTitle
:
'编辑应用信息'
,
editDone
:
'应用信息已更新'
,
editFailed
:
'更新应用信息失败'
,
emoji
:
{
ok
:
'确认'
,
cancel
:
'取消'
,
...
...
web/models/explore.ts
View file @
4edaa95c
import
type
{
AppMode
}
from
'@/types/app'
export
type
AppBasicInfo
=
{
id
:
string
name
:
string
mode
:
AppMode
icon
:
string
icon_background
:
string
is_agent
:
boolean
name
:
string
description
:
string
}
export
type
AppCategory
=
'Writing'
|
'Translate'
|
'HR'
|
'Programming'
|
'Assistant'
...
...
web/service/apps.ts
View file @
4edaa95c
import
type
{
Fetcher
}
from
'swr'
import
{
del
,
get
,
post
}
from
'./base'
import
{
del
,
get
,
post
,
put
}
from
'./base'
import
type
{
ApikeysListResponse
,
AppDailyConversationsResponse
,
AppDailyEndUsersResponse
,
AppDetailResponse
,
AppListResponse
,
AppStatisticsResponse
,
AppTemplatesResponse
,
AppTokenCostsResponse
,
AppVoicesListResponse
,
CreateApiKeyResponse
,
GenerationIntroductionResponse
,
UpdateAppModelConfigResponse
,
UpdateAppSiteCodeResponse
,
UpdateOpenAIKeyResponse
,
ValidateOpenAIKeyResponse
}
from
'@/models/app'
import
type
{
CommonResponse
}
from
'@/models/common'
import
type
{
AppMode
,
ModelConfig
}
from
'@/types/app'
...
...
@@ -20,6 +20,10 @@ export const createApp: Fetcher<AppDetailResponse, { name: string; icon: string;
return
post
<
AppDetailResponse
>
(
'apps'
,
{
body
:
{
name
,
icon
,
icon_background
,
mode
,
description
,
model_config
:
config
}
})
}
export
const
updateAppInfo
:
Fetcher
<
AppDetailResponse
,
{
appID
:
string
;
name
:
string
;
icon
:
string
;
icon_background
:
string
;
description
:
string
}
>
=
({
appID
,
name
,
icon
,
icon_background
,
description
})
=>
{
return
put
<
AppDetailResponse
>
(
`apps/
${
appID
}
`
,
{
body
:
{
name
,
icon
,
icon_background
,
description
}
})
}
export
const
copyApp
:
Fetcher
<
AppDetailResponse
,
{
appID
:
string
;
name
:
string
;
icon
:
string
;
icon_background
:
string
;
mode
:
AppMode
;
description
?:
string
}
>
=
({
appID
,
name
,
icon
,
icon_background
,
mode
,
description
})
=>
{
return
post
<
AppDetailResponse
>
(
`apps/
${
appID
}
/copy`
,
{
body
:
{
name
,
icon
,
icon_background
,
mode
,
description
}
})
}
...
...
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