Commit 345399a3 authored by Gillian97's avatar Gillian97

feat: app setting support embed

parent 561c9cab
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import {
Cog8ToothIcon,
......@@ -11,6 +12,7 @@ import { usePathname, useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import SettingsModal from './settings'
import ShareLink from './share-link'
import EmbeddedModal from './embedded'
import CustomizeModal from './customize'
import Tooltip from '@/app/components/base/tooltip'
import AppBasic, { randomString } from '@/app/components/app-sidebar/basic'
......@@ -18,6 +20,7 @@ import Button from '@/app/components/base/button'
import Tag from '@/app/components/base/tag'
import Switch from '@/app/components/base/switch'
import type { AppDetailResponse } from '@/models/app'
import './style.css'
export type IAppCardProps = {
className?: string
......@@ -29,6 +32,10 @@ export type IAppCardProps = {
onGenerateCode?: () => Promise<any>
}
const EmbedIcon: FC<{ className?: string }> = ({ className = '' }) => {
return <div className={`codeBrowserIcon ${className}`}></div>
}
function AppCard({
appInfo,
cardType = 'app',
......@@ -42,6 +49,7 @@ function AppCard({
const pathname = usePathname()
const [showSettingsModal, setShowSettingsModal] = useState(false)
const [showShareModal, setShowShareModal] = useState(false)
const [showEmbedded, setShowEmbedded] = useState(false)
const [showCustomizeModal, setShowCustomizeModal] = useState(false)
const { t } = useTranslation()
......@@ -49,6 +57,7 @@ function AppCard({
webapp: [
{ opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon },
{ opName: t('appOverview.overview.appInfo.share.entry'), opIcon: ShareIcon },
{ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon },
{ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon },
],
api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }],
......@@ -80,6 +89,10 @@ function AppCard({
return () => {
setShowSettingsModal(true)
}
case t('appOverview.overview.appInfo.embedded.entry'):
return () => {
setShowEmbedded(true)
}
default:
// jump to page develop
return () => {
......@@ -152,7 +165,7 @@ function AppCard({
}
>
<div className="flex flex-row items-center">
<op.opIcon className="h-4 w-4 mr-1.5" />
<op.opIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
<span className="text-xs">{op.opName}</span>
</div>
</Tooltip>
......@@ -193,6 +206,12 @@ function AppCard({
onClose={() => setShowSettingsModal(false)}
onSave={onSaveSiteConfig}
/>
<EmbeddedModal
isShow={showEmbedded}
onClose={() => setShowEmbedded(false)}
appBaseUrl={app_base_url}
accessToken={access_token}
/>
<CustomizeModal
isShow={showCustomizeModal}
linkUrl=""
......
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6667 6H1.33337M9.33337 11.6667L11 10L9.33337 8.33333M6.66671 8.33333L5.00004 10L6.66671 11.6667M1.33337 5.2L1.33337 10.8C1.33337 11.9201 1.33337 12.4802 1.55136 12.908C1.74311 13.2843 2.04907 13.5903 2.42539 13.782C2.85322 14 3.41327 14 4.53337 14H11.4667C12.5868 14 13.1469 14 13.5747 13.782C13.951 13.5903 14.257 13.2843 14.4487 12.908C14.6667 12.4802 14.6667 11.9201 14.6667 10.8V5.2C14.6667 4.0799 14.6667 3.51984 14.4487 3.09202C14.257 2.7157 13.951 2.40973 13.5747 2.21799C13.1469 2 12.5868 2 11.4667 2L4.53337 2C3.41327 2 2.85322 2 2.42539 2.21799C2.04907 2.40973 1.74311 2.71569 1.55136 3.09202C1.33337 3.51984 1.33337 4.0799 1.33337 5.2Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
This diff is collapsed.
This diff is collapsed.
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import style from './style.module.css'
import Modal from '@/app/components/base/modal'
import useCopyToClipboard from '@/hooks/use-copy-to-clipboard'
import copyStyle from '@/app/components/app/chat/copy-btn/style.module.css'
type Props = {
isShow: boolean
onClose: () => void
accessToken: string
appBaseUrl: string
}
const OPTION_MAP = {
iframe: {
getContent: (url: string, token: string) =>
`<iframe
src="${url}/completion/${token}"
style="width: 100%; height: 100%; min-height: 700px"
frameborder="0" >
</iframe>`,
},
scripts: {
getContent: (url: string, token: string) =>
`<script
src="${url}/embed.min.js"
id="${token}"
defer>
</script>`,
},
}
const prefixEmbedded = 'appOverview.overview.appInfo.embedded'
type Option = keyof typeof OPTION_MAP
const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
const { t } = useTranslation()
const [option, setOption] = useState<Option>('iframe')
const [isCopied, setIsCopied] = useState({ iframe: false, scripts: false })
const [_, copy] = useCopyToClipboard()
const onClickCopy = () => {
copy(OPTION_MAP[option].getContent(appBaseUrl, accessToken))
setIsCopied({ ...isCopied, [option]: true })
}
return (
<Modal
title={t(`${prefixEmbedded}.title`)}
isShow={isShow}
onClose={onClose}
className="!max-w-2xl w-[640px]"
closable={true}
>
<div className="mb-4 mt-8 text-gray-900 text-[14px] font-medium leading-tight">
{t(`${prefixEmbedded}.explanation`)}
</div>
<div className="flex gap-4 items-center">
{Object.keys(OPTION_MAP).map((v, index) => {
return (
<div
key={index}
className={cn(
style.option,
style[`${v}Icon`],
option === v && style.active,
)}
onClick={() => setOption(v as Option)}
></div>
)
})}
</div>
<div className="mt-6 w-full bg-gray-100 rounded-lg flex-col justify-start items-start inline-flex">
<div className="self-stretch pl-3 pr-1 py-1 bg-gray-50 rounded-tl-lg rounded-tr-lg border border-black border-opacity-5 justify-start items-center gap-2 inline-flex">
<div className="grow shrink basis-0 text-slate-700 text-[13px] font-medium leading-none">
{t(`${prefixEmbedded}.${option}`)}
</div>
<div className="p-2 rounded-lg justify-center items-center gap-1 flex">
<div className="w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg">
<div onClick={onClickCopy} className={`w-full h-full ${copyStyle.copyIcon} ${isCopied[option] ? copyStyle.copied : ''}`}></div>
</div>
</div>
</div>
<div className="self-stretch p-3 justify-start items-start gap-2 inline-flex">
<div className="grow shrink basis-0 text-slate-700 text-[13px] leading-tight font-mono">
<pre>{OPTION_MAP[option].getContent(appBaseUrl, accessToken)}</pre>
</div>
</div>
</div>
</Modal>
)
}
export default Embedded
.option {
width: 188px;
height: 128px;
@apply box-border cursor-pointer bg-auto bg-no-repeat bg-center rounded-md;
}
.active {
@apply border-[1.5px] border-[#2970FF];
}
.iframeIcon {
background-image: url(../assets/iframe-option.svg);
}
.scriptsIcon {
background-image: url(../assets/scripts-option.svg);
}
......@@ -11,3 +11,8 @@
transform: rotate(360deg);
}
}
.codeBrowserIcon {
@apply w-4 h-4 bg-center bg-no-repeat;
background-image: url(./assets/code-browser.svg);
}
......@@ -36,6 +36,13 @@ const translation = {
privacyPolicyTip: 'Helps visitors understand the data the application collects, see Dify\'s <privacyPolicyLink>Privacy Policy</privacyPolicyLink>.',
},
},
embedded: {
entry: 'Embedded',
title: 'Embed on website',
explanation: 'Choose the way to embed chat app to your website',
iframe: 'To add the chat app any where on your website, add this iframe to your html code.',
scripts: 'To add a chat app to the bottom right of your website add this code to your html.',
},
customize: {
way: 'way',
entry: 'Want to customize your WebApp?',
......
......@@ -36,6 +36,13 @@ const translation = {
privacyPolicyTip: '帮助访问者了解该应用收集的数据,可参考 Dify 的<privacyPolicyLink>隐私政策</privacyPolicyLink>。',
},
},
embedded: {
entry: '嵌入',
title: '嵌入到网站中',
explanation: '选择一种方式将聊天应用嵌入到你的网站中',
iframe: '将以下 iframe 嵌入到你的网站中的目标位置',
scripts: '将以下代码嵌入到你的网站中',
},
customize: {
way: '方法',
entry: '想要进一步自定义 WebApp?',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment