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
1d7a65f6
Commit
1d7a65f6
authored
Jul 24, 2023
by
John Wang
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'main' into feat/universal-chat
parents
5759122d
dd1172b5
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
182 additions
and
94 deletions
+182
-94
commands.py
api/commands.py
+62
-4
config.py
api/config.py
+3
-1
datasets.py
api/controllers/console/datasets/datasets.py
+0
-1
azure_provider.py
api/core/llm/provider/azure_provider.py
+32
-44
index.tsx
web/app/components/app/overview/embedded/index.tsx
+24
-3
utils.ts
web/app/components/base/icons/utils.ts
+1
-0
input-copy.tsx
web/app/components/develop/secret-key/input-copy.tsx
+7
-4
secret-key-generate.tsx
...app/components/develop/secret-key/secret-key-generate.tsx
+1
-1
secret-key-modal.tsx
web/app/components/develop/secret-key/secret-key-modal.tsx
+6
-8
invitation-link.tsx
...nt-setting/members-page/invited-modal/invitation-link.tsx
+5
-3
index.tsx
web/app/components/share/chat/sidebar/list/index.tsx
+4
-1
installForm.tsx
web/app/install/installForm.tsx
+11
-1
use-copy-to-clipboard.ts
web/hooks/use-copy-to-clipboard.ts
+20
-18
common.zh.ts
web/i18n/lang/common.zh.ts
+1
-1
package.json
web/package.json
+1
-1
embed.js
web/public/embed.js
+2
-1
embed.min.js
web/public/embed.min.js
+2
-2
No files found.
api/commands.py
View file @
1d7a65f6
...
...
@@ -2,6 +2,7 @@ import datetime
import
logging
import
random
import
string
import
time
import
click
from
flask
import
current_app
...
...
@@ -13,7 +14,7 @@ from libs.helper import email as email_validate
from
extensions.ext_database
import
db
from
libs.rsa
import
generate_key_pair
from
models.account
import
InvitationCode
,
Tenant
from
models.dataset
import
Dataset
from
models.dataset
import
Dataset
,
DatasetQuery
,
Document
,
DocumentSegment
from
models.model
import
Account
import
secrets
import
base64
...
...
@@ -172,7 +173,7 @@ def recreate_all_dataset_indexes():
page
=
1
while
True
:
try
:
datasets
=
db
.
session
.
query
(
Dataset
)
.
filter
(
Dataset
.
indexing_technique
==
'high_quality'
)
\
datasets
=
db
.
session
.
query
(
Dataset
)
.
filter
(
Dataset
.
indexing_technique
==
'high_quality'
)
\
.
order_by
(
Dataset
.
created_at
.
desc
())
.
paginate
(
page
=
page
,
per_page
=
50
)
except
NotFound
:
break
...
...
@@ -188,12 +189,66 @@ def recreate_all_dataset_indexes():
else
:
click
.
echo
(
'passed.'
)
except
Exception
as
e
:
click
.
echo
(
click
.
style
(
'Recreate dataset index error: {} {}'
.
format
(
e
.
__class__
.
__name__
,
str
(
e
)),
fg
=
'red'
))
click
.
echo
(
click
.
style
(
'Recreate dataset index error: {} {}'
.
format
(
e
.
__class__
.
__name__
,
str
(
e
)),
fg
=
'red'
))
continue
click
.
echo
(
click
.
style
(
'Congratulations! Recreate {} dataset indexes.'
.
format
(
recreate_count
),
fg
=
'green'
))
@
click
.
command
(
'clean-unused-dataset-indexes'
,
help
=
'Clean unused dataset indexes.'
)
def
clean_unused_dataset_indexes
():
click
.
echo
(
click
.
style
(
'Start clean unused dataset indexes.'
,
fg
=
'green'
))
clean_days
=
int
(
current_app
.
config
.
get
(
'CLEAN_DAY_SETTING'
))
start_at
=
time
.
perf_counter
()
thirty_days_ago
=
datetime
.
datetime
.
now
()
-
datetime
.
timedelta
(
days
=
clean_days
)
page
=
1
while
True
:
try
:
datasets
=
db
.
session
.
query
(
Dataset
)
.
filter
(
Dataset
.
created_at
<
thirty_days_ago
)
\
.
order_by
(
Dataset
.
created_at
.
desc
())
.
paginate
(
page
=
page
,
per_page
=
50
)
except
NotFound
:
break
page
+=
1
for
dataset
in
datasets
:
dataset_query
=
db
.
session
.
query
(
DatasetQuery
)
.
filter
(
DatasetQuery
.
created_at
>
thirty_days_ago
,
DatasetQuery
.
dataset_id
==
dataset
.
id
)
.
all
()
if
not
dataset_query
or
len
(
dataset_query
)
==
0
:
documents
=
db
.
session
.
query
(
Document
)
.
filter
(
Document
.
dataset_id
==
dataset
.
id
,
Document
.
indexing_status
==
'completed'
,
Document
.
enabled
==
True
,
Document
.
archived
==
False
,
Document
.
updated_at
>
thirty_days_ago
)
.
all
()
if
not
documents
or
len
(
documents
)
==
0
:
try
:
# remove index
vector_index
=
IndexBuilder
.
get_index
(
dataset
,
'high_quality'
)
kw_index
=
IndexBuilder
.
get_index
(
dataset
,
'economy'
)
# delete from vector index
if
vector_index
:
vector_index
.
delete
()
kw_index
.
delete
()
# update document
update_params
=
{
Document
.
enabled
:
False
}
Document
.
query
.
filter_by
(
dataset_id
=
dataset
.
id
)
.
update
(
update_params
)
db
.
session
.
commit
()
click
.
echo
(
click
.
style
(
'Cleaned unused dataset {} from db success!'
.
format
(
dataset
.
id
),
fg
=
'green'
))
except
Exception
as
e
:
click
.
echo
(
click
.
style
(
'clean dataset index error: {} {}'
.
format
(
e
.
__class__
.
__name__
,
str
(
e
)),
fg
=
'red'
))
end_at
=
time
.
perf_counter
()
click
.
echo
(
click
.
style
(
'Cleaned unused dataset from db success latency: {}'
.
format
(
end_at
-
start_at
),
fg
=
'green'
))
@
click
.
command
(
'sync-anthropic-hosted-providers'
,
help
=
'Sync anthropic hosted providers.'
)
def
sync_anthropic_hosted_providers
():
click
.
echo
(
click
.
style
(
'Start sync anthropic hosted providers.'
,
fg
=
'green'
))
...
...
@@ -218,7 +273,9 @@ def sync_anthropic_hosted_providers():
)
count
+=
1
except
Exception
as
e
:
click
.
echo
(
click
.
style
(
'Sync tenant anthropic hosted provider error: {} {}'
.
format
(
e
.
__class__
.
__name__
,
str
(
e
)),
fg
=
'red'
))
click
.
echo
(
click
.
style
(
'Sync tenant anthropic hosted provider error: {} {}'
.
format
(
e
.
__class__
.
__name__
,
str
(
e
)),
fg
=
'red'
))
continue
click
.
echo
(
click
.
style
(
'Congratulations! Synced {} anthropic hosted providers.'
.
format
(
count
),
fg
=
'green'
))
...
...
@@ -231,3 +288,4 @@ def register_commands(app):
app
.
cli
.
add_command
(
reset_encrypt_key_pair
)
app
.
cli
.
add_command
(
recreate_all_dataset_indexes
)
app
.
cli
.
add_command
(
sync_anthropic_hosted_providers
)
app
.
cli
.
add_command
(
clean_unused_dataset_indexes
)
api/config.py
View file @
1d7a65f6
...
...
@@ -53,7 +53,8 @@ DEFAULTS = {
'DEFAULT_LLM_PROVIDER'
:
'openai'
,
'OPENAI_HOSTED_QUOTA_LIMIT'
:
200
,
'ANTHROPIC_HOSTED_QUOTA_LIMIT'
:
1000
,
'TENANT_DOCUMENT_COUNT'
:
100
'TENANT_DOCUMENT_COUNT'
:
100
,
'CLEAN_DAY_SETTING'
:
30
}
...
...
@@ -215,6 +216,7 @@ class Config:
self
.
NOTION_INTEGRATION_TOKEN
=
get_env
(
'NOTION_INTEGRATION_TOKEN'
)
self
.
TENANT_DOCUMENT_COUNT
=
get_env
(
'TENANT_DOCUMENT_COUNT'
)
self
.
CLEAN_DAY_SETTING
=
get_env
(
'CLEAN_DAY_SETTING'
)
class
CloudEditionConfig
(
Config
):
...
...
api/controllers/console/datasets/datasets.py
View file @
1d7a65f6
...
...
@@ -3,7 +3,6 @@ from flask import request
from
flask_login
import
login_required
,
current_user
from
flask_restful
import
Resource
,
reqparse
,
fields
,
marshal
,
marshal_with
from
werkzeug.exceptions
import
NotFound
,
Forbidden
import
services
from
controllers.console
import
api
from
controllers.console.datasets.error
import
DatasetNameDuplicateError
...
...
api/core/llm/provider/azure_provider.py
View file @
1d7a65f6
...
...
@@ -2,6 +2,7 @@ import json
import
logging
from
typing
import
Optional
,
Union
import
openai
import
requests
from
core.llm.provider.base
import
BaseProvider
...
...
@@ -14,30 +15,37 @@ AZURE_OPENAI_API_VERSION = '2023-07-01-preview'
class
AzureProvider
(
BaseProvider
):
def
get_models
(
self
,
model_id
:
Optional
[
str
]
=
None
,
credentials
:
Optional
[
dict
]
=
None
)
->
list
[
dict
]:
credentials
=
self
.
get_credentials
(
model_id
)
if
not
credentials
else
credentials
url
=
"{}/openai/deployments?api-version={}"
.
format
(
str
(
credentials
.
get
(
'openai_api_base'
)),
str
(
credentials
.
get
(
'openai_api_version'
))
)
headers
=
{
"api-key"
:
str
(
credentials
.
get
(
'openai_api_key'
))
,
"content-type"
:
"application/json; charset=utf-8"
}
response
=
requests
.
get
(
url
,
headers
=
headers
)
if
response
.
status_code
==
200
:
r
esult
=
response
.
json
(
)
return
[{
'id'
:
deployment
[
'id'
],
'
name'
:
'{} ({})'
.
format
(
deployment
[
'id'
],
deployment
[
'model'
]
)
}
for
deployment
in
result
[
'data'
]
if
deployment
[
'status'
]
==
'succeeded'
]
else
:
if
response
.
status_code
==
401
:
raise
AzureAuthenticationError
(
)
return
[]
def
check_embedding_model
(
self
,
credentials
:
Optional
[
dict
]
=
None
):
credentials
=
self
.
get_credentials
(
'text-embedding-ada-002'
)
if
not
credentials
else
credentials
try
:
result
=
openai
.
Embedding
.
create
(
input
=
[
'test'
],
engine
=
'text-embedding-ada-002'
,
timeout
=
60
,
api_key
=
str
(
credentials
.
get
(
'openai_api_key'
)),
api_base
=
str
(
credentials
.
get
(
'openai_api_base'
)),
api_type
=
'azure'
,
api_version
=
str
(
credentials
.
get
(
'openai_api_version'
)))[
"data"
][
0
][
"embedding"
]
except
openai
.
error
.
AuthenticationError
as
e
:
r
aise
AzureAuthenticationError
(
str
(
e
)
)
except
openai
.
error
.
APIConnectionError
as
e
:
raise
AzureRequestFailedError
(
'
Failed to request Azure OpenAI, please check your API Base Endpoint, The format is `https://xxx.openai.azure.com/`'
)
except
openai
.
error
.
InvalidRequestError
as
e
:
if
e
.
http_status
==
404
:
raise
AzureRequestFailedError
(
"Please check your 'gpt-3.5-turbo' or 'text-embedding-ada-002' "
"deployment name is exists in Azure AI"
)
else
:
raise
AzureRequestFailedError
(
'Failed to request Azure OpenAI. Status code: {}'
.
format
(
response
.
status_code
))
raise
AzureRequestFailedError
(
'Failed to request Azure OpenAI. cause: {}'
.
format
(
str
(
e
)))
except
openai
.
error
.
OpenAIError
as
e
:
raise
AzureRequestFailedError
(
'Failed to request Azure OpenAI. cause: {}'
.
format
(
str
(
e
)))
if
not
isinstance
(
result
,
list
):
raise
AzureRequestFailedError
(
'Failed to request Azure OpenAI.'
)
def
get_credentials
(
self
,
model_id
:
Optional
[
str
]
=
None
)
->
dict
:
"""
...
...
@@ -98,31 +106,11 @@ class AzureProvider(BaseProvider):
if
'openai_api_version'
not
in
config
:
config
[
'openai_api_version'
]
=
AZURE_OPENAI_API_VERSION
models
=
self
.
get_models
(
credentials
=
config
)
if
not
models
:
raise
ValidateFailedError
(
"Please add deployments for "
"'gpt-3.5-turbo', 'text-embedding-ada-002' (required) "
"and 'gpt-4', 'gpt-35-turbo-16k', 'text-davinci-003' (optional)."
)
fixed_model_ids
=
[
'gpt-35-turbo'
,
'text-embedding-ada-002'
]
current_model_ids
=
[
model
[
'id'
]
for
model
in
models
]
missing_model_ids
=
[
fixed_model_id
for
fixed_model_id
in
fixed_model_ids
if
fixed_model_id
not
in
current_model_ids
]
if
missing_model_ids
:
raise
ValidateFailedError
(
"Please add deployments for '{}'."
.
format
(
", "
.
join
(
missing_model_ids
)))
self
.
check_embedding_model
(
credentials
=
config
)
except
ValidateFailedError
as
e
:
raise
e
except
AzureAuthenticationError
:
raise
ValidateFailedError
(
'Validation failed, please check your API Key.'
)
except
(
requests
.
ConnectionError
,
requests
.
RequestException
):
raise
ValidateFailedError
(
'Validation failed, please check your API Base Endpoint.'
)
except
AzureRequestFailedError
as
ex
:
raise
ValidateFailedError
(
'Validation failed, error: {}.'
.
format
(
str
(
ex
)))
except
Exception
as
ex
:
...
...
web/app/components/app/overview/embedded/index.tsx
View file @
1d7a65f6
import
React
,
{
useState
}
from
'react'
import
React
,
{
use
Effect
,
use
State
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
cn
from
'classnames'
import
style
from
'./style.module.css'
...
...
@@ -43,10 +43,15 @@ const prefixEmbedded = 'appOverview.overview.appInfo.embedded'
type
Option
=
keyof
typeof
OPTION_MAP
type
OptionStatus
=
{
iframe
:
boolean
scripts
:
boolean
}
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
[
isCopied
,
setIsCopied
]
=
useState
<
OptionStatus
>
({
iframe
:
false
,
scripts
:
false
})
const
[
_
,
copy
]
=
useCopyToClipboard
()
const
{
langeniusVersionInfo
}
=
useAppContext
()
...
...
@@ -56,6 +61,19 @@ const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
setIsCopied
({
...
isCopied
,
[
option
]:
true
})
}
// when toggle option, reset then copy status
const
resetCopyStatus
=
()
=>
{
const
cache
=
{
...
isCopied
}
Object
.
keys
(
cache
).
forEach
((
key
)
=>
{
cache
[
key
as
keyof
OptionStatus
]
=
false
})
setIsCopied
(
cache
)
}
useEffect
(()
=>
{
resetCopyStatus
()
},
[
isShow
])
return
(
<
Modal
title=
{
t
(
`${prefixEmbedded}.title`
)
}
...
...
@@ -77,7 +95,10 @@ const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
style
[
`${v}Icon`
],
option
===
v
&&
style
.
active
,
)
}
onClick=
{
()
=>
setOption
(
v
as
Option
)
}
onClick=
{
()
=>
{
setOption
(
v
as
Option
)
resetCopyStatus
()
}
}
></
div
>
)
})
}
...
...
web/app/components/base/icons/utils.ts
View file @
1d7a65f6
...
...
@@ -15,6 +15,7 @@ export type Attrs = {
export
function
normalizeAttrs
(
attrs
:
Attrs
=
{}):
Attrs
{
return
Object
.
keys
(
attrs
).
reduce
((
acc
:
Attrs
,
key
)
=>
{
const
val
=
attrs
[
key
]
key
=
key
.
replace
(
/
([
-
]\w)
/g
,
(
g
:
string
)
=>
g
[
1
].
toUpperCase
())
switch
(
key
)
{
case
'class'
:
acc
.
className
=
val
...
...
web/app/components/develop/secret-key/input-copy.tsx
View file @
1d7a65f6
'use client'
import
React
,
{
useEffect
,
useState
}
from
'react'
import
React
,
{
useEffect
,
use
Ref
,
use
State
}
from
'react'
import
copy
from
'copy-to-clipboard'
import
{
t
}
from
'i18next'
import
s
from
'./style.module.css'
import
{
randomString
}
from
'@/app/components/app-sidebar/basic'
import
Tooltip
from
'@/app/components/base/tooltip'
type
IInputCopyProps
=
{
...
...
@@ -13,13 +14,15 @@ type IInputCopyProps = {
}
const
InputCopy
=
({
value
,
value
=
''
,
className
,
readOnly
=
true
,
children
,
}:
IInputCopyProps
)
=>
{
const
[
isCopied
,
setIsCopied
]
=
useState
(
false
)
const
selector
=
useRef
(
`input-tooltip-
${
randomString
(
4
)}
`
)
useEffect
(()
=>
{
if
(
isCopied
)
{
const
timeout
=
setTimeout
(()
=>
{
...
...
@@ -38,7 +41,7 @@ const InputCopy = ({
{
children
}
<
div
className=
'flex-grow bg-gray-50 text-[13px] relative h-full'
>
<
Tooltip
selector=
"top-uniq"
selector=
{
selector
.
current
}
content=
{
isCopied
?
`${t('appApi.copied')}`
:
`${t('appApi.copy')}`
}
className=
'z-10'
>
...
...
@@ -50,7 +53,7 @@ const InputCopy = ({
</
div
>
<
div
className=
"flex-shrink-0 h-4 bg-gray-200 border"
/>
<
Tooltip
selector=
"top-uniq"
selector=
{
selector
.
current
}
content=
{
isCopied
?
`${t('appApi.copied')}`
:
`${t('appApi.copy')}`
}
className=
'z-10'
>
...
...
web/app/components/develop/secret-key/secret-key-generate.tsx
View file @
1d7a65f6
...
...
@@ -18,7 +18,7 @@ const SecretKeyGenerateModal = ({
isShow
=
false
,
onClose
,
newKey
,
className
className
,
}:
ISecretKeyGenerateModalProps
)
=>
{
const
{
t
}
=
useTranslation
()
return
(
...
...
web/app/components/develop/secret-key/secret-key-modal.tsx
View file @
1d7a65f6
...
...
@@ -6,6 +6,7 @@ import {
import
{
useTranslation
}
from
'react-i18next'
import
{
PlusIcon
,
XMarkIcon
}
from
'@heroicons/react/20/solid'
import
useSWR
,
{
useSWRConfig
}
from
'swr'
import
{
useContext
}
from
'use-context-selector'
import
SecretKeyGenerateModal
from
'./secret-key-generate'
import
s
from
'./style.module.css'
import
Modal
from
'@/app/components/base/modal'
...
...
@@ -16,7 +17,6 @@ import Tooltip from '@/app/components/base/tooltip'
import
Loading
from
'@/app/components/base/loading'
import
Confirm
from
'@/app/components/base/confirm'
import
useCopyToClipboard
from
'@/hooks/use-copy-to-clipboard'
import
{
useContext
}
from
'use-context-selector'
import
I18n
from
'@/context/i18n'
type
ISecretKeyModalProps
=
{
...
...
@@ -58,12 +58,11 @@ const SecretKeyModal = ({
}
},
[
copyValue
])
const
onDel
=
async
()
=>
{
setShowConfirmDelete
(
false
)
if
(
!
delKeyID
)
{
if
(
!
delKeyID
)
return
}
await
delApikey
({
url
:
`/apps/
${
appId
}
/api-keys/
${
delKeyID
}
`
,
params
:
{}
})
mutate
(
commonParams
)
}
...
...
@@ -80,11 +79,10 @@ const SecretKeyModal = ({
}
const
formatDate
=
(
timestamp
:
any
)
=>
{
if
(
locale
===
'en'
)
{
if
(
locale
===
'en'
)
return
new
Intl
.
DateTimeFormat
(
'en-US'
,
{
year
:
'numeric'
,
month
:
'long'
,
day
:
'numeric'
}).
format
((
+
timestamp
)
*
1000
)
}
else
{
else
return
new
Intl
.
DateTimeFormat
(
'fr-CA'
,
{
year
:
'numeric'
,
month
:
'2-digit'
,
day
:
'2-digit'
}).
format
((
+
timestamp
)
*
1000
)
}
}
return
(
...
...
@@ -111,7 +109,7 @@ const SecretKeyModal = ({
<
div
className=
'flex-shrink-0 px-3 truncate w-28'
>
{
api
.
last_used_at
?
formatDate
(
api
.
last_used_at
)
:
t
(
'appApi.never'
)
}
</
div
>
<
div
className=
'flex flex-grow px-3'
>
<
Tooltip
selector=
"top-uniq"
selector=
{
`key-${api.token}`
}
content=
{
copyValue
===
api
.
token
?
`${t('appApi.copied')}`
:
`${t('appApi.copy')}`
}
className=
'z-10'
>
...
...
web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx
View file @
1d7a65f6
'use client'
import
React
,
{
useCallback
,
useEffect
,
useState
}
from
'react'
import
React
,
{
useCallback
,
useEffect
,
use
Ref
,
use
State
}
from
'react'
import
{
t
}
from
'i18next'
import
s
from
'./index.module.css'
import
Tooltip
from
'@/app/components/base/tooltip'
import
useCopyToClipboard
from
'@/hooks/use-copy-to-clipboard'
import
{
randomString
}
from
'@/app/components/app-sidebar/basic'
type
IInvitationLinkProps
=
{
value
?:
string
...
...
@@ -13,6 +14,7 @@ const InvitationLink = ({
value
=
''
,
}:
IInvitationLinkProps
)
=>
{
const
[
isCopied
,
setIsCopied
]
=
useState
(
false
)
const
selector
=
useRef
(
`invite-link-
${
randomString
(
4
)}
`
)
const
[
_
,
copy
]
=
useCopyToClipboard
()
const
copyHandle
=
useCallback
(()
=>
{
...
...
@@ -37,7 +39,7 @@ const InvitationLink = ({
<
div
className=
"flex items-center flex-grow h-5"
>
<
div
className=
'flex-grow bg-gray-100 text-[13px] relative h-full'
>
<
Tooltip
selector=
"top-uniq"
selector=
{
selector
.
current
}
content=
{
isCopied
?
`${t('appApi.copied')}`
:
`${t('appApi.copy')}`
}
className=
'z-10'
>
...
...
@@ -46,7 +48,7 @@ const InvitationLink = ({
</
div
>
<
div
className=
"flex-shrink-0 h-4 bg-gray-200 border"
/>
<
Tooltip
selector=
"top-uniq"
selector=
{
selector
.
current
}
content=
{
isCopied
?
`${t('appApi.copied')}`
:
`${t('appApi.copy')}`
}
className=
'z-10'
>
...
...
web/app/components/share/chat/sidebar/list/index.tsx
View file @
1d7a65f6
...
...
@@ -48,7 +48,10 @@ const List: FC<IListProps> = ({
useInfiniteScroll
(
async
()
=>
{
if
(
!
isNoMore
)
{
const
lastId
=
!
isClearConversationList
?
list
[
list
.
length
-
1
]?.
id
:
undefined
let
lastId
=
!
isClearConversationList
?
list
[
list
.
length
-
1
]?.
id
:
undefined
if
(
lastId
===
'-1'
)
lastId
=
undefined
const
{
data
:
conversations
,
has_more
}:
any
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
lastId
,
isPinned
)
onMoreLoaded
({
data
:
conversations
,
has_more
})
}
...
...
web/app/install/installForm.tsx
View file @
1d7a65f6
...
...
@@ -17,6 +17,7 @@ const InstallForm = () => {
const
[
email
,
setEmail
]
=
React
.
useState
(
''
)
const
[
name
,
setName
]
=
React
.
useState
(
''
)
const
[
password
,
setPassword
]
=
React
.
useState
(
''
)
const
[
showPassword
,
setShowPassword
]
=
React
.
useState
(
false
)
const
showErrorMessage
=
(
message
:
string
)
=>
{
Toast
.
notify
({
type
:
'error'
,
...
...
@@ -108,12 +109,21 @@ const InstallForm = () => {
<
div
className=
"mt-1 relative rounded-md shadow-sm"
>
<
input
id=
"password"
type=
'password'
type=
{
showPassword
?
'text'
:
'password'
}
value=
{
password
}
onChange=
{
e
=>
setPassword
(
e
.
target
.
value
)
}
placeholder=
{
t
(
'login.passwordPlaceholder'
)
||
''
}
className=
{
'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'
}
/>
<
div
className=
"absolute inset-y-0 right-0 flex items-center pr-3"
>
<
button
type=
"button"
onClick=
{
()
=>
setShowPassword
(
!
showPassword
)
}
className=
"text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500"
>
{
showPassword
?
'👀'
:
'😝'
}
</
button
>
</
div
>
</
div
>
<
div
className=
'mt-1 text-xs text-gray-500'
>
{
t
(
'login.error.passwordInvalid'
)
}
</
div
>
...
...
web/hooks/use-copy-to-clipboard.ts
View file @
1d7a65f6
import
{
useState
}
from
'react'
import
{
useCallback
,
useState
}
from
'react'
import
writeText
from
'copy-to-clipboard'
type
CopiedValue
=
string
|
null
type
CopyFn
=
(
text
:
string
)
=>
Promise
<
boolean
>
function
useCopyToClipboard
():
[
CopiedValue
,
CopyFn
]
{
const
[
copiedText
,
setCopiedText
]
=
useState
<
CopiedValue
>
(
null
)
const
[
copiedText
,
setCopiedText
]
=
useState
<
CopiedValue
>
(
null
)
const
copy
:
CopyFn
=
async
text
=>
{
if
(
!
navigator
?.
clipboard
)
{
console
.
warn
(
'Clipboard not supported'
)
return
false
}
const
copy
:
CopyFn
=
useCallback
(
async
(
text
:
string
)
=>
{
if
(
!
navigator
?.
clipboard
)
{
console
.
warn
(
'Clipboard not supported'
)
return
false
}
try
{
await
navigator
.
clipboard
.
writeText
(
text
)
setCopiedText
(
text
)
return
true
}
catch
(
error
)
{
console
.
warn
(
'Copy failed'
,
error
)
setCopiedText
(
null
)
return
false
}
try
{
writeText
(
text
)
setCopiedText
(
text
)
return
true
}
catch
(
error
)
{
console
.
warn
(
'Copy failed'
,
error
)
setCopiedText
(
null
)
return
false
}
},
[])
return
[
copiedText
,
copy
]
return
[
copiedText
,
copy
]
}
export
default
useCopyToClipboard
\ No newline at end of file
export
default
useCopyToClipboard
web/i18n/lang/common.zh.ts
View file @
1d7a65f6
...
...
@@ -43,7 +43,7 @@ const translation = {
'较高的 Temperature 设置将导致更多样和创造性的输出,而较低的 Temperature 将产生更保守的输出并且类似于训练数据。'
,
topP
:
'采样范围'
,
topPTip
:
'Top P值越
高,输出与训练文本越相似,Top P值越低
,输出越有创意和变化。它可用于使输出更适合特定用例。'
,
'Top P值越
低,输出与训练文本越相似,Top P值越高
,输出越有创意和变化。它可用于使输出更适合特定用例。'
,
presencePenalty
:
'词汇控制'
,
presencePenaltyTip
:
'Presence penalty 是根据新词是否出现在目前的文本中来对其进行惩罚。正值将降低模型谈论新话题的可能性。'
,
...
...
web/package.json
View file @
1d7a65f6
...
...
@@ -54,7 +54,7 @@
"katex"
:
"^0.16.7"
,
"lodash-es"
:
"^4.17.21"
,
"negotiator"
:
"^0.6.3"
,
"next"
:
"
^13.4.7
"
,
"next"
:
"
13.3.0
"
,
"qs"
:
"^6.11.1"
,
"react"
:
"^18.2.0"
,
"react-dom"
:
"^18.2.0"
,
...
...
web/public/embed.js
View file @
1d7a65f6
...
...
@@ -15,6 +15,7 @@ async function embedChatbot () {
return
;
}
const
isDev
=
!!
difyChatbotConfig
.
isDev
const
baseUrl
=
difyChatbotConfig
.
baseUrl
||
`https://
${
isDev
?
'dev.'
:
''
}
udify.app`
const
openIcon
=
`<svg
id="openIcon"
width="24"
...
...
@@ -53,7 +54,7 @@ async function embedChatbot () {
iframe
.
allow
=
"fullscreen;microphone"
iframe
.
title
=
"dify chatbot bubble window"
iframe
.
id
=
'dify-chatbot-bubble-window'
iframe
.
src
=
`
https://
${
isDev
?
'dev.'
:
''
}
udify.app/chatbot/
${
difyChatbotConfig
.
token
}
`
;
iframe
.
src
=
`
${
baseUrl
}
/chatbot/
${
difyChatbotConfig
.
token
}
`
iframe
.
style
.
cssText
=
'border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;'
document
.
body
.
appendChild
(
iframe
);
}
...
...
web/public/embed.min.js
View file @
1d7a65f6
async
function
embedChatbot
(){
const
t
=
window
.
difyChatbotConfig
;
if
(
t
&&
t
.
token
){
const
o
=!!
t
.
isDev
,
n
=
`<svg
async
function
embedChatbot
(){
const
t
=
window
.
difyChatbotConfig
;
if
(
t
&&
t
.
token
){
var
e
=!!
t
.
isDev
;
const
o
=
t
.
baseUrl
||
`https://
${
e
?
"dev."
:
""
}
udify.app`
,
n
=
`<svg
id="openIcon"
width="24"
height="24"
...
...
@@ -27,4 +27,4 @@ async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){co
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>`
;
if
(
!
document
.
getElementById
(
"dify-chatbot-bubble-button"
)){
var
e
=
document
.
createElement
(
"div"
);
e
.
id
=
"dify-chatbot-bubble-button"
,
e
.
style
.
cssText
=
"position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}"
;
const
d
=
document
.
createElement
(
"div"
);
d
.
style
.
cssText
=
"display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;"
,
d
.
innerHTML
=
n
,
e
.
appendChild
(
d
),
document
.
body
.
appendChild
(
e
),
e
.
addEventListener
(
"click"
,
function
(){
var
e
=
document
.
getElementById
(
"dify-chatbot-bubble-window"
);
e
?
"none"
===
e
.
style
.
display
?(
e
.
style
.
display
=
"block"
,
d
.
innerHTML
=
i
):(
e
.
style
.
display
=
"none"
,
d
.
innerHTML
=
n
):((
e
=
document
.
createElement
(
"iframe"
)).
allow
=
"fullscreen;microphone"
,
e
.
title
=
"dify chatbot bubble window"
,
e
.
id
=
"dify-chatbot-bubble-window"
,
e
.
src
=
`https://
${
o
?
"dev."
:
""
}
udify.app/chatbot/`
+
t
.
token
,
e
.
style
.
cssText
=
"border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;"
,
document
.
body
.
appendChild
(
e
),
d
.
innerHTML
=
i
)})}}
else
console
.
error
(
"difyChatbotConfig is empty or token is not provided"
)}
document
.
body
.
onload
=
embedChatbot
;
\ No newline at end of file
</svg>`
;
if
(
!
document
.
getElementById
(
"dify-chatbot-bubble-button"
)){
e
=
document
.
createElement
(
"div"
);
e
.
id
=
"dify-chatbot-bubble-button"
,
e
.
style
.
cssText
=
"position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}"
;
const
d
=
document
.
createElement
(
"div"
);
d
.
style
.
cssText
=
"display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;"
,
d
.
innerHTML
=
n
,
e
.
appendChild
(
d
),
document
.
body
.
appendChild
(
e
),
e
.
addEventListener
(
"click"
,
function
(){
var
e
=
document
.
getElementById
(
"dify-chatbot-bubble-window"
);
e
?
"none"
===
e
.
style
.
display
?(
e
.
style
.
display
=
"block"
,
d
.
innerHTML
=
i
):(
e
.
style
.
display
=
"none"
,
d
.
innerHTML
=
n
):((
e
=
document
.
createElement
(
"iframe"
)).
allow
=
"fullscreen;microphone"
,
e
.
title
=
"dify chatbot bubble window"
,
e
.
id
=
"dify-chatbot-bubble-window"
,
e
.
src
=
o
+
"/chatbot/"
+
t
.
token
,
e
.
style
.
cssText
=
"border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;"
,
document
.
body
.
appendChild
(
e
),
d
.
innerHTML
=
i
)})}}
else
console
.
error
(
"difyChatbotConfig is empty or token is not provided"
)}
document
.
body
.
onload
=
embedChatbot
;
\ No newline at end of file
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