Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
e31d6ec2
authored
Sep 25, 2024
by
Archer
Committed by
GitHub
Sep 25, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
remove invalid code (#2785)
parent
6bb10ca1
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
94 additions
and
191 deletions
+94
-191
projects/app/src/pages/api/core/app/version/list.ts
+0
-35
projects/app/src/pages/api/core/app/version/list.tsx
+0
-0
projects/app/src/pages/api/core/app/version/revert.ts
+0
-83
projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx
+1
-1
projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx
+15
-13
projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx
+19
-19
projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx
+29
-6
projects/app/src/pages/app/detail/components/Workflow/Header.tsx
+9
-7
projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
+13
-13
projects/app/src/web/core/app/api/version.ts
+4
-10
projects/app/src/web/core/workflow/utils.ts
+4
-4
No files found.
projects/app/src/pages/api/core/app/version/list.ts
deleted
100644 → 0
View file @
6bb10ca1
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
{
MongoAppVersion
}
from
'@fastgpt/service/core/app/version/schema'
;
import
{
PaginationProps
,
PaginationResponse
}
from
'@fastgpt/web/common/fetch/type'
;
import
{
AppVersionSchemaType
}
from
'@fastgpt/global/core/app/version'
;
import
{
ApiRequestProps
}
from
'@fastgpt/service/type/next'
;
type
Props
=
PaginationProps
<
{
appId
:
string
;
}
>
;
type
Response
=
PaginationResponse
<
AppVersionSchemaType
>
;
async
function
handler
(
req
:
ApiRequestProps
<
Props
>
,
res
:
NextApiResponse
<
any
>
):
Promise
<
Response
>
{
const
{
offset
,
pageSize
,
appId
}
=
req
.
body
;
const
[
result
,
total
]
=
await
Promise
.
all
([
MongoAppVersion
.
find
({
appId
})
.
sort
({
time
:
-
1
})
.
skip
(
offset
)
.
limit
(
pageSize
),
MongoAppVersion
.
countDocuments
({
appId
})
]);
return
{
total
,
list
:
result
};
}
export
default
NextAPI
(
handler
);
projects/app/src/pages/api/core/app/version/list
Workflow
.tsx
→
projects/app/src/pages/api/core/app/version/list.tsx
View file @
e31d6ec2
File moved
projects/app/src/pages/api/core/app/version/revert.ts
deleted
100644 → 0
View file @
6bb10ca1
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
{
authApp
}
from
'@fastgpt/service/support/permission/app/auth'
;
import
{
MongoAppVersion
}
from
'@fastgpt/service/core/app/version/schema'
;
import
{
mongoSessionRun
}
from
'@fastgpt/service/common/mongo/sessionRun'
;
import
{
MongoApp
}
from
'@fastgpt/service/core/app/schema'
;
import
{
beforeUpdateAppFormat
}
from
'@fastgpt/service/core/app/controller'
;
import
{
getNextTimeByCronStringAndTimezone
}
from
'@fastgpt/global/common/string/time'
;
import
{
PostRevertAppProps
}
from
'@/global/core/app/api'
;
import
{
WritePermissionVal
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
AppTypeEnum
}
from
'@fastgpt/global/core/app/constants'
;
type
Response
=
{};
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
):
Promise
<
{}
>
{
const
{
appId
}
=
req
.
query
as
{
appId
:
string
};
const
{
editNodes
=
[],
editEdges
=
[],
editChatConfig
,
versionId
}
=
req
.
body
as
PostRevertAppProps
;
const
{
app
}
=
await
authApp
({
appId
,
req
,
per
:
WritePermissionVal
,
authToken
:
true
});
const
version
=
await
MongoAppVersion
.
findOne
({
_id
:
versionId
,
appId
});
if
(
!
version
)
{
throw
new
Error
(
'version not found'
);
}
const
{
nodes
:
formatEditNodes
}
=
beforeUpdateAppFormat
({
nodes
:
editNodes
});
const
scheduledTriggerConfig
=
version
.
chatConfig
?.
scheduledTriggerConfig
;
await
mongoSessionRun
(
async
(
session
)
=>
{
// 为编辑中的数据创建一个版本
await
MongoAppVersion
.
create
(
[
{
appId
,
nodes
:
formatEditNodes
,
edges
:
editEdges
,
chatConfig
:
editChatConfig
}
],
{
session
}
);
// 为历史版本再创建一个版本
const
[{
_id
}]
=
await
MongoAppVersion
.
create
(
[
{
appId
,
nodes
:
version
.
nodes
,
edges
:
version
.
edges
,
chatConfig
:
version
.
chatConfig
}
],
{
session
}
);
// update app
await
MongoApp
.
findByIdAndUpdate
(
appId
,
{
modules
:
version
.
nodes
,
edges
:
version
.
edges
,
chatConfig
:
version
.
chatConfig
,
updateTime
:
new
Date
(),
scheduledTriggerConfig
:
scheduledTriggerConfig
?
scheduledTriggerConfig
:
null
,
scheduledTriggerNextTime
:
scheduledTriggerConfig
?.
cronString
?
getNextTimeByCronStringAndTimezone
(
scheduledTriggerConfig
)
:
null
,
'pluginData.nodeVersion'
:
_id
});
});
return
{};
}
export
default
NextAPI
(
handler
);
projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx
View file @
e31d6ec2
...
...
@@ -23,7 +23,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import
{
useRequest2
}
from
'@fastgpt/web/hooks/useRequest'
;
import
{
useSystemStore
}
from
'@/web/common/system/useSystemStore'
;
import
{
useToast
}
from
'@fastgpt/web/hooks/useToast'
;
import
{
versionListResponse
}
from
'@/pages/api/core/app/version/listWorkflow
'
;
import
type
{
versionListResponse
}
from
'@/pages/api/core/app/version/list
'
;
const
PublishHistoriesSlider
=
({
onClose
,
...
...
projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx
View file @
e31d6ec2
...
...
@@ -15,10 +15,11 @@ import { cardStyles } from '../constants';
import
styles
from
'./styles.module.scss'
;
import
{
useSystem
}
from
'@fastgpt/web/hooks/useSystem'
;
import
{
store
EdgesRenderEdge
,
store
Node2FlowNode
}
from
'@/web/core/workflow/utils'
;
import
{
storeNode2FlowNode
}
from
'@/web/core/workflow/utils'
;
import
{
useTranslation
}
from
'next-i18next'
;
import
{
uiWorkflow2StoreWorkflow
}
from
'../WorkflowComponents/utils'
;
import
{
SnapshotsType
}
from
'../WorkflowComponents/context'
;
import
{
SaveSnapshotFnType
}
from
'./useSnapshots'
;
const
Edit
=
({
appForm
,
...
...
@@ -29,32 +30,33 @@ const Edit = ({
appForm
:
AppSimpleEditFormType
;
setAppForm
:
React
.
Dispatch
<
React
.
SetStateAction
<
AppSimpleEditFormType
>>
;
past
:
SnapshotsType
[];
saveSnapshot
:
(
this
:
any
,
{
pastNodes
,
pastEdges
,
chatConfig
,
customTitle
,
isSaved
}:
any
)
=>
Promise
<
boolean
>
;
saveSnapshot
:
SaveSnapshotFnType
;
})
=>
{
const
{
isPc
}
=
useSystem
();
const
{
loadAllDatasets
}
=
useDatasetStore
();
const
{
appDetail
}
=
useContextSelector
(
AppContext
,
(
v
)
=>
v
);
const
{
t
}
=
useTranslation
();
//
show selected dataset
//
Init app form
useMount
(()
=>
{
// show selected dataset
loadAllDatasets
();
// Get the latest snapshot
if
(
past
.
length
>
0
)
{
const
storeWorkflow
=
uiWorkflow2StoreWorkflow
(
past
[
0
]);
const
currentAppForm
=
appWorkflow2Form
({
...
storeWorkflow
,
chatConfig
:
past
[
0
].
chatConfig
});
return
setAppForm
(
currentAppForm
);
}
// Set the first snapshot
saveSnapshot
({
pastNodes
:
appDetail
.
modules
?.
map
((
item
)
=>
storeNode2FlowNode
({
item
,
t
})),
pastEdges
:
appDetail
.
edges
?.
map
((
item
)
=>
storeEdgesRenderEdge
({
edge
:
item
})),
chatConfig
:
appDetail
.
chatConfig
,
isSaved
:
true
});
if
(
past
.
length
>
0
)
{
const
storeWorkflow
=
uiWorkflow2StoreWorkflow
(
past
[
0
]);
const
currentAppForm
=
appWorkflow2Form
({
...
storeWorkflow
,
chatConfig
:
past
[
0
].
chatConfig
});
setAppForm
(
currentAppForm
);
return
;
}
setAppForm
(
appWorkflow2Form
({
nodes
:
appDetail
.
modules
,
...
...
projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx
View file @
e31d6ec2
...
...
@@ -30,6 +30,7 @@ import {
storeNode2FlowNode
}
from
'@/web/core/workflow/utils'
;
import
{
uiWorkflow2StoreWorkflow
}
from
'../WorkflowComponents/utils'
;
import
{
SaveSnapshotFnType
}
from
'./useSnapshots'
;
const
PublishHistories
=
dynamic
(()
=>
import
(
'../PublishHistoriesSlider'
));
...
...
@@ -44,15 +45,12 @@ const Header = ({
setAppForm
:
(
form
:
AppSimpleEditFormType
)
=>
void
;
past
:
SnapshotsType
[];
setPast
:
(
value
:
React
.
SetStateAction
<
SnapshotsType
[]
>
)
=>
void
;
saveSnapshot
:
(
this
:
any
,
{
pastNodes
,
pastEdges
,
chatConfig
,
customTitle
,
isSaved
}:
any
)
=>
Promise
<
boolean
>
;
saveSnapshot
:
SaveSnapshotFnType
;
})
=>
{
const
{
t
}
=
useTranslation
();
const
{
isPc
}
=
useSystem
();
const
router
=
useRouter
();
const
{
appId
,
appDetail
,
onSaveApp
,
currentTab
}
=
useContextSelector
(
AppContext
,
(
v
)
=>
v
);
const
{
appId
,
onSaveApp
,
currentTab
}
=
useContextSelector
(
AppContext
,
(
v
)
=>
v
);
const
{
lastAppListRouteType
}
=
useSystemStore
();
const
{
allDatasets
}
=
useDatasetStore
();
...
...
@@ -62,8 +60,6 @@ const Header = ({
});
const
onClickRoute
=
useCallback
(
(
parentId
:
string
)
=>
{
localStorage
.
removeItem
(
`
${
appDetail
.
_id
}
-past`
);
router
.
push
({
pathname
:
'/app/list'
,
query
:
{
...
...
@@ -75,8 +71,6 @@ const Header = ({
[
router
,
lastAppListRouteType
]
);
const
[
isPublished
,
setIsPublished
]
=
useState
(
false
);
const
{
runAsync
:
onClickSave
,
loading
}
=
useRequest2
(
async
({
isPublish
,
...
...
@@ -109,20 +103,23 @@ const Header = ({
const
[
historiesDefaultData
,
setHistoriesDefaultData
]
=
useState
<
InitProps
>
();
const
resetSnapshot
=
(
data
:
SnapshotsType
)
=>
{
const
storeWorkflow
=
uiWorkflow2StoreWorkflow
(
data
);
const
currentAppForm
=
appWorkflow2Form
({
...
storeWorkflow
,
chatConfig
:
data
.
chatConfig
});
const
resetSnapshot
=
useCallback
(
(
data
:
SnapshotsType
)
=>
{
const
storeWorkflow
=
uiWorkflow2StoreWorkflow
(
data
);
const
currentAppForm
=
appWorkflow2Form
({
...
storeWorkflow
,
chatConfig
:
data
.
chatConfig
});
setAppForm
(
currentAppForm
);
};
setAppForm
(
currentAppForm
);
},
[
setAppForm
]
);
// Save snapshot to local
useDebounceEffect
(
()
=>
{
const
data
=
form2AppWorkflow
(
appForm
,
t
);
saveSnapshot
({
pastNodes
:
data
.
nodes
?.
map
((
item
)
=>
storeNode2FlowNode
({
item
,
t
})),
pastEdges
:
data
.
edges
?.
map
((
item
)
=>
storeEdgesRenderEdge
({
edge
:
item
})),
chatConfig
:
data
.
chatConfig
});
},
...
...
@@ -130,10 +127,13 @@ const Header = ({
{
wait
:
500
}
);
// Check if the workflow is published
const
[
isPublished
,
setIsPublished
]
=
useState
(
false
);
useDebounceEffect
(
()
=>
{
const
savedSnapshot
=
past
.
find
((
snapshot
)
=>
snapshot
.
isSaved
);
const
data
=
form2AppWorkflow
(
appForm
,
t
);
const
editFormData
=
form2AppWorkflow
(
appForm
,
t
);
console
.
log
(
savedSnapshot
?.
nodes
,
editFormData
.
chatConfig
);
const
val
=
compareSnapshot
(
{
nodes
:
savedSnapshot
?.
nodes
,
...
...
@@ -141,14 +141,14 @@ const Header = ({
chatConfig
:
savedSnapshot
?.
chatConfig
},
{
nodes
:
d
ata
.
nodes
?.
map
((
item
)
=>
storeNode2FlowNode
({
item
,
t
})),
nodes
:
editFormD
ata
.
nodes
?.
map
((
item
)
=>
storeNode2FlowNode
({
item
,
t
})),
edges
:
[],
chatConfig
:
d
ata
.
chatConfig
chatConfig
:
editFormD
ata
.
chatConfig
}
);
setIsPublished
(
val
);
},
[
past
],
[
past
,
allDatasets
],
{
wait
:
500
}
);
...
...
projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx
View file @
e31d6ec2
import
{
useLocalStorageState
,
useMemoizedFn
}
from
'ahooks'
;
import
{
SnapshotsType
}
from
'../WorkflowComponents/context'
;
import
{
SetStateAction
}
from
'react'
;
import
{
S
aveSnapshotParams
,
S
napshotsType
}
from
'../WorkflowComponents/context'
;
import
{
SetStateAction
,
useEffect
}
from
'react'
;
import
{
compareSnapshot
}
from
'@/web/core/workflow/utils'
;
import
{
formatTime2YMDHMS
}
from
'@fastgpt/global/common/string/time'
;
import
{
AppChatConfigType
}
from
'@fastgpt/global/core/app/type'
;
import
{
Node
}
from
'reactflow'
;
export
type
SaveSnapshotFnType
=
(
props
:
SaveSnapshotParams
&
{
isSaved
?:
boolean
;
}
)
=>
Promise
<
boolean
>
;
const
useSnapshots
=
(
appId
:
string
)
=>
{
const
[
past
,
setPast
]
=
useLocalStorageState
<
SnapshotsType
[]
>
(
`
${
appId
}
-past-simple`
,
{
...
...
@@ -10,13 +18,16 @@ const useSnapshots = (appId: string) => {
listenStorageChange
:
true
})
as
[
SnapshotsType
[],
(
value
:
SetStateAction
<
SnapshotsType
[]
>
)
=>
void
];
const
saveSnapshot
=
useMemoizedFn
(
async
({
pastNodes
,
pastEdges
,
chatConfig
,
customTitle
,
isSaved
})
=>
{
const
saveSnapshot
:
SaveSnapshotFnType
=
useMemoizedFn
(
async
({
pastNodes
,
chatConfig
,
customTitle
,
isSaved
})
=>
{
if
(
!
pastNodes
)
return
false
;
const
pastState
=
past
[
0
];
const
isPastEqual
=
compareSnapshot
(
{
nodes
:
pastNodes
,
edges
:
pastEdges
,
edges
:
[]
,
chatConfig
:
chatConfig
},
{
...
...
@@ -30,7 +41,7 @@ const useSnapshots = (appId: string) => {
setPast
((
past
)
=>
[
{
nodes
:
pastNodes
,
edges
:
pastEdges
,
edges
:
[]
,
title
:
customTitle
||
formatTime2YMDHMS
(
new
Date
()),
chatConfig
,
isSaved
...
...
@@ -41,6 +52,18 @@ const useSnapshots = (appId: string) => {
}
);
// remove other app's snapshot
useEffect
(()
=>
{
const
keys
=
Object
.
keys
(
localStorage
);
const
snapshotKeys
=
keys
.
filter
((
key
)
=>
key
.
endsWith
(
'-past-simple'
));
snapshotKeys
.
forEach
((
key
)
=>
{
const
keyAppId
=
key
.
split
(
'-'
)[
0
];
if
(
keyAppId
!==
appId
)
{
localStorage
.
removeItem
(
key
);
}
});
},
[
appId
]);
return
{
past
,
setPast
,
saveSnapshot
};
};
...
...
projects/app/src/pages/app/detail/components/Workflow/Header.tsx
View file @
e31d6ec2
...
...
@@ -279,20 +279,22 @@ const Header = () => {
currentTab
,
isPublished
,
onBack
,
isOpenBackConfirm
,
onOpenBackConfirm
,
onCloseBackConfirm
,
t
,
loading
,
isV2Workflow
,
historiesDefaultData
,
t
,
loading
,
onClickSave
,
flowData2StoreDataAndCheck
,
past
,
saveSnapshot
,
resetSnapshot
,
isOpenBackConfirm
,
onCloseBackConfirm
,
setHistoriesDefaultData
,
appDetail
.
chatConfig
,
flowData2StoreDataAndCheck
,
setWorkflowTestData
,
toast
,
past
toast
]);
return
Render
;
...
...
projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
View file @
e31d6ec2
...
...
@@ -63,7 +63,7 @@ export type SaveSnapshotParams = {
pastNodes
?:
Node
[];
pastEdges
?:
Edge
[];
customTitle
?:
string
;
chatConfig
?
:
AppChatConfigType
;
chatConfig
:
AppChatConfigType
;
};
export
type
InitProps
=
{
nodes
:
AppSchema
[
'modules'
];
...
...
@@ -892,6 +892,18 @@ const WorkflowContextProvider = ({
}
});
// remove other app's snapshot
useEffect
(()
=>
{
const
keys
=
Object
.
keys
(
localStorage
);
const
snapshotKeys
=
keys
.
filter
((
key
)
=>
key
.
endsWith
(
'-past'
)
||
key
.
endsWith
(
'-future'
));
snapshotKeys
.
forEach
((
key
)
=>
{
const
keyAppId
=
key
.
split
(
'-'
)[
0
];
if
(
keyAppId
!==
appId
)
{
localStorage
.
removeItem
(
key
);
}
});
},
[
appId
]);
const
initData
=
useMemoizedFn
(
async
(
e
:
Parameters
<
WorkflowContextType
[
'initData'
]
>
[
0
],
isInit
?:
boolean
)
=>
{
/*
...
...
@@ -925,18 +937,6 @@ const WorkflowContextProvider = ({
}
);
// remove other app's snapshot
useEffect
(()
=>
{
const
keys
=
Object
.
keys
(
localStorage
);
const
snapshotKeys
=
keys
.
filter
((
key
)
=>
key
.
endsWith
(
'-past'
)
||
key
.
endsWith
(
'-future'
));
snapshotKeys
.
forEach
((
key
)
=>
{
const
keyAppId
=
key
.
split
(
'-'
)[
0
];
if
(
keyAppId
!==
appId
)
{
localStorage
.
removeItem
(
key
);
}
});
},
[
appId
]);
/* Version histories */
const
[
historiesDefaultData
,
setHistoriesDefaultData
]
=
useState
<
InitProps
>
();
...
...
projects/app/src/web/core/app/api/version.ts
View file @
e31d6ec2
import
{
PostPublishAppProps
,
PostRevertAppProps
}
from
'@/global/core/app/api'
;
import
{
GET
,
POST
,
DELETE
,
PUT
}
from
'@/web/common/api/request'
;
import
{
AppVersionSchemaType
}
from
'@fastgpt/global/core/app/version'
;
import
type
{
AppVersionSchemaType
}
from
'@fastgpt/global/core/app/version'
;
import
{
PaginationProps
,
PaginationResponse
}
from
'@fastgpt/web/common/fetch/type'
;
import
type
{
getLatestVersionQuery
,
getLatestVersionResponse
}
from
'@/pages/api/core/app/version/latest'
;
import
{
UpdateAppVersionBody
}
from
'@/pages/api/core/app/version/update'
;
import
{
versionListResponse
}
from
'@/pages/api/core/app/version/listWorkflow
'
;
import
type
{
UpdateAppVersionBody
}
from
'@/pages/api/core/app/version/update'
;
import
type
{
versionListResponse
}
from
'@/pages/api/core/app/version/list
'
;
export
const
getAppLatestVersion
=
(
data
:
getLatestVersionQuery
)
=>
GET
<
getLatestVersionResponse
>
(
'/core/app/version/latest'
,
data
);
...
...
@@ -15,17 +15,11 @@ export const getAppLatestVersion = (data: getLatestVersionQuery) =>
export
const
postPublishApp
=
(
appId
:
string
,
data
:
PostPublishAppProps
)
=>
POST
(
`/core/app/version/publish?appId=
${
appId
}
`
,
data
);
// export const getPublishList = (data: PaginationProps<{ appId: string }>) =>
// POST<PaginationResponse<AppVersionSchemaType>>('/core/app/version/list', data);
export
const
getWorkflowVersionList
=
(
data
:
PaginationProps
<
{
appId
:
string
}
>
)
=>
POST
<
PaginationResponse
<
versionListResponse
>>
(
'/core/app/version/list
Workflow
'
,
data
);
POST
<
PaginationResponse
<
versionListResponse
>>
(
'/core/app/version/list'
,
data
);
export
const
getAppVersionDetail
=
(
versionId
:
string
,
appId
:
string
)
=>
GET
<
AppVersionSchemaType
>
(
`/core/app/version/detail?versionId=
${
versionId
}
&appId=
${
appId
}
`
);
// export const postRevertVersion = (appId: string, data: PostRevertAppProps) =>
// POST(`/core/app/version/revert?appId=${appId}`, data);
export
const
updateAppVersion
=
(
data
:
UpdateAppVersionBody
)
=>
POST
(
`/core/app/version/update`
,
data
);
projects/app/src/web/core/workflow/utils.ts
View file @
e31d6ec2
...
...
@@ -440,14 +440,14 @@ export const getLatestNodeTemplate = (
export
const
compareSnapshot
=
(
snapshot1
:
{
nodes
:
Node
<
FlowNodeItemType
,
string
|
undefined
>
[]
|
undefined
;
nodes
?:
Node
[]
;
edges
:
Edge
<
any
>
[]
|
undefined
;
chatConfig
:
AppChatConfigType
|
undefined
;
chatConfig
?:
AppChatConfigType
;
},
snapshot2
:
{
nodes
:
Node
<
FlowNodeItemType
,
string
|
undefined
>
[];
nodes
?:
Node
[];
edges
:
Edge
<
any
>
[];
chatConfig
:
AppChatConfigType
;
chatConfig
?
:
AppChatConfigType
;
}
)
=>
{
const
clone1
=
cloneDeep
(
snapshot1
);
...
...
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