Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Dominik Schwabe
comparefile
Commits
04ea2fd4
Commit
04ea2fd4
authored
Aug 18, 2021
by
Dominik Schwabe
Browse files
small adjustments
parent
6b42b6a9
Changes
11
Hide whitespace changes
Inline
Side-by-side
api/package.json
View file @
04ea2fd4
{
"name"
:
"api"
,
"version"
:
"
1.6
.0"
,
"version"
:
"
2.0
.0"
,
"description"
:
""
,
"main"
:
"index.js"
,
"directories"
:
{
...
...
cli/__init__.py
View file @
04ea2fd4
...
...
@@ -94,9 +94,14 @@ class Service(ABC):
self
.
config
=
get_config
()
self
.
path
=
f
"./
{
self
.
__type__
}
/"
self
.
name
=
self
.
__type__
with
open
(
f
"./
{
self
.
__type__
}
/package.json"
)
as
file
:
self
.
version
=
json
.
load
(
file
)[
"version"
]
self
.
host
=
self
.
config
.
get
(
"deploy"
,
{}).
get
(
"host"
)
self
.
load_version
()
def
load_version
(
self
):
package_json_path
=
Path
(
f
"./
{
self
.
__type__
}
/package.json"
)
if
package_json_path
.
exists
():
with
open
(
package_json_path
)
as
file
:
self
.
version
=
json
.
load
(
file
)[
"version"
]
def
docker_username
(
self
):
try
:
...
...
@@ -198,12 +203,12 @@ class Ingress(Service):
__type__
=
"ingress"
def
gen_kubernetes
(
self
):
if
not
self
.
host
()
:
if
not
self
.
host
:
return
ingress
=
load_yaml
(
f
"./templates/kubernetes/basic/
{
self
.
__type__
}
.yaml"
)
ingress
[
"spec"
][
"rules"
][
0
][
"host"
]
=
self
.
host
()
ingress
[
"spec"
][
"rules"
][
0
][
"host"
]
=
self
.
host
path
=
Path
(
self
.
__deploy_path__
/
f
"
{
self
.
__type__
}
.yaml"
)
path
.
parent
.
mkdir
(
exist_ok
=
True
,
parents
=
True
)
dump_yaml
(
ingress
,
path
)
...
...
@@ -310,8 +315,8 @@ class Docker:
def
build_all
(
self
,
force
):
for
services
in
self
.
services
.
values
():
for
service
in
services
:
if
not
force
and
self
.
exists
(
service
.
tag
):
service_tag
=
colored
(
service
.
tag
,
"green"
)
if
not
force
and
self
.
exists
(
service
.
tag
()
):
service_tag
=
colored
(
service
.
tag
()
,
"green"
)
print
(
f
"image
{
service_tag
}
already present"
)
else
:
service
.
build
()
...
...
cli/plugins.py
View file @
04ea2fd4
...
...
@@ -403,7 +403,7 @@ class Plugins(ABC):
@
property
def
api_kubernetes_env
(
self
):
envs
=
[
plugin
.
url_env
()
for
plugin
in
self
]
envs
=
[
plugin
.
url_env
for
plugin
in
self
]
return
list
(
dict
(
zip
((
"name"
,
"value"
),
env
))
for
env
in
envs
)
def
gen_kubernetes
(
self
):
...
...
frontend/package.json
View file @
04ea2fd4
{
"name"
:
"frontend"
,
"version"
:
"
1.6
.0"
,
"version"
:
"
2.0
.0"
,
"private"
:
true
,
"dependencies"
:
{
"dexie-react-hooks"
:
"^1.0.6"
,
...
...
frontend/src/components/Model.js
View file @
04ea2fd4
import
React
from
"
react
"
;
import
React
,
{
useMemo
}
from
"
react
"
;
import
{
randomColor
,
RGBToHex
}
from
"
../utils/color
"
;
import
{
usePagination
}
from
"
../hooks/pagination
"
;
import
{
RGBToHex
,
randomColor
}
from
"
../utils/color
"
;
import
{
BadgeButton
}
from
"
./utils/Button
"
;
import
{
Pagination
}
from
"
./utils/Pagination
"
;
const
typeToColor
=
(
type
)
=>
{
switch
(
type
)
{
...
...
@@ -18,7 +20,7 @@ const typeToColor = (type) => {
}
};
const
Model
=
({
info
,
onClick
,
isSet
})
=>
(
const
Model
=
({
info
,
onClick
,
style
,
isSet
})
=>
(
<
BadgeButton
onClick
=
{
onClick
}
style
=
{{
...
...
@@ -28,10 +30,37 @@ const Model = ({ info, onClick, isSet }) => (
padding
:
"
14px
"
,
color
:
"
black
"
,
backgroundColor
:
isSet
?
"
#ffcccb
"
:
"
white
"
,
...
style
,
}}
>
{
info
.
name
}
<
span
title
=
{
info
.
name
}
style
=
{{
overflow
:
"
hidden
"
,
textOverflow
:
"
ellipsis
"
}}
>
{
info
.
name
}
<
/span
>
<
/BadgeButton
>
);
export
{
Model
};
const
ModelGrid
=
({
keys
,
models
,
settings
,
selectModel
})
=>
{
const
[
page
,
setPage
,
size
,
_
,
numItems
]
=
usePagination
(
keys
.
length
);
return
(
<
div
>
<
div
style
=
{{
display
:
"
grid
"
,
gridTemplateColumns
:
"
50% 50%
"
,
gridGap
:
"
10px
"
}}
>
{
Object
.
values
(
models
).
length
?
(
keys
.
slice
((
page
-
1
)
*
size
,
page
*
size
).
map
((
key
)
=>
(
<
Model
key
=
{
key
}
info
=
{
models
[
key
]}
onClick
=
{()
=>
selectModel
(
key
)}
style
=
{{
width
:
"
100%
"
}}
isSet
=
{
settings
[
key
]}
/
>
))
)
:
(
<
div
>
no
models
configured
<
/div
>
)}
<
/div
>
{
numItems
>
size
&&
<
Pagination
activePage
=
{
page
}
size
=
{
size
}
numItems
=
{
numItems
}
onChange
=
{
setPage
}
width
=
"
250px
"
/>
}
<
/div
>
);
};
export
{
Model
,
ModelGrid
};
frontend/src/components/ResultInfo.js
View file @
04ea2fd4
import
React
,
{
useContext
,
useMemo
}
from
"
react
"
;
import
{
MetricsContext
}
from
"
../contexts/MetricsContext
"
;
import
{
useMarkups
}
from
"
../hooks/markup
"
;
import
{
use
Pairwise
Markups
}
from
"
../hooks/markup
"
;
import
{
flatten
}
from
"
../utils/flatScores
"
;
import
{
CompareTable
}
from
"
./CompareTable
"
;
import
{
ScoreTable
}
from
"
./ScoreTable
"
;
const
ResultInfo
=
({
scores
,
references
,
hypotheses
})
=>
{
const
comparisons
=
useMarkups
(
references
,
hypotheses
);
const
comparisons
=
use
Pairwise
Markups
(
references
,
hypotheses
);
const
{
metrics
}
=
useContext
(
MetricsContext
);
const
flatScores
=
useMemo
(()
=>
flatten
(
scores
,
metrics
),
[
scores
,
metrics
]);
...
...
frontend/src/components/SavedInfo.js
View file @
04ea2fd4
...
...
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import
UIkit
from
"
uikit
"
;
import
{
flatten
}
from
"
../utils/flatScores
"
;
import
{
useMarkups
}
from
"
../hooks/markup
"
;
import
{
use
Pairwise
Markups
}
from
"
../hooks/markup
"
;
import
{
CompareTable
}
from
"
./CompareTable
"
;
import
{
ScoreTable
}
from
"
./ScoreTable
"
;
import
{
DeleteButton
}
from
"
./utils/DeleteButton
"
;
...
...
@@ -13,7 +13,7 @@ const SavedInfo = ({ index, calculation, deleteCalculation }) => {
const
toggleID
=
`toggle-saved-calculation-
${
index
}
`
;
const
loadRef
=
useRef
();
const
[
showMarkups
,
setShowMarkups
]
=
useState
(
false
)
const
comparisons
=
useMarkups
(
showMarkups
&&
hypotheses
,
references
)
const
comparisons
=
use
Pairwise
Markups
(
showMarkups
&&
hypotheses
,
references
)
const
showEvent
=
useCallback
(()
=>
{
if
(
comparisons
.
length
)
return
;
if
(
loadRef
.
current
&&
loadRef
.
current
.
className
.
includes
(
"
uk-active
"
))
{
...
...
frontend/src/components/Settings.js
View file @
04ea2fd4
...
...
@@ -2,7 +2,7 @@ import React, { useContext, useMemo, useState } from "react";
import
{
MetricsContext
}
from
"
../contexts/MetricsContext
"
;
import
{
PluginCard
}
from
"
./About
"
;
import
{
Model
}
from
"
./Model
"
;
import
{
Model
Grid
}
from
"
./Model
"
;
import
{
DismissableBadge
}
from
"
./utils/Badge
"
;
import
{
Card
,
CardBody
,
CardHeader
,
CardTitle
}
from
"
./utils/Card
"
;
import
{
Checkboxes
}
from
"
./utils/Checkboxes
"
;
...
...
@@ -20,8 +20,9 @@ const Settings = () => {
const
[
selectedMetric
,
setSelectedMetric
]
=
useState
(
null
);
const
selectMetric
=
(
key
)
=>
{
if
(
settings
[
key
])
setSelectedMetric
(
null
);
else
setSelectedMetric
(
key
);
toggleSetting
(
key
);
if
(
selectedMetric
===
key
)
setSelectedMetric
(
null
);
};
const
unselectMetric
=
(
key
)
=>
{
toggleSetting
(
key
);
...
...
@@ -39,24 +40,14 @@ const Settings = () => {
<
/CardTitle
>
<
/CardHeader
>
<
CardBody
>
<
div
className
=
"
margin-between-10
"
>
{
Object
.
values
(
metrics
).
length
?
(
filteredKeys
.
map
((
key
)
=>
(
<
Model
key
=
{
key
}
info
=
{
metrics
[
key
]}
onClick
=
{()
=>
selectMetric
(
key
)}
isSet
=
{
settings
[
key
]}
/
>
))
)
:
(
<
div
>
no
metrics
configured
<
/div
>
)}
<
/div
>
<
div
className
=
"
colored-header
"
style
=
{{
marginTop
:
"
30px
"
}}
>
Selected
Metrics
<
/div
>
<
div
className
=
"
margin-between-5
"
style
=
{{
marginLeft
:
"
20px
"
,
marginBottom
:
"
10px
"
}}
>
<
ModelGrid
keys
=
{
filteredKeys
}
models
=
{
metrics
}
settings
=
{
settings
}
selectModel
=
{
selectMetric
}
/
>
<
div
className
=
"
uk-flex
"
style
=
{{
alignItems
:
"
center
"
,
gap
:
"
5px
"
,
marginTop
:
"
30px
"
}}
>
<
span
className
=
"
colored-header
"
>
selected
:
<
/span
>
{
chosenMetrics
.
map
((
model
)
=>
(
<
DismissableBadge
onClick
=
{()
=>
unselectMetric
(
model
)}
key
=
{
model
}
>
<
a
...
...
@@ -72,7 +63,11 @@ const Settings = () => {
<
/DismissableBadge
>
))}
<
/div
>
{
selectedMetric
&&
<
PluginCard
plugin
=
{
metrics
[
selectedMetric
]}
inline
=
{
false
}
/>
}
{
selectedMetric
&&
(
<
div
style
=
{{
marginTop
:
"
30px
"
}}
>
<
PluginCard
plugin
=
{
metrics
[
selectedMetric
]}
inline
=
{
false
}
/
>
<
/div
>
)}
<
/CardBody
>
<
/Card
>
);
...
...
frontend/src/components/Summarize.js
View file @
04ea2fd4
...
...
@@ -12,10 +12,10 @@ import { CSSTransition } from "react-transition-group";
import
{
feedbackRequest
,
summarizeRequest
}
from
"
../api
"
;
import
{
SettingsContext
}
from
"
../contexts/SettingsContext
"
;
import
{
SummarizersContext
}
from
"
../contexts/SummarizersContext
"
;
import
{
useMarkups
}
from
"
../hooks/markup
"
;
import
{
useMarkups
,
usePairwiseMarkups
}
from
"
../hooks/markup
"
;
import
{
displayError
,
displayMessage
}
from
"
../utils/message
"
;
import
{
PluginCard
}
from
"
./About
"
;
import
{
Model
}
from
"
./Model
"
;
import
{
Model
Grid
}
from
"
./Model
"
;
import
{
Badge
,
DismissableBadge
}
from
"
./utils/Badge
"
;
import
{
Button
}
from
"
./utils/Button
"
;
import
{
Checkboxes
}
from
"
./utils/Checkboxes
"
;
...
...
@@ -95,8 +95,9 @@ const InputDocument = ({ summarize, isComputing }) => {
const
{
query
,
setQuery
,
filteredKeys
}
=
useFilter
(
summarizerKeys
);
const
[
selectedSummarizer
,
setSelectedSummarizer
]
=
useState
(
null
);
const
selectSummarizer
=
(
key
)
=>
{
if
(
settings
[
key
])
setSelectedSummarizer
(
null
);
else
setSelectedSummarizer
(
key
);
toggleSetting
(
key
);
if
(
selectedSummarizer
===
key
)
setSelectedSummarizer
(
null
);
};
const
unselectSummarizer
=
(
key
)
=>
{
toggleSetting
(
key
);
...
...
@@ -140,24 +141,11 @@ const InputDocument = ({ summarize, isComputing }) => {
className
=
"
uk-card uk-card-default uk-card-body uk-flex-stretch
"
style
=
{{
height
:
"
100%
"
}}
>
<
div
className
=
"
margin-between-10
"
>
{
Object
.
values
(
summarizers
).
length
?
(
filteredKeys
.
map
((
key
)
=>
(
<
Model
key
=
{
key
}
info
=
{
summarizers
[
key
]}
onClick
=
{()
=>
selectSummarizer
(
key
)}
isSet
=
{
settings
[
key
]}
/
>
))
)
:
(
<
div
>
no
summarizers
configured
<
/div
>
)}
<
/div
>
<
div
className
=
"
colored-header
"
style
=
{{
marginTop
:
"
30px
"
}}
>
Selected
Summarizers
<
/div
>
<
div
className
=
"
margin-between-5
"
style
=
{{
marginLeft
:
"
20px
"
,
marginBottom
:
"
10px
"
}}
>
<
ModelGrid
keys
=
{
filteredKeys
}
models
=
{
summarizers
}
settings
=
{
settings
}
selectModel
=
{
selectSummarizer
}
/
>
<
div
className
=
"
uk-flex
"
style
=
{{
alignItems
:
"
center
"
,
gap
:
"
5px
"
,
marginBottom
:
"
10px
"
,
marginTop
:
"
30px
"
}}
>
<
span
className
=
"
colored-header
"
>
selected
:
<
/span
>
{
chosenModels
.
map
((
model
)
=>
(
<
DismissableBadge
onClick
=
{()
=>
unselectSummarizer
(
model
)}
key
=
{
model
}
>
<
a
...
...
@@ -174,9 +162,11 @@ const InputDocument = ({ summarize, isComputing }) => {
))}
<
/div
>
{
selectedSummarizer
&&
(
<
PluginCard
plugin
=
{
summarizers
[
selectedSummarizer
]}
inline
=
{
false
}
/
>
<
div
style
=
{{
marginTop
:
"
20px
"
,
marginBottom
:
"
20px
"
}}
>
<
PluginCard
plugin
=
{
summarizers
[
selectedSummarizer
]}
inline
=
{
false
}
/
>
<
/div
>
)}
<
div
className
=
"
uk-flex uk-flex-center
"
>
<
div
className
=
"
uk-flex uk-flex-center
"
style
=
{{
marginTop
:
"
40px
"
}}
>
{
isComputing
?
(
<
Loading
/>
)
:
(
...
...
@@ -235,11 +225,10 @@ const Summary = ({ markup, summary, markupState, scrollState, showMarkup }) => {
<
/div
>
);
};
// Processed document
const
Document
=
({
title
,
markup
,
markupState
,
scrollState
,
showMarkup
})
=>
(
const
Document
=
({
markup
,
markupState
,
scrollState
,
showMarkup
})
=>
(
<>
<
div
>
<
h4
style
=
{{
margin
:
"
10px
"
,
marginBottom
:
"
0
"
}}
>
{
title
}
<
/h4
>
<
div
className
=
"
uk-card uk-card-default uk-card-body
"
style
=
{{
height
:
"
60vh
"
,
width
:
"
auto
"
,
overflow
:
"
auto
"
,
padding
:
"
20px
"
}}
...
...
@@ -269,7 +258,7 @@ const SummaryTabView = ({ title, showOverlap, summaries, markups, documentLength
return
(
<
div
className
=
"
uk-flex
"
>
<
div
style
=
{{
flexBasis
:
"
60%
"
}}
>
<
Header
text
=
"
Document
"
fontSize
=
"
16pt
"
>
<
Header
text
=
{
title
||
"
Document
"
}
fontSize
=
"
16pt
"
>
<
span
style
=
{{
fontSize
:
"
12pt
"
}}
>
{
documentLength
}
words
<
/span
>
<
/Header
>
<
Document
...
...
@@ -336,21 +325,23 @@ const buildGrids = (list) => {
};
const
SummaryCompareView
=
({
summaries
,
markups
,
showOverlap
})
=>
{
const
grids
=
buildGrids
(
markups
.
map
((
markup
,
index
)
=>
[
markup
,
summaries
[
index
]]));
const
{
summarizers
}
=
useContext
(
SummarizersContext
);
const
grids
=
buildGrids
(
markups
.
map
((
markup
,
index
)
=>
[
markup
,
summarizers
[
summaries
[
index
].
name
].
name
])
);
return
(
<>
{
grids
.
map
((
grid
,
gridIndex
)
=>
(
<
div
key
=
{
gridIndex
}
className
=
"
uk-margin uk-grid uk-child-width-expand@s
"
>
{
grid
.
map
(([
markup
,
summar
y
],
markupIndex
)
=>
(
{
grid
.
map
(([
markup
,
summar
izer
],
markupIndex
)
=>
(
<
div
key
=
{
markupIndex
}
>
<
Header
text
=
{
summarizer
s
[
summary
.
name
].
name
}
fontSize
=
"
16pt
"
/>
<
Header
text
=
{
summarizer
}
fontSize
=
"
16pt
"
/>
<
div
style
=
{{
maxHeight
:
"
500px
"
,
overflow
:
"
auto
"
}}
className
=
"
uk-card uk-card-default uk-card-body
"
>
<
Summary
m
arkup
=
{
markup
}
summary
=
{
summary
}
showMarkup
=
{
showOverlap
}
/
>
<
M
arkup
markup
s
=
{
markup
}
showMarkup
=
{
showOverlap
}
/
>
<
/div
>
<
/div
>
))}
...
...
@@ -391,17 +382,18 @@ const ToggleOverlap = ({ show, toggle }) => (
CloseIcon
=
{
EyeClosed
}
show
=
{
show
}
toggle
=
{
toggle
}
descriptionOpen
=
"
s
how
overlap
"
descriptionClose
=
"
h
ide
overlap
"
descriptionOpen
=
"
S
how
Agreement
"
descriptionClose
=
"
H
ide
Agreement
"
/>
);
const
SummaryView
=
({
title
,
summaries
,
documentLength
})
=>
{
const
[
showTab
,
toggleShowTab
]
=
useReducer
((
e
)
=>
!
e
,
true
);
const
[
showOverlap
,
toggleShowOverlap
]
=
useReducer
((
e
)
=>
!
e
,
false
);
const
hypotheses
=
useMemo
(()
=>
summaries
.
map
(({
summaryText
})
=>
summaryText
),
[
summaries
]);
const
references
=
useMemo
(()
=>
summaries
.
map
(({
originalText
})
=>
originalText
),
[
summaries
]);
const
markups
=
useMarkups
(
references
,
hypotheses
);
const
sums
=
useMemo
(()
=>
summaries
.
map
(({
summaryText
})
=>
summaryText
),
[
summaries
]);
const
originals
=
useMemo
(()
=>
summaries
.
map
(({
originalText
})
=>
originalText
),
[
summaries
]);
const
pairwiseMarkups
=
usePairwiseMarkups
(
originals
,
sums
);
const
summaryMarkups
=
useMarkups
(
sums
);
const
scrollRef
=
useRef
();
useEffect
(()
=>
{
...
...
@@ -422,10 +414,14 @@ const SummaryView = ({ title, summaries, documentLength }) => {
showOverlap
=
{
showOverlap
}
summaries
=
{
summaries
}
title
=
{
title
}
markups
=
{
m
arkups
}
markups
=
{
pairwiseM
arkups
}
/
>
)
:
(
<
SummaryCompareView
showOverlap
=
{
showOverlap
}
summaries
=
{
summaries
}
markups
=
{
markups
}
/
>
<
SummaryCompareView
showOverlap
=
{
showOverlap
}
summaries
=
{
summaries
}
markups
=
{
summaryMarkups
}
/
>
)}
<
/div
>
<
div
...
...
frontend/src/components/utils/Pagination.js
View file @
04ea2fd4
...
...
@@ -68,7 +68,7 @@ const PaginationAfter = ({ activePage, lastPage, itemsAfter, onClickFrom }) => {
return
null
;
};
const
Pagination
=
({
activePage
,
size
,
numItems
,
onChange
,
pageRange
=
5
})
=>
{
const
Pagination
=
({
activePage
,
size
,
numItems
,
onChange
,
pageRange
=
5
,
width
=
"
400px
"
})
=>
{
const
itemsLeftRight
=
Math
.
floor
(
pageRange
/
2
);
const
lastPage
=
Math
.
ceil
(
numItems
/
size
);
const
nop
=
(
e
)
=>
e
.
preventDefault
();
...
...
@@ -86,7 +86,7 @@ const Pagination = ({ activePage, size, numItems, onChange, pageRange = 5 }) =>
data
-
uk
-
slidenav
-
previous
onClick
=
{
prevDisabled
?
nop
:
onClickFrom
(
activePage
-
1
)}
/
>
<
ul
className
=
"
uk-pagination uk-flex-center
"
data
-
uk
-
margin
style
=
{{
width
:
"
400px
"
}}
>
<
ul
className
=
"
uk-pagination uk-flex-center
"
data
-
uk
-
margin
style
=
{{
width
}}
>
<
PaginationBefore
activePage
=
{
activePage
}
itemsBefore
=
{
itemsLeftRight
}
...
...
frontend/src/hooks/markup.js
View file @
04ea2fd4
...
...
@@ -6,11 +6,13 @@ import { computeMarkup } from "../utils/markup";
const
useMarkup
=
(
hypothese
,
reference
)
=>
{
const
{
minOverlap
,
allowSelfSimilarities
,
colorMap
}
=
useContext
(
SettingsContext
);
return
useMemo
(()
=>
{
if
(
hypothese
&&
reference
)
return
computeMarkup
([
hypothese
,
reference
],
colorMap
,
minOverlap
,
allowSelfSimilarities
);
if
(
hypothese
&&
reference
)
return
computeMarkup
([
hypothese
,
reference
],
colorMap
,
minOverlap
,
allowSelfSimilarities
);
return
[];
},
[
hypothese
,
reference
,
minOverlap
,
allowSelfSimilarities
,
colorMap
]);
};
const
useMarkups
=
(
hypotheses
,
references
)
=>
{
const
usePairwiseMarkups
=
(
hypotheses
,
references
)
=>
{
const
{
minOverlap
,
allowSelfSimilarities
,
colorMap
}
=
useContext
(
SettingsContext
);
return
useMemo
(()
=>
{
if
(
hypotheses
&&
references
)
{
...
...
@@ -22,4 +24,12 @@ const useMarkups = (hypotheses, references) => {
},
[
hypotheses
,
references
,
minOverlap
,
allowSelfSimilarities
,
colorMap
]);
};
export
{
useMarkup
,
useMarkups
};
const
useMarkups
=
(
texts
)
=>
{
const
{
minOverlap
,
allowSelfSimilarities
,
colorMap
}
=
useContext
(
SettingsContext
);
return
useMemo
(
()
=>
computeMarkup
(
texts
,
colorMap
,
minOverlap
,
allowSelfSimilarities
),
[
texts
,
minOverlap
,
allowSelfSimilarities
,
colorMap
]
);
};
export
{
useMarkup
,
useMarkups
,
usePairwiseMarkups
};
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