You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
6.9 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. <template>
  2. <layout-text v-if="doc.id" v-shortkey="shortKeys" @shortkey="changeSelectedLabel">
  3. <template #header>
  4. <toolbar-laptop
  5. :doc-id="doc.id"
  6. :enable-auto-labeling.sync="enableAutoLabeling"
  7. :guideline-text="project.guideline"
  8. :is-reviewd="doc.isConfirmed"
  9. :total="docs.count"
  10. class="d-none d-sm-block"
  11. @click:clear-label="clear"
  12. @click:review="confirm"
  13. />
  14. <toolbar-mobile
  15. :total="docs.count"
  16. class="d-flex d-sm-none"
  17. />
  18. </template>
  19. <template #content>
  20. <v-card>
  21. <div class="annotation-text pa-4">
  22. <entity-editor
  23. :dark="$vuetify.theme.dark"
  24. :rtl="isRTL"
  25. :text="doc.text"
  26. :entities="annotations"
  27. :entity-labels="labels"
  28. :relations="links"
  29. :relation-labels="linkTypes"
  30. :allow-overlapping="project.allowOverlapping"
  31. :grapheme-mode="project.graphemeMode"
  32. :selected-label="selectedLabel"
  33. @addEntity="addEntity"
  34. @click:entity="updateEntity"
  35. @contextmenu:entity="deleteEntity"
  36. />
  37. </div>
  38. </v-card>
  39. </template>
  40. <template #sidebar>
  41. <annotation-progress :progress="progress" />
  42. <list-metadata :metadata="doc.meta" class="mt-4" />
  43. <v-card class="mt-4">
  44. <v-card-title>Label Types</v-card-title>
  45. <v-card-text>
  46. <v-chip-group
  47. v-model="selectedLabelIndex"
  48. column
  49. >
  50. <v-chip
  51. v-for="(item, index) in labels"
  52. :key="item.id"
  53. v-shortkey="[item.suffixKey]"
  54. :color="item.backgroundColor"
  55. filter
  56. :text-color="$contrastColor(item.backgroundColor)"
  57. @shortkey="selectedLabelIndex = index"
  58. >
  59. {{ item.text }}
  60. <v-avatar
  61. v-if="item.suffixKey"
  62. right
  63. color="white"
  64. class="black--text font-weight-bold"
  65. >
  66. {{ item.suffixKey }}
  67. </v-avatar>
  68. </v-chip>
  69. </v-chip-group>
  70. </v-card-text>
  71. </v-card>
  72. </template>
  73. </layout-text>
  74. </template>
  75. <script>
  76. import _ from 'lodash'
  77. import { mapGetters } from 'vuex'
  78. import LayoutText from '@/components/tasks/layout/LayoutText'
  79. import ListMetadata from '@/components/tasks/metadata/ListMetadata'
  80. import ToolbarLaptop from '@/components/tasks/toolbar/ToolbarLaptop'
  81. import ToolbarMobile from '@/components/tasks/toolbar/ToolbarMobile'
  82. import EntityEditor from '@/components/tasks/sequenceLabeling/EntityEditor.vue'
  83. import AnnotationProgress from '@/components/tasks/sidebar/AnnotationProgress.vue'
  84. export default {
  85. components: {
  86. AnnotationProgress,
  87. EntityEditor,
  88. LayoutText,
  89. ListMetadata,
  90. ToolbarLaptop,
  91. ToolbarMobile
  92. },
  93. layout: 'workspace',
  94. validate({ params, query }) {
  95. return /^\d+$/.test(params.id) && /^\d+$/.test(query.page)
  96. },
  97. data() {
  98. return {
  99. annotations: [],
  100. docs: [],
  101. labels: [],
  102. links: [],
  103. linkTypes: [],
  104. project: {},
  105. enableAutoLabeling: false,
  106. rtl: false,
  107. selectedLabelIndex: null,
  108. progress: {},
  109. }
  110. },
  111. async fetch() {
  112. this.docs = await this.$services.example.fetchOne(
  113. this.projectId,
  114. this.$route.query.page,
  115. this.$route.query.q,
  116. this.$route.query.isChecked
  117. )
  118. const doc = this.docs.items[0]
  119. if (this.enableAutoLabeling) {
  120. await this.autoLabel(doc.id)
  121. }
  122. await this.list(doc.id)
  123. },
  124. computed: {
  125. ...mapGetters('auth', ['isAuthenticated', 'getUsername', 'getUserId']),
  126. ...mapGetters('config', ['isRTL']),
  127. shortKeys() {
  128. return Object.fromEntries(this.labels.map(item => [item.id, [item.suffixKey]]))
  129. },
  130. projectId() {
  131. return this.$route.params.id
  132. },
  133. doc() {
  134. if (_.isEmpty(this.docs) || this.docs.items.length === 0) {
  135. return {}
  136. } else {
  137. return this.docs.items[0]
  138. }
  139. },
  140. selectedLabel() {
  141. if (Number.isInteger(this.selectedLabelIndex)) {
  142. return this.labels[this.selectedLabelIndex]
  143. } else {
  144. return null
  145. }
  146. }
  147. },
  148. watch: {
  149. '$route.query': '$fetch',
  150. enableAutoLabeling(val) {
  151. if (val) {
  152. this.list(this.doc.id)
  153. }
  154. }
  155. },
  156. async created() {
  157. this.labels = await this.$services.spanType.list(this.projectId)
  158. this.linkTypes = await this.$services.linkTypes.list(this.projectId)
  159. this.project = await this.$services.project.findById(this.projectId)
  160. this.progress = await this.$services.metrics.fetchMyProgress(this.projectId)
  161. },
  162. methods: {
  163. async maybeFetchLabels(annotations) {
  164. const labelIds = new Set(this.labels.map((label) => label.id));
  165. if (annotations.some((item) => !labelIds.has(item.label))) {
  166. this.labels = await this.$services.spanType.list(this.projectId);
  167. }
  168. },
  169. async list(docId) {
  170. const annotations = await this.$services.sequenceLabeling.list(this.projectId, docId);
  171. const links = await this.$services.sequenceLabeling.listLinks(this.projectId);
  172. // In colab mode, if someone add a new label and annotate data with the label during your work,
  173. // it occurs exception because there is no corresponding label.
  174. await this.maybeFetchLabels(annotations);
  175. this.annotations = annotations;
  176. this.links = links;
  177. },
  178. async deleteEntity(id) {
  179. await this.$services.sequenceLabeling.delete(this.projectId, this.doc.id, id)
  180. await this.list(this.doc.id)
  181. },
  182. async addEntity(startOffset, endOffset, labelId) {
  183. await this.$services.sequenceLabeling.create(this.projectId, this.doc.id, labelId, startOffset, endOffset)
  184. await this.list(this.doc.id)
  185. },
  186. async updateEntity(annotationId, labelId) {
  187. await this.$services.sequenceLabeling.changeLabel(this.projectId, this.doc.id, annotationId, labelId)
  188. await this.list(this.doc.id)
  189. },
  190. async clear() {
  191. await this.$services.sequenceLabeling.clear(this.projectId, this.doc.id)
  192. await this.list(this.doc.id)
  193. },
  194. async autoLabel(docId) {
  195. try {
  196. await this.$services.sequenceLabeling.autoLabel(this.projectId, docId)
  197. } catch (e) {
  198. console.log(e.response.data.detail)
  199. }
  200. },
  201. async updateProgress() {
  202. this.progress = await this.$services.metrics.fetchMyProgress(this.projectId)
  203. },
  204. async confirm() {
  205. await this.$services.example.confirm(this.projectId, this.doc.id)
  206. await this.$fetch()
  207. this.updateProgress()
  208. },
  209. changeSelectedLabel(event) {
  210. this.selectedLabelIndex = this.labels.findIndex((item) => item.suffixKey === event.srcKey)
  211. }
  212. }
  213. }
  214. </script>
  215. <style scoped>
  216. .annotation-text {
  217. font-size: 1.25rem !important;
  218. font-weight: 500;
  219. line-height: 2rem;
  220. font-family: "Roboto", sans-serif !important;
  221. opacity: 0.6;
  222. }
  223. </style>