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
fd5c9afc
Commit
fd5c9afc
authored
Jul 20, 2023
by
Joel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: support view config detail
parent
f277a347
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
101 additions
and
39 deletions
+101
-39
index.tsx
...ents/app/configuration/dataset-config/card-item/index.tsx
+9
-8
index.tsx
...nents/explore/universal-chat/config-view/detail/index.tsx
+47
-0
style.module.css
...xplore/universal-chat/config-view/detail/style.module.css
+9
-0
panel.tsx
...p/components/explore/universal-chat/config-view/panel.tsx
+0
-11
index.tsx
...nents/explore/universal-chat/config/data-config/index.tsx
+9
-5
index.tsx
web/app/components/explore/universal-chat/config/index.tsx
+11
-7
index.tsx
...ents/explore/universal-chat/config/model-config/index.tsx
+2
-2
index.tsx
...ts/explore/universal-chat/config/plugins-config/index.tsx
+6
-3
item.tsx
...nts/explore/universal-chat/config/plugins-config/item.tsx
+3
-1
index.tsx
web/app/components/explore/universal-chat/index.tsx
+3
-2
explore.en.ts
web/i18n/lang/explore.en.ts
+1
-0
explore.zh.ts
web/i18n/lang/explore.zh.ts
+1
-0
No files found.
web/app/components/app/configuration/dataset-config/card-item/index.tsx
View file @
fd5c9afc
'use client'
'use client'
import
React
,
{
FC
}
from
'react'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
cn
from
'classnames'
import
cn
from
'classnames'
import
TypeIcon
from
'../type-icon'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
formatNumber
}
from
'@/utils/format
'
import
TypeIcon
from
'../type-icon
'
import
RemoveIcon
from
'../../base/icons/remove-icon'
import
RemoveIcon
from
'../../base/icons/remove-icon'
import
s
from
'./style.module.css'
import
s
from
'./style.module.css'
import
{
formatNumber
}
from
'@/utils/format'
export
interface
ICardItemProps
{
export
type
ICardItemProps
=
{
className
?:
string
className
?:
string
config
:
any
config
:
any
onRemove
:
(
id
:
string
)
=>
void
onRemove
:
(
id
:
string
)
=>
void
readonly
?:
boolean
}
}
// const RemoveIcon = ({ className, onClick }: { className: string, onClick: () => void }) => (
// const RemoveIcon = ({ className, onClick }: { className: string, onClick: () => void }) => (
// <svg className={className} onClick={onClick} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
// <svg className={className} onClick={onClick} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
// <path d="M10 6H14M6 8H18M16.6667 8L16.1991 15.0129C16.129 16.065 16.0939 16.5911 15.8667 16.99C15.6666 17.3412 15.3648 17.6235 15.0011 17.7998C14.588 18 14.0607 18 13.0062 18H10.9938C9.93927 18 9.41202 18 8.99889 17.7998C8.63517 17.6235 8.33339 17.3412 8.13332 16.99C7.90607 16.5911 7.871 16.065 7.80086 15.0129L7.33333 8M10.6667 11V14.3333M13.3333 11V14.3333" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
// <path d="M10 6H14M6 8H18M16.6667 8L16.1991 15.0129C16.129 16.065 16.0939 16.5911 15.8667 16.99C15.6666 17.3412 15.3648 17.6235 15.0011 17.7998C14.588 18 14.0607 18 13.0062 18H10.9938C9.93927 18 9.41202 18 8.99889 17.7998C8.63517 17.6235 8.33339 17.3412 8.13332 16.99C7.90607 16.5911 7.871 16.065 7.80086 15.0129L7.33333 8M10.6667 11V14.3333M13.3333 11V14.3333" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
...
@@ -24,7 +24,8 @@ export interface ICardItemProps {
...
@@ -24,7 +24,8 @@ export interface ICardItemProps {
const
CardItem
:
FC
<
ICardItemProps
>
=
({
const
CardItem
:
FC
<
ICardItemProps
>
=
({
className
,
className
,
config
,
config
,
onRemove
onRemove
,
readonly
,
})
=>
{
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
...
@@ -44,7 +45,7 @@ const CardItem: FC<ICardItemProps> = ({
...
@@ -44,7 +45,7 @@ const CardItem: FC<ICardItemProps> = ({
</
div
>
</
div
>
</
div
>
</
div
>
<
RemoveIcon
className=
{
`${s.deleteBtn} shrink-0`
}
onClick=
{
()
=>
onRemove
(
config
.
id
)
}
/>
{
!
readonly
&&
<
RemoveIcon
className=
{
`${s.deleteBtn} shrink-0`
}
onClick=
{
()
=>
onRemove
(
config
.
id
)
}
/>
}
</
div
>
</
div
>
)
)
}
}
...
...
web/app/components/explore/universal-chat/config-view/detail/index.tsx
0 → 100644
View file @
fd5c9afc
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
{
useBoolean
,
useClickAway
}
from
'ahooks'
import
s
from
'./style.module.css'
import
Config
from
'@/app/components/explore/universal-chat/config'
type
Props
=
{
modelId
:
string
plugins
:
Record
<
string
,
boolean
>
dataSets
:
any
[]
}
const
ConfigViewPanel
:
FC
<
Props
>
=
({
modelId
,
plugins
,
dataSets
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
[
isShowConfig
,
{
setFalse
:
hideConfig
,
toggle
:
toggleShowConfig
}]
=
useBoolean
(
false
)
const
configContentRef
=
React
.
useRef
(
null
)
useClickAway
(()
=>
{
hideConfig
()
},
configContentRef
)
return
(
<
div
ref=
{
configContentRef
}
className=
'relative'
>
<
div
onClick=
{
toggleShowConfig
}
className=
{
cn
(
s
.
btn
,
'flex h-8 w-8 rounded-lg border border-gray-200 bg-white cursor-pointer'
)
}
></
div
>
{
isShowConfig
&&
(
<
div
className=
{
cn
(
'absolute top-9 right-0 z-20 p-4 bg-white rounded-2xl shadow-md'
,
s
.
panelBorder
)
}
>
<
div
className=
'w-[368px]'
>
<
Config
readonly
modelId=
{
modelId
}
plugins=
{
plugins
}
dataSets=
{
dataSets
}
/>
<
div
className=
'mt-3 text-xs leading-[18px] text-500 font-normal'
>
{
t
(
'explore.universalChat.viewConfigDetailTip'
)
}
</
div
>
</
div
>
</
div
>
)
}
</
div
>
)
}
export
default
React
.
memo
(
ConfigViewPanel
)
web/app/components/explore/universal-chat/config-view/detail/style.module.css
0 → 100644
View file @
fd5c9afc
.btn
{
background
:
url(~@/app/components/datasets/documents/assets/action.svg)
center
center
no-repeat
transparent
;
background-size
:
16px
16px
;
/* mask-image: ; */
}
.panelBorder
{
border
:
0.5px
solid
rgba
(
0
,
0
,
0
,
.05
);
}
\ No newline at end of file
web/app/components/explore/universal-chat/config-view/panel.tsx
deleted
100644 → 0
View file @
f277a347
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
const
ConfigViewPanel
:
FC
=
()
=>
{
return
(
<
div
>
</
div
>
)
}
export
default
React
.
memo
(
ConfigViewPanel
)
web/app/components/explore/universal-chat/config/data-config/index.tsx
View file @
fd5c9afc
...
@@ -12,11 +12,13 @@ import SelectDataSet from '@/app/components/app/configuration/dataset-config/sel
...
@@ -12,11 +12,13 @@ import SelectDataSet from '@/app/components/app/configuration/dataset-config/sel
import
type
{
DataSet
}
from
'@/models/datasets'
import
type
{
DataSet
}
from
'@/models/datasets'
type
Props
=
{
type
Props
=
{
readonly
?:
boolean
dataSets
:
DataSet
[]
dataSets
:
DataSet
[]
onChange
:
(
data
:
DataSet
[])
=>
void
onChange
?
:
(
data
:
DataSet
[])
=>
void
}
}
const
DatasetConfig
:
FC
<
Props
>
=
({
const
DatasetConfig
:
FC
<
Props
>
=
({
readonly
,
dataSets
,
dataSets
,
onChange
,
onChange
,
})
=>
{
})
=>
{
...
@@ -42,22 +44,22 @@ const DatasetConfig: FC<Props> = ({
...
@@ -42,22 +44,22 @@ const DatasetConfig: FC<Props> = ({
}
}
})
})
})
})
onChange
(
newSelected
)
onChange
?.
(
newSelected
)
}
}
else
{
else
{
onChange
(
data
)
onChange
?.
(
data
)
}
}
hideSelectDataSet
()
hideSelectDataSet
()
}
}
const
onRemove
=
(
id
:
string
)
=>
{
const
onRemove
=
(
id
:
string
)
=>
{
onChange
(
dataSets
.
filter
(
item
=>
item
.
id
!==
id
))
onChange
?.
(
dataSets
.
filter
(
item
=>
item
.
id
!==
id
))
}
}
return
(
return
(
<
FeaturePanel
<
FeaturePanel
className=
'mt-3'
className=
'mt-3'
title=
{
t
(
'appDebug.feature.dataSet.title'
)
}
title=
{
t
(
'appDebug.feature.dataSet.title'
)
}
headerRight=
{
<
OperationBtn
type=
"add"
onClick=
{
showSelectDataSet
}
/>
}
headerRight=
{
!
readonly
&&
<
OperationBtn
type=
"add"
onClick=
{
showSelectDataSet
}
/>
}
hasHeaderBottomBorder=
{
!
hasData
}
hasHeaderBottomBorder=
{
!
hasData
}
>
>
{
hasData
{
hasData
...
@@ -69,6 +71,8 @@ const DatasetConfig: FC<Props> = ({
...
@@ -69,6 +71,8 @@ const DatasetConfig: FC<Props> = ({
key=
{
item
.
id
}
key=
{
item
.
id
}
config=
{
item
}
config=
{
item
}
onRemove=
{
onRemove
}
onRemove=
{
onRemove
}
readonly=
{
readonly
}
// TODO: readonly remove btn
/>
/>
))
}
))
}
</
div
>
</
div
>
...
...
web/app/components/explore/universal-chat/config/index.tsx
View file @
fd5c9afc
...
@@ -9,11 +9,11 @@ export type IConfigProps = {
...
@@ -9,11 +9,11 @@ export type IConfigProps = {
className
?:
string
className
?:
string
readonly
?:
boolean
readonly
?:
boolean
modelId
:
string
modelId
:
string
onModelChange
:
(
modelId
:
string
)
=>
void
onModelChange
?
:
(
modelId
:
string
)
=>
void
plugins
:
Record
<
string
,
boolean
>
plugins
:
Record
<
string
,
boolean
>
onPluginChange
:
(
key
:
string
,
value
:
boolean
)
=>
void
onPluginChange
?
:
(
key
:
string
,
value
:
boolean
)
=>
void
dataSets
:
any
[]
dataSets
:
any
[]
onDataSetsChange
:
(
contexts
:
any
[])
=>
void
onDataSetsChange
?
:
(
contexts
:
any
[])
=>
void
}
}
const
Config
:
FC
<
IConfigProps
>
=
({
const
Config
:
FC
<
IConfigProps
>
=
({
...
@@ -34,13 +34,17 @@ const Config: FC<IConfigProps> = ({
...
@@ -34,13 +34,17 @@ const Config: FC<IConfigProps> = ({
onChange=
{
onModelChange
}
onChange=
{
onModelChange
}
/>
/>
<
PluginConfig
<
PluginConfig
readonly=
{
readonly
}
config=
{
plugins
}
config=
{
plugins
}
onChange=
{
onPluginChange
}
onChange=
{
onPluginChange
}
/>
/>
<
DataConfig
{
(
!
readonly
||
(
readonly
&&
dataSets
.
length
>
0
))
&&
(
dataSets=
{
dataSets
}
<
DataConfig
onChange=
{
onDataSetsChange
}
readonly=
{
readonly
}
/>
dataSets=
{
dataSets
}
onChange=
{
onDataSetsChange
}
/>
)
}
</
div
>
</
div
>
)
)
}
}
...
...
web/app/components/explore/universal-chat/config/model-config/index.tsx
View file @
fd5c9afc
...
@@ -9,7 +9,7 @@ import { UNIVERSAL_CHAT_MODEL_LIST as MODEL_LIST } from '@/config'
...
@@ -9,7 +9,7 @@ import { UNIVERSAL_CHAT_MODEL_LIST as MODEL_LIST } from '@/config'
export
type
IModelConfigProps
=
{
export
type
IModelConfigProps
=
{
modelId
:
string
modelId
:
string
onChange
:
(
model
:
string
)
=>
void
onChange
?
:
(
model
:
string
)
=>
void
readonly
?:
boolean
readonly
?:
boolean
}
}
...
@@ -37,7 +37,7 @@ const ModelConfig: FC<IModelConfigProps> = ({
...
@@ -37,7 +37,7 @@ const ModelConfig: FC<IModelConfigProps> = ({
{
isShowOption
&&
(
{
isShowOption
&&
(
<
div
className=
{
cn
(
'min-w-[159px] absolute right-0 bg-gray-50 rounded-lg shadow'
)
}
>
<
div
className=
{
cn
(
'min-w-[159px] absolute right-0 bg-gray-50 rounded-lg shadow'
)
}
>
{
MODEL_LIST
.
map
(
item
=>
(
{
MODEL_LIST
.
map
(
item
=>
(
<
div
key=
{
item
.
id
}
onClick=
{
()
=>
onChange
(
item
.
id
)
}
className=
"flex items-center h-9 px-3 rounded-lg cursor-pointer hover:bg-gray-100"
>
<
div
key=
{
item
.
id
}
onClick=
{
()
=>
onChange
?.
(
item
.
id
)
}
className=
"flex items-center h-9 px-3 rounded-lg cursor-pointer hover:bg-gray-100"
>
<
ModelIcon
className=
'shrink-0 mr-2'
provider=
{
item
?.
provider
}
/>
<
ModelIcon
className=
'shrink-0 mr-2'
provider=
{
item
?.
provider
}
/>
<
div
className=
"text-sm gray-900 whitespace-nowrap"
>
{
item
.
name
}
</
div
>
<
div
className=
"text-sm gray-900 whitespace-nowrap"
>
{
item
.
name
}
</
div
>
</
div
>
</
div
>
...
...
web/app/components/explore/universal-chat/config/plugins-config/index.tsx
View file @
fd5c9afc
...
@@ -6,8 +6,9 @@ import Item from './item'
...
@@ -6,8 +6,9 @@ import Item from './item'
import
FeaturePanel
from
'@/app/components/app/configuration/base/feature-panel'
import
FeaturePanel
from
'@/app/components/app/configuration/base/feature-panel'
export
type
IPluginsProps
=
{
export
type
IPluginsProps
=
{
readonly
?:
boolean
config
:
Record
<
string
,
boolean
>
config
:
Record
<
string
,
boolean
>
onChange
:
(
key
:
string
,
value
:
boolean
)
=>
void
onChange
?
:
(
key
:
string
,
value
:
boolean
)
=>
void
}
}
const
plugins
=
[
const
plugins
=
[
...
@@ -16,6 +17,7 @@ const plugins = [
...
@@ -16,6 +17,7 @@ const plugins = [
{
key
:
'wikipedia'
,
icon
:
''
},
{
key
:
'wikipedia'
,
icon
:
''
},
]
]
const
Plugins
:
FC
<
IPluginsProps
>
=
({
const
Plugins
:
FC
<
IPluginsProps
>
=
({
readonly
,
config
,
config
,
onChange
,
onChange
,
})
=>
{
})
=>
{
...
@@ -28,7 +30,7 @@ const Plugins: FC<IPluginsProps> = ({
...
@@ -28,7 +30,7 @@ const Plugins: FC<IPluginsProps> = ({
if
(
key
===
'web_reader'
)
if
(
key
===
'web_reader'
)
res
.
description
=
t
(
`explore.universalChat.plugins.
${
key
}
.description`
)
res
.
description
=
t
(
`explore.universalChat.plugins.
${
key
}
.description`
)
if
(
key
===
'google_search'
)
{
if
(
key
===
'google_search'
&&
!
readonly
)
{
res
.
more
=
(
res
.
more
=
(
<
div
className=
'border-t border-[#FEF0C7] flex items-center h-[34px] pl-2 bg-[#FFFAEB] text-gray-700 text-xs '
>
<
div
className=
'border-t border-[#FEF0C7] flex items-center h-[34px] pl-2 bg-[#FFFAEB] text-gray-700 text-xs '
>
<
span
className=
'whitespace-pre'
>
{
t
(
'explore.universalChat.plugins.google_search.more.left'
)
}
</
span
>
<
span
className=
'whitespace-pre'
>
{
t
(
'explore.universalChat.plugins.google_search.more.left'
)
}
</
span
>
...
@@ -61,7 +63,8 @@ const Plugins: FC<IPluginsProps> = ({
...
@@ -61,7 +63,8 @@ const Plugins: FC<IPluginsProps> = ({
description=
{
item
.
description
}
description=
{
item
.
description
}
more=
{
item
.
more
}
more=
{
item
.
more
}
enabled=
{
config
[
item
.
key
]
}
enabled=
{
config
[
item
.
key
]
}
onChange=
{
enabled
=>
onChange
(
item
.
key
,
enabled
)
}
onChange=
{
enabled
=>
onChange
?.(
item
.
key
,
enabled
)
}
readonly=
{
readonly
}
/>
/>
))
}
))
}
</
div
>
</
div
>
...
...
web/app/components/explore/universal-chat/config/plugins-config/item.tsx
View file @
fd5c9afc
...
@@ -12,6 +12,7 @@ export type IItemProps = {
...
@@ -12,6 +12,7 @@ export type IItemProps = {
more
?:
React
.
ReactNode
more
?:
React
.
ReactNode
enabled
:
boolean
enabled
:
boolean
onChange
:
(
enabled
:
boolean
)
=>
void
onChange
:
(
enabled
:
boolean
)
=>
void
readonly
?:
boolean
}
}
const
Item
:
FC
<
IItemProps
>
=
({
const
Item
:
FC
<
IItemProps
>
=
({
...
@@ -21,6 +22,7 @@ const Item: FC<IItemProps> = ({
...
@@ -21,6 +22,7 @@ const Item: FC<IItemProps> = ({
more
,
more
,
enabled
,
enabled
,
onChange
,
onChange
,
readonly
,
})
=>
{
})
=>
{
return
(
return
(
<
div
className=
{
cn
(
'bg-white rounded-xl border border-gray-200 overflow-hidden'
,
s
.
shadow
)
}
>
<
div
className=
{
cn
(
'bg-white rounded-xl border border-gray-200 overflow-hidden'
,
s
.
shadow
)
}
>
...
@@ -32,7 +34,7 @@ const Item: FC<IItemProps> = ({
...
@@ -32,7 +34,7 @@ const Item: FC<IItemProps> = ({
{
description
&&
<
div
className=
'text-xs leading-[18px] text-gray-500'
>
{
description
}
</
div
>
}
{
description
&&
<
div
className=
'text-xs leading-[18px] text-gray-500'
>
{
description
}
</
div
>
}
</
div
>
</
div
>
</
div
>
</
div
>
<
Switch
size=
'md'
defaultValue=
{
enabled
}
onChange=
{
onChange
}
/>
<
Switch
size=
'md'
defaultValue=
{
enabled
}
onChange=
{
onChange
}
disabled=
{
readonly
}
/>
</
div
>
</
div
>
{
more
}
{
more
}
</
div
>
</
div
>
...
...
web/app/components/explore/universal-chat/index.tsx
View file @
fd5c9afc
...
@@ -36,9 +36,9 @@ import { userInputsFormToPromptVariables } from '@/utils/model-config'
...
@@ -36,9 +36,9 @@ import { userInputsFormToPromptVariables } from '@/utils/model-config'
import
Confirm
from
'@/app/components/base/confirm'
import
Confirm
from
'@/app/components/base/confirm'
import
type
{
DataSet
}
from
'@/models/datasets'
import
type
{
DataSet
}
from
'@/models/datasets'
import
ConfigSummary
from
'@/app/components/explore/universal-chat/config-view/summary'
import
ConfigSummary
from
'@/app/components/explore/universal-chat/config-view/summary'
import
ConfigDetail
from
'@/app/components/explore/universal-chat/config-view/detail'
const
APP_ID
=
'universal-chat'
const
APP_ID
=
'universal-chat'
const
isUniversalChat
=
true
export
type
IMainProps
=
{}
export
type
IMainProps
=
{}
...
@@ -566,9 +566,10 @@ const Main: FC<IMainProps> = () => {
...
@@ -566,9 +566,10 @@ const Main: FC<IMainProps> = () => {
{
(
!
isNewConversation
||
isResponsing
)
&&
(
{
(
!
isNewConversation
||
isResponsing
)
&&
(
<
div
className=
'absolute z-10 top-0 left-0 right-0 flex items-center justify-between border-b border-gray-100 mobile:h-12 tablet:h-16 px-8 bg-white'
>
<
div
className=
'absolute z-10 top-0 left-0 right-0 flex items-center justify-between border-b border-gray-100 mobile:h-12 tablet:h-16 px-8 bg-white'
>
<
div
className=
'text-gray-900'
>
{
conversationName
}
</
div
>
<
div
className=
'text-gray-900'
>
{
conversationName
}
</
div
>
<
div
className=
'flex items-center shrink-0 ml-2'
>
<
div
className=
'flex items-center shrink-0 ml-2
space-x-2
'
>
<
ConfigSummary
modelId=
{
modelId
}
pluginIds=
{
Object
.
keys
(
plugins
).
filter
(
key
=>
plugins
[
key
])
}
<
ConfigSummary
modelId=
{
modelId
}
pluginIds=
{
Object
.
keys
(
plugins
).
filter
(
key
=>
plugins
[
key
])
}
/>
/>
<
ConfigDetail
modelId=
{
modelId
}
plugins=
{
plugins
}
dataSets=
{
dataSets
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
)
}
...
...
web/i18n/lang/explore.en.ts
View file @
fd5c9afc
...
@@ -56,6 +56,7 @@ const translation = {
...
@@ -56,6 +56,7 @@ const translation = {
name
:
'Wikipedia'
,
name
:
'Wikipedia'
,
},
},
},
},
viewConfigDetailTip
:
'In conversation, cannot change above settings'
,
},
},
}
}
...
...
web/i18n/lang/explore.zh.ts
View file @
fd5c9afc
...
@@ -55,6 +55,7 @@ const translation = {
...
@@ -55,6 +55,7 @@ const translation = {
name
:
'维基百科'
,
name
:
'维基百科'
,
},
},
},
},
viewConfigDetailTip
:
'在对话中,无法更改上述设置'
,
},
},
}
}
...
...
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