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
57024614
Unverified
Commit
57024614
authored
Jan 24, 2024
by
Yeuoly
Committed by
GitHub
Jan 24, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: Fix typo in credentials field name (#2155)
parent
a31b5026
Changes
23
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
51 additions
and
51 deletions
+51
-51
tool_scale_out.md
api/core/tools/docs/en_US/tool_scale_out.md
+1
-1
tool_scale_out.md
api/core/tools/docs/zh_Hans/tool_scale_out.md
+1
-1
user_entities.py
api/core/tools/entities/user_entities.py
+1
-1
chart.yaml
api/core/tools/provider/builtin/chart/chart.yaml
+1
-1
dalle.yaml
api/core/tools/provider/builtin/dalle/dalle.yaml
+1
-1
google.yaml
api/core/tools/provider/builtin/google/google.yaml
+1
-1
stablediffusion.yaml
...ols/provider/builtin/stablediffusion/stablediffusion.yaml
+1
-1
time.yaml
api/core/tools/provider/builtin/time/time.yaml
+1
-1
vectorizer.yaml
api/core/tools/provider/builtin/vectorizer/vectorizer.yaml
+1
-1
webscraper.yaml
api/core/tools/provider/builtin/webscraper/webscraper.yaml
+1
-1
wikipedia.yaml
api/core/tools/provider/builtin/wikipedia/wikipedia.yaml
+1
-1
wolframalpha.yaml
...ore/tools/provider/builtin/wolframalpha/wolframalpha.yaml
+1
-1
yahoo.yaml
api/core/tools/provider/builtin/yahoo/yahoo.yaml
+1
-1
youtube.yaml
api/core/tools/provider/builtin/youtube/youtube.yaml
+1
-1
builtin_tool_provider.py
api/core/tools/provider/builtin_tool_provider.py
+7
-7
tool_provider.py
api/core/tools/provider/tool_provider.py
+3
-3
api_tool.py
api/core/tools/tool/api_tool.py
+1
-1
dataset_retriever_tool.py
api/core/tools/tool/dataset_retriever_tool.py
+1
-1
tool.py
api/core/tools/tool/tool.py
+2
-2
tool_manager.py
api/core/tools/tool_manager.py
+13
-13
configration.py
api/core/tools/utils/configration.py
+7
-7
8ec536f3c800_rename_api_provider_credentails.py
.../versions/8ec536f3c800_rename_api_provider_credentails.py
+1
-1
tools_manage_service.py
api/services/tools_manage_service.py
+2
-2
No files found.
api/core/tools/docs/en_US/tool_scale_out.md
View file @
57024614
...
...
@@ -45,7 +45,7 @@ identity:
en_US
:
Google
zh_Hans
:
Google
icon
:
icon.svg
credent
ai
ls_for_provider
:
# Credential field
credent
ia
ls_for_provider
:
# Credential field
serpapi_api_key
:
# Credential field name
type
:
secret-input
# Credential field type
required
:
true
# Required or not
...
...
api/core/tools/docs/zh_Hans/tool_scale_out.md
View file @
57024614
...
...
@@ -45,7 +45,7 @@ identity:
en_US
:
Google
zh_Hans
:
Google
icon
:
icon.svg
credent
ai
ls_for_provider
:
# 凭据字段
credent
ia
ls_for_provider
:
# 凭据字段
serpapi_api_key
:
# 凭据字段名称
type
:
secret-input
# 凭据字段类型
required
:
true
# 是否必填
...
...
api/core/tools/entities/user_entities.py
View file @
57024614
...
...
@@ -38,7 +38,7 @@ class UserToolProvider(BaseModel):
}
class
UserToolProviderCredentials
(
BaseModel
):
credent
ai
ls
:
Dict
[
str
,
ToolProviderCredentials
]
credent
ia
ls
:
Dict
[
str
,
ToolProviderCredentials
]
class
UserTool
(
BaseModel
):
author
:
str
...
...
api/core/tools/provider/builtin/chart/chart.yaml
View file @
57024614
...
...
@@ -10,4 +10,4 @@ identity:
zh_Hans
:
图表生成是一个用于生成可视化图表的工具,你可以通过它来生成柱状图、折线图、饼图等各类图表
pt_BR
:
O Gerador de gráficos é uma ferramenta para gerar gráficos estatísticos como gráfico de barras, gráfico de linhas, gráfico de pizza, etc.
icon
:
icon.png
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
api/core/tools/provider/builtin/dalle/dalle.yaml
View file @
57024614
...
...
@@ -10,7 +10,7 @@ identity:
zh_Hans
:
DALL-E 绘画
pt_BR
:
DALL-E art
icon
:
icon.png
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
openai_api_key
:
type
:
secret-input
required
:
true
...
...
api/core/tools/provider/builtin/google/google.yaml
View file @
57024614
...
...
@@ -10,7 +10,7 @@ identity:
zh_Hans
:
GoogleSearch
pt_BR
:
Google
icon
:
icon.svg
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
serpapi_api_key
:
type
:
secret-input
required
:
true
...
...
api/core/tools/provider/builtin/stablediffusion/stablediffusion.yaml
View file @
57024614
...
...
@@ -10,7 +10,7 @@ identity:
zh_Hans
:
Stable Diffusion 是一个可以在本地部署的图片生成的工具。
pt_BR
:
Stable Diffusion is a tool for generating images which can be deployed locally.
icon
:
icon.png
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
base_url
:
type
:
secret-input
required
:
true
...
...
api/core/tools/provider/builtin/time/time.yaml
View file @
57024614
...
...
@@ -10,4 +10,4 @@ identity:
zh_Hans
:
一个用于获取当前时间的工具。
pt_BR
:
A tool for getting the current time.
icon
:
icon.svg
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
api/core/tools/provider/builtin/vectorizer/vectorizer.yaml
View file @
57024614
...
...
@@ -10,7 +10,7 @@ identity:
zh_Hans
:
一个将 PNG 和 JPG 图像快速轻松地转换为 SVG 矢量图的工具。
pt_BR
:
Convert your PNG and JPG images to SVG vectors quickly and easily. Fully automatically. Using AI.
icon
:
icon.png
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
api_key_name
:
type
:
secret-input
required
:
true
...
...
api/core/tools/provider/builtin/webscraper/webscraper.yaml
View file @
57024614
...
...
@@ -10,4 +10,4 @@ identity:
zh_Hans
:
一个用于抓取网页的工具。
pt_BR
:
Web Scrapper tool kit is used to scrape web
icon
:
icon.svg
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
api/core/tools/provider/builtin/wikipedia/wikipedia.yaml
View file @
57024614
...
...
@@ -10,4 +10,4 @@ identity:
zh_Hans
:
维基百科是一个由全世界的志愿者创建和编辑的免费在线百科全书。
pt_BR
:
Wikipedia is a free online encyclopedia, created and edited by volunteers around the world.
icon
:
icon.svg
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
api/core/tools/provider/builtin/wolframalpha/wolframalpha.yaml
View file @
57024614
...
...
@@ -10,7 +10,7 @@ identity:
zh_Hans
:
WolframAlpha 是一个强大的计算知识引擎。
pt_BR
:
WolframAlpha is a powerful computational knowledge engine.
icon
:
icon.svg
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
appid
:
type
:
secret-input
required
:
true
...
...
api/core/tools/provider/builtin/yahoo/yahoo.yaml
View file @
57024614
...
...
@@ -10,4 +10,4 @@ identity:
zh_Hans
:
雅虎财经,获取并整理出最新的新闻、股票报价等一切你想要的财经信息。
pt_BR
:
Finance, and Yahoo! get the latest news, stock quotes, and interactive chart with Yahoo!
icon
:
icon.png
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
api/core/tools/provider/builtin/youtube/youtube.yaml
View file @
57024614
...
...
@@ -10,7 +10,7 @@ identity:
zh_Hans
:
Youtube(油管)是全球最大的视频分享网站,用户可以在上面上传、观看和分享视频。
pt_BR
:
Youtube é o maior site de compartilhamento de vídeos do mundo, onde os usuários podem fazer upload, assistir e compartilhar vídeos.
icon
:
icon.png
credent
ai
ls_for_provider
:
credent
ia
ls_for_provider
:
google_api_key
:
type
:
secret-input
required
:
true
...
...
api/core/tools/provider/builtin_tool_provider.py
View file @
57024614
...
...
@@ -30,14 +30,14 @@ class BuiltinToolProviderController(ToolProviderController):
except
:
raise
ToolProviderNotFoundError
(
f
'can not load provider yaml for {provider}'
)
if
'credent
ails_for_provider'
in
provider_yaml
and
provider_yaml
[
'credentai
ls_for_provider'
]
is
not
None
:
if
'credent
ials_for_provider'
in
provider_yaml
and
provider_yaml
[
'credentia
ls_for_provider'
]
is
not
None
:
# set credentials name
for
credential_name
in
provider_yaml
[
'credent
ai
ls_for_provider'
]:
provider_yaml
[
'credent
ai
ls_for_provider'
][
credential_name
][
'name'
]
=
credential_name
for
credential_name
in
provider_yaml
[
'credent
ia
ls_for_provider'
]:
provider_yaml
[
'credent
ia
ls_for_provider'
][
credential_name
][
'name'
]
=
credential_name
super
()
.
__init__
(
**
{
'identity'
:
provider_yaml
[
'identity'
],
'credentials_schema'
:
provider_yaml
[
'credent
ails_for_provider'
]
if
'credentai
ls_for_provider'
in
provider_yaml
else
None
,
'credentials_schema'
:
provider_yaml
[
'credent
ials_for_provider'
]
if
'credentia
ls_for_provider'
in
provider_yaml
else
None
,
})
def
_get_bulitin_tools
(
self
)
->
List
[
Tool
]:
...
...
@@ -75,7 +75,7 @@ class BuiltinToolProviderController(ToolProviderController):
self
.
tools
=
tools
return
tools
def
get_credent
ai
ls_schema
(
self
)
->
Dict
[
str
,
ToolProviderCredentials
]:
def
get_credent
ia
ls_schema
(
self
)
->
Dict
[
str
,
ToolProviderCredentials
]:
"""
returns the credentials schema of the provider
...
...
@@ -86,14 +86,14 @@ class BuiltinToolProviderController(ToolProviderController):
return
self
.
credentials_schema
.
copy
()
def
user_get_credent
ai
ls_schema
(
self
)
->
UserToolProviderCredentials
:
def
user_get_credent
ia
ls_schema
(
self
)
->
UserToolProviderCredentials
:
"""
returns the credentials schema of the provider, this method is used for user
:return: the credentials schema
"""
credentials
=
self
.
credentials_schema
.
copy
()
return
UserToolProviderCredentials
(
credent
ai
ls
=
credentials
)
return
UserToolProviderCredentials
(
credent
ia
ls
=
credentials
)
def
get_tools
(
self
)
->
List
[
Tool
]:
"""
...
...
api/core/tools/provider/tool_provider.py
View file @
57024614
...
...
@@ -15,7 +15,7 @@ class ToolProviderController(BaseModel, ABC):
tools
:
Optional
[
List
[
Tool
]]
=
None
credentials_schema
:
Optional
[
Dict
[
str
,
ToolProviderCredentials
]]
=
None
def
get_credent
ai
ls_schema
(
self
)
->
Dict
[
str
,
ToolProviderCredentials
]:
def
get_credent
ia
ls_schema
(
self
)
->
Dict
[
str
,
ToolProviderCredentials
]:
"""
returns the credentials schema of the provider
...
...
@@ -23,14 +23,14 @@ class ToolProviderController(BaseModel, ABC):
"""
return
self
.
credentials_schema
.
copy
()
def
user_get_credent
ai
ls_schema
(
self
)
->
UserToolProviderCredentials
:
def
user_get_credent
ia
ls_schema
(
self
)
->
UserToolProviderCredentials
:
"""
returns the credentials schema of the provider, this method is used for user
:return: the credentials schema
"""
credentials
=
self
.
credentials_schema
.
copy
()
return
UserToolProviderCredentials
(
credent
ai
ls
=
credentials
)
return
UserToolProviderCredentials
(
credent
ia
ls
=
credentials
)
@
abstractmethod
def
get_tools
(
self
)
->
List
[
Tool
]:
...
...
api/core/tools/tool/api_tool.py
View file @
57024614
...
...
@@ -30,7 +30,7 @@ class ApiTool(Tool):
runtime
=
Tool
.
Runtime
(
**
meta
)
)
def
validate_credentials
(
self
,
credent
ai
ls
:
Dict
[
str
,
Any
],
parameters
:
Dict
[
str
,
Any
],
format_only
:
bool
=
False
)
->
None
:
def
validate_credentials
(
self
,
credent
ia
ls
:
Dict
[
str
,
Any
],
parameters
:
Dict
[
str
,
Any
],
format_only
:
bool
=
False
)
->
None
:
"""
validate the credentials for Api tool
"""
...
...
api/core/tools/tool/dataset_retriever_tool.py
View file @
57024614
...
...
@@ -88,7 +88,7 @@ class DatasetRetrieverTool(Tool):
return
self
.
create_text_message
(
text
=
result
)
def
validate_credentials
(
self
,
credent
ai
ls
:
Dict
[
str
,
Any
],
parameters
:
Dict
[
str
,
Any
])
->
None
:
def
validate_credentials
(
self
,
credent
ia
ls
:
Dict
[
str
,
Any
],
parameters
:
Dict
[
str
,
Any
])
->
None
:
"""
validate the credentials for dataset retriever tool
"""
...
...
api/core/tools/tool/tool.py
View file @
57024614
...
...
@@ -228,11 +228,11 @@ class Tool(BaseModel, ABC):
def
_invoke
(
self
,
user_id
:
str
,
tool_paramters
:
Dict
[
str
,
Any
])
->
Union
[
ToolInvokeMessage
,
List
[
ToolInvokeMessage
]]:
pass
def
validate_credentials
(
self
,
credent
ai
ls
:
Dict
[
str
,
Any
],
parameters
:
Dict
[
str
,
Any
])
->
None
:
def
validate_credentials
(
self
,
credent
ia
ls
:
Dict
[
str
,
Any
],
parameters
:
Dict
[
str
,
Any
])
->
None
:
"""
validate the credentials
:param credent
ai
ls: the credentials
:param credent
ia
ls: the credentials
:param parameters: the parameters
"""
pass
...
...
api/core/tools/tool_manager.py
View file @
57024614
...
...
@@ -175,11 +175,11 @@ class ToolManager:
controller
=
ToolManager
.
get_builtin_provider
(
provider_name
)
tool_configuration
=
ToolConfiguration
(
tenant_id
=
tanent_id
,
provider_controller
=
controller
)
decrypted_credent
ai
ls
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
)
decrypted_credent
ia
ls
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
)
return
builtin_tool
.
fork_tool_runtime
(
meta
=
{
'tenant_id'
:
tanent_id
,
'credentials'
:
decrypted_credent
ai
ls
,
'credentials'
:
decrypted_credent
ia
ls
,
'runtime_parameters'
:
{}
},
agent_callback
=
agent_callback
)
...
...
@@ -191,11 +191,11 @@ class ToolManager:
# decrypt the credentials
tool_configuration
=
ToolConfiguration
(
tenant_id
=
tanent_id
,
provider_controller
=
api_provider
)
decrypted_credent
ai
ls
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
)
decrypted_credent
ia
ls
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
)
return
api_provider
.
get_tool
(
tool_name
)
.
fork_tool_runtime
(
meta
=
{
'tenant_id'
:
tanent_id
,
'credentials'
:
decrypted_credent
ai
ls
,
'credentials'
:
decrypted_credent
ia
ls
,
})
elif
provider_type
==
'app'
:
raise
NotImplementedError
(
'app provider not implemented'
)
...
...
@@ -295,7 +295,7 @@ class ToolManager:
)
# get credentials schema
schema
=
provider
.
get_credent
ai
ls_schema
()
schema
=
provider
.
get_credent
ia
ls_schema
()
for
name
,
value
in
schema
.
items
():
result_providers
[
provider
.
identity
.
name
]
.
team_credentials
[
name
]
=
\
ToolProviderCredentials
.
CredentialsType
.
defaut
(
value
.
type
)
...
...
@@ -311,7 +311,7 @@ class ToolManager:
for
db_builtin_provider
in
db_builtin_providers
:
# add provider into providers
credent
ai
ls
=
db_builtin_provider
.
credentials
credent
ia
ls
=
db_builtin_provider
.
credentials
provider_name
=
db_builtin_provider
.
provider
result_providers
[
provider_name
]
.
is_team_authorization
=
True
...
...
@@ -321,8 +321,8 @@ class ToolManager:
# init tool configuration
tool_configuration
=
ToolConfiguration
(
tenant_id
=
tenant_id
,
provider_controller
=
controller
)
# decrypt the credentials and mask the credentials
decrypted_credent
ails
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
=
credentai
ls
)
masked_credentials
=
tool_configuration
.
mask_tool_credentials
(
credentials
=
decrypted_credent
ai
ls
)
decrypted_credent
ials
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
=
credentia
ls
)
masked_credentials
=
tool_configuration
.
mask_tool_credentials
(
credentials
=
decrypted_credent
ia
ls
)
result_providers
[
provider_name
]
.
team_credentials
=
masked_credentials
...
...
@@ -337,7 +337,7 @@ class ToolManager:
except
Exception
as
e
:
logger
.
error
(
f
'failed to get user name for api provider {db_api_provider.id}: {str(e)}'
)
# add provider into providers
credent
ai
ls
=
db_api_provider
.
credentials
credent
ia
ls
=
db_api_provider
.
credentials
provider_name
=
db_api_provider
.
name
result_providers
[
provider_name
]
=
UserToolProvider
(
id
=
db_api_provider
.
id
,
...
...
@@ -367,8 +367,8 @@ class ToolManager:
tool_configuration
=
ToolConfiguration
(
tenant_id
=
tenant_id
,
provider_controller
=
controller
)
# decrypt the credentials and mask the credentials
decrypted_credent
ails
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
=
credentai
ls
)
masked_credentials
=
tool_configuration
.
mask_tool_credentials
(
credentials
=
decrypted_credent
ai
ls
)
decrypted_credent
ials
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
=
credentia
ls
)
masked_credentials
=
tool_configuration
.
mask_tool_credentials
(
credentials
=
decrypted_credent
ia
ls
)
result_providers
[
provider_name
]
.
team_credentials
=
masked_credentials
...
...
@@ -426,8 +426,8 @@ class ToolManager:
# init tool configuration
tool_configuration
=
ToolConfiguration
(
tenant_id
=
tenant_id
,
provider_controller
=
controller
)
decrypted_credent
ai
ls
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
)
masked_credentials
=
tool_configuration
.
mask_tool_credentials
(
decrypted_credent
ai
ls
)
decrypted_credent
ia
ls
=
tool_configuration
.
decrypt_tool_credentials
(
credentials
)
masked_credentials
=
tool_configuration
.
mask_tool_credentials
(
decrypted_credent
ia
ls
)
try
:
icon
=
json
.
loads
(
provider
.
icon
)
...
...
api/core/tools/utils/configration.py
View file @
57024614
...
...
@@ -9,22 +9,22 @@ class ToolConfiguration(BaseModel):
tenant_id
:
str
provider_controller
:
ToolProviderController
def
_deep_copy
(
self
,
credent
ai
ls
:
Dict
[
str
,
str
])
->
Dict
[
str
,
str
]:
def
_deep_copy
(
self
,
credent
ia
ls
:
Dict
[
str
,
str
])
->
Dict
[
str
,
str
]:
"""
deep copy credentials
"""
return
{
key
:
value
for
key
,
value
in
credent
ai
ls
.
items
()}
return
{
key
:
value
for
key
,
value
in
credent
ia
ls
.
items
()}
def
encrypt_tool_credentials
(
self
,
credent
ai
ls
:
Dict
[
str
,
str
])
->
Dict
[
str
,
str
]:
def
encrypt_tool_credentials
(
self
,
credent
ia
ls
:
Dict
[
str
,
str
])
->
Dict
[
str
,
str
]:
"""
encrypt tool credentials with tanent id
return a deep copy of credentials with encrypted values
"""
credentials
=
self
.
_deep_copy
(
credent
ai
ls
)
credentials
=
self
.
_deep_copy
(
credent
ia
ls
)
# get fields need to be decrypted
fields
=
self
.
provider_controller
.
get_credent
ai
ls_schema
()
fields
=
self
.
provider_controller
.
get_credent
ia
ls_schema
()
for
field_name
,
field
in
fields
.
items
():
if
field
.
type
==
ToolProviderCredentials
.
CredentialsType
.
SECRET_INPUT
:
if
field_name
in
credentials
:
...
...
@@ -42,7 +42,7 @@ class ToolConfiguration(BaseModel):
credentials
=
self
.
_deep_copy
(
credentials
)
# get fields need to be decrypted
fields
=
self
.
provider_controller
.
get_credent
ai
ls_schema
()
fields
=
self
.
provider_controller
.
get_credent
ia
ls_schema
()
for
field_name
,
field
in
fields
.
items
():
if
field
.
type
==
ToolProviderCredentials
.
CredentialsType
.
SECRET_INPUT
:
if
field_name
in
credentials
:
...
...
@@ -65,7 +65,7 @@ class ToolConfiguration(BaseModel):
credentials
=
self
.
_deep_copy
(
credentials
)
# get fields need to be decrypted
fields
=
self
.
provider_controller
.
get_credent
ai
ls_schema
()
fields
=
self
.
provider_controller
.
get_credent
ia
ls_schema
()
for
field_name
,
field
in
fields
.
items
():
if
field
.
type
==
ToolProviderCredentials
.
CredentialsType
.
SECRET_INPUT
:
if
field_name
in
credentials
:
...
...
api/migrations/versions/8ec536f3c800_rename_api_provider_credentails.py
View file @
57024614
"""rename api provider credent
ai
ls
"""rename api provider credent
ia
ls
Revision ID: 8ec536f3c800
Revises: ad472b61a054
...
...
api/services/tools_manage_service.py
View file @
57024614
...
...
@@ -112,7 +112,7 @@ class ToolManageService:
except
Exception
as
e
:
raise
ValueError
(
f
'invalid schema: {str(e)}'
)
credent
ai
ls_schema
=
[
credent
ia
ls_schema
=
[
ToolProviderCredentials
(
name
=
'auth_type'
,
type
=
ToolProviderCredentials
.
CredentialsType
.
SELECT
,
...
...
@@ -163,7 +163,7 @@ class ToolManageService:
{
'schema_type'
:
schema_type
,
'parameters_schema'
:
tool_bundles
,
'credentials_schema'
:
credent
ai
ls_schema
,
'credentials_schema'
:
credent
ia
ls_schema
,
'warning'
:
warnings
}
))
...
...
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