@adminforth/i18n 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.log +3 -2
- package/custom/BulkActionButton.vue +62 -18
- package/custom/TranslationJobViewComponent.vue +82 -0
- package/dist/custom/BulkActionButton.vue +62 -18
- package/dist/custom/TranslationJobViewComponent.vue +82 -0
- package/dist/index.js +281 -95
- package/index.ts +384 -120
- package/package.json +3 -2
- package/types.ts +14 -0
package/build.log
CHANGED
|
@@ -10,6 +10,7 @@ custom/LanguageInUserMenu.vue
|
|
|
10
10
|
custom/LanguageUnderLogin.vue
|
|
11
11
|
custom/ListCell.vue
|
|
12
12
|
custom/SingleMultiInput.vue
|
|
13
|
+
custom/TranslationJobViewComponent.vue
|
|
13
14
|
custom/datepickerLocales.ts
|
|
14
15
|
custom/dayjsLocales.ts
|
|
15
16
|
custom/eventBus.ts
|
|
@@ -18,5 +19,5 @@ custom/package-lock.json
|
|
|
18
19
|
custom/package.json
|
|
19
20
|
custom/tsconfig.json
|
|
20
21
|
|
|
21
|
-
sent
|
|
22
|
-
total size is
|
|
22
|
+
sent 43,756 bytes received 286 bytes 88,084.00 bytes/sec
|
|
23
|
+
total size is 42,662 speedup is 0.97
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:buttons="[
|
|
5
5
|
{
|
|
6
6
|
label: 'Translate',
|
|
7
|
-
onclick: (dialog) => { runTranslation(); dialog.hide(); } ,
|
|
7
|
+
onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
|
|
8
8
|
options: {
|
|
9
9
|
disabled: noneChecked
|
|
10
10
|
}
|
|
@@ -19,29 +19,30 @@
|
|
|
19
19
|
]"
|
|
20
20
|
>
|
|
21
21
|
<template #trigger>
|
|
22
|
-
<button
|
|
23
|
-
|
|
24
|
-
class="flex
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
font-medium rounded-sm text-xs px-1 ml-1 text-center ">
|
|
30
|
-
AI
|
|
22
|
+
<button class="flex items-center justify-center w-full">
|
|
23
|
+
<IconLanguageOutline class="text-gray-500 dark:text-gray-400 w-5 h-5" />
|
|
24
|
+
<div class="flex items-end justify-start gap-2 cursor-pointer">
|
|
25
|
+
<p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ t('Translate filtered') }}</p>
|
|
26
|
+
<div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
|
|
27
|
+
{{t('AI')}}
|
|
28
|
+
</div>
|
|
31
29
|
</div>
|
|
32
30
|
</button>
|
|
33
31
|
</template>
|
|
34
32
|
|
|
35
|
-
<div class="grid grid-cols-2 gap-1 w-full">
|
|
33
|
+
<div class="af-i18n-translations-selector grid grid-cols-2 gap-1 w-full">
|
|
34
|
+
<div v-if="isLoading" class="top-0 left-0 z-10 absolute bg-black/30 w-full h-full rounded-lg flex items-center justify-center">
|
|
35
|
+
<Spinner class="w-10 h-10" />
|
|
36
|
+
</div>
|
|
36
37
|
<Button @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
|
|
37
38
|
<Button @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
|
|
38
39
|
<div class="col-span-2 grid grid-cols-3 gap-1 mt-4">
|
|
39
|
-
<div class="group hover:bg-gray-100 dark:hover:bg-gray-
|
|
40
|
+
<div class="group hover:bg-gray-100 dark:hover:bg-gray-600 px-2 py-2 flex items-center cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="index" @click="toggleLanguage(lang)">
|
|
40
41
|
<Checkbox v-model="checkedLanguages[lang]" />
|
|
41
42
|
<span class="flag-icon mr-2"
|
|
42
43
|
:class="`flag-icon-${getCountryCodeFromLangCode(lang)}`"
|
|
43
44
|
></span>
|
|
44
|
-
<span class="group-hover:text-gray-900 text-gray-600">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
|
|
45
|
+
<span class="group-hover:text-gray-900 text-gray-600 dark:text-white dark:group-hover:text-gray-200">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
|
|
45
46
|
</div>
|
|
46
47
|
</div>
|
|
47
48
|
</div>
|
|
@@ -52,14 +53,18 @@
|
|
|
52
53
|
<script setup lang="ts">
|
|
53
54
|
import { IconLanguageOutline } from '@iconify-prerendered/vue-flowbite';
|
|
54
55
|
import { useI18n } from 'vue-i18n';
|
|
55
|
-
import { Dialog, Button, Checkbox } from '@/afcl';
|
|
56
|
-
import { computed, onMounted, ref,
|
|
56
|
+
import { Dialog, Button, Checkbox, Spinner } from '@/afcl';
|
|
57
|
+
import { computed, onMounted, ref, onUnmounted } from 'vue';
|
|
57
58
|
import { callAdminForthApi } from '@/utils';
|
|
58
59
|
import { useAdminforth } from '@/adminforth';
|
|
59
60
|
import { getCountryCodeFromLangCode } from './langCommon';
|
|
60
61
|
import { getName, overwrite } from 'country-list';
|
|
61
62
|
import ISO6391 from 'iso-639-1';
|
|
62
63
|
import 'flag-icon-css/css/flag-icons.min.css';
|
|
64
|
+
import websocket from '@/websocket';
|
|
65
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
66
|
+
|
|
67
|
+
const filtersStore = useFiltersStore();
|
|
63
68
|
|
|
64
69
|
const { t } = useI18n();
|
|
65
70
|
const adminforth = useAdminforth();
|
|
@@ -80,14 +85,22 @@
|
|
|
80
85
|
}>();
|
|
81
86
|
|
|
82
87
|
const checkedLanguages = ref<Record<string, boolean>>({});
|
|
88
|
+
const isLoading = ref(false);
|
|
83
89
|
const allChecked = computed(() => Object.values(checkedLanguages.value).every(Boolean));
|
|
84
90
|
const noneChecked = computed(() => Object.values(checkedLanguages.value).every(value => !value));
|
|
85
91
|
|
|
86
92
|
onMounted(() => {
|
|
93
|
+
websocket.subscribe('/translation_progress', (data) => {
|
|
94
|
+
adminforth.list.refresh();
|
|
95
|
+
});
|
|
87
96
|
for (const lang of props.meta.supportedLanguages) {
|
|
88
97
|
checkedLanguages.value[lang] = true;
|
|
89
98
|
}
|
|
90
99
|
});
|
|
100
|
+
|
|
101
|
+
onUnmounted( () => {
|
|
102
|
+
websocket.unsubscribe('/translation_progress');
|
|
103
|
+
} )
|
|
91
104
|
|
|
92
105
|
function selectAll() {
|
|
93
106
|
for (const lang of props.meta.supportedLanguages) {
|
|
@@ -106,20 +119,28 @@
|
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
async function runTranslation() {
|
|
122
|
+
isLoading.value = true;
|
|
123
|
+
const listOfIds = await getListOfIds();
|
|
109
124
|
try {
|
|
110
125
|
const res = await callAdminForthApi({
|
|
111
126
|
path: `/plugin/${props.meta.pluginInstanceId}/translate-selected-to-languages`,
|
|
112
127
|
method: 'POST',
|
|
113
128
|
body: {
|
|
114
|
-
selectedIds:
|
|
129
|
+
selectedIds: listOfIds,
|
|
115
130
|
selectedLanguages: Object.keys(checkedLanguages.value).filter(lang => checkedLanguages.value[lang]),
|
|
116
131
|
},
|
|
117
132
|
silentError: true,
|
|
118
133
|
});
|
|
119
|
-
adminforth.list.refresh();
|
|
120
134
|
props.clearCheckboxes();
|
|
121
135
|
if (res.ok) {
|
|
122
|
-
adminforth.alert({ message:
|
|
136
|
+
adminforth.alert({ message: `Running translation job`, variant: 'success' });
|
|
137
|
+
console.log('Received record IDs for filtered selector:', res);
|
|
138
|
+
const jobId = res.jobId;
|
|
139
|
+
if (jobId) {
|
|
140
|
+
console.log('Opening job info popup for jobId:', jobId);
|
|
141
|
+
//@ts-ignore
|
|
142
|
+
window.OpenJobInfoPopup(jobId);
|
|
143
|
+
}
|
|
123
144
|
} else {
|
|
124
145
|
adminforth.alert({ message: res.errorMessage || t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
125
146
|
}
|
|
@@ -127,6 +148,29 @@
|
|
|
127
148
|
console.error('Failed to translate selected items:', e);
|
|
128
149
|
adminforth.alert({ message: t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
129
150
|
}
|
|
151
|
+
isLoading.value = false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function getListOfIds() {
|
|
155
|
+
const filters = filtersStore.getFilters();
|
|
156
|
+
let res;
|
|
157
|
+
try {
|
|
158
|
+
res = await callAdminForthApi({
|
|
159
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get_filtered_ids`,
|
|
160
|
+
method: 'POST',
|
|
161
|
+
body: { filters },
|
|
162
|
+
silentError: true,
|
|
163
|
+
});
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.error('Failed to get records for filtered selector:', e);
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
if (!res?.ok || !res?.recordIds) {
|
|
169
|
+
console.error('Failed to get records for filtered selector, response error:', res);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
return res.recordIds;
|
|
130
173
|
}
|
|
174
|
+
|
|
131
175
|
|
|
132
176
|
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
<template>
|
|
3
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 my-3">
|
|
4
|
+
<div class="flex items-center space-x-1">
|
|
5
|
+
<span class=" text-gray-500">{{ t('Total tokens will be used for translation:') }}</span>
|
|
6
|
+
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalTranslationTokenCost).toLocaleString() || 0 }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex items-center space-x-1">
|
|
9
|
+
<span class=" text-gray-500">{{ t('Total translation token used:') }}</span>
|
|
10
|
+
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalUsedTokens).toLocaleString() || 0 }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="grid grid-cols-3 gap-2">
|
|
14
|
+
<div class="bg-gray-50 hover:bg-gray-100 transition-all px-2 py-2 rounded-md border max-w-64 w-full flex items-center gap-2" v-for="(task, index) in translationTasks" :key="index">
|
|
15
|
+
{{ task.state?.taskName }} to
|
|
16
|
+
<span class="flag-icon"
|
|
17
|
+
:class="`flag-icon-${getCountryCodeFromLangCode(task.state?.lang)}`"
|
|
18
|
+
></span>
|
|
19
|
+
<component
|
|
20
|
+
:is="getCustomComponent({file: '@@/plugins/BackgroundJobsPlugin/StateToIcon.vue'})"
|
|
21
|
+
:status="task.status"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { AdminForthComponentDeclarationFull } from 'adminforth';
|
|
30
|
+
import { useI18n } from 'vue-i18n'
|
|
31
|
+
import { onMounted, onUnmounted, ref } from 'vue';
|
|
32
|
+
import websocket from '@/websocket';
|
|
33
|
+
import { getCountryCodeFromLangCode } from './langCommon';
|
|
34
|
+
import { getCustomComponent } from '@/utils';
|
|
35
|
+
|
|
36
|
+
const { t } = useI18n();
|
|
37
|
+
|
|
38
|
+
const translationTasks = ref<{state: Record<string, any>, status: string}[]>([]);
|
|
39
|
+
const currentPaginationWindow = ref({limit: 25, offset: 0});
|
|
40
|
+
|
|
41
|
+
const props = defineProps<{
|
|
42
|
+
meta: any;
|
|
43
|
+
getJobTasks: (limit?: number, offset?: number) => Promise<
|
|
44
|
+
{tasks: {state: Record<string, any>, status: string}[], total: number}>;
|
|
45
|
+
job: {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
|
|
49
|
+
state: {
|
|
50
|
+
totalTranslationTokenCost: number;
|
|
51
|
+
totalUsedTokens: number;
|
|
52
|
+
};
|
|
53
|
+
progress: number; // 0 to 100
|
|
54
|
+
createdAt: Date;
|
|
55
|
+
customComponent?: AdminForthComponentDeclarationFull;
|
|
56
|
+
};
|
|
57
|
+
}>();
|
|
58
|
+
|
|
59
|
+
onMounted(async () => {
|
|
60
|
+
const {tasks, total} = await props.getJobTasks(currentPaginationWindow.value.limit, currentPaginationWindow.value.offset);
|
|
61
|
+
translationTasks.value = tasks;
|
|
62
|
+
|
|
63
|
+
websocket.subscribe(`/background-jobs-task-update/${props.job.id}`, (data: { taskIndex: number, status?: string, state?: Record<string, any> }) => {
|
|
64
|
+
if ( data.taskIndex <= currentPaginationWindow.value.offset + currentPaginationWindow.value.limit && data.taskIndex >= currentPaginationWindow.value.offset ) {
|
|
65
|
+
if (data.state) {
|
|
66
|
+
translationTasks.value[data.taskIndex].state = data.state;
|
|
67
|
+
}
|
|
68
|
+
if (data.status) {
|
|
69
|
+
translationTasks.value[data.taskIndex].status = data.status;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
onUnmounted(() => {
|
|
78
|
+
websocket.unsubscribe(`/background-jobs-task-update/${props.job.id}`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
</script>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:buttons="[
|
|
5
5
|
{
|
|
6
6
|
label: 'Translate',
|
|
7
|
-
onclick: (dialog) => { runTranslation(); dialog.hide(); } ,
|
|
7
|
+
onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
|
|
8
8
|
options: {
|
|
9
9
|
disabled: noneChecked
|
|
10
10
|
}
|
|
@@ -19,29 +19,30 @@
|
|
|
19
19
|
]"
|
|
20
20
|
>
|
|
21
21
|
<template #trigger>
|
|
22
|
-
<button
|
|
23
|
-
|
|
24
|
-
class="flex
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
font-medium rounded-sm text-xs px-1 ml-1 text-center ">
|
|
30
|
-
AI
|
|
22
|
+
<button class="flex items-center justify-center w-full">
|
|
23
|
+
<IconLanguageOutline class="text-gray-500 dark:text-gray-400 w-5 h-5" />
|
|
24
|
+
<div class="flex items-end justify-start gap-2 cursor-pointer">
|
|
25
|
+
<p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ t('Translate filtered') }}</p>
|
|
26
|
+
<div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
|
|
27
|
+
{{t('AI')}}
|
|
28
|
+
</div>
|
|
31
29
|
</div>
|
|
32
30
|
</button>
|
|
33
31
|
</template>
|
|
34
32
|
|
|
35
|
-
<div class="grid grid-cols-2 gap-1 w-full">
|
|
33
|
+
<div class="af-i18n-translations-selector grid grid-cols-2 gap-1 w-full">
|
|
34
|
+
<div v-if="isLoading" class="top-0 left-0 z-10 absolute bg-black/30 w-full h-full rounded-lg flex items-center justify-center">
|
|
35
|
+
<Spinner class="w-10 h-10" />
|
|
36
|
+
</div>
|
|
36
37
|
<Button @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
|
|
37
38
|
<Button @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
|
|
38
39
|
<div class="col-span-2 grid grid-cols-3 gap-1 mt-4">
|
|
39
|
-
<div class="group hover:bg-gray-100 dark:hover:bg-gray-
|
|
40
|
+
<div class="group hover:bg-gray-100 dark:hover:bg-gray-600 px-2 py-2 flex items-center cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="index" @click="toggleLanguage(lang)">
|
|
40
41
|
<Checkbox v-model="checkedLanguages[lang]" />
|
|
41
42
|
<span class="flag-icon mr-2"
|
|
42
43
|
:class="`flag-icon-${getCountryCodeFromLangCode(lang)}`"
|
|
43
44
|
></span>
|
|
44
|
-
<span class="group-hover:text-gray-900 text-gray-600">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
|
|
45
|
+
<span class="group-hover:text-gray-900 text-gray-600 dark:text-white dark:group-hover:text-gray-200">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
|
|
45
46
|
</div>
|
|
46
47
|
</div>
|
|
47
48
|
</div>
|
|
@@ -52,14 +53,18 @@
|
|
|
52
53
|
<script setup lang="ts">
|
|
53
54
|
import { IconLanguageOutline } from '@iconify-prerendered/vue-flowbite';
|
|
54
55
|
import { useI18n } from 'vue-i18n';
|
|
55
|
-
import { Dialog, Button, Checkbox } from '@/afcl';
|
|
56
|
-
import { computed, onMounted, ref,
|
|
56
|
+
import { Dialog, Button, Checkbox, Spinner } from '@/afcl';
|
|
57
|
+
import { computed, onMounted, ref, onUnmounted } from 'vue';
|
|
57
58
|
import { callAdminForthApi } from '@/utils';
|
|
58
59
|
import { useAdminforth } from '@/adminforth';
|
|
59
60
|
import { getCountryCodeFromLangCode } from './langCommon';
|
|
60
61
|
import { getName, overwrite } from 'country-list';
|
|
61
62
|
import ISO6391 from 'iso-639-1';
|
|
62
63
|
import 'flag-icon-css/css/flag-icons.min.css';
|
|
64
|
+
import websocket from '@/websocket';
|
|
65
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
66
|
+
|
|
67
|
+
const filtersStore = useFiltersStore();
|
|
63
68
|
|
|
64
69
|
const { t } = useI18n();
|
|
65
70
|
const adminforth = useAdminforth();
|
|
@@ -80,14 +85,22 @@
|
|
|
80
85
|
}>();
|
|
81
86
|
|
|
82
87
|
const checkedLanguages = ref<Record<string, boolean>>({});
|
|
88
|
+
const isLoading = ref(false);
|
|
83
89
|
const allChecked = computed(() => Object.values(checkedLanguages.value).every(Boolean));
|
|
84
90
|
const noneChecked = computed(() => Object.values(checkedLanguages.value).every(value => !value));
|
|
85
91
|
|
|
86
92
|
onMounted(() => {
|
|
93
|
+
websocket.subscribe('/translation_progress', (data) => {
|
|
94
|
+
adminforth.list.refresh();
|
|
95
|
+
});
|
|
87
96
|
for (const lang of props.meta.supportedLanguages) {
|
|
88
97
|
checkedLanguages.value[lang] = true;
|
|
89
98
|
}
|
|
90
99
|
});
|
|
100
|
+
|
|
101
|
+
onUnmounted( () => {
|
|
102
|
+
websocket.unsubscribe('/translation_progress');
|
|
103
|
+
} )
|
|
91
104
|
|
|
92
105
|
function selectAll() {
|
|
93
106
|
for (const lang of props.meta.supportedLanguages) {
|
|
@@ -106,20 +119,28 @@
|
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
async function runTranslation() {
|
|
122
|
+
isLoading.value = true;
|
|
123
|
+
const listOfIds = await getListOfIds();
|
|
109
124
|
try {
|
|
110
125
|
const res = await callAdminForthApi({
|
|
111
126
|
path: `/plugin/${props.meta.pluginInstanceId}/translate-selected-to-languages`,
|
|
112
127
|
method: 'POST',
|
|
113
128
|
body: {
|
|
114
|
-
selectedIds:
|
|
129
|
+
selectedIds: listOfIds,
|
|
115
130
|
selectedLanguages: Object.keys(checkedLanguages.value).filter(lang => checkedLanguages.value[lang]),
|
|
116
131
|
},
|
|
117
132
|
silentError: true,
|
|
118
133
|
});
|
|
119
|
-
adminforth.list.refresh();
|
|
120
134
|
props.clearCheckboxes();
|
|
121
135
|
if (res.ok) {
|
|
122
|
-
adminforth.alert({ message:
|
|
136
|
+
adminforth.alert({ message: `Running translation job`, variant: 'success' });
|
|
137
|
+
console.log('Received record IDs for filtered selector:', res);
|
|
138
|
+
const jobId = res.jobId;
|
|
139
|
+
if (jobId) {
|
|
140
|
+
console.log('Opening job info popup for jobId:', jobId);
|
|
141
|
+
//@ts-ignore
|
|
142
|
+
window.OpenJobInfoPopup(jobId);
|
|
143
|
+
}
|
|
123
144
|
} else {
|
|
124
145
|
adminforth.alert({ message: res.errorMessage || t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
125
146
|
}
|
|
@@ -127,6 +148,29 @@
|
|
|
127
148
|
console.error('Failed to translate selected items:', e);
|
|
128
149
|
adminforth.alert({ message: t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
129
150
|
}
|
|
151
|
+
isLoading.value = false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function getListOfIds() {
|
|
155
|
+
const filters = filtersStore.getFilters();
|
|
156
|
+
let res;
|
|
157
|
+
try {
|
|
158
|
+
res = await callAdminForthApi({
|
|
159
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get_filtered_ids`,
|
|
160
|
+
method: 'POST',
|
|
161
|
+
body: { filters },
|
|
162
|
+
silentError: true,
|
|
163
|
+
});
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.error('Failed to get records for filtered selector:', e);
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
if (!res?.ok || !res?.recordIds) {
|
|
169
|
+
console.error('Failed to get records for filtered selector, response error:', res);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
return res.recordIds;
|
|
130
173
|
}
|
|
174
|
+
|
|
131
175
|
|
|
132
176
|
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
<template>
|
|
3
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 my-3">
|
|
4
|
+
<div class="flex items-center space-x-1">
|
|
5
|
+
<span class=" text-gray-500">{{ t('Total tokens will be used for translation:') }}</span>
|
|
6
|
+
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalTranslationTokenCost).toLocaleString() || 0 }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex items-center space-x-1">
|
|
9
|
+
<span class=" text-gray-500">{{ t('Total translation token used:') }}</span>
|
|
10
|
+
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalUsedTokens).toLocaleString() || 0 }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="grid grid-cols-3 gap-2">
|
|
14
|
+
<div class="bg-gray-50 hover:bg-gray-100 transition-all px-2 py-2 rounded-md border max-w-64 w-full flex items-center gap-2" v-for="(task, index) in translationTasks" :key="index">
|
|
15
|
+
{{ task.state?.taskName }} to
|
|
16
|
+
<span class="flag-icon"
|
|
17
|
+
:class="`flag-icon-${getCountryCodeFromLangCode(task.state?.lang)}`"
|
|
18
|
+
></span>
|
|
19
|
+
<component
|
|
20
|
+
:is="getCustomComponent({file: '@@/plugins/BackgroundJobsPlugin/StateToIcon.vue'})"
|
|
21
|
+
:status="task.status"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { AdminForthComponentDeclarationFull } from 'adminforth';
|
|
30
|
+
import { useI18n } from 'vue-i18n'
|
|
31
|
+
import { onMounted, onUnmounted, ref } from 'vue';
|
|
32
|
+
import websocket from '@/websocket';
|
|
33
|
+
import { getCountryCodeFromLangCode } from './langCommon';
|
|
34
|
+
import { getCustomComponent } from '@/utils';
|
|
35
|
+
|
|
36
|
+
const { t } = useI18n();
|
|
37
|
+
|
|
38
|
+
const translationTasks = ref<{state: Record<string, any>, status: string}[]>([]);
|
|
39
|
+
const currentPaginationWindow = ref({limit: 25, offset: 0});
|
|
40
|
+
|
|
41
|
+
const props = defineProps<{
|
|
42
|
+
meta: any;
|
|
43
|
+
getJobTasks: (limit?: number, offset?: number) => Promise<
|
|
44
|
+
{tasks: {state: Record<string, any>, status: string}[], total: number}>;
|
|
45
|
+
job: {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
|
|
49
|
+
state: {
|
|
50
|
+
totalTranslationTokenCost: number;
|
|
51
|
+
totalUsedTokens: number;
|
|
52
|
+
};
|
|
53
|
+
progress: number; // 0 to 100
|
|
54
|
+
createdAt: Date;
|
|
55
|
+
customComponent?: AdminForthComponentDeclarationFull;
|
|
56
|
+
};
|
|
57
|
+
}>();
|
|
58
|
+
|
|
59
|
+
onMounted(async () => {
|
|
60
|
+
const {tasks, total} = await props.getJobTasks(currentPaginationWindow.value.limit, currentPaginationWindow.value.offset);
|
|
61
|
+
translationTasks.value = tasks;
|
|
62
|
+
|
|
63
|
+
websocket.subscribe(`/background-jobs-task-update/${props.job.id}`, (data: { taskIndex: number, status?: string, state?: Record<string, any> }) => {
|
|
64
|
+
if ( data.taskIndex <= currentPaginationWindow.value.offset + currentPaginationWindow.value.limit && data.taskIndex >= currentPaginationWindow.value.offset ) {
|
|
65
|
+
if (data.state) {
|
|
66
|
+
translationTasks.value[data.taskIndex].state = data.state;
|
|
67
|
+
}
|
|
68
|
+
if (data.status) {
|
|
69
|
+
translationTasks.value[data.taskIndex].status = data.status;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
onUnmounted(() => {
|
|
78
|
+
websocket.unsubscribe(`/background-jobs-task-update/${props.job.id}`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
</script>
|