merge: Allow custom timeouts for translation API requests (!1026)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1026

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
Hazelnoot 2025-05-14 18:36:30 +00:00
commit c8d3ee5500
9 changed files with 65 additions and 2 deletions

8
locales/index.d.ts vendored
View file

@ -13045,6 +13045,14 @@ export interface Locale extends ILocale {
* Note: the bubble timeline is hidden by default, and must be enabled via roles. * Note: the bubble timeline is hidden by default, and must be enabled via roles.
*/ */
"bubbleTimelineMustBeEnabled": string; "bubbleTimelineMustBeEnabled": string;
/**
* Translation timeout
*/
"translationTimeoutLabel": string;
/**
* Timeout in milliseconds for translation API requests.
*/
"translationTimeoutCaption": string;
} }
declare const locales: { declare const locales: {
[lang: string]: Locale; [lang: string]: Locale;

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddMetaTranslationTimeout1747023091463 {
name = 'AddMetaTranslationTimeout1747023091463'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "translationTimeout" integer NOT NULL DEFAULT '5000'`);
await queryRunner.query(`COMMENT ON COLUMN "meta"."translationTimeout" IS 'Timeout in milliseconds for translation API requests'`);
}
async down(queryRunner) {
await queryRunner.query(`COMMENT ON COLUMN "meta"."translationTimeout" IS 'Timeout in milliseconds for translation API requests'`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "translationTimeout"`);
}
}

View file

@ -382,6 +382,12 @@ export class MiMeta {
}) })
public swPrivateKey: string | null; public swPrivateKey: string | null;
@Column('integer', {
default: 5000,
comment: 'Timeout in milliseconds for translation API requests',
})
public translationTimeout: number;
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,
nullable: true, nullable: true,

View file

@ -445,6 +445,10 @@ export const meta = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
translationTimeout: {
type: 'number',
optional: false, nullable: false,
},
deeplAuthKey: { deeplAuthKey: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
@ -723,6 +727,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
objectStorageUseProxy: instance.objectStorageUseProxy, objectStorageUseProxy: instance.objectStorageUseProxy,
objectStorageSetPublicRead: instance.objectStorageSetPublicRead, objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
translationTimeout: instance.translationTimeout,
deeplAuthKey: instance.deeplAuthKey, deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro, deeplIsPro: instance.deeplIsPro,
deeplFreeMode: instance.deeplFreeMode, deeplFreeMode: instance.deeplFreeMode,

View file

@ -103,6 +103,7 @@ export const paramDef = {
type: 'string', type: 'string',
}, },
}, },
translationTimeout: { type: 'number' },
deeplAuthKey: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true },
deeplIsPro: { type: 'boolean' }, deeplIsPro: { type: 'boolean' },
deeplFreeMode: { type: 'boolean' }, deeplFreeMode: { type: 'boolean' },
@ -560,6 +561,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle; set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
} }
if (ps.translationTimeout !== undefined) {
set.translationTimeout = ps.translationTimeout;
}
if (ps.deeplAuthKey !== undefined) { if (ps.deeplAuthKey !== undefined) {
if (ps.deeplAuthKey === '') { if (ps.deeplAuthKey === '') {
set.deeplAuthKey = null; set.deeplAuthKey = null;

View file

@ -116,6 +116,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
Accept: 'application/json, */*', Accept: 'application/json, */*',
}, },
body: params.toString(), body: params.toString(),
timeout: this.serverSettings.translationTimeout,
}); });
if (this.serverSettings.deeplAuthKey) { if (this.serverSettings.deeplAuthKey) {
const json = (await res.json()) as { const json = (await res.json()) as {
@ -165,6 +166,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
format: 'text', format: 'text',
api_key: this.serverSettings.libreTranslateKey ?? '', api_key: this.serverSettings.libreTranslateKey ?? '',
}), }),
timeout: this.serverSettings.translationTimeout,
}); });
const json = (await res.json()) as { const json = (await res.json()) as {

View file

@ -8,6 +8,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;"> <div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<FormSuspense :p="init"> <FormSuspense :p="init">
<div class="_gaps_m"> <div class="_gaps_m">
<MkInput v-model="translationTimeout" type="number" manualSave @update:modelValue="saveTranslationTimeout">
<template #label>{{ i18n.ts.translationTimeoutLabel }}</template>
<template #caption>{{ i18n.ts.translationTimeoutCaption }}</template>
</MkInput>
<MkFolder> <MkFolder>
<template #label>DeepL Translation</template> <template #label>DeepL Translation</template>
@ -69,6 +74,7 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
const translationTimeout = ref(0);
const deeplAuthKey = ref<string | null>(''); const deeplAuthKey = ref<string | null>('');
const deeplIsPro = ref<boolean>(false); const deeplIsPro = ref<boolean>(false);
const deeplFreeMode = ref<boolean>(false); const deeplFreeMode = ref<boolean>(false);
@ -78,6 +84,7 @@ const libreTranslateKey = ref<string | null>('');
async function init() { async function init() {
const meta = await misskeyApi('admin/meta'); const meta = await misskeyApi('admin/meta');
translationTimeout.value = meta.translationTimeout;
deeplAuthKey.value = meta.deeplAuthKey; deeplAuthKey.value = meta.deeplAuthKey;
deeplIsPro.value = meta.deeplIsPro; deeplIsPro.value = meta.deeplIsPro;
deeplFreeMode.value = meta.deeplFreeMode; deeplFreeMode.value = meta.deeplFreeMode;
@ -86,6 +93,13 @@ async function init() {
libreTranslateKey.value = meta.libreTranslateKey; libreTranslateKey.value = meta.libreTranslateKey;
} }
async function saveTranslationTimeout() {
await os.apiWithDialog('admin/update-meta', {
translationTimeout: translationTimeout.value,
});
await os.promiseDialog(fetchInstance(true));
}
function save_deepl() { function save_deepl() {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
deeplAuthKey: deeplAuthKey.value, deeplAuthKey: deeplAuthKey.value,
@ -93,7 +107,7 @@ function save_deepl() {
deeplFreeMode: deeplFreeMode.value, deeplFreeMode: deeplFreeMode.value,
deeplFreeInstance: deeplFreeInstance.value, deeplFreeInstance: deeplFreeInstance.value,
}).then(() => { }).then(() => {
fetchInstance(true); os.promiseDialog(fetchInstance(true));
}); });
} }
@ -102,7 +116,7 @@ function save_libre() {
libreTranslateURL: libreTranslateURL.value, libreTranslateURL: libreTranslateURL.value,
libreTranslateKey: libreTranslateKey.value, libreTranslateKey: libreTranslateKey.value,
}).then(() => { }).then(() => {
fetchInstance(true); os.promiseDialog(fetchInstance(true));
}); });
} }

View file

@ -9249,6 +9249,7 @@ export type operations = {
enableReactionsBuffering: boolean; enableReactionsBuffering: boolean;
notesPerOneAd: number; notesPerOneAd: number;
backgroundImageUrl: string | null; backgroundImageUrl: string | null;
translationTimeout: number;
deeplAuthKey: string | null; deeplAuthKey: string | null;
deeplIsPro: boolean; deeplIsPro: boolean;
deeplFreeMode: boolean; deeplFreeMode: boolean;
@ -12159,6 +12160,7 @@ export type operations = {
maintainerName?: string | null; maintainerName?: string | null;
maintainerEmail?: string | null; maintainerEmail?: string | null;
langs?: string[]; langs?: string[];
translationTimeout?: number;
deeplAuthKey?: string | null; deeplAuthKey?: string | null;
deeplIsPro?: boolean; deeplIsPro?: boolean;
deeplFreeMode?: boolean; deeplFreeMode?: boolean;

View file

@ -564,3 +564,6 @@ wordMuteTestNoMatch: "Text does not match any patterns."
bubbleTimeline: "Bubble timeline" bubbleTimeline: "Bubble timeline"
bubbleTimelineDescription: "Choose which instances should be displayed in the bubble." bubbleTimelineDescription: "Choose which instances should be displayed in the bubble."
bubbleTimelineMustBeEnabled: "Note: the bubble timeline is hidden by default, and must be enabled via roles." bubbleTimelineMustBeEnabled: "Note: the bubble timeline is hidden by default, and must be enabled via roles."
translationTimeoutLabel: "Translation timeout"
translationTimeoutCaption: "Timeout in milliseconds for translation API requests."