Browse Source

Add seq2seq components

pull/1251/head
Hironsan 4 years ago
parent
commit
f11da0ceb0
6 changed files with 347 additions and 5 deletions
  1. 126
      frontend/components/tasks/Seq2Seq/Seq2seqBox.vue
  2. 24
      frontend/models/tasks/seq2seq.ts
  3. 138
      frontend/pages/projects/_id/sequence-to-sequence/index.vue
  4. 7
      frontend/plugins/services.ts
  5. 19
      frontend/repositories/tasks/seq2seq/api.ts
  6. 38
      frontend/services/application/tasks/seq2seqService.ts

126
frontend/components/tasks/Seq2Seq/Seq2seqBox.vue

@ -0,0 +1,126 @@
<template>
<v-card>
<v-data-table
:headers="headers"
:items="annotations"
item-key="id"
hide-default-header
hide-default-footer
disable-pagination
class="elevation-1"
@input="update"
>
<template v-slot:top>
<v-text-field
v-model="newText"
prepend-inner-icon="mdi-pencil"
:label="$t('annotation.newText')"
autofocus
single-line
hide-details
filled
@keyup.enter="create"
@compositionstart="compositionStart"
@compositionend="compositionEnd"
/>
</template>
<template v-slot:[`item.text`]="{ item }">
<v-edit-dialog>
<span class="title" style="font-weight:400">
{{ item.text }}
</span>
<template v-slot:input>
<v-textarea
:value="item.text"
:label="$t('generic.edit')"
autofocus
@change="update(item.id, $event)"
/>
</template>
</v-edit-dialog>
</template>
<template v-slot:[`item.action`]="{ item }">
<v-icon
small
@click="deleteAnnotation(item.id)"
>
mdi-delete-outline
</v-icon>
</template>
</v-data-table>
</v-card>
</template>
<script>
export default {
props: {
annotations: {
type: Array,
default: () => ([]),
required: true
},
deleteAnnotation: {
type: Function,
default: () => ([]),
required: true
},
updateAnnotation: {
type: Function,
default: () => ([]),
required: true
},
createAnnotation: {
type: Function,
default: () => ([]),
required: true
}
},
data() {
return {
newText: '',
headers: [
{
text: 'Text',
align: 'left',
value: 'text'
},
{
text: 'Actions',
align: 'right',
value: 'action'
}
],
isComposing: false,
hasCompositionJustEnded: false
}
},
methods: {
update(annotationId, text) {
if (text.length > 0) {
this.updateAnnotation(annotationId, text)
} else {
this.deleteAnnotation(annotationId)
}
},
create() {
if (this.isComposing || this.hasCompositionJustEnded) {
this.hasCompositionJustEnded = false
return
}
if (this.newText.length > 0) {
this.createAnnotation(this.newText)
this.newText = ''
}
},
compositionStart() {
this.isComposing = true
},
compositionEnd() {
this.isComposing = false
this.hasCompositionJustEnded = true
}
}
}
</script>

24
frontend/models/tasks/seq2seq.ts

@ -0,0 +1,24 @@
import { AnnotationModel } from './interface';
export class Seq2seqLabel implements AnnotationModel{
constructor(
public id: number,
public text: string,
public user: number,
) {}
static valueOf(
{ id, text, user }:
{ id: number, text: string, user: number }
) {
return new Seq2seqLabel(id, text, user)
}
toObject() {
return {
id: this.id,
text: this.text,
user: this.user
}
}
}

138
frontend/pages/projects/_id/sequence-to-sequence/index.vue

@ -1,15 +1,145 @@
<template>
<seq2seq-container />
<v-container v-if="doc.id" fluid>
<toolbar-laptop
:doc-id="doc.id"
:enable-auto-labeling.sync="enableAutoLabeling"
:guideline-text="project.guideline"
:is-reviewd="doc.isApproved"
:show-approve-button="project.permitApprove"
:total="docs.count"
class="d-none d-sm-block"
@click:clear-label="clear"
@click:review="approve"
/>
<toolbar-mobile
:total="docs.count"
class="d-flex d-sm-none"
/>
<v-row justify="center">
<v-col cols="12" md="9">
<v-card class="mb-5">
<v-card-text class="title" v-text="doc.text" />
</v-card>
<seq2seq-box
:text="doc.text"
:annotations="annotations"
:delete-annotation="remove"
:update-annotation="update"
:create-annotation="add"
/>
</v-col>
<v-col cols="12" md="3">
<list-metadata :metadata="JSON.parse(doc.meta)" />
</v-col>
</v-row>
</v-container>
</template>
<script>
import Seq2seqContainer from '~/components/containers/annotation/Seq2seqContainer'
import _ from 'lodash'
import ListMetadata from '@/components/tasks/metadata/ListMetadata'
import ToolbarLaptop from '@/components/tasks/toolbar/ToolbarLaptop'
import ToolbarMobile from '@/components/tasks/toolbar/ToolbarMobile'
import Seq2seqBox from '~/components/tasks/Seq2Seq/Seq2seqBox'
export default {
layout: 'annotation',
layout: 'workspace',
components: {
Seq2seqContainer
Seq2seqBox,
ListMetadata,
ToolbarLaptop,
ToolbarMobile
},
async fetch() {
this.docs = await this.$services.document.fetchOne(
this.projectId,
this.$route.query.page,
this.$route.query.q,
this.$route.query.isChecked,
this.project.filterOption
)
const doc = this.docs.items[0]
if (this.enableAutoLabeling) {
await this.autoLabel(doc.id)
}
await this.list(doc.id)
},
data() {
return {
annotations: [],
docs: [],
project: {},
enableAutoLabeling: false
}
},
computed: {
projectId() {
return this.$route.params.id
},
doc() {
if (_.isEmpty(this.docs) || this.docs.items.length === 0) {
return {}
} else {
return this.docs.items[0]
}
}
},
watch: {
'$route.query': '$fetch',
enableAutoLabeling(val) {
if (val) {
this.list(this.doc.id)
}
}
},
async created() {
this.project = await this.$services.project.findById(this.projectId)
},
methods: {
async list(docId) {
this.annotations = await this.$services.seq2seq.list(this.projectId, docId)
},
async remove(id) {
await this.$services.seq2seq.delete(this.projectId, this.doc.id, id)
await this.list(this.doc.id)
},
async add(text) {
await this.$services.seq2seq.create(this.projectId, this.doc.id, text)
await this.list(this.doc.id)
},
async update(annotationId, text) {
await this.$services.seq2seq.changeText(this.projectId, this.doc.id, annotationId, text)
await this.list(this.doc.id)
},
async clear() {
await this.$services.seq2seq.clear(this.projectId, this.doc.id)
await this.list(this.doc.id)
},
async autoLabel(docId) {
try {
await this.$services.seq2seq.autoLabel(this.projectId, docId)
} catch (e) {
console.log(e.response.data.detail)
}
},
async approve() {
const approved = !this.doc.isApproved
await this.$services.document.approve(this.projectId, this.doc.id, approved)
await this.$fetch()
}
},
validate({ params, query }) {

7
frontend/plugins/services.ts

@ -19,11 +19,12 @@ import { DocumentApplicationService } from '@/services/application/document.serv
import { OptionApplicationService } from '@/services/application/option.service'
import { FromApiSequenceLabelingRepository } from '@/repositories/tasks/sequenceLabeling/api'
import { SequenceLabelingApplicationService } from '@/services/application/tasks/sequenceLabelingService'
import { FromApiSeq2seqRepository } from '@/repositories/tasks/seq2seq/api'
import { Seq2seqApplicationService } from '@/services/application/tasks/seq2seqService'
import { FromApiTextClassificationRepository } from '~/repositories/tasks/textClassification/api'
import { TextClassificationApplicationService } from '~/services/application/tasks/textClassificationService'
export interface Services {
label: LabelApplicationService,
member: MemberApplicationService,
@ -35,6 +36,7 @@ export interface Services {
document: DocumentApplicationService,
textClassification: TextClassificationApplicationService,
sequenceLabeling: SequenceLabelingApplicationService,
seq2seq: Seq2seqApplicationService,
option: OptionApplicationService
}
@ -55,6 +57,7 @@ const plugin: Plugin = (context, inject) => {
const documentRepository = new FromApiDocumentItemListRepository()
const textClassificationRepository = new FromApiTextClassificationRepository()
const sequenceLabelingRepository = new FromApiSequenceLabelingRepository()
const seq2seqRepository = new FromApiSeq2seqRepository()
const optionRepository = new LocalStorageOptionRepository()
const label = new LabelApplicationService(labelRepository)
@ -67,6 +70,7 @@ const plugin: Plugin = (context, inject) => {
const document = new DocumentApplicationService(documentRepository)
const textClassification = new TextClassificationApplicationService(textClassificationRepository)
const sequenceLabeling = new SequenceLabelingApplicationService(sequenceLabelingRepository)
const seq2seq = new Seq2seqApplicationService(seq2seqRepository)
const option = new OptionApplicationService(optionRepository)
const services: Services = {
@ -80,6 +84,7 @@ const plugin: Plugin = (context, inject) => {
document,
textClassification,
sequenceLabeling,
seq2seq,
option
}
inject('services', services)

19
frontend/repositories/tasks/seq2seq/api.ts

@ -0,0 +1,19 @@
import { AnnotationRepository } from '../interface'
import { Seq2seqLabel } from '~/models/tasks/seq2seq'
export class FromApiSeq2seqRepository extends AnnotationRepository<Seq2seqLabel> {
constructor() {
super(Seq2seqLabel)
}
public async update(projectId: string, docId: number, annotationId: number, text: string) {
const url = this.baseUrl(projectId, docId) + `/${annotationId}`
const payload = { text }
await this.request.patch(url, payload)
}
protected baseUrl(projectId: string, docId: number): string {
return `/projects/${projectId}/docs/${docId}/annotations`
}
}

38
frontend/services/application/tasks/seq2seqService.ts

@ -0,0 +1,38 @@
import { FromApiSeq2seqRepository } from '@/repositories/tasks/seq2seq/api'
import { Seq2seqLabel } from '@/models/tasks/seq2seq'
import { AnnotationApplicationService } from './annotationService'
import annotation from '~/i18n/de/projects/annotation'
export class Seq2seqDTO {
id: number
text: string
user: number
constructor(item: Seq2seqLabel) {
this.id = item.id
this.text = item.text
this.user = item.user
}
}
export class Seq2seqApplicationService extends AnnotationApplicationService<Seq2seqLabel> {
constructor(
readonly repository: FromApiSeq2seqRepository
) {
super(new FromApiSeq2seqRepository())
}
public async list(projectId: string, docId: number): Promise<Seq2seqDTO[]> {
const items = await this.repository.list(projectId, docId)
return items.map(item => new Seq2seqDTO(item))
}
public async create(projectId: string, docId: number, text: string): Promise<void> {
const item = new Seq2seqLabel(0, text, 0)
await this.repository.create(projectId, docId, item)
}
public async changeText(projectId: string, docId: number, annotationId: number, text: string): Promise<void> {
await this.repository.update(projectId, docId, annotationId, text)
}
}
Loading…
Cancel
Save