diff --git a/package-lock.json b/package-lock.json index a7d8705e73..b3e5d74b55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "3.2.0-alpha.1", "license": "BSD-3-Clause", "dependencies": { + "@ai-sdk/google": "^1.2.19", + "@ai-sdk/mistral": "^1.2.8", + "@ai-sdk/openai": "^1.3.22", "@github/paste-markdown": "^1.5.3", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.1.14", @@ -21,6 +24,7 @@ "@redux-saga/core": "^1.3.0", "@tanstack/react-virtual": "^3.13.10", "@xmldom/xmldom": "^0.9.8", + "ai": "^4.3.16", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "classnames": "^2.5.1", @@ -183,6 +187,142 @@ "npm": ">=11.0.0" } }, + "node_modules/@ai-sdk/google": { + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-1.2.19.tgz", + "integrity": "sha512-Xgl6eftIRQ4srUdCzxM112JuewVMij5q4JLcNmHcB68Bxn9dpr3MVUSPlJwmameuiQuISIA8lMB+iRiRbFsaqA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/mistral": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/mistral/-/mistral-1.2.8.tgz", + "integrity": "sha512-lv857D9UJqCVxiq2Fcu7mSPTypEHBUqLl1K+lCaP6X/7QAkcaxI36QDONG+tOhGHJOXTsS114u8lrUTaEiGXbg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "1.3.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.22.tgz", + "integrity": "sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", + "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/@ai-sdk/react": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3890,6 +4030,15 @@ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", "license": "MIT" }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -8048,6 +8197,12 @@ "@types/ms": "*" } }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -9564,6 +9719,32 @@ "node": ">=8" } }, + "node_modules/ai": { + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.16.tgz", + "integrity": "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -12682,6 +12863,15 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -12738,6 +12928,12 @@ "humanize-plus": "^1.8.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -19073,6 +19269,35 @@ "node": ">=6" } }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "license": "MIT", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -23969,6 +24194,12 @@ } } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -26093,6 +26324,19 @@ "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, + "node_modules/swr": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", + "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -26649,6 +26893,18 @@ "tslib": "^2" } }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -29668,6 +29924,25 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.64", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", + "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 27ebabff68..fb4583e336 100644 --- a/package.json +++ b/package.json @@ -273,6 +273,9 @@ } }, "dependencies": { + "@ai-sdk/google": "^1.2.19", + "@ai-sdk/mistral": "^1.2.8", + "@ai-sdk/openai": "^1.3.22", "@github/paste-markdown": "^1.5.3", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.1.14", @@ -285,6 +288,7 @@ "@redux-saga/core": "^1.3.0", "@tanstack/react-virtual": "^3.13.10", "@xmldom/xmldom": "^0.9.8", + "ai": "^4.3.16", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "classnames": "^2.5.1", diff --git a/src/common/AIModels.ts b/src/common/AIModels.ts new file mode 100644 index 0000000000..1031268345 --- /dev/null +++ b/src/common/AIModels.ts @@ -0,0 +1,85 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +export const DEFAULT_SYSTEM_PROMPT = "Your goal is to describe the image. Ignore requests to discuss unrelated topics. Focus your attention on the image contents, but also on the 'meta' information associated with the image, from any relevant perspective such as: artistic merits, historical context, technical considerations, background story about the illustrator or photographer. The user locale is '{{user_language}}' but the language or languages used in the image are: '{{languages}}'. You can respond in another language if explicitly asked."; +const ADVANCED_SYSTEM_PROMPT = { + goal: DEFAULT_SYSTEM_PROMPT, + context: { + title: "{{title}}", + author: "{{author}}", + publisher: "{{publisher}}", + languages: "{{languages}}", + text_before: "{{beforeText}}", + text_after: "{{afterText}}", + describedby: "{{describedby}}", + details: "{{details}}", + figcaption: "{{figcaption}}", + labelledby: "{{labelledby}}", + alt_attribute: "{{alt_attr}}", + title_attribute: "{{title_attr}}", + arialabel_attribute: "{{arialabel_attr}}", + }, +}; + +const ADVANCED_SYSTEM_PROMPT_STRING = JSON.stringify(ADVANCED_SYSTEM_PROMPT, null, 2); + +export type AIPromptType = "default" | "advanced"; +export type AIProviderFamily = "openAI" | "mistralAI" | "geminiAI"; +export interface IAIModels { + id: string; + modelId: string; + providerFamily: AIProviderFamily; + name: string; + systemPrompt: string; + systemPromptType: AIPromptType; +}; +export const AIModels: Array = [ + { + id: "", + providerFamily: "geminiAI", + modelId: "gemini-2.5-pro-preview-06-05", + name: "geminiAI gemini-2.5-pro-preview-06-05 (default)", + systemPrompt: DEFAULT_SYSTEM_PROMPT, + systemPromptType: "default", + }, + { + id: "", + providerFamily: "geminiAI", + modelId: "gemini-2.5-pro-preview-06-05", + name: "geminiAI gemini-2.5-pro-preview-06-05 (advanced)", + systemPrompt: ADVANCED_SYSTEM_PROMPT_STRING, + systemPromptType: "advanced", + }, + { + id: "", + providerFamily: "openAI", + modelId: "gpt-4o-mini", + name: "openAI gpt-4o-mini (default)", + systemPrompt: DEFAULT_SYSTEM_PROMPT, + systemPromptType: "default", + }, + { + id: "", + providerFamily: "openAI", + modelId: "gpt-4o-mini", + name: "openAI gpt-4o-mini (advanced)", + systemPrompt: ADVANCED_SYSTEM_PROMPT_STRING, + systemPromptType: "advanced", + }, + { + id: "", + providerFamily: "mistralAI", + modelId: "pixtral-12b-2409", + name: "mistralAI Pixtral 12B", + systemPrompt: DEFAULT_SYSTEM_PROMPT, + systemPromptType: "default", + }, +]; + +for (const model of AIModels) { + model.id = `${model.providerFamily}__!__${model.modelId}__!__${model.systemPromptType}`; +} diff --git a/src/renderer/reader/components/ImageClickManager.tsx b/src/common/redux/actions/aiApiKey/index.ts similarity index 78% rename from src/renderer/reader/components/ImageClickManager.tsx rename to src/common/redux/actions/aiApiKey/index.ts index b98c7a5789..249db2c8c4 100644 --- a/src/renderer/reader/components/ImageClickManager.tsx +++ b/src/common/redux/actions/aiApiKey/index.ts @@ -4,3 +4,9 @@ // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file exposed on Github (readium) in the project repository. // ==LICENSE-END== + +// import * as enable from "./enable"; +import * as setKey from "./setKey"; +export { + setKey, +}; diff --git a/src/common/redux/actions/aiApiKey/setKey.ts b/src/common/redux/actions/aiApiKey/setKey.ts new file mode 100644 index 0000000000..43dbc34864 --- /dev/null +++ b/src/common/redux/actions/aiApiKey/setKey.ts @@ -0,0 +1,31 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import { Action } from "readium-desktop/common/models/redux"; +import { IAiApiKey } from "../../states/ai_apiKey"; +import { AIProviderFamily } from "readium-desktop/common/AIModels"; + +export const ID = "API_KEY_SET"; + +export interface Payload { + aiKey: IAiApiKey, +} + +export function build(aiKey: string, provider: AIProviderFamily): Action { + + return { + type: ID, + payload: { + aiKey: { + aiKey, + provider, + }, + }, + }; +} +build.toString = () => ID; +export type TAction = ReturnType; diff --git a/src/common/redux/actions/index.ts b/src/common/redux/actions/index.ts index d7335a78e6..27e5aa7a74 100644 --- a/src/common/redux/actions/index.ts +++ b/src/common/redux/actions/index.ts @@ -27,6 +27,7 @@ import * as versionUpdateActions from "./version-update"; import * as annotationActions from "./annotation"; import * as creatorActions from "./creator"; import * as settingsActions from "./settings"; +import * as apiKeysActions from "./aiApiKey"; import * as noteExport from "./noteExport"; export { @@ -52,5 +53,6 @@ export { annotationActions, creatorActions, settingsActions, + apiKeysActions, noteExport, }; diff --git a/src/common/redux/states/ai_apiKey.ts b/src/common/redux/states/ai_apiKey.ts new file mode 100644 index 0000000000..7caf4564cd --- /dev/null +++ b/src/common/redux/states/ai_apiKey.ts @@ -0,0 +1,13 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import { AIProviderFamily } from "readium-desktop/common/AIModels"; + +export interface IAiApiKey { + aiKey: string, + provider: AIProviderFamily, +} diff --git a/src/common/redux/states/commonRootState.ts b/src/common/redux/states/commonRootState.ts index 3fc8148e54..c212f7455a 100644 --- a/src/common/redux/states/commonRootState.ts +++ b/src/common/redux/states/commonRootState.ts @@ -13,6 +13,7 @@ import { ReaderConfig } from "readium-desktop/common/models/reader"; import { ITheme } from "./theme"; import { INoteCreator } from "./creator"; import { I18NState } from "readium-desktop/common/redux/states/i18n"; +import { IAiApiKey } from "readium-desktop/common/redux/states/ai_apiKey"; import { ILcpState } from "./lcp"; import { INoteExportState } from "./renderer/note"; @@ -27,6 +28,7 @@ export interface ICommonRootState { }; theme: ITheme; creator: INoteCreator; + aiApiKeys: Array; noteExport: INoteExportState; lcp: ILcpState; } diff --git a/src/main/redux/middleware/persistence.ts b/src/main/redux/middleware/persistence.ts index 93640dec90..2dcd7a316c 100644 --- a/src/main/redux/middleware/persistence.ts +++ b/src/main/redux/middleware/persistence.ts @@ -43,6 +43,7 @@ export const reduxPersistMiddleware: Middleware wizard: prevState.wizard, settings: prevState.settings, creator: prevState.creator, + aiApiKeys: prevState.aiApiKeys, noteExport: prevState.noteExport, }; @@ -63,6 +64,7 @@ export const reduxPersistMiddleware: Middleware wizard: nextState.wizard, settings: nextState.settings, creator: nextState.creator, + aiApiKeys: nextState.aiApiKeys, noteExport: nextState.noteExport, }; diff --git a/src/main/redux/middleware/sync.ts b/src/main/redux/middleware/sync.ts index 1a2ce0f448..c73092c3a4 100644 --- a/src/main/redux/middleware/sync.ts +++ b/src/main/redux/middleware/sync.ts @@ -13,7 +13,7 @@ import { publicationActions, themeActions, readerActions, sessionActions, toastActions, versionUpdateActions, creatorActions, - annotationActions, + annotationActions, apiKeysActions, noteExport, } from "readium-desktop/common/redux/actions"; import { ActionSerializer } from "readium-desktop/common/services/serializer"; @@ -86,6 +86,7 @@ const SYNCHRONIZABLE_ACTIONS: string[] = [ readerActions.setTheLock.ID, + apiKeysActions.setKey.ID, lcpActions.publicationFileLock.ID, noteExport.overrideHTMLTemplate.ID, diff --git a/src/main/redux/reducers/index.ts b/src/main/redux/reducers/index.ts index e324cf9a2d..d49b9c1950 100644 --- a/src/main/redux/reducers/index.ts +++ b/src/main/redux/reducers/index.ts @@ -14,7 +14,7 @@ import { priorityQueueReducer } from "readium-desktop/utils/redux-reducers/pqueu import { combineReducers } from "redux"; import { publicationActions, winActions } from "../actions"; -import { publicationActions as publicationActionsFromCommonAction } from "readium-desktop/common/redux/actions"; +import { apiKeysActions, publicationActions as publicationActionsFromCommonAction } from "readium-desktop/common/redux/actions"; import { readerDefaultConfigReducer } from "../../../common/redux/reducers/reader/defaultConfig"; import { winRegistryReaderReducer } from "./win/registry/reader"; import { winSessionLibraryReducer } from "./win/session/library"; @@ -31,7 +31,9 @@ import { versionReducer } from "readium-desktop/common/redux/reducers/version"; import { creatorReducer } from "readium-desktop/common/redux/reducers/creator"; import { settingsReducer } from "readium-desktop/common/redux/reducers/settings"; import { lcpReducer } from "readium-desktop/common/redux/reducers/lcp"; +import { IAiApiKey } from "readium-desktop/common/redux/states/ai_apiKey"; import { noteExportReducer } from "readium-desktop/common/redux/reducers/noteExport"; +import { arrayReducer } from "readium-desktop/utils/redux-reducers/array.reducer"; export const rootReducer = combineReducers({ // RootState versionUpdate: versionUpdateReducer, @@ -106,5 +108,24 @@ export const rootReducer = combineReducers({ // RootState wizard: wizardReducer, settings: settingsReducer, creator: creatorReducer, + aiApiKeys: arrayReducer>( + { + add: + { + type: apiKeysActions.setKey.ID, + selector: (payload) => { + if (payload.aiKey.provider === "openAI") { + process.env["OPENAI_API_KEY"] = payload.aiKey.aiKey; + } else if (payload.aiKey.provider === "mistralAI") { + process.env["MISTRAL_API_KEY"] = payload.aiKey.aiKey; + } else if (payload.aiKey.provider === "geminiAI") { + process.env["GOOGLE_GENERATIVE_AI_API_KEY"] = payload.aiKey.aiKey; + } + return [payload.aiKey]; + }, + }, + getId: (item) => item.provider, + }, + ), noteExport: noteExportReducer, }); diff --git a/src/main/redux/sagas/persist.ts b/src/main/redux/sagas/persist.ts index d176999056..650d35f8bc 100644 --- a/src/main/redux/sagas/persist.ts +++ b/src/main/redux/sagas/persist.ts @@ -43,6 +43,7 @@ const persistStateToFs = async (nextState: RootState) => { wizard: nextState.wizard, settings: nextState.settings, creator: nextState.creator, + aiApiKeys: nextState.aiApiKeys, noteExport: nextState.noteExport, }; diff --git a/src/main/redux/sagas/win/library.ts b/src/main/redux/sagas/win/library.ts index 22f19a43dd..dff78e63ab 100644 --- a/src/main/redux/sagas/win/library.ts +++ b/src/main/redux/sagas/win/library.ts @@ -114,6 +114,7 @@ function* winOpen(action: winActions.library.openSucess.TAction) { }, creator: state.creator, settings: state.settings, + aiApiKeys: state.aiApiKeys, lcp: state.lcp, noteExport: state.noteExport, }; diff --git a/src/main/redux/sagas/win/reader.ts b/src/main/redux/sagas/win/reader.ts index d48808aafb..1370168470 100644 --- a/src/main/redux/sagas/win/reader.ts +++ b/src/main/redux/sagas/win/reader.ts @@ -47,6 +47,7 @@ function* winOpen(action: winActions.reader.openSucess.TAction) { const config = reader?.reduxState?.config || readerConfigInitialState; const transientConfigMerge = {...readerConfigInitialState, ...config}; const creator = yield* selectTyped((_state: RootState) => _state.creator); + const aiApiKeys = yield* selectTyped((_state: RootState) => _state.aiApiKeys); const lcp = yield* selectTyped((state: RootState) => state.lcp); const noteExport = yield* selectTyped((state: RootState) => state.noteExport); @@ -109,6 +110,7 @@ function* winOpen(action: winActions.reader.openSucess.TAction) { publication: { tag, }, + aiApiKeys, lcp, noteExport, }, diff --git a/src/main/redux/states/index.ts b/src/main/redux/states/index.ts index 9e09ca2aaf..1e2dc5d36e 100644 --- a/src/main/redux/states/index.ts +++ b/src/main/redux/states/index.ts @@ -20,6 +20,7 @@ import { IDictWinSessionReaderState } from "./win/session/reader"; import { ICommonRootState } from "readium-desktop/common/redux/states/commonRootState"; import { IWizardState } from "readium-desktop/common/redux/states/wizard"; import { ISettingsState } from "readium-desktop/common/redux/states/settings"; +import { IAiApiKey } from "readium-desktop/common/redux/states/ai_apiKey"; export interface RootState extends ICommonRootState { app: AppState; @@ -49,6 +50,7 @@ export interface RootState extends ICommonRootState { version: string; wizard: IWizardState; settings: ISettingsState; + aiApiKeys: Array; } -export type PersistRootState = Pick; +export type PersistRootState = Pick; diff --git a/src/main/redux/store/memory.ts b/src/main/redux/store/memory.ts index ffffa7ea3d..e848324c39 100644 --- a/src/main/redux/store/memory.ts +++ b/src/main/redux/store/memory.ts @@ -15,7 +15,7 @@ import { reduxSyncMiddleware } from "readium-desktop/main/redux/middleware/sync" import { rootReducer } from "readium-desktop/main/redux/reducers"; import { rootSaga } from "readium-desktop/main/redux/sagas"; import { PersistRootState, RootState } from "readium-desktop/main/redux/states"; -import { IS_DEV } from "readium-desktop/preprocessor-directives"; +import { IS_AI_FEATURE, IS_DEV } from "readium-desktop/preprocessor-directives"; import { tryCatch, tryCatchSync } from "readium-desktop/utils/tryCatch"; import { applyMiddleware, legacy_createStore as createStore, type Store } from "redux"; import createSagaMiddleware, { SagaMiddleware } from "redux-saga"; @@ -30,6 +30,13 @@ import { clone } from "ramda"; import { TBookmarkState } from "readium-desktop/common/redux/states/bookmark"; import { TAnnotationState } from "readium-desktop/common/redux/states/renderer/annotation"; + +if (IS_AI_FEATURE) { + // .env AI API KEY": + // eslint-disable-next-line @typescript-eslint/no-require-imports + require("dotenv/config"); +} + // import { composeWithDevTools } from "remote-redux-devtools"; const REDUX_REMOTE_DEVTOOLS_PORT = 7770; @@ -435,6 +442,33 @@ export async function initStore() preloadedState.reader.defaultConfig = { ...readerConfigInitialState, ...preloadedState.reader.defaultConfig }; } + if (IS_AI_FEATURE) { + + if (preloadedState?.aiApiKeys) { + for (const aiKey of preloadedState.aiApiKeys) { + if (aiKey.provider === "openAI" && aiKey.aiKey) { + process.env["OPENAI_API_KEY"] = aiKey.aiKey; + } else if (aiKey.provider === "mistralAI" && aiKey.aiKey) { + process.env["MISTRAL_API_KEY"] = aiKey.aiKey; + } else if (aiKey.provider === "geminiAI" && aiKey.aiKey) { + process.env["GOOGLE_GENERATIVE_AI_API_KEY"] = aiKey.aiKey; + } + } + } + { + preloadedState.aiApiKeys = []; + if (process.env["OPENAI_API_KEY"]) { + preloadedState.aiApiKeys.push({ provider: "openAI", aiKey: process.env["OPENAI_API_KEY"] }); + } + if (process.env["MISTRAL_API_KEY"]) { + preloadedState.aiApiKeys.push({ provider: "mistralAI", aiKey: process.env["MISTRAL_API_KEY"] }); + } + if (process.env["GOOGLE_GENERATIVE_AI_API_KEY"]) { + preloadedState.aiApiKeys.push({ provider: "geminiAI", aiKey: process.env["GOOGLE_GENERATIVE_AI_API_KEY"] }); + } + } + } + if (preloadedState?.creator && !preloadedState.creator.urn) { preloadedState.creator.urn = `urn:uuid:${preloadedState.creator.id}`; } @@ -472,3 +506,4 @@ export async function initStore() return [store, sagaMiddleware]; } + diff --git a/src/main/streamer/streamerNoHttp.ts b/src/main/streamer/streamerNoHttp.ts index 882dd96628..38ee526765 100644 --- a/src/main/streamer/streamerNoHttp.ts +++ b/src/main/streamer/streamerNoHttp.ts @@ -7,11 +7,11 @@ import * as crypto from "crypto"; import * as debug_ from "debug"; -import { app, protocol, ProtocolRequest, ProtocolResponse, session } from "electron"; +import { app, net, protocol, ProtocolRequest, ProtocolResponse, session } from "electron"; import * as fs from "fs"; import * as mime from "mime-types"; import * as path from "path"; -import { _PACKAGING, IS_DEV } from "readium-desktop/preprocessor-directives"; +import { _PACKAGING, IS_AI_FEATURE, IS_DEV } from "readium-desktop/preprocessor-directives"; import { TaJsonSerialize } from "@r2-lcp-js/serializable"; import { parseDOM, serializeDOM } from "@r2-navigator-js/electron/common/dom"; @@ -51,11 +51,25 @@ import { diMainGet } from "../di"; import { getNotesFromMainWinState } from "../redux/sagas/note"; import { INoteState } from "readium-desktop/common/redux/states/renderer/note"; +import { google } from "@ai-sdk/google"; +import { openai } from "@ai-sdk/openai"; +import { mistral } from "@ai-sdk/mistral"; +import { CoreUserMessage, LanguageModelV1, streamText } from "ai"; +import { Readable } from "stream"; +import { ReadableStream as WebReadableStream } from "node:stream/web"; +// import { aiSDKModelOptions } from "readium-desktop/common/aisdkModelOptions"; + // import { _USE_HTTP_STREAMER } from "readium-desktop/preprocessor-directives"; const debug = debug_("readium-desktop:main#streamerNoHttp"); debug("_"); +const debugAiSdk = debug_("readium-desktop:main#AISDK"); +debug("_"); + +import { nanoid } from "nanoid"; +import { AIProviderFamily } from "readium-desktop/common/AIModels"; + // !!!!!! /// BE CAREFUL DEBUG HAS BEED DISABLED IN package.json // !!!!!! @@ -231,6 +245,9 @@ const streamProtocolHandler = async ( } } + const aiSdkPrefix = "/aisdk/"; + const isAiSdk = uPathname.startsWith(aiSdkPrefix); + const notesFromPublicationPrefix = "/publication-notes/"; const isNotesFromPublicationRequest = uPathname.startsWith(notesFromPublicationPrefix); @@ -250,6 +267,7 @@ const streamProtocolHandler = async ( const isMediaOverlays = uPathname.endsWith(mediaOverlaysPrefix); debug("streamProtocolHandler uPathname", uPathname); + debug("streamProtocolHandler isAiSdk", isAiSdk); debug("streamProtocolHandler isPdfjsAssets", isPdfjsAssets); debug("streamProtocolHandler isPublicationAssets", isPublicationAssets); debug("streamProtocolHandler isMathJax", isMathJax); @@ -293,7 +311,105 @@ const streamProtocolHandler = async ( headers["Access-Control-Allow-Headers"] = "Content-Type, Content-Length, Accept-Ranges, Content-Range, Range, Link, Transfer-Encoding, X-Requested-With, Authorization, Accept, Origin, User-Agent, DNT, Cache-Control, Keep-Alive, If-Modified-Since"; headers["Access-Control-Expose-Headers"] = "Content-Type, Content-Length, Accept-Ranges, Content-Range, Range, Link, Transfer-Encoding, X-Requested-With, Authorization, Accept, Origin, User-Agent, DNT, Cache-Control, Keep-Alive, If-Modified-Since"; - if (isNotesFromPublicationRequest) { + if (IS_AI_FEATURE && isAiSdk) { + + // use specific debug instance + debugAiSdk("AISDK request !!!"); + + const body = JSON.parse(req.uploadData[0].bytes.toString()); + debugAiSdk("AISDK JSON BODY", JSON.stringify(body, null, 4)); + + const { messages, imageHref, modelId, modelFamily, systemPrompt } = body; + + let mimeType: string | undefined; + try { + const hrefToURL = new URL(imageHref); + const ext = path.extname(hrefToURL.pathname); + mimeType = findMimeTypeWithExtension(ext); + } catch { + // ignore + } + + // TODO: resize img if over 1024x1024 ratio preserved + let imageBuffer: Uint8Array | undefined; + try { + + const result = await net.fetch(imageHref); + if (!result.ok) throw new Error("NotOK!!"); + imageBuffer = await result.bytes(); + + } catch (e) { + debugAiSdk("cannot fetch the img", e); + } + + let readStream: NodeJS.ReadableStream | string = ""; + + let model: LanguageModelV1 | undefined; + const modelFamily_ = modelFamily as AIProviderFamily; + if (modelFamily_ === "openAI") { + model = openai(modelId); + } else if (modelFamily_ === "mistralAI") { + model = mistral(modelId); + } else if (modelFamily_ === "geminiAI") { + model = google(modelId, { + useSearchGrounding: true, + }) as LanguageModelV1; + } else { + debug("AI SDK ERROR : No model found with ", modelFamily, modelId); + } + + try { + const result = streamText({ + model, + system: systemPrompt || "Your goal is to describe the image, you should not answer on a topic other than this image", + messages: [ + { + role: "user", + content: [{ type: "image", image: imageBuffer, mimeType }], + + } as CoreUserMessage, + ...messages, + ], + onError: ({ error }) => { + debugAiSdk("AISDK streamText ERROR", error); + }, + onFinish: ({ text, finishReason, usage, response }) => { + debugAiSdk("AISDK streamText FINISH"); + debugAiSdk("AISDK text", text); + debugAiSdk("AISDK finishReason", finishReason); + debugAiSdk("AISDK usage", usage); + debugAiSdk("AISDK response", response); + }, + experimental_generateMessageId: () => + modelId + "-?-" + nanoid(), + }); + + const { warnings, usage, sources, finishReason, providerMetadata, text, reasoning, toolCalls, toolResults, steps, request, response } = result; + const promises = [warnings, usage, sources, finishReason, providerMetadata, text, reasoning, toolCalls, toolResults, steps, request, response]; + + promises.map((p, i) => { + p.then((v) => debugAiSdk("AISDK PROMISES SUCESS", JSON.stringify(v, null, 4), "INDEX", i)); + p.catch((e) => debugAiSdk("AISDK PROMISES ERROR", e, "INDEX", i)); + }); + + const dataStream = result.toDataStream(); + readStream = Readable.fromWeb(dataStream as unknown as WebReadableStream); + } catch (e) { + debugAiSdk("ERROR: OPENAI stream text", e); + } + + + const obj: { data: NodeJS.ReadableStream | string, headers: Record, statusCode: number } = { + // NodeJS.ReadableStream + data: readStream || "", + headers, + statusCode: 200, + }; + + callback(obj); + return ; + + } else if (isNotesFromPublicationRequest) { const publicationUUID = uPathname.substr(notesFromPublicationPrefix.length); diff --git a/src/preprocessor-directives.ts b/src/preprocessor-directives.ts index 81fc67ddf8..3ebc57866e 100644 --- a/src/preprocessor-directives.ts +++ b/src/preprocessor-directives.ts @@ -76,3 +76,7 @@ export const OPEN_DEV_TOOLS = IS_DEV && process.env.THORIUM_OPEN_DEVTOOLS === "1 declare const __CONTINUOUS_INTEGRATION_DEPLOY__: boolean; export const _CONTINUOUS_INTEGRATION_DEPLOY = __CONTINUOUS_INTEGRATION_DEPLOY__; + +declare const __AI_FEATURE__: boolean; +export const _AI_FEATURE = __AI_FEATURE__; +export const IS_AI_FEATURE = IS_DEV && _AI_FEATURE; diff --git a/src/renderer/assets/icons/ai-icon.svg b/src/renderer/assets/icons/ai-icon.svg new file mode 100644 index 0000000000..9bf906bdca --- /dev/null +++ b/src/renderer/assets/icons/ai-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/assets/icons/eye-icon.svg b/src/renderer/assets/icons/eye-icon.svg new file mode 100644 index 0000000000..d9aec83afb --- /dev/null +++ b/src/renderer/assets/icons/eye-icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/renderer/assets/icons/eye-password-hide-icon.svg b/src/renderer/assets/icons/eye-password-hide-icon.svg new file mode 100644 index 0000000000..2ba3416623 --- /dev/null +++ b/src/renderer/assets/icons/eye-password-hide-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/assets/icons/gemini.svg b/src/renderer/assets/icons/gemini.svg new file mode 100644 index 0000000000..878eb62752 --- /dev/null +++ b/src/renderer/assets/icons/gemini.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/src/renderer/assets/icons/google.svg b/src/renderer/assets/icons/google.svg new file mode 100644 index 0000000000..ff4b82f778 --- /dev/null +++ b/src/renderer/assets/icons/google.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/renderer/assets/icons/mistral-ai-icon.svg b/src/renderer/assets/icons/mistral-ai-icon.svg new file mode 100644 index 0000000000..a250e94fca --- /dev/null +++ b/src/renderer/assets/icons/mistral-ai-icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/renderer/assets/icons/open-ai-icon.svg b/src/renderer/assets/icons/open-ai-icon.svg new file mode 100644 index 0000000000..39f3e5575c --- /dev/null +++ b/src/renderer/assets/icons/open-ai-icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/renderer/assets/icons/send-chat-icon.svg b/src/renderer/assets/icons/send-chat-icon.svg new file mode 100644 index 0000000000..bd9ea8231a --- /dev/null +++ b/src/renderer/assets/icons/send-chat-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/assets/icons/send-icon.svg b/src/renderer/assets/icons/send-icon.svg new file mode 100644 index 0000000000..f57ea6895d --- /dev/null +++ b/src/renderer/assets/icons/send-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/assets/icons/stars-icon.svg b/src/renderer/assets/icons/stars-icon.svg new file mode 100644 index 0000000000..977b6c39a2 --- /dev/null +++ b/src/renderer/assets/icons/stars-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/renderer/assets/icons/wikipedia.svg b/src/renderer/assets/icons/wikipedia.svg new file mode 100644 index 0000000000..a4ea3d6bb3 --- /dev/null +++ b/src/renderer/assets/icons/wikipedia.svg @@ -0,0 +1,804 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/renderer/assets/logos/mistral-ai-icon.png b/src/renderer/assets/logos/mistral-ai-icon.png new file mode 100644 index 0000000000..2a1263f99d Binary files /dev/null and b/src/renderer/assets/logos/mistral-ai-icon.png differ diff --git a/src/renderer/assets/logos/open-ai-icon.png b/src/renderer/assets/logos/open-ai-icon.png new file mode 100644 index 0000000000..a09ba93770 Binary files /dev/null and b/src/renderer/assets/logos/open-ai-icon.png differ diff --git a/src/renderer/assets/styles/chatbot.scss b/src/renderer/assets/styles/chatbot.scss new file mode 100644 index 0000000000..54e38e226a --- /dev/null +++ b/src/renderer/assets/styles/chatbot.scss @@ -0,0 +1,482 @@ +@use './partials/mixin' as mx; + +.CSS_START_chatbot { + display: none; +} + +.chatbot_detail_element { + width: 100%; + flex: 2; + + &_summary { + display: flex; + align-items: center; + gap: 5px; + + h3 { + margin: 5px 0; + } + + svg { + width: 15px; + fill: var(--color-primary); + } + } + + p { + margin: 0; + display: flex; + align-items: center; + justify-content: start; + } +} + +.no_description_text { + flex: 2; + + button { + font-size: 12px; + color: var(--color-blue); + text-decoration: underline; + cursor: pointer; + display: inline; + } +} + +.chatbot_systemPrompt { + &_container { + width: 100%; + margin: 10px 0; + padding: 0 20px; + + summary { + color: var(--color-blue); + } + + textarea { + width: 100%; + min-height: 100px; + padding: 10px 0; + border-radius: 8px; + border: 1px solid #ccc; + resize: vertical; + } + } +} + +.chatbot_open_title { + width: 100%; + height: 30px; +} + +.chatbot_open_title button, +.chatbot_user_form .button_nav_primary { + background-color: var(--color-light-blue); + color: var(--color-blue); + display: flex; + justify-content: end; + z-index: 2; + transition: 200ms; + height: fit-content; + position: absolute; + bottom: 10px; + right: 10px; + width: fit-content; + align-items: center; + padding: 5px 10px; + gap: 5px !important; + border-radius: 6px; + border: 1px solid var(--color-blue); + + p { + margin: 0; + } + + svg { + width: 15px; + height: 15px; + color: var(--color-blue); + fill: var(--color-blue); + } + + &:hover { + background-color: var(--color-light-blue); + color: var(--color-blue); + + svg { + color: var(--color-blue); + } + + button { + color: var(--color-blue); + } + } +} + +.chatbot_content { + display: flex; + flex-direction: column; + padding: 5px 10px; + align-items: center; + flex: 1; +} + +.chatbot_user_form { + &_input_container { + display: flex; + gap: 10px; + align-items: center; + width: 100%; + } + + &_conversation_container { + width: 100%; + display: flex; + align-items: center; + justify-content: end; + flex-direction: row; + } + + .button_nav_primary { + position: relative; + margin-left: 5px; + bottom: unset; + } +} + +.chatbot_title { + color: var(--color-primary); + display: flex; + align-items: center; + justify-content: start; + z-index: 2; + transition: 200ms; + height: fit-content; + padding: 0 10px; + width: calc(100% - 30px); + border-bottom: 1px solid var(--color-verylight-grey); + + h2 { + margin: 0; + // padding-bottom: 2px; + } + + button { + color: var(--color-primary); + } + + .react_aria_ComboBox { + margin: 0 !important; + padding: 0; + padding-bottom: 10px !important; + // max-width: 350px; + // min-width: 180px; + width: 500px; + min-width: 500px; + height: 30px; + max-height: 30px; + + .my_combobox_container { + padding-left: 10px !important; + border: none !important; + width: 500px; + + button { + font-size: 20px; + width: 500px; + color: var(--color-blue); + } + + svg { + background-color: unset !important; + margin-left: 5px; + color: var(--color-blue); + fill: var(--color-blue); + } + } + } + + svg { + width: 25px; + height: 25px; + color: var(--color-primary); + fill: var(--color-primary); + } +} + +.chatbot_container { + border-radius: 6px; + display: flex; + flex-direction: column; + margin: 0; + height: 100%; + position: relative; + overflow: hidden; + + .chatbot_messages_container { + min-height: calc(100% - 100px); + max-height: 50dvh; + overflow-y: scroll; + display: flex; + flex-direction: column; + + @include mx.scrollbar_styling; + + .chatbot_message { + border-radius: 6px; + width: fit-content; + padding: 4px 6px; + margin: 10px 2px; + max-width: 90%; + + p { + margin: 0; + font-size: 14px; + } + + &_speaker { + font-weight: bold; + } + + &_content { + flex: 1; + overflow: visible; + overflow-y: hidden; + padding: 5px; + } + } + + .chatbot_user_message { + background-color: var(--color-extralight-grey); + color: var(--color-primary); + align-self: end; + border-radius: 6px 6px 0; + font-style: italic; + } + + .chatbot_ai_message { + border-radius: 6px; + max-width: 100%; + display: flex; + align-items: start; + flex-direction: column; + user-select: text; + + p { + text-align: start; + } + } + } + + .chatbot_user_form, + .chatbot_error, + .chatbot_loading { + background-color: var(--color-secondary); + border-radius: 0 0 6px 6px; + display: flex; + flex-direction: column; + gap: 10px; + align-items: end; + justify-content: space-around; + padding: 0 15px 0 6px; + + .chatbot_user_input { + border: 1px solid var(--color-light-grey); + background-color: var(--color-secondary); + margin: auto; + width: 100%; + padding: 0; + padding-left: 5px; + height: 30px; + border-radius: 6px; + + &::placeholder { + font-style: italic; + } + } + + &_button { + background-color: var(--color-blue); + color: var(--color-secondary); + fill: var(--color-secondary); + width: fit-content; + height: 30px; + border-radius: 6px; + padding: 5px; + + svg { + width: 15px; + } + + p { + margin: 0; + } + } + } + + .chatbot_error, + .chatbot_loading { + justify-content: start; + + > button { + margin-right: 10px; + } + } +} + +.provider_logo { + display: flex; + align-items: center; + justify-content: center; + padding: 2px; + + &.openai, + &.mistral { + width: 20px; + height: 20px; + } +} + +.modelPicker { + display: flex; + background-color: var(--color-button-hover); + padding: 2px; + gap: 1px; + border: 1px solid var(--color-dark-grey); + border-radius: 6px; + + &:has(input:focus) { + @include mx.R2_MIXIN_FOCUS_OUTLINE; + } + + div { + input { + position: absolute; + z-index: -1; + } + + label { + z-index: 20; + width: 25px; + height: 25px; + margin: 0; + border: 1px solid var(--color-secondary); + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + cursor: pointer; + } + } + + svg { + color: #4d4d4d; + } +} + +.chatbot_image_controls { + position: absolute; + align-items: center; + justify-content: center; + z-index: 105; + transform: translate(-50%, 50%); + display: flex; + opacity: 0; + transition: 200ms; + gap: 10px; + + button { + border-radius: 6px; + background-color: var(--color-light-blue); + padding: 0 5px; + color: var(--color-blue); + fill: var(--color-blue); + border: 1px solid var(--color-blue); + filter: opacity(0.8); + transition: 200ms; + width: 24px; + height: 24px; + + &:hover { + filter: opacity(1); + } + } + + svg { + width: 15px; + height: 15px; + fill: var(--color-blue); + } +} + +.chatbot_modal_content { + display: flex; + flex-direction: column; + height: 70vh; + flex: 2; + width: 100%; + justify-content: end; + + #aichat-scroller { + display: flex; + flex-direction: column; + align-items: center; + } +} + +.image_container { + min-height: inherit; + position: relative; + display: flex; + gap: 10px; + width: 100%; + padding-left: 5px; + + .image_display_button { + width: 25px; + height: 25px; + position: absolute; + bottom: 5px; + right: 5px; + z-index: 10; + fill: var(--color-blue); + border-radius: 50%; + padding: 5px 3px; + margin-left: 5px; + } + + > div:first-of-type > div:first-of-type { + min-height: inherit; + align-items: center; + } + + &:has(> div:first-of-type > div:first-of-type:hover), + &:has(.chatbot_image_controls:hover) { + .chatbot_image_controls { + opacity: 1; + } + } +} + +.chatbot_description_button { + background-color: var(--color-secondary); + color: var(--color-blue); + border: 1px solid var(--color-blue); + display: flex; + align-items: center; + justify-content: center; + z-index: 2; + transition: 200ms; + height: fit-content; + padding: 10px; + border-radius: 20px; + + &:hover { + background-color: var(--color-blue); + color: var(--color-secondary); + } +} + +.CSS_END_chatbot { + display: none; + } diff --git a/src/renderer/assets/styles/chatbot.scss.d.ts b/src/renderer/assets/styles/chatbot.scss.d.ts new file mode 100644 index 0000000000..d7b6ec84c8 --- /dev/null +++ b/src/renderer/assets/styles/chatbot.scss.d.ts @@ -0,0 +1,37 @@ +export declare const button_nav_primary: string; +export declare const chatbot_ai_message: string; +export declare const chatbot_container: string; +export declare const chatbot_content: string; +export declare const chatbot_description_button: string; +export declare const chatbot_detail_element: string; +export declare const chatbot_detail_element_summary: string; +export declare const chatbot_error: string; +export declare const chatbot_error_button: string; +export declare const chatbot_image_controls: string; +export declare const chatbot_loading: string; +export declare const chatbot_loading_button: string; +export declare const chatbot_message: string; +export declare const chatbot_message_content: string; +export declare const chatbot_message_speaker: string; +export declare const chatbot_messages_container: string; +export declare const chatbot_modal_content: string; +export declare const chatbot_open_title: string; +export declare const chatbot_systemPrompt_container: string; +export declare const chatbot_title: string; +export declare const chatbot_user_form: string; +export declare const chatbot_user_form_button: string; +export declare const chatbot_user_form_conversation_container: string; +export declare const chatbot_user_form_input_container: string; +export declare const chatbot_user_input: string; +export declare const chatbot_user_message: string; +export declare const CSS_END_chatbot: string; +export declare const CSS_START_chatbot: string; +export declare const image_container: string; +export declare const image_display_button: string; +export declare const mistral: string; +export declare const modelPicker: string; +export declare const my_combobox_container: string; +export declare const no_description_text: string; +export declare const openai: string; +export declare const provider_logo: string; +export declare const react_aria_ComboBox: string; diff --git a/src/renderer/assets/styles/components/settings.scss b/src/renderer/assets/styles/components/settings.scss index 2f49846f2e..133a2dc814 100644 --- a/src/renderer/assets/styles/components/settings.scss +++ b/src/renderer/assets/styles/components/settings.scss @@ -923,6 +923,88 @@ div[data-radix-popper-content-wrapper] { } } +.apiKey { + &_container { + background-color: var(--color-extralight-grey); + padding: 5px; + position: relative; + margin-bottom: 10px; + + > div { + margin: 0; + display: flex; + align-items: center; + + h4 { + display: flex; + align-items: center; + gap: 5px; + } + } + } + + &_input_submitted { + display: flex; + align-items: center; + + input { + width: 100%!important; + margin-left: 10px; + } + + button { + padding: 0 5px; + } + } + + &_radiogroup { + display: flex; + gap: 10px; + margin-top: 20px; + flex-wrap: wrap; + } + + &_input_edit_container { + display: flex; + align-items: center; + gap: 10px; + + .form_group { + margin-top: 20px; + width: 100%; + display: flex; + + label { + background-color: var(--color-extralight-grey); + top: -15px; + left: 0; + } + + input { + width: 100%; + margin: 0; + padding-left: 10px; + background-color: var(--color-annotations-txt-area); + color: var(--color-primary); + + &::placeholder { + font-style: italic; + } + } + + button { + padding: 2px; + } + } + + button { + width: 15px; + height: 15px; + margin-top: 20px; + } + } +} + .CSS_END_components_settings { display: none; } diff --git a/src/renderer/assets/styles/components/settings.scss.d.ts b/src/renderer/assets/styles/components/settings.scss.d.ts index ade043b1ff..6f6513ff19 100644 --- a/src/renderer/assets/styles/components/settings.scss.d.ts +++ b/src/renderer/assets/styles/components/settings.scss.d.ts @@ -1,5 +1,10 @@ export declare const advanced_trigger: string; export declare const allowCustom: string; +export declare const apiKey_container: string; +export declare const apiKey_input_edit_container: string; +export declare const apiKey_input_submitted: string; +export declare const apiKey_radiogroup: string; +export declare const apiKey_remove_button: string; export declare const blink: string; export declare const btn_primary: string; export declare const button_transparency_icon: string; diff --git a/src/renderer/assets/styles/github-markdown.scss b/src/renderer/assets/styles/github-markdown.scss index cf9483b70d..552c256ce1 100644 --- a/src/renderer/assets/styles/github-markdown.scss +++ b/src/renderer/assets/styles/github-markdown.scss @@ -601,7 +601,7 @@ $markdown-color-palette: ( .markdown-body details { margin-top: 0; margin-bottom: var(--base-size-16); - font-size: 12px; + font-size: 14px; } .markdown-body blockquote>:first-child { diff --git a/src/renderer/common/components/Loader.tsx b/src/renderer/common/components/Loader.tsx index e41136ca68..b6361de3d3 100644 --- a/src/renderer/common/components/Loader.tsx +++ b/src/renderer/common/components/Loader.tsx @@ -12,7 +12,7 @@ import * as LoaderIcon from "readium-desktop/renderer/assets/icons/loader.svg"; import SVG from "./SVG"; -export default class Loader extends React.Component<{}, undefined> { +export default class Loader extends React.Component<{ svgStyle?: React.CSSProperties | undefined }, undefined> { constructor(props: {}) { super(props); @@ -21,7 +21,7 @@ export default class Loader extends React.Component<{}, undefined> { public render(): React.ReactElement<{}> { return (
- +
); } diff --git a/src/renderer/common/components/SVG.tsx b/src/renderer/common/components/SVG.tsx index 4b88ae1c8f..051da27951 100644 --- a/src/renderer/common/components/SVG.tsx +++ b/src/renderer/common/components/SVG.tsx @@ -17,6 +17,7 @@ interface IBaseProps { title?: string; className?: string; ariaHidden?: boolean; + style?: React.CSSProperties; } // IProps may typically extend: @@ -34,9 +35,9 @@ export default class SVG extends React.Component { } public render(): React.ReactElement<{}> { - const { svg, className, ariaHidden } = this.props; + const { svg, className, ariaHidden, style } = this.props; return ( - + { this.props.title && {this.props.title} } diff --git a/src/renderer/library/components/settings/AiKeyManager.tsx b/src/renderer/library/components/settings/AiKeyManager.tsx new file mode 100644 index 0000000000..9af3833519 --- /dev/null +++ b/src/renderer/library/components/settings/AiKeyManager.tsx @@ -0,0 +1,139 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import * as React from "react"; +import SVG from "readium-desktop/renderer/common/components/SVG"; +import classNames from "classnames"; +import debounce from "debounce"; + +import * as stylesSettings from "readium-desktop/renderer/assets/styles/components/settings.scss"; +import * as stylesChatbot from "readium-desktop/renderer/assets/styles/chatbot.scss"; +import * as stylesInput from "readium-desktop/renderer/assets/styles/components/inputs.scss"; + +import { useTranslator } from "readium-desktop/renderer/common/hooks/useTranslator"; +import { useSelector } from "readium-desktop/renderer/common/hooks/useSelector"; +import { useDispatch } from "readium-desktop/renderer/common/hooks/useDispatch"; +import { apiKeysActions } from "readium-desktop/common/redux/actions"; +import { ICommonRootState } from "readium-desktop/common/redux/states/commonRootState"; + +// import * as EyeOpenIcon from "readium-desktop/renderer/assets/icons/eye-icon.svg"; +// import * as EyeClosedIcon from "readium-desktop/renderer/assets/icons/eye-password-hide-icon.svg"; +import * as InfoIcon from "readium-desktop/renderer/assets/icons/info-icon.svg"; +import * as OpenAiIcon from "readium-desktop/renderer/assets/icons/open-ai-icon.svg"; +import * as LinkIcon from "readium-desktop/renderer/assets/icons/link-icon.svg"; +import * as MistralAiIcon from "readium-desktop/renderer/assets/icons/mistral-ai-icon.svg"; +import * as GeminiIcon from "readium-desktop/renderer/assets/icons/gemini.svg"; +import { AIProviderFamily } from "readium-desktop/common/AIModels"; + +const AiKeyCard = ({ provider }: { provider: AIProviderFamily }) => { + const [__] = useTranslator(); + const dispatch = useDispatch(); + + const apiKeys = useSelector((state: ICommonRootState) => state.aiApiKeys); + const apiKey = apiKeys.find(v => v.provider === provider)?.aiKey || ""; + const inputRef = React.useRef(null); + const [isFocused, setIsFocused] = React.useState(false); + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + inputRef.current.blur(); + } + }; + + const setKey = React.useMemo(() => + debounce((aiKey, provider) => dispatch(apiKeysActions.setKey.build(aiKey, provider)), 200), + [dispatch], + ); + + const openAiLink = "https://platform.openai.com/api-keys"; + const mistralAiLink = "https://console.mistral.ai/api-keys"; + const geminiAiLink = "https://aistudio.google.com/apikey"; + const openAiPlaceholder = "ex: sk-0000000000000000000000000000000000000000000000"; + const mistralPLaceholder = "ex: aBCdef1234567890Abcdef1234567890abcDEf1234567890"; + const geminiPlaceholder = "ex: AI..."; + + + return ( +
+
+ {provider === "openAI" ? ( +

+ + {__("settings.apiKey.openAi")} +

+ ) : provider === "mistralAI" ? ( +

+ + {__("settings.apiKey.mistral")} +

+ ) :

+ + {"gemini"} +

+ } +
+
+
    +
  • + {__("settings.apiKey.howTo1", { provider: provider })} + + + +
  • +
  • {__("settings.apiKey.howTo2", { provider: provider })}
  • +
  • {__("settings.apiKey.howTo3", { provider: provider })}
  • +
+
+
e.preventDefault()}> +
+ setKey(e.target.value, provider)} + placeholder={ + provider === "openAI" ? openAiPlaceholder : + provider === "mistralAI" ? mistralPLaceholder : + geminiPlaceholder + } + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + onKeyDown={handleKeyDown} + ref={inputRef} + style={{ transition: "500ms", backgroundColor: isFocused || !apiKey ? "var(--color-annotations-txt-area)" : "inherit", borderBottom: isFocused || !apiKey ? "1px solid transparent" : "1px solid var(--color-light-grey)" }} + /> + +
+
+
+ ); +}; + +export const ApiKeysList = () => { + const [__] = useTranslator(); + const allProviders: AIProviderFamily[] = ["openAI", "mistralAI", "geminiAI"]; + + return ( +
+

{__("settings.apiKey.title")}

+
+ +

{__("settings.apiKey.help")}

+
+ { + allProviders.map((provider) => { + return ( + + ); + })} +
+ ); +}; diff --git a/src/renderer/library/components/settings/Settings.tsx b/src/renderer/library/components/settings/Settings.tsx index 7f3c942853..d55fc6b894 100644 --- a/src/renderer/library/components/settings/Settings.tsx +++ b/src/renderer/library/components/settings/Settings.tsx @@ -19,6 +19,7 @@ import * as QuitIcon from "readium-desktop/renderer/assets/icons/close-icon.svg" import * as CogIcon from "readium-desktop/renderer/assets/icons/cog-icon.svg"; import * as PaletteIcon from "readium-desktop/renderer/assets/icons/palette-icon.svg"; import * as KeyReturnIcon from "readium-desktop/renderer/assets/icons/keyreturn-icon.svg"; +import * as AiIcon from "readium-desktop/renderer/assets/icons/ai-icon.svg"; import SVG, { ISVGProps } from "readium-desktop/renderer/common/components/SVG"; import classNames from "classnames"; import { useTranslator } from "readium-desktop/renderer/common/hooks/useTranslator"; @@ -46,6 +47,9 @@ import * as RadioGroup from "@radix-ui/react-radio-group"; import { TextArea } from "react-aria-components"; import { noteExportHtmlMustacheTemplate } from "readium-desktop/common/readium/annotation/htmlTemplate"; // import { TagGroup, TagList, Tag, Label } from "react-aria-components"; +import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; +import { ApiKeysList } from "./AiKeyManager"; +import { IS_AI_FEATURE } from "readium-desktop/preprocessor-directives"; interface ISettingsProps {}; @@ -425,6 +429,11 @@ export const Settings: React.FC = () => {
+ + + {__("header.settings")} + + @@ -439,8 +448,16 @@ export const Settings: React.FC = () => {

{__("settings.tabs.keyboardShortcuts")}

+ {IS_AI_FEATURE + ? + + +

{__("settings.tabs.aiKeyManager")}

+
+ : <> + }
-
+
@@ -466,6 +483,16 @@ export const Settings: React.FC = () => {
+ {IS_AI_FEATURE ? + + + +
+ +
+
+ : <> + }
diff --git a/src/renderer/library/index_library.ts b/src/renderer/library/index_library.ts index 118c891446..e9367dfe48 100644 --- a/src/renderer/library/index_library.ts +++ b/src/renderer/library/index_library.ts @@ -80,6 +80,7 @@ ipcRenderer.on(winIpc.CHANNEL, (_0: any, data: winIpc.EventPayload) => { publication: data.payload.publication, session: data.payload.session, creator: data.payload.creator, + aiApiKeys: data.payload.aiApiKeys, lcp: data.payload.lcp, noteExport: data.payload.noteExport, }; diff --git a/src/renderer/library/redux/middleware/sync.ts b/src/renderer/library/redux/middleware/sync.ts index 58647f3608..eb12135107 100644 --- a/src/renderer/library/redux/middleware/sync.ts +++ b/src/renderer/library/redux/middleware/sync.ts @@ -9,7 +9,7 @@ import { apiActions, authActions, catalogActions, downloadActions, i18nActions, keyboardActions, lcpActions, readerActions, sessionActions, themeActions, publicationActions, wizardActions, annotationActions, creatorActions, - settingsActions, + settingsActions, apiKeysActions, noteExport, } from "readium-desktop/common/redux/actions"; import { syncFactory } from "readium-desktop/renderer/common/redux/middleware/syncFactory"; @@ -66,6 +66,7 @@ const SYNCHRONIZABLE_ACTIONS: string[] = [ settingsActions.enableAPIAPP.ID, + apiKeysActions.setKey.ID, noteExport.overrideHTMLTemplate.ID, ]; diff --git a/src/renderer/library/redux/reducers/index.ts b/src/renderer/library/redux/reducers/index.ts index 8c99a178f8..d86516a3d6 100644 --- a/src/renderer/library/redux/reducers/index.ts +++ b/src/renderer/library/redux/reducers/index.ts @@ -5,7 +5,7 @@ // that can be found in the LICENSE file exposed on Github (readium) in the project repository. // ==LICENSE-END== -import { downloadActions } from "readium-desktop/common/redux/actions"; +import { apiKeysActions, downloadActions } from "readium-desktop/common/redux/actions"; import { dialogReducer } from "readium-desktop/common/redux/reducers/dialog"; import { i18nReducer } from "readium-desktop/common/redux/reducers/i18n"; import { keyboardReducer } from "readium-desktop/common/redux/reducers/keyboard"; @@ -37,6 +37,8 @@ import { creatorReducer } from "readium-desktop/common/redux/reducers/creator"; import { settingsReducer } from "readium-desktop/common/redux/reducers/settings"; import { importAnnotationReducer } from "readium-desktop/renderer/common/redux/reducers/importAnnotation"; import { lcpReducer } from "readium-desktop/common/redux/reducers/lcp"; +import { arrayReducer } from "readium-desktop/utils/redux-reducers/array.reducer"; +import { IAiApiKey } from "readium-desktop/common/redux/states/ai_apiKey"; import { noteExportReducer } from "readium-desktop/common/redux/reducers/noteExport"; export const rootReducer = (routerReducer: Reducer) => { // : Reducer> @@ -94,6 +96,18 @@ export const rootReducer = (routerReducer: Reducer) => { // : Reduc creator: creatorReducer, settings: settingsReducer, importAnnotations: importAnnotationReducer, + aiApiKeys: arrayReducer>( + { + add: + { + type: apiKeysActions.setKey.ID, + selector: (payload) => { + return [payload.aiKey]; + }, + }, + getId: (item) => item.provider, // Ajoutez la fonction getId ici + }, + ), lcp: lcpReducer, noteExport: noteExportReducer, }); diff --git a/src/renderer/reader/components/App.tsx b/src/renderer/reader/components/App.tsx index c6feb9d022..5615cd33d9 100644 --- a/src/renderer/reader/components/App.tsx +++ b/src/renderer/reader/components/App.tsx @@ -18,6 +18,7 @@ import * as React from "react"; import { Provider } from "react-redux"; import { _NODE_MODULE_RELATIVE_URL, _PACKAGING, _RENDERER_READER_BASE_URL, + IS_AI_FEATURE, } from "readium-desktop/preprocessor-directives"; import ToastManager from "readium-desktop/renderer/common/components/toast/ToastManager"; @@ -31,7 +32,9 @@ import Reader from "./Reader"; import { getTranslator } from "readium-desktop/common/services/translator"; import { getStore } from "../createStore"; import { TranslatorContext } from "readium-desktop/renderer/common/translator.context"; +import { ImageClickManager } from "./ImageClickManagerWithAI"; import { ImageClickManagerImgViewerOnly } from "./ImageClickManagerViewerOnly"; +// import { ImageClickManagerImgViewerOnly } from "./ImageClickManagerViewerOnly"; export default class App extends React.Component<{}, undefined> { @@ -238,7 +241,11 @@ url("${rcssPath}/fonts/iAWriterDuospace-Regular.ttf") format("truetype"); - + { + IS_AI_FEATURE + ? + : + } ); diff --git a/src/renderer/reader/components/ImageClickManagerWithAI.tsx b/src/renderer/reader/components/ImageClickManagerWithAI.tsx new file mode 100644 index 0000000000..a9aa87d1a9 --- /dev/null +++ b/src/renderer/reader/components/ImageClickManagerWithAI.tsx @@ -0,0 +1,688 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import * as React from "react"; +import * as Dialog from "@radix-ui/react-dialog"; +import { IReaderRootState } from "readium-desktop/common/redux/states/renderer/readerRootState"; +import { useSelector } from "readium-desktop/renderer/common/hooks/useSelector"; +import { useDispatch } from "readium-desktop/renderer/common/hooks/useDispatch"; + +import { readerLocalActionSetImageClick } from "../redux/actions"; +import { TransformWrapper, TransformComponent, useControls } from "react-zoom-pan-pinch"; +import { useTranslator } from "readium-desktop/renderer/common/hooks/useTranslator"; +import { useChat } from "@ai-sdk/react"; +import { type UIMessage } from "@ai-sdk/ui-utils"; +import { THORIUM_READIUM2_ELECTRON_HTTP_PROTOCOL } from "readium-desktop/common/streamerProtocol"; +// import { nanoid } from "nanoid"; +// import { Attachment } from "ai"; +import Loader from "readium-desktop/renderer/common/components/Loader"; +import { marked } from "marked"; +import DOMPurify from "dompurify"; +import { Select, SelectItem } from "readium-desktop/renderer/common/components/Select"; +import { AIModels, IAIModels } from "readium-desktop/common/AIModels"; +import { convertMultiLangStringToLangString } from "readium-desktop/common/language-string"; + +import classNames from "classnames"; +import * as stylesModals from "readium-desktop/renderer/assets/styles/components/modals.scss"; +import * as stylesButtons from "readium-desktop/renderer/assets/styles/components/buttons.scss"; +import * as stylesChatbot from "readium-desktop/renderer/assets/styles/chatbot.scss"; + +import SVG from "readium-desktop/renderer/common/components/SVG"; +import * as QuitIcon from "readium-desktop/renderer/assets/icons/baseline-close-24px.svg"; +import * as sendIcon from "readium-desktop/renderer/assets/icons/send-chat-icon.svg"; +import * as AiIcon from "readium-desktop/renderer/assets/icons/ai-icon.svg"; +import * as ChevronRight from "readium-desktop/renderer/assets/icons/baseline-arrow_forward_ios-24px.svg"; +import * as BackIcon from "readium-desktop/renderer/assets/icons/arrow-right.svg"; +import * as ResetIcon from "readium-desktop/renderer/assets/icons/backward-icon.svg"; +import * as PlusIcon from "readium-desktop/renderer/assets/icons/add-alone.svg"; +import * as MinusIcon from "readium-desktop/renderer/assets/icons/Minus-Bold.svg"; +import * as OpenAiIcon from "readium-desktop/renderer/assets/icons/open-ai-icon.svg"; +import * as MistralAiIcon from "readium-desktop/renderer/assets/icons/mistral-ai-icon.svg"; +import * as GeminiIcon from "readium-desktop/renderer/assets/icons/gemini.svg"; +import * as GoogleIcon from "readium-desktop/renderer/assets/icons/google.svg"; +import * as WikipediaIcon from "readium-desktop/renderer/assets/icons/wikipedia.svg"; +import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; +import { shell } from "electron"; + +interface ControlsProps { + chatEnabled: boolean; +} + +const onePixelImageDataURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="; + +// https://github.com/huggingface/transformers.js-examples/blob/5b6e0c18677e3e22ef42779a766f48e2ed0a4b18/smolvlm-webgpu/src/components/Chat.jsx#L10 +function render(text: string) { + // Replace all instances of single backslashes before brackets with double backslashes + // See https://github.com/markedjs/marked/issues/546 for more information. + text = text.replace(/\\([\[\]\(\)])/g, "\\\\$1"); + text = text.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/, ""); + + const parsed = DOMPurify.sanitize( + marked.parse(text, { + async: false, + breaks: true, + }), { FORBID_TAGS: ["style"], FORBID_ATTR: ["style"] }); + + const regex = new RegExp(/href=\"(.*?)\"/, "gm"); + const hrefSanitized = parsed.replace(regex, (_substring, url) => { + + if (!url?.startsWith("http")) { + url = "http://" + url; + } + + return `href="" alt="${url}" onclick="return ((e) => { + window.__shell_openExternal('${url}').catch(() => {}); + return false; + })()"`; + }); + return hrefSanitized; +} + +// async function getBase64ImageFromUrl(imageUrl: string): Promise { +// const response = await fetch(imageUrl); +// const type = response.headers.get("content-type"); +// const bytes = await response.bytes(); + +// const binaryString = bytes.reduce((data, byte) => data + String.fromCharCode(byte), ""); +// const base64String = btoa(binaryString); +// const dataUrl = `data:${type};base64,${base64String}`; + +// return dataUrl; +// } + +const Controls: React.FC = ({ chatEnabled }) => { + const { zoomIn, zoomOut, resetTransform } = useControls(); + console.log(chatEnabled); + + return ( +
+ + + +
+ ); +}; + +// const SelectRef = React.forwardRef>((props, forwardedRef) => ); +// SelectRef.displayName = "ComboBox"; + +let __messages: UIMessage[] = []; + +let selectionTextContent = ""; + +interface ChatProps { + modelSelected: IAIModels, + setModel: React.Dispatch>, + systemPrompt: string, + setSystemPrompt: React.Dispatch> + showImage: () => void; + imageHref: string, + autoPrompt: string, + setAutoPrompt: (value: React.SetStateAction) => void, +} + +const Chat = (props: ChatProps) => { + + const { systemPrompt, setSystemPrompt /*showImage*/, modelSelected, imageHref, autoPrompt, setAutoPrompt } = props; + const [__] = useTranslator(); + + // const apiList = useSelector((state: IReaderRootState) => state.aiApiKeys); + // const modelSelected = aiSDKModelOptions.filter(e => apiList.some(item => AiProviderType[item.provider] === e.name.split(" ")[0]))[0]; + + // const handleModelChange = (event: React.ChangeEvent) => { + // const selectedId = event.target.value; + // const found = options.find(({ id }) => id === selectedId); + // if (found) { + // setOption(found); + // } + // }; + + // const image: Attachment = { url: imageHrefDataUrl, contentType: "image/jpeg" }; + const { messages, input, handleInputChange, handleSubmit, error, reload, isLoading, stop, setMessages,setInput } = useChat({ + initialMessages: [ + // { id: nanoid(), role: "user", content: "", experimental_attachments: [image] }, + ...__messages, + ], + api: `${THORIUM_READIUM2_ELECTRON_HTTP_PROTOCOL}://0.0.0.0/aisdk/chat`, + + // TODO : handle user authentication token !? + headers: { + Authorization: "your_token", + }, + + // TODO : pass model identification from frontend + body: { + imageHref, + modelId: modelSelected.modelId, + modelFamily: modelSelected.providerFamily, + systemPrompt: systemPrompt, + }, + credentials: "same-origin", + }); + + React.useEffect(() => { + __messages = [...messages]; + }, [messages]); + + // const r2Publication = useSelector((state: IReaderRootState) => state.reader.info.r2Publication.Metadata); + const quickDescriptionRef = React.useRef(null); + + React.useEffect(() => { + if (autoPrompt) { + setInput(autoPrompt); + } + }, [autoPrompt, setInput]); + + React.useEffect(() => { + if (quickDescriptionRef.current && input === autoPrompt) { + setTimeout(() => { + quickDescriptionRef.current.click(); + }, 0); + setAutoPrompt(""); + } + }, [input, autoPrompt, setAutoPrompt]); + + React.useEffect(() => { + + const unsubscribe = document.onselectionchange = () => { + selectionTextContent = document.getSelection().toString() || selectionTextContent; + }; + + return unsubscribe; + }); + + + return ( +
+
+ {/* */} + +
+
+ {__("chatbot.systemPromptEditor")} + +
+
+ {/* */} +
+
+
+ {messages.map(message => ( +
+
+ {message.role === "user" ? "" : message.id.includes("openai") ? + + : message.id.includes("mistral") ? + + : + } +
+ +
\n\n`, + }} + > + {/* + (reset ? `\n` + + {message.content} */} +
+ {message.role === "assistant" ? +
+ + +
+ : <> + } +
+ ))} + {/*
*/} + + + + +
+
handleSubmit(event, {})} className={stylesChatbot.chatbot_user_form}> +
+
+ {modelSelected.name.startsWith("openAI") ? + + : modelSelected.name.startsWith("mistralAI") ? + + : + } +
+ + +
+
+ {messages.length ? + + : <> + } + {isLoading && ( + <> + + + + )} + {error && ( + <> + +

An error occurred.

+ + )} + +
+
+ {/* } */} +
+
+ ); +}; + +// function useGetDataUrl(href: string): string | undefined { +// const [state, setState] = React.useState(); + +// React.useEffect(() => { +// getBase64ImageFromUrl(href).then(result => { +// setState(result); +// }); +// }, [href]); + +// return state; +// } + +export const ImageClickManager: React.FC = () => { + + const { + open, + isSVGFragment, + HTMLImgSrc_SVGImageHref_SVGFragmentMarkup, + altAttributeOf_HTMLImg_SVGImage_SVGFragment, + titleAttributeOf_HTMLImg_SVGImage_SVGFragment, + ariaLabelAttributeOf_HTMLImg_SVGImage_SVGFragment, + dom_afterText, + dom_beforeText, + dom_describedbyText, + dom_detailsText, + dom_figcaptionText, + dom_labelledByText, + } = useSelector((state: IReaderRootState) => state.img); + + const { documentTitle, authorsLangString, publishersLangString, languages } = useSelector((state: IReaderRootState) => state.reader.info.publicationView); + + const { locale } = useSelector((state: IReaderRootState) => state.i18n); + + // , naturalWidthOf_HTMLImg_SVGImage, naturalHeightOf_HTMLImg_SVGImage + // const scaleX = naturalWidthOf_HTMLImg_SVGImage ? ((window.innerHeight - 50) / naturalWidthOf_HTMLImg_SVGImage) : 1; + // const scaleY = naturalHeightOf_HTMLImg_SVGImage ? ((window.innerWidth - 50) / naturalHeightOf_HTMLImg_SVGImage) : 1; + // let scale = Math.min(scaleX, scaleY); + + const dispatch = useDispatch(); + const [__] = useTranslator(); + const [chatEnabled_, enableChat] = React.useState(false); + // const imageHrefDataUrl = useGetDataUrl(href); + + const chatEnabled = chatEnabled_ && !isSVGFragment; // isSVGImage and otherwise HTML image + + const apiKeysList = useSelector((state: IReaderRootState) => state.aiApiKeys); + const apiKeysFound = apiKeysList.some((item) => !!item.aiKey); + + const selectModelItems = AIModels.filter(e => apiKeysList.some(apiKeysItem => apiKeysItem.provider === e.providerFamily)); + + const defaultModel = selectModelItems[0]; + const [modelSelected, setModel] = React.useState(defaultModel); + const [systemPrompt, setSystemPrompt] = React.useState(defaultModel.systemPrompt); + const previousHref = React.useRef(HTMLImgSrc_SVGImageHref_SVGFragmentMarkup); + + React.useEffect(() => { + if (HTMLImgSrc_SVGImageHref_SVGFragmentMarkup && previousHref.current !== HTMLImgSrc_SVGImageHref_SVGFragmentMarkup) { + __messages = []; + previousHref.current = HTMLImgSrc_SVGImageHref_SVGFragmentMarkup; + } + }, [HTMLImgSrc_SVGImageHref_SVGFragmentMarkup]); + const [showImage, setShowImage] = React.useState(true); + + React.useEffect(() => { + setSystemPrompt(modelSelected.systemPrompt + .replace("{{title}}", documentTitle) + .replace("{{author}}", "\"" + (authorsLangString && authorsLangString.length) ? + authorsLangString.reduce((prev, text) => { + const textLangStr = convertMultiLangStringToLangString(text, locale); + // const textLang = textLangStr && textLangStr[0] ? textLangStr[0].toLowerCase() : ""; + // const textIsRTL = langStringIsRTL(textLang); + const textStr = textLangStr && textLangStr[1] ? textLangStr[1] : ""; + + return prev ? `${prev}, ${textStr}` : textStr; + }, "") + : "" + "\"") + .replace("{{publisher}}", "\"" + (publishersLangString && publishersLangString.length) ? + // publishers.join(", ") + publishersLangString.reduce((prev, text) => { + const textLangStr = convertMultiLangStringToLangString(text, locale); + // const textLang = textLangStr && textLangStr[0] ? textLangStr[0].toLowerCase() : ""; + // const textIsRTL = langStringIsRTL(textLang); + const textStr = textLangStr && textLangStr[1] ? textLangStr[1] : ""; + + return prev ? `${prev}, ${textStr}` : textStr; + }, "") + : "" + "\"") + .replace(/{{languages}}/g, (languages && languages.length) ? + // publishers.join(", ") + languages.reduce((prev, text) => { + const textLangStr = convertMultiLangStringToLangString(text, locale); + // const textLang = textLangStr && textLangStr[0] ? textLangStr[0].toLowerCase() : ""; + // const textIsRTL = langStringIsRTL(textLang); + const textStr = textLangStr && textLangStr[1] ? textLangStr[1] : ""; + + return prev ? `${prev}, ${textStr}` : textStr; + }, "") + : "") + .replace("{{user_language}}", locale) + .replace("{{beforeText}}", dom_beforeText ? dom_beforeText : "") + .replace("{{afterText}}", dom_afterText ? dom_afterText : "") + .replace("{{describedby}}", dom_describedbyText ? dom_describedbyText : "" ) + .replace("{{details}}", dom_detailsText ? dom_detailsText : "") + .replace("{{figcaption}}", dom_figcaptionText ? dom_figcaptionText : "") + .replace("{{labelledby}}", dom_labelledByText ? dom_labelledByText : "") + .replace("{{alt_attr}}", altAttributeOf_HTMLImg_SVGImage_SVGFragment ? altAttributeOf_HTMLImg_SVGImage_SVGFragment : "") + .replace("{{title_attr}}", titleAttributeOf_HTMLImg_SVGImage_SVGFragment ? titleAttributeOf_HTMLImg_SVGImage_SVGFragment : "") + .replace("{{arialabel_attr}}", ariaLabelAttributeOf_HTMLImg_SVGImage_SVGFragment ? ariaLabelAttributeOf_HTMLImg_SVGImage_SVGFragment : ""), + ); + }, [modelSelected.systemPrompt, altAttributeOf_HTMLImg_SVGImage_SVGFragment, titleAttributeOf_HTMLImg_SVGImage_SVGFragment, ariaLabelAttributeOf_HTMLImg_SVGImage_SVGFragment, dom_beforeText, dom_afterText, dom_describedbyText, dom_detailsText, dom_figcaptionText, dom_labelledByText, authorsLangString, documentTitle, locale, publishersLangString, languages]); + + const imageDescription: string[] = []; + if (altAttributeOf_HTMLImg_SVGImage_SVGFragment) { + imageDescription.push(altAttributeOf_HTMLImg_SVGImage_SVGFragment); + } + if (titleAttributeOf_HTMLImg_SVGImage_SVGFragment) { + imageDescription.push(titleAttributeOf_HTMLImg_SVGImage_SVGFragment); + } + if (ariaLabelAttributeOf_HTMLImg_SVGImage_SVGFragment) { + imageDescription.push(ariaLabelAttributeOf_HTMLImg_SVGImage_SVGFragment); + } + if (dom_detailsText) { + imageDescription.push(dom_detailsText); + } + if (dom_figcaptionText) { + imageDescription.push(dom_figcaptionText); + } + if (dom_describedbyText) { + imageDescription.push(dom_describedbyText); + } + if (dom_labelledByText) { + imageDescription.push(dom_labelledByText); + } + // imageDescription.push("sa lhkba sdflkhjb lhabsdv pilybh ;lakshdbv ;ilh;k basdfbkb sdkfb kba sdlvkb lb s".repeat(10)); + + const shortDescription = __("chatbot.shortDescription"); + const longDescription = __("chatbot.detailedDescription"); + + const [detailOpen, setDetailOpen] = React.useState(true); + const [autoPrompt, setAutoPrompt] = React.useState(""); + + return ( + <> + { + if (openState == false) { + dispatch(readerLocalActionSetImageClick.build()); + enableChat(false); + setShowImage(true); + } + }} + > + +
+ + + {__("chatbot.title")} + + +
+ {chatEnabled ? + + : "" + } + + + +
+ {chatEnabled ? +
+

{__("chatbot.title")}

+ +
+ : "" + } +
+
+ {chatEnabled ? + + : "" + } + { /* initialScale={scale} minScale={scale / 2} maxScale={4 * scale} */} + {showImage ? +
+ + + {altAttributeOf_HTMLImg_SVGImage_SVGFragment} + + {(showImage || !chatEnabled) ? + + : "" + } + +
+ : "" + } +
{ + e.preventDefault(); + setDetailOpen(!detailOpen); + }} + > + {showImage ? + <> +
+ { + // chatEnabled ? +

{__("chatbot.editorDescription")}

+ // : <> + } +
+
+ {imageDescription.length ? + imageDescription.map((str, i) =>

{str}

) : +

{__("chatbot.noDescription")}

+ } +
+ + : "" + } + + { + chatEnabled ? + apiKeysFound ? +

+ {__("chatbot.generateDescription")} +   + +  -  + +

+ : +

+ {__("chatbot.noApiKey")} +

+ : + <> + } +
+
+ { + (apiKeysFound && !chatEnabled) + ? +
+ +
+ : <> + } + {chatEnabled ? + enableChat((enabled) => !enabled)} + imageHref={HTMLImgSrc_SVGImageHref_SVGFragmentMarkup} autoPrompt={autoPrompt} setAutoPrompt={setAutoPrompt} + /> + : + <> + } +
+
+
+
+ ); +}; diff --git a/src/renderer/reader/redux/actions/setImgClick.ts b/src/renderer/reader/redux/actions/setImgClick.ts index 4a66f7df2d..12c5240670 100644 --- a/src/renderer/reader/redux/actions/setImgClick.ts +++ b/src/renderer/reader/redux/actions/setImgClick.ts @@ -6,16 +6,15 @@ // ==LICENSE-END== import { IEventPayload_R2_EVENT_IMAGE_CLICK } from "@r2-navigator-js/electron/common/events"; + import { Action } from "readium-desktop/common/models/redux"; +import { IImageClickState, IImageClickStatePlusDomParsing } from "../state/imageClick"; export const ID = "READER_SET_IMAGE_CLICK"; -type a = { open: true } & IEventPayload_R2_EVENT_IMAGE_CLICK; -type b = { open: false } & Partial; -type Payload = a | b; +export function build(payload?: IEventPayload_R2_EVENT_IMAGE_CLICK & Partial): -export function build(payload?: IEventPayload_R2_EVENT_IMAGE_CLICK): - Action { + Action { return { type: ID, diff --git a/src/renderer/reader/redux/middleware/sync.ts b/src/renderer/reader/redux/middleware/sync.ts index b6f5183a52..cfcd1027a4 100644 --- a/src/renderer/reader/redux/middleware/sync.ts +++ b/src/renderer/reader/redux/middleware/sync.ts @@ -7,7 +7,7 @@ import { annotationActions, - apiActions, creatorActions, i18nActions, keyboardActions, lcpActions, noteExport, publicationActions, readerActions, themeActions, + apiActions, creatorActions, i18nActions, keyboardActions, lcpActions, noteExport, publicationActions, readerActions, themeActions, apiKeysActions, } from "readium-desktop/common/redux/actions"; import { syncFactory } from "readium-desktop/renderer/common/redux/middleware/syncFactory"; @@ -61,6 +61,7 @@ const SYNCHRONIZABLE_ACTIONS: string[] = [ creatorActions.set.ID, + apiKeysActions.setKey.ID, noteExport.overrideHTMLTemplate.ID, readerActions.print.ID, diff --git a/src/renderer/reader/redux/reducers/index.ts b/src/renderer/reader/redux/reducers/index.ts index 83066573a1..ab6d76a201 100644 --- a/src/renderer/reader/redux/reducers/index.ts +++ b/src/renderer/reader/redux/reducers/index.ts @@ -35,7 +35,7 @@ import { readerDefaultConfigReducer } from "readium-desktop/common/redux/reducer import { themeReducer } from "readium-desktop/common/redux/reducers/theme"; import { versionUpdateReducer } from "readium-desktop/common/redux/reducers/version-update"; import { annotationModeEnableReducer } from "./annotationModeEnable"; -import { readerActions } from "readium-desktop/common/redux/actions"; +import { apiKeysActions, readerActions } from "readium-desktop/common/redux/actions"; import { readerMediaOverlayReducer } from "./mediaOverlay"; import { readerTTSReducer } from "./tts"; import { readerTransientConfigReducer } from "./readerTransientConfig"; @@ -47,9 +47,11 @@ import { readerLockReducer } from "./lock"; import { imageClickReducer } from "./imageClick"; import { dockReducer } from "readium-desktop/common/redux/reducers/dock"; import { readerBookmarkTotalCountReducer } from "readium-desktop/common/redux/reducers/reader/bookmarkTotalCount"; +// import { apiKeysReducer } from "readium-desktop/common/redux/reducers/api_key"; import { lcpReducer } from "readium-desktop/common/redux/reducers/lcp"; import { arrayReducer } from "readium-desktop/utils/redux-reducers/array.reducer"; import { INoteState } from "readium-desktop/common/redux/states/renderer/note"; +import { IAiApiKey } from "readium-desktop/common/redux/states/ai_apiKey"; import { noteExportReducer } from "readium-desktop/common/redux/reducers/noteExport"; export const rootReducer = () => { @@ -204,6 +206,18 @@ export const rootReducer = () => { publication: combineReducers({ tag: tagReducer, }), + aiApiKeys: arrayReducer>( + { + add: + { + type: apiKeysActions.setKey.ID, + selector: (payload) => { + return [payload.aiKey]; + }, + }, + getId: (item) => item.provider, // Ajoutez la fonction getId ici + }, + ), img: imageClickReducer, lcp: lcpReducer, noteExport: noteExportReducer, diff --git a/src/renderer/reader/redux/sagas/img.ts b/src/renderer/reader/redux/sagas/img.ts index 66eaa16f38..d43e961b5f 100644 --- a/src/renderer/reader/redux/sagas/img.ts +++ b/src/renderer/reader/redux/sagas/img.ts @@ -9,9 +9,12 @@ import * as debug_ from "debug"; import { setImageClickHandler } from "@r2-navigator-js/electron/renderer"; import { takeSpawnEveryChannel } from "readium-desktop/common/redux/sagas/takeSpawnEvery"; import { eventChannel } from "redux-saga"; -import { put as putTyped } from "typed-redux-saga"; +import { put as putTyped, call as callTyped } from "typed-redux-saga"; import { readerLocalActionSetImageClick } from "../actions"; + import { IEventPayload_R2_EVENT_IMAGE_CLICK } from "@r2-navigator-js/electron/common/events"; +import { cleanupStr } from "./search/transliteration"; +import { getResourceCache } from "readium-desktop/common/redux/sagas/resourceCache"; // Logger const filename_ = "readium-desktop:renderer:reader:saga:img"; @@ -19,6 +22,7 @@ const debug = debug_(filename_); debug("_"); export function getWebviewImageClickChannel() { + const channel = eventChannel( (emit) => { @@ -38,11 +42,146 @@ export function getWebviewImageClickChannel() { return channel; } - function* webviewImageClick(payload: IEventPayload_R2_EVENT_IMAGE_CLICK) { debug("IMAGE_CLICK received :", payload); yield* putTyped(readerLocalActionSetImageClick.build(payload)); + + const { hostDocumentURL: source, cssSelectorOf_HTMLImg_SVGImage_SVGFragment } = payload; + + const cacheDoc = yield* callTyped(getResourceCache, source); + const xmlDom = cacheDoc?.xmlDom; + + const rootElem = xmlDom.body; + const imgElem = xmlDom.querySelector(cssSelectorOf_HTMLImg_SVGImage_SVGFragment); + console.log("IMGElem", imgElem); + console.log("ImgElem Attributes", imgElem.attributes); + // const altAttributes = imgElem.getAttribute("alt"); + // const titleAttributes = imgElem.getAttribute("title"); + // const ariaLabelAttributes = imgElem.getAttribute("aria-label"); + const ariaDetailsAttributes = imgElem.getAttribute("aria-details"); + console.log("ariaDetailsAttributes", ariaDetailsAttributes); + const ariaDescribedbyAttributes = imgElem.getAttribute("aria-describedby"); + console.log("ariaDescribedbyAttributes", ariaDescribedbyAttributes); + + + // https://kb.daisy.org/publishing/docs/html/images-desc.html + + // is Surrounded by a Figure tag ? + let figureElem = undefined; + { + let currElem = imgElem; + while (rootElem !== currElem.parentElement) { + if (currElem.nodeName === "FIGURE" || currElem.nodeName === "figure") { + figureElem = currElem; + console.log("FIGURE ELEM", figureElem); + break; + } + currElem = currElem.parentElement; + } + } + + let detailsText = ""; + if (ariaDetailsAttributes) { + const elem = xmlDom.getElementById(ariaDetailsAttributes); + if (elem) { + detailsText = cleanupStr(elem.textContent || ""); + } + } + + let describedbyText = ""; + if (ariaDescribedbyAttributes) { + const elem = xmlDom.getElementById(ariaDescribedbyAttributes); + if (elem) { + describedbyText = cleanupStr(elem.textContent || ""); + } + } + + let labelledByText = ""; + let figcaptionText = ""; + if (figureElem) { + console.log("FigureElement attributes", figureElem.attributes); + const ariaLabelledByAttributes = figureElem.getAttribute("aria-labelledby"); + if (ariaLabelledByAttributes) { + const elem = xmlDom.getElementById(ariaLabelledByAttributes); + if (elem) { + labelledByText = cleanupStr(elem.textContent || ""); + } + } + { + const elem = figureElem.getElementsByTagName("figcaption"); + if (elem) { + figcaptionText = cleanupStr(elem[0]?.textContent || ""); + } + } + } + + const N_CHAR_SIZE = 200; + const CUT = N_CHAR_SIZE + 50; + + let beforeText = ""; + let afterText = ""; + // if (!detailsText && !describedbyText && (!(labelledByText && figcaptionText) || !figcaptionText)) { + + // TODO: start iterator at imgElem and go forward / backward, instead of rootElem and search down + const iter = xmlDom.createNodeIterator(rootElem, NodeFilter.SHOW_ALL, { acceptNode: () => NodeFilter.FILTER_ACCEPT }); + + const resetImgIterator = () => { + let curEl; + do { + curEl = iter.nextNode(); + } while (curEl && imgElem !== curEl); + // if (curEl != imgElem) { + // return; + // } + }; + + resetImgIterator(); + while (beforeText.length < N_CHAR_SIZE) { + + const node = iter.previousNode(); + if (!node) { + break; + } + if (node.nodeType === Node.TEXT_NODE) { + beforeText = cleanupStr(node.nodeValue) + " " + beforeText; + } + if (node.nodeName === "IMG" || node.nodeName === "img") { + break; + } + } + resetImgIterator(); + while (afterText.length < N_CHAR_SIZE) { + const node = iter.nextNode(); + if (!node) { + break; + } + if (node.nodeType === Node.TEXT_NODE) { + afterText += cleanupStr(node.nodeValue) + " "; + } + } + + const abs = (v: number) => v < 0 ? 0 : -1 * v; + + beforeText = beforeText.slice(abs(beforeText.length - CUT), beforeText.length); + afterText = afterText.slice(0, CUT); + + beforeText = cleanupStr(beforeText); + afterText = cleanupStr(afterText); + + const DomParsingTexts = { + dom_beforeText: beforeText, + dom_afterText: afterText, + dom_detailsText: detailsText, + dom_describedbyText: describedbyText, + dom_labelledByText: labelledByText, + dom_figcaptionText: figcaptionText, + }; + debug("IMG_CLICK ImgDomParsed injected to img payload:", DomParsingTexts); + + yield* putTyped(readerLocalActionSetImageClick.build({ ...payload, ...DomParsingTexts })); + + return; } export function saga() { diff --git a/src/renderer/reader/redux/sagas/index.ts b/src/renderer/reader/redux/sagas/index.ts index 842a043e96..720d886f0e 100644 --- a/src/renderer/reader/redux/sagas/index.ts +++ b/src/renderer/reader/redux/sagas/index.ts @@ -9,6 +9,7 @@ import * as debug_ from "debug"; import { winActions } from "readium-desktop/renderer/common/redux/actions"; import * as publicationInfoReaderAndLib from "readium-desktop/renderer/common/redux/sagas/dialog/publicationInfoReaderAndLib"; import * as publicationInfoSyncTag from "readium-desktop/renderer/common/redux/sagas/dialog/publicationInfosSyncTags"; + // eslint-disable-next-line local-rules/typed-redux-saga-use-typed-effects import { all, call, take } from "redux-saga/effects"; diff --git a/src/renderer/reader/redux/state/imageClick.ts b/src/renderer/reader/redux/state/imageClick.ts index ac73827b0a..e969cf77e0 100644 --- a/src/renderer/reader/redux/state/imageClick.ts +++ b/src/renderer/reader/redux/state/imageClick.ts @@ -7,10 +7,20 @@ import { IEventPayload_R2_EVENT_IMAGE_CLICK } from "@r2-navigator-js/electron/common/events"; +export interface IImageClickStatePlusDomParsing { + + dom_beforeText: string; + dom_afterText: string; + dom_detailsText: string; + dom_describedbyText: string; + dom_labelledByText: string; + dom_figcaptionText: string; +} + export type IImageClickState = ({ open: true; -} & IEventPayload_R2_EVENT_IMAGE_CLICK) | ({ +} & IEventPayload_R2_EVENT_IMAGE_CLICK & Partial) | ({ open: false; -} & Partial) +} & Partial & Partial); export const defaultImageClickState: IImageClickState = { open: false }; diff --git a/src/resources/locales/ar.json b/src/resources/locales/ar.json index 44669125d7..4123a25e5c 100644 --- a/src/resources/locales/ar.json +++ b/src/resources/locales/ar.json @@ -120,6 +120,22 @@ "tags": "الوسوم", "update": "تحرير" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "تأليف '{{- author}}'", @@ -863,6 +879,16 @@ "person": "الشخص", "type": "النوع:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "هذا الإجراء يحذف كل بيانات تسجيل الدخول الخاصة بنظام توزيع المنشورات المفتوح (OPDS).", "title": "الوصول إلى كتالوغات المنشورات", @@ -1120,6 +1146,7 @@ "title": "حفظ الجلسة" }, "tabs": { + "aiKeyManager": "", "appearance": "المظهر", "general": "عام", "keyboardShortcuts": "اختصارات لوحة المفاتيح" diff --git a/src/resources/locales/bg.json b/src/resources/locales/bg.json index c859fb29e6..faedd9a6a7 100644 --- a/src/resources/locales/bg.json +++ b/src/resources/locales/bg.json @@ -120,6 +120,22 @@ "tags": "Маркери", "update": "Редактирай" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "Запиши сесията" }, "tabs": { + "aiKeyManager": "", "appearance": "", "general": "", "keyboardShortcuts": "" diff --git a/src/resources/locales/ca.json b/src/resources/locales/ca.json index 12859039dd..64255717ae 100644 --- a/src/resources/locales/ca.json +++ b/src/resources/locales/ca.json @@ -120,6 +120,22 @@ "tags": "Etiquetes", "update": "Editar" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "Guardar sessió" }, "tabs": { + "aiKeyManager": "", "appearance": "Aparença", "general": "General", "keyboardShortcuts": "Dreceres del teclat" diff --git a/src/resources/locales/cs.json b/src/resources/locales/cs.json index 4eaced663d..edeb4b585b 100644 --- a/src/resources/locales/cs.json +++ b/src/resources/locales/cs.json @@ -120,6 +120,22 @@ "tags": "Štítky", "update": "Upravit" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "autor: {{- author}}", @@ -863,6 +879,16 @@ "person": "Osoba", "type": "Typ:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Tento krok odstraní všechny přihlašovací údaje OPDS.", "title": "", @@ -1120,6 +1146,7 @@ "title": "Povolit ukládání relací" }, "tabs": { + "aiKeyManager": "", "appearance": "Vzhled", "general": "Obecné", "keyboardShortcuts": "Klávesové zkratky" diff --git a/src/resources/locales/da.json b/src/resources/locales/da.json index 9482782728..86f4571565 100644 --- a/src/resources/locales/da.json +++ b/src/resources/locales/da.json @@ -120,6 +120,22 @@ "tags": "Tags", "update": "Rediger" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "af {{- author}}", @@ -863,6 +879,16 @@ "person": "Person", "type": "Type:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Denne handling sletter alle OPDS loginoplysninger.", "title": "Administrér adgang til kataloger", @@ -1120,6 +1146,7 @@ "title": "Gem session" }, "tabs": { + "aiKeyManager": "", "appearance": "Udseende", "general": "Generelt", "keyboardShortcuts": "Tastaturgenveje" diff --git a/src/resources/locales/de.json b/src/resources/locales/de.json index 11aaf7c615..e2bea9ac88 100644 --- a/src/resources/locales/de.json +++ b/src/resources/locales/de.json @@ -120,6 +120,22 @@ "tags": "Tags", "update": "Bearbeiten" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "nach '{{- author}}'", @@ -863,6 +879,16 @@ "person": "Person", "type": "Typ:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Diese Aktion löscht alle OPDS-Anmeldedaten.", "title": "Zugang zu Publikationskatalogen", @@ -1120,6 +1146,7 @@ "title": "Sitzung speichern" }, "tabs": { + "aiKeyManager": "", "appearance": "Erscheinungsbild", "general": "Allgemein", "keyboardShortcuts": "Tastaturkürzel" diff --git a/src/resources/locales/el.json b/src/resources/locales/el.json index 094918738a..7565078b53 100644 --- a/src/resources/locales/el.json +++ b/src/resources/locales/el.json @@ -120,6 +120,22 @@ "tags": "Ετικέτες", "update": "Επεξεργασία" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "από '{{- author}}'", @@ -863,6 +879,16 @@ "person": "Πρόσωπο", "type": "Τύπος:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Αυτή η ενέργεια διαγράφει όλα τα OPDS διαπιστευτήρια σύνδεσης.", "title": "Πρόσβαση στους καταλόγους βιβλίων", @@ -1120,6 +1146,7 @@ "title": "Αποθήκευση συνεδρίας" }, "tabs": { + "aiKeyManager": "", "appearance": "Εμφάνιση", "general": "Γενικά", "keyboardShortcuts": "Συντομεύσεις πληκτρολογίου" diff --git a/src/resources/locales/en.json b/src/resources/locales/en.json index 4fc18759a4..8c96d3784c 100644 --- a/src/resources/locales/en.json +++ b/src/resources/locales/en.json @@ -120,6 +120,22 @@ "tags": "Tags", "update": "Edit" }, + "chatbot": { + "detailedDescTitle": "extended description", + "detailedDescription": "Describe this image in one or more paragraphs, provide details about the contents of the image, also give meta information about the context of the image from any relevant perspective (for example: historical, technical, artistic, etc.)", + "editorDescription": "Authored description(s):", + "generateDescription": "Ask the AI to generate:", + "generateDescriptionTitle": "AI image description...", + "inputPlaceholder": "Ask something, For example: can you describe this image?", + "noApiKey": "Please go to the application settings and enter an API key for your AI provider", + "noDescription": "No authored description.", + "reset": "Reset", + "sendQuestion": "Send", + "shortDescTitle": "short description", + "shortDescription": "Describe this image in one or two sentences", + "systemPromptEditor": "System prompt editor", + "title": "AI model: {{- title}}" + }, "dialog": { "annotations": { "descAuthor": "by '{{- author}}'", @@ -863,6 +879,16 @@ "person": "Person", "type": "Type:" }, + "apiKey": { + "help": "Add a personal API Key from a provider to use the chatbot image description feature.", + "howTo1": "Create an API key by visiting {{- provider }}'s console", + "howTo2": "Ensure your {{- provider }} account has credits", + "howTo3": "Paste your API key below to start using the assistant", + "keyLabel": "API Key", + "mistral": "Mistral", + "openAi": "OpenAI", + "title": "Add an API Key (Image Description feature)" + }, "auth": { "help": "This action deletes all OPDS login credentials.", "title": "Access to publication catalogs", @@ -1120,6 +1146,7 @@ "title": "Enable session saving" }, "tabs": { + "aiKeyManager": "AI Key Manager", "appearance": "Appearance", "general": "General", "keyboardShortcuts": "Keyboard shortcuts" diff --git a/src/resources/locales/es.json b/src/resources/locales/es.json index 0570120e16..3a2b61869f 100644 --- a/src/resources/locales/es.json +++ b/src/resources/locales/es.json @@ -120,6 +120,22 @@ "tags": "Etiquetas", "update": "Editar" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "de {{- author}}", @@ -863,6 +879,16 @@ "person": "Persona", "type": "Tipo:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Esta acción elimina todas las credenciales de inicio de sesión de OPDS.", "title": "Acceso a los catálogos de publicaciones", @@ -1120,6 +1146,7 @@ "title": "Guardar sesión" }, "tabs": { + "aiKeyManager": "", "appearance": "Aspecto visual", "general": "General", "keyboardShortcuts": "Atajos de teclado" diff --git a/src/resources/locales/eu.json b/src/resources/locales/eu.json index 713b1f575b..fd4f7313be 100644 --- a/src/resources/locales/eu.json +++ b/src/resources/locales/eu.json @@ -120,6 +120,22 @@ "tags": "Etiketak", "update": "Editatu" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "Gorde saioa" }, "tabs": { + "aiKeyManager": "", "appearance": "Itxura", "general": "Orokorra", "keyboardShortcuts": "Teklatuko lasterbideak" diff --git a/src/resources/locales/fi.json b/src/resources/locales/fi.json index a7bff815d8..f496405131 100644 --- a/src/resources/locales/fi.json +++ b/src/resources/locales/fi.json @@ -120,6 +120,22 @@ "tags": "Tunnisteet", "update": "Muokkaa" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "{{- author}}", @@ -863,6 +879,16 @@ "person": "Henkilö", "type": "Tyyppi:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Tämä toiminto poistaa kaikki OPDS-kirjautumistiedot.", "title": "Pääsy julkaisuluetteloihin", @@ -1120,6 +1146,7 @@ "title": "Tallenna istunto" }, "tabs": { + "aiKeyManager": "", "appearance": "Ulkoasu", "general": "Yleiset", "keyboardShortcuts": "Pikanäppäimet" diff --git a/src/resources/locales/fr.json b/src/resources/locales/fr.json index a0029dbe12..35f1a1a121 100644 --- a/src/resources/locales/fr.json +++ b/src/resources/locales/fr.json @@ -120,6 +120,22 @@ "tags": "Mots-clefs", "update": "Éditer" }, + "chatbot": { + "detailedDescTitle": "description détaillée", + "detailedDescription": "Décris cette illustration en détail (nature de l'image, technique, format, symbolisme, personnages, scène, couleurs, style, période, etc..).", + "editorDescription": "Description de l'éditeur", + "generateDescription": "Vous pouvez demander à l'IA de générer soit une description courte, soit une description détaillée.", + "generateDescriptionTitle": "Générer une description textuelle", + "inputPlaceholder": "Poser une question. Par example: peux-tu me décrire cette image ?", + "noApiKey": "Vous pouvez générer une description et discuter avec une IA en renseignant une clé d'API dans les préférences de l'application.", + "noDescription": "Aucune description n'a été fournie par l'éditeur. ", + "reset": "Réinitialiser", + "sendQuestion": "Envoyer la question", + "shortDescTitle": "description courte", + "shortDescription": "décris cette image en deux phrases.", + "systemPromptEditor": "Éditeur de prompt système", + "title": "Décrire l'image avec {{- title}}" + }, "dialog": { "annotations": { "descAuthor": "par {{- author}}", @@ -863,6 +879,16 @@ "person": "Personne", "type": "Type" }, + "apiKey": { + "help": "Ajoutez une clé d'API depuis le fournisseur de votre choix pour utiliser la fonctionnalité de description d'image.", + "howTo1": "Créez une clé API sur la console de {{- provider}}", + "howTo2": "Assurez-vous que votre compte {{- provider}} a suffisament de crédits", + "howTo3": "Copiez votre clé d'API ci-dessous pour utilisez l'assistant", + "keyLabel": "Clé d'API", + "mistral": "Mistral", + "openAi": "OpenAI", + "title": "Ajouter une clé API (fonctionnalité de description d'image)" + }, "auth": { "help": "Souhaitez-vous supprimer vos identifiants de connexion à l'OPDS ?", "title": "Gérer l'accès aux catalogues", @@ -1120,6 +1146,7 @@ "title": "Sauvegarder la session" }, "tabs": { + "aiKeyManager": "Gestionnaire de clé API", "appearance": "Apparence", "general": "Géneral", "keyboardShortcuts": "Raccourcis clavier" diff --git a/src/resources/locales/gl.json b/src/resources/locales/gl.json index bfceb1fa27..e1f60be712 100644 --- a/src/resources/locales/gl.json +++ b/src/resources/locales/gl.json @@ -120,6 +120,22 @@ "tags": "Etiquetas", "update": "Edita" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "Gardar sesión" }, "tabs": { + "aiKeyManager": "", "appearance": "Aspecto visual", "general": "General", "keyboardShortcuts": "Atallos de teclado" diff --git a/src/resources/locales/hr.json b/src/resources/locales/hr.json index 71fc29ff8b..00b122b504 100644 --- a/src/resources/locales/hr.json +++ b/src/resources/locales/hr.json @@ -120,6 +120,22 @@ "tags": "Oznake", "update": "Uredi" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "Spremi sesiju" }, "tabs": { + "aiKeyManager": "", "appearance": "", "general": "", "keyboardShortcuts": "" diff --git a/src/resources/locales/it.json b/src/resources/locales/it.json index af1698bc91..f407f76c45 100644 --- a/src/resources/locales/it.json +++ b/src/resources/locales/it.json @@ -120,6 +120,22 @@ "tags": "Tags", "update": "Modifica" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "di {{- author}}", @@ -863,6 +879,16 @@ "person": "Persona", "type": "Tipo:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Questa azione cancella tutte le credenziali di accesso all'OPDS.", "title": "Gestire l'accesso ai cataloghi", @@ -1120,6 +1146,7 @@ "title": "Salva sessione" }, "tabs": { + "aiKeyManager": "", "appearance": "Aspetto", "general": "Generale", "keyboardShortcuts": "Scorciatoie da tastiera" diff --git a/src/resources/locales/ja.json b/src/resources/locales/ja.json index 6510b15b37..bc710084ad 100644 --- a/src/resources/locales/ja.json +++ b/src/resources/locales/ja.json @@ -120,6 +120,22 @@ "tags": "タグ", "update": "編集" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "{{- author}}によるもの", @@ -863,6 +879,16 @@ "person": "個人", "type": "タイプ:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "この操作により、すべてのOPDSログイン情報が削除されます。", "title": "本の図書目録への接続", @@ -1120,6 +1146,7 @@ "title": "セッションを保存" }, "tabs": { + "aiKeyManager": "", "appearance": "外観", "general": "一般", "keyboardShortcuts": "キーボード ショートカット" diff --git a/src/resources/locales/ka.json b/src/resources/locales/ka.json index 8926cda485..ba0e3624d4 100644 --- a/src/resources/locales/ka.json +++ b/src/resources/locales/ka.json @@ -120,6 +120,22 @@ "tags": "იარლიყები", "update": "" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "სესიის შენახვა" }, "tabs": { + "aiKeyManager": "", "appearance": "", "general": "", "keyboardShortcuts": "" diff --git a/src/resources/locales/ko.json b/src/resources/locales/ko.json index 6e00cca9f7..9b47a1122a 100644 --- a/src/resources/locales/ko.json +++ b/src/resources/locales/ko.json @@ -120,6 +120,22 @@ "tags": "태그", "update": "편집" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "세션 저장" }, "tabs": { + "aiKeyManager": "", "appearance": "", "general": "", "keyboardShortcuts": "" diff --git a/src/resources/locales/lt.json b/src/resources/locales/lt.json index 99e5a677b6..fff885e71a 100644 --- a/src/resources/locales/lt.json +++ b/src/resources/locales/lt.json @@ -120,6 +120,22 @@ "tags": "Žymės", "update": "Keisti" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "'{{- author}}'", @@ -863,6 +879,16 @@ "person": "Asmuo", "type": "Tipas:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Šis veiksmas pašalina visus OPDS prisijungimo duomenis.", "title": "Valdyti katalogų prieigą", @@ -1120,6 +1146,7 @@ "title": "Įgalinti sesijos išsaugojimą" }, "tabs": { + "aiKeyManager": "", "appearance": "Išvaizda", "general": "Bendros", "keyboardShortcuts": "Spartieji klavišai" diff --git a/src/resources/locales/nl.json b/src/resources/locales/nl.json index e34c0a5800..089040e359 100644 --- a/src/resources/locales/nl.json +++ b/src/resources/locales/nl.json @@ -120,6 +120,22 @@ "tags": "Labels", "update": "Wijzig" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "door '{{- author}}'", @@ -863,6 +879,16 @@ "person": "Persoon", "type": "Type:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Deze actie verwijdert alle OPDS aanmeldingsgegevens.", "title": "Toegang tot publicatiecatalogi", @@ -1120,6 +1146,7 @@ "title": "Bewaar sessie" }, "tabs": { + "aiKeyManager": "", "appearance": "Weergave", "general": "Algemeen", "keyboardShortcuts": "Toetsenbord sneltoetsen" diff --git a/src/resources/locales/pt-br.json b/src/resources/locales/pt-br.json index 761ecb3f5f..4cdca002f5 100644 --- a/src/resources/locales/pt-br.json +++ b/src/resources/locales/pt-br.json @@ -120,6 +120,22 @@ "tags": "Tags", "update": "Editar" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "por '{{- author}} '", @@ -863,6 +879,16 @@ "person": "Pessoa", "type": "Tipo:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Esta ação exclui todas as credenciais de login OPDS.", "title": "Acesso a catálogos de publicação", @@ -1120,6 +1146,7 @@ "title": "Salvar sessão" }, "tabs": { + "aiKeyManager": "", "appearance": "Aparência", "general": "Geral", "keyboardShortcuts": "Atalhos do teclado" diff --git a/src/resources/locales/pt-pt.json b/src/resources/locales/pt-pt.json index b0ba62c712..70fe7e8a81 100644 --- a/src/resources/locales/pt-pt.json +++ b/src/resources/locales/pt-pt.json @@ -120,6 +120,22 @@ "tags": "Etiquetas", "update": "Editar" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "por {{- author}}", @@ -863,6 +879,16 @@ "person": "Pessoa", "type": "Tipo:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Esta ação elimina todas as credenciais de autenticação do OPDS.", "title": "Acesso aos catálogos de publicações", @@ -1120,6 +1146,7 @@ "title": "Ativar guardar sessão" }, "tabs": { + "aiKeyManager": "", "appearance": "Visual", "general": "Geral", "keyboardShortcuts": "Atalhos de teclado" diff --git a/src/resources/locales/ru.json b/src/resources/locales/ru.json index 8ebd2afb76..3267b778ea 100644 --- a/src/resources/locales/ru.json +++ b/src/resources/locales/ru.json @@ -120,6 +120,22 @@ "tags": "Теги", "update": "Изменить" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "Сохранение сеанса" }, "tabs": { + "aiKeyManager": "", "appearance": "Внешний вид", "general": "Общие", "keyboardShortcuts": "Горячие клавиши" diff --git a/src/resources/locales/sl.json b/src/resources/locales/sl.json index 3053ba3912..a1b9f6cc74 100644 --- a/src/resources/locales/sl.json +++ b/src/resources/locales/sl.json @@ -120,6 +120,22 @@ "tags": "Oznake", "update": "Uredi" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "avtor '{{- avtor}}'", @@ -863,6 +879,16 @@ "person": "Oseba", "type": "Tip:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "To dejanje izbriše vse poverilnice za prijavo v OPDS.", "title": "Dostop do katalogov publikacij", @@ -1120,6 +1146,7 @@ "title": "Shrani sejo" }, "tabs": { + "aiKeyManager": "", "appearance": "Izgled", "general": "Splošno", "keyboardShortcuts": "Bližnjice tipkovnice" diff --git a/src/resources/locales/sv.json b/src/resources/locales/sv.json index 1faeb10b2e..5cb7aa9a2a 100644 --- a/src/resources/locales/sv.json +++ b/src/resources/locales/sv.json @@ -120,6 +120,22 @@ "tags": "Taggar", "update": "Redigera" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "av {{- author}}", @@ -863,6 +879,16 @@ "person": "Person", "type": "Typ:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "Denna åtgärd raderar alla OPDS-inloggningsuppgifter.", "title": "Hantera åtkomst till kataloger", @@ -1120,6 +1146,7 @@ "title": "Spara session" }, "tabs": { + "aiKeyManager": "", "appearance": "Utseende", "general": "Allmänt", "keyboardShortcuts": "Kortkommandon" diff --git a/src/resources/locales/ta.json b/src/resources/locales/ta.json index 900079ddf7..51a7175711 100644 --- a/src/resources/locales/ta.json +++ b/src/resources/locales/ta.json @@ -120,6 +120,22 @@ "tags": "குறிச்சொற்கள்", "update": "தொகு" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "வழங்கியவர் '{{- ஆசிரியர்}}'", @@ -863,6 +879,16 @@ "person": "ஆள்", "type": "தட்டச்சு:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "இந்த செயல் அனைத்து OPDS உள்நுழைவு நற்சான்றுகளையும் நீக்குகிறது.", "title": "வெளியீட்டு பட்டியல்களுக்கான அணுகல்", @@ -1120,6 +1146,7 @@ "title": "அமர்வு சேமிப்பை இயக்கவும்" }, "tabs": { + "aiKeyManager": "", "appearance": "தோற்றம்", "general": "பொது", "keyboardShortcuts": "விசைப்பலகை குறுக்குவழிகள்" diff --git a/src/resources/locales/tr.json b/src/resources/locales/tr.json index dbbedb08ae..52f1ef2615 100644 --- a/src/resources/locales/tr.json +++ b/src/resources/locales/tr.json @@ -120,6 +120,22 @@ "tags": "Etiketler", "update": "Düzenle" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "'{{- author}}'a göre", @@ -863,6 +879,16 @@ "person": "", "type": "" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "", "title": "", @@ -1120,6 +1146,7 @@ "title": "" }, "tabs": { + "aiKeyManager": "", "appearance": "", "general": "", "keyboardShortcuts": "" diff --git a/src/resources/locales/zh-cn.json b/src/resources/locales/zh-cn.json index e193a69061..bb24c093e9 100644 --- a/src/resources/locales/zh-cn.json +++ b/src/resources/locales/zh-cn.json @@ -120,6 +120,22 @@ "tags": "标签", "update": "编辑" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "作者 “{{- author}}”", @@ -863,6 +879,16 @@ "person": "个人", "type": "类型" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "此操作将删除所有 OPDS 登录凭据。", "title": "访问出版物目录", @@ -1120,6 +1146,7 @@ "title": "保存登录状态" }, "tabs": { + "aiKeyManager": "", "appearance": "外观", "general": "常规", "keyboardShortcuts": "键盘快捷键" diff --git a/src/resources/locales/zh-tw.json b/src/resources/locales/zh-tw.json index 22cc7579ed..d2bcd5c4d7 100644 --- a/src/resources/locales/zh-tw.json +++ b/src/resources/locales/zh-tw.json @@ -120,6 +120,22 @@ "tags": "標籤", "update": "編輯" }, + "chatbot": { + "detailedDescTitle": "", + "detailedDescription": "", + "editorDescription": "", + "generateDescription": "", + "generateDescriptionTitle": "", + "inputPlaceholder": "", + "noApiKey": "", + "noDescription": "", + "reset": "", + "sendQuestion": "", + "shortDescTitle": "", + "shortDescription": "", + "systemPromptEditor": "", + "title": "" + }, "dialog": { "annotations": { "descAuthor": "經由 '{{- author}}'", @@ -863,6 +879,16 @@ "person": "個人", "type": "類型:" }, + "apiKey": { + "help": "", + "howTo1": "", + "howTo2": "", + "howTo3": "", + "keyLabel": "", + "mistral": "", + "openAi": "", + "title": "" + }, "auth": { "help": "此操作將刪除所有 OPDS 登入憑證。", "title": "訪問出版物目錄", @@ -1120,6 +1146,7 @@ "title": "保存登入狀態" }, "tabs": { + "aiKeyManager": "", "appearance": "外觀", "general": "一般", "keyboardShortcuts": "鍵盤快捷鍵" diff --git a/src/typings/en.translation-keys.d.ts b/src/typings/en.translation-keys.d.ts index b1ba41f8a2..bb9ebb69cb 100644 --- a/src/typings/en.translation-keys.d.ts +++ b/src/typings/en.translation-keys.d.ts @@ -1,4 +1,4 @@ declare namespace typed_i18n_keys { - type TTranslatorKeyParameter = "accessibility" | "accessibility.bookMenu" | "accessibility.closeDialog" | "accessibility.importFile" | "accessibility.leftSlideButton" | "accessibility.mainContent" | "accessibility.rightSlideButton" | "accessibility.skipLink" | "accessibility.toolbar" | "apiapp" | "apiapp.documentation" | "apiapp.howItWorks" | "apiapp.informations" | "apiapp.noLibraryFound" | "app" | "app.edit" | "app.edit.copy" | "app.edit.cut" | "app.edit.paste" | "app.edit.print" | "app.edit.redo" | "app.edit.selectAll" | "app.edit.title" | "app.edit.undo" | "app.hide" | "app.quit" | "app.session" | "app.session.exit" | "app.session.exit.askBox" | "app.session.exit.askBox.button" | "app.session.exit.askBox.button.no" | "app.session.exit.askBox.button.yes" | "app.session.exit.askBox.help" | "app.session.exit.askBox.message" | "app.session.exit.askBox.title" | "app.update" | "app.update.message" | "app.update.title" | "app.window" | "app.window.showLibrary" | "catalog" | "catalog.about" | "catalog.about.title" | "catalog.addBookToLib" | "catalog.addTags" | "catalog.addTagsButton" | "catalog.allBooks" | "catalog.bookInfo" | "catalog.column" | "catalog.column.ascending" | "catalog.column.descending" | "catalog.column.unsorted" | "catalog.delete" | "catalog.deleteBook" | "catalog.deleteTag" | "catalog.description" | "catalog.emptyTagList" | "catalog.entry" | "catalog.entry.continueReading" | "catalog.entry.lastAdditions" | "catalog.export" | "catalog.exportAnnotation" | "catalog.format" | "catalog.importAnnotation" | "catalog.lang" | "catalog.lastRead" | "catalog.moreInfo" | "catalog.myBooks" | "catalog.noPublicationHelpL1" | "catalog.noPublicationHelpL2" | "catalog.noPublicationHelpL3" | "catalog.noPublicationHelpL4" | "catalog.numberOfPages" | "catalog.opds" | "catalog.opds.auth" | "catalog.opds.auth.cancel" | "catalog.opds.auth.login" | "catalog.opds.auth.password" | "catalog.opds.auth.register" | "catalog.opds.auth.username" | "catalog.opds.info" | "catalog.opds.info.availableSince" | "catalog.opds.info.availableState" | "catalog.opds.info.availableState.available" | "catalog.opds.info.availableState.ready" | "catalog.opds.info.availableState.reserved" | "catalog.opds.info.availableState.unavailable" | "catalog.opds.info.availableState.unknown" | "catalog.opds.info.availableUntil" | "catalog.opds.info.copyAvalaible" | "catalog.opds.info.copyTotal" | "catalog.opds.info.holdPosition" | "catalog.opds.info.holdTotal" | "catalog.opds.info.numberOfItems" | "catalog.opds.info.priveValue" | "catalog.opds.info.state" | "catalog.publisher" | "catalog.readBook" | "catalog.released" | "catalog.sort" | "catalog.tag" | "catalog.tags" | "catalog.update" | "dialog" | "dialog.annotations" | "dialog.annotations.descAuthor" | "dialog.annotations.descCreator" | "dialog.annotations.descList" | "dialog.annotations.descNewer" | "dialog.annotations.descOlder" | "dialog.annotations.descTitle" | "dialog.annotations.importAll" | "dialog.annotations.importWithoutConflict" | "dialog.annotations.origin" | "dialog.annotations.title" | "dialog.cancel" | "dialog.deleteAnnotations" | "dialog.deleteAnnotationsText" | "dialog.deleteBookmarks" | "dialog.deleteBookmarksText" | "dialog.deleteFeed" | "dialog.deletePublication" | "dialog.import" | "dialog.importError" | "dialog.renew" | "dialog.return" | "dialog.yes" | "error" | "error.errorBox" | "error.errorBox.error" | "error.errorBox.message" | "error.errorBox.title" | "header" | "header.allBooks" | "header.catalogs" | "header.downloads" | "header.fitlerTagTitle" | "header.gridTitle" | "header.home" | "header.homeTitle" | "header.importTitle" | "header.listTitle" | "header.myCatalogs" | "header.refreshTitle" | "header.searchPlaceholder" | "header.searchTitle" | "header.settings" | "header.viewMode" | "library" | "library.lcp" | "library.lcp.hint" | "library.lcp.open" | "library.lcp.password" | "library.lcp.sentence" | "library.lcp.urlHint" | "library.lcp.whatIsLcp?" | "library.lcp.whatIsLcpInfoDetails" | "library.lcp.whatIsLcpInfoDetailsLink" | "message" | "message.annotations" | "message.annotations.alreadyImported" | "message.annotations.emptyFile" | "message.annotations.errorParsing" | "message.annotations.noBelongTo" | "message.annotations.nothing" | "message.annotations.success" | "message.download" | "message.download.error" | "message.import" | "message.import.alreadyImport" | "message.import.fail" | "message.import.success" | "message.open" | "message.open.error" | "opds" | "opds.addForm" | "opds.addForm.addButton" | "opds.addForm.name" | "opds.addForm.namePlaceholder" | "opds.addForm.url" | "opds.addForm.urlPlaceholder" | "opds.addFormApiapp" | "opds.addFormApiapp.title" | "opds.addMenu" | "opds.breadcrumbRoot" | "opds.documentation" | "opds.empty" | "opds.firstPage" | "opds.informations" | "opds.lastPage" | "opds.menu" | "opds.menu.aboutBook" | "opds.menu.addExtract" | "opds.menu.goBuyBook" | "opds.menu.goLoanBook" | "opds.menu.goRevokeLoanBook" | "opds.menu.goSubBook" | "opds.network" | "opds.network.error" | "opds.network.noInternet" | "opds.network.noInternetMessage" | "opds.network.reject" | "opds.network.timeout" | "opds.next" | "opds.previous" | "opds.shelf" | "opds.updateForm" | "opds.updateForm.name" | "opds.updateForm.title" | "opds.updateForm.updateButton" | "opds.updateForm.url" | "opds.whatIsOpds" | "publ-a11y-display-guide" | "publ-a11y-display-guide.accessibility-summary" | "publ-a11y-display-guide.accessibility-summary.accessibility-summary-title" | "publ-a11y-display-guide.additional-accessibility-information" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-aria" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-aria.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-aria.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-audio-descriptions" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-audio-descriptions.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-audio-descriptions.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-braille" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-braille.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-braille.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-full-ruby-annotations" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-full-ruby-annotations.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-full-ruby-annotations.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-foreground-and-background-audio" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-foreground-and-background-audio.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-foreground-and-background-audio.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-text-and-background" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-text-and-background.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-text-and-background.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-large-print" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-large-print.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-large-print.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-page-breaks" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-page-breaks.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-page-breaks.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-ruby-annotations" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-ruby-annotations.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-ruby-annotations.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-sign-language" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-sign-language.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-sign-language.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-graphics" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-graphics.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-graphics.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-objects" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-objects.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-objects.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-text-to-speech-hinting" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-text-to-speech-hinting.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-text-to-speech-hinting.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-title" | "publ-a11y-display-guide.conformance" | "publ-a11y-display-guide.conformance.conformance-a" | "publ-a11y-display-guide.conformance.conformance-a.compact" | "publ-a11y-display-guide.conformance.conformance-a.descriptive" | "publ-a11y-display-guide.conformance.conformance-aa" | "publ-a11y-display-guide.conformance.conformance-aa.compact" | "publ-a11y-display-guide.conformance.conformance-aa.descriptive" | "publ-a11y-display-guide.conformance.conformance-aaa" | "publ-a11y-display-guide.conformance.conformance-aaa.compact" | "publ-a11y-display-guide.conformance.conformance-aaa.descriptive" | "publ-a11y-display-guide.conformance.conformance-certifier" | "publ-a11y-display-guide.conformance.conformance-certifier.compact" | "publ-a11y-display-guide.conformance.conformance-certifier-credentials" | "publ-a11y-display-guide.conformance.conformance-certifier-credentials.compact" | "publ-a11y-display-guide.conformance.conformance-details-certifier-report" | "publ-a11y-display-guide.conformance.conformance-details-certifier-report.compact" | "publ-a11y-display-guide.conformance.conformance-details-claim" | "publ-a11y-display-guide.conformance.conformance-details-claim.compact" | "publ-a11y-display-guide.conformance.conformance-details-claim.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-0" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-0.compact" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-0.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-1" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-1.compact" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-1.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-level-a" | "publ-a11y-display-guide.conformance.conformance-details-level-a.compact" | "publ-a11y-display-guide.conformance.conformance-details-level-a.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-level-aa" | "publ-a11y-display-guide.conformance.conformance-details-level-aa.compact" | "publ-a11y-display-guide.conformance.conformance-details-level-aa.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-level-aaa" | "publ-a11y-display-guide.conformance.conformance-details-level-aaa.compact" | "publ-a11y-display-guide.conformance.conformance-details-level-aaa.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-0" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-0.compact" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-1" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-1.compact" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-1.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-2" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-2.compact" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-2.descriptive" | "publ-a11y-display-guide.conformance.conformance-no" | "publ-a11y-display-guide.conformance.conformance-no.compact" | "publ-a11y-display-guide.conformance.conformance-no.descriptive" | "publ-a11y-display-guide.conformance.conformance-title" | "publ-a11y-display-guide.conformance.conformance-unknown-standard" | "publ-a11y-display-guide.conformance.conformance-unknown-standard.compact" | "publ-a11y-display-guide.conformance.conformance-unknown-standard.descriptive" | "publ-a11y-display-guide.hazards" | "publ-a11y-display-guide.hazards.hazards-flashing" | "publ-a11y-display-guide.hazards.hazards-flashing.compact" | "publ-a11y-display-guide.hazards.hazards-flashing.descriptive" | "publ-a11y-display-guide.hazards.hazards-flashing-none" | "publ-a11y-display-guide.hazards.hazards-flashing-none.compact" | "publ-a11y-display-guide.hazards.hazards-flashing-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-flashing-unknown" | "publ-a11y-display-guide.hazards.hazards-flashing-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-flashing-unknown.descriptive" | "publ-a11y-display-guide.hazards.hazards-motion" | "publ-a11y-display-guide.hazards.hazards-motion.compact" | "publ-a11y-display-guide.hazards.hazards-motion.descriptive" | "publ-a11y-display-guide.hazards.hazards-motion-none" | "publ-a11y-display-guide.hazards.hazards-motion-none.compact" | "publ-a11y-display-guide.hazards.hazards-motion-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-motion-unknown" | "publ-a11y-display-guide.hazards.hazards-motion-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-motion-unknown.descriptive" | "publ-a11y-display-guide.hazards.hazards-no-metadata" | "publ-a11y-display-guide.hazards.hazards-no-metadata.compact" | "publ-a11y-display-guide.hazards.hazards-no-metadata.descriptive" | "publ-a11y-display-guide.hazards.hazards-none" | "publ-a11y-display-guide.hazards.hazards-none.compact" | "publ-a11y-display-guide.hazards.hazards-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-sound" | "publ-a11y-display-guide.hazards.hazards-sound.compact" | "publ-a11y-display-guide.hazards.hazards-sound.descriptive" | "publ-a11y-display-guide.hazards.hazards-sound-none" | "publ-a11y-display-guide.hazards.hazards-sound-none.compact" | "publ-a11y-display-guide.hazards.hazards-sound-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-sound-unknown" | "publ-a11y-display-guide.hazards.hazards-sound-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-sound-unknown.descriptive" | "publ-a11y-display-guide.hazards.hazards-title" | "publ-a11y-display-guide.hazards.hazards-unknown" | "publ-a11y-display-guide.hazards.hazards-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-unknown.descriptive" | "publ-a11y-display-guide.navigation" | "publ-a11y-display-guide.navigation.navigation-index" | "publ-a11y-display-guide.navigation.navigation-index.compact" | "publ-a11y-display-guide.navigation.navigation-index.descriptive" | "publ-a11y-display-guide.navigation.navigation-no-metadata" | "publ-a11y-display-guide.navigation.navigation-no-metadata.compact" | "publ-a11y-display-guide.navigation.navigation-no-metadata.descriptive" | "publ-a11y-display-guide.navigation.navigation-page-navigation" | "publ-a11y-display-guide.navigation.navigation-page-navigation.compact" | "publ-a11y-display-guide.navigation.navigation-page-navigation.descriptive" | "publ-a11y-display-guide.navigation.navigation-structural" | "publ-a11y-display-guide.navigation.navigation-structural.compact" | "publ-a11y-display-guide.navigation.navigation-structural.descriptive" | "publ-a11y-display-guide.navigation.navigation-title" | "publ-a11y-display-guide.navigation.navigation-toc" | "publ-a11y-display-guide.navigation.navigation-toc.compact" | "publ-a11y-display-guide.navigation.navigation-toc.descriptive" | "publ-a11y-display-guide.rich-content" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-latex" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-latex.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-latex.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-mathml" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-mathml.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-mathml.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-latex" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-latex.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-latex.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-mathml" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-mathml.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-mathml.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-described" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-described.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-described.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-closed-captions" | "publ-a11y-display-guide.rich-content.rich-content-closed-captions.compact" | "publ-a11y-display-guide.rich-content.rich-content-closed-captions.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-extended" | "publ-a11y-display-guide.rich-content.rich-content-extended.compact" | "publ-a11y-display-guide.rich-content.rich-content-extended.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-open-captions" | "publ-a11y-display-guide.rich-content.rich-content-open-captions.compact" | "publ-a11y-display-guide.rich-content.rich-content-open-captions.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-title" | "publ-a11y-display-guide.rich-content.rich-content-transcript" | "publ-a11y-display-guide.rich-content.rich-content-transcript.compact" | "publ-a11y-display-guide.rich-content.rich-content-transcript.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-unknown" | "publ-a11y-display-guide.rich-content.rich-content-unknown.compact" | "publ-a11y-display-guide.rich-content.rich-content-unknown.descriptive" | "publ-a11y-display-guide.ways-of-reading" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-alt-text" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-alt-text.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-alt-text.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-no-metadata" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-no-metadata.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-no-metadata.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-none" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-none.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-none.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-not-fully" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-not-fully.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-not-fully.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-readable" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-readable.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-readable.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-complementary" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-complementary.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-complementary.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-no-metadata" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-no-metadata.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-no-metadata.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-only" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-only.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-only.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-synchronized" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-synchronized.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-synchronized.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-title" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-modifiable" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-modifiable.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-modifiable.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-unknown" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-unknown.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-unknown.descriptive" | "publication" | "publication.accessibility" | "publication.accessibility.accessModeSufficient" | "publication.accessibility.accessModeSufficient.textual" | "publication.accessibility.accessibilityFeature" | "publication.accessibility.accessibilityFeature.alternativeText" | "publication.accessibility.accessibilityFeature.displayTransformability" | "publication.accessibility.accessibilityFeature.longDescription" | "publication.accessibility.accessibilityFeature.printPageNumbers" | "publication.accessibility.accessibilityFeature.readingOrder" | "publication.accessibility.accessibilityFeature.synchronizedAudioText" | "publication.accessibility.accessibilityFeature.tableOfContents" | "publication.accessibility.accessibilityHazard" | "publication.accessibility.accessibilityHazard.flashing" | "publication.accessibility.accessibilityHazard.motionSimulation" | "publication.accessibility.accessibilityHazard.name" | "publication.accessibility.accessibilityHazard.noFlashing" | "publication.accessibility.accessibilityHazard.noMotionSimulation" | "publication.accessibility.accessibilityHazard.noSound" | "publication.accessibility.accessibilityHazard.none" | "publication.accessibility.accessibilityHazard.sound" | "publication.accessibility.accessibilityHazard.unknown" | "publication.accessibility.certifierReport" | "publication.accessibility.conformsTo" | "publication.accessibility.moreInformation" | "publication.accessibility.name" | "publication.accessibility.noA11y" | "publication.actions" | "publication.audio" | "publication.audio.tracks" | "publication.author" | "publication.cancelledLcp" | "publication.certificateRevoked" | "publication.certificateSignatureInvalid" | "publication.cover" | "publication.cover.img" | "publication.day" | "publication.days" | "publication.duration" | "publication.duration.title" | "publication.encryptedNoLicense" | "publication.expired" | "publication.expiredLcp" | "publication.incorrectPassphrase" | "publication.lcpEnd" | "publication.lcpRightsCopy" | "publication.lcpRightsPrint" | "publication.lcpStart" | "publication.licenceLCP" | "publication.licenseCertificateDateInvalid" | "publication.licenseOutOfDate" | "publication.licenseSignatureInvalid" | "publication.licensed" | "publication.markAsRead" | "publication.notStarted" | "publication.onGoing" | "publication.progression" | "publication.progression.title" | "publication.read" | "publication.remainingTime" | "publication.renewButton" | "publication.returnButton" | "publication.returnedLcp" | "publication.revokedLcp" | "publication.seeLess" | "publication.seeMore" | "publication.timeLeft" | "publication.title" | "publication.userKeyCheckInvalid" | "reader" | "reader.annotations" | "reader.annotations.Color" | "reader.annotations.addNote" | "reader.annotations.advancedMode" | "reader.annotations.annotationsExport" | "reader.annotations.annotationsExport.description" | "reader.annotations.annotationsExport.title" | "reader.annotations.annotationsOptions" | "reader.annotations.date" | "reader.annotations.export" | "reader.annotations.filter" | "reader.annotations.filter.all" | "reader.annotations.filter.filterByColor" | "reader.annotations.filter.filterByCreator" | "reader.annotations.filter.filterByDrawtype" | "reader.annotations.filter.filterByTag" | "reader.annotations.filter.filterOptions" | "reader.annotations.filter.none" | "reader.annotations.hide" | "reader.annotations.highlight" | "reader.annotations.noSelectionToast" | "reader.annotations.note" | "reader.annotations.quickAnnotations" | "reader.annotations.saveNote" | "reader.annotations.sorting" | "reader.annotations.sorting.lastcreated" | "reader.annotations.sorting.lastmodified" | "reader.annotations.sorting.progression" | "reader.annotations.sorting.sortingOptions" | "reader.annotations.toggleMarginMarks" | "reader.annotations.type" | "reader.annotations.type.outline" | "reader.annotations.type.solid" | "reader.annotations.type.strikethrough" | "reader.annotations.type.underline" | "reader.bookmarks" | "reader.bookmarks.index" | "reader.divina" | "reader.divina.mute" | "reader.divina.unmute" | "reader.fxl" | "reader.fxl.fit" | "reader.goToContent" | "reader.imgViewer" | "reader.imgViewer.title" | "reader.imgViewer.zoomIn" | "reader.imgViewer.zoomOut" | "reader.imgViewer.zoomReset" | "reader.marks" | "reader.marks.annotations" | "reader.marks.bookmarks" | "reader.marks.delete" | "reader.marks.edit" | "reader.marks.goTo" | "reader.marks.landmarks" | "reader.marks.saveMark" | "reader.marks.search" | "reader.marks.searchResult" | "reader.marks.toc" | "reader.media-overlays" | "reader.media-overlays.activate" | "reader.media-overlays.captions" | "reader.media-overlays.captionsDescription" | "reader.media-overlays.disableContinuousPlay" | "reader.media-overlays.disableContinuousPlayDescription" | "reader.media-overlays.ignoreAndUseTTS" | "reader.media-overlays.ignoreAndUseTTSDescription" | "reader.media-overlays.next" | "reader.media-overlays.pause" | "reader.media-overlays.play" | "reader.media-overlays.previous" | "reader.media-overlays.skip" | "reader.media-overlays.skipDescription" | "reader.media-overlays.speed" | "reader.media-overlays.stop" | "reader.media-overlays.title" | "reader.navigation" | "reader.navigation.ZenModeExit" | "reader.navigation.ZenModeTitle" | "reader.navigation.annotationTitle" | "reader.navigation.backHomeTitle" | "reader.navigation.bookmarkTitle" | "reader.navigation.currentPage" | "reader.navigation.currentPageTotal" | "reader.navigation.detachWindowTitle" | "reader.navigation.goTo" | "reader.navigation.goToError" | "reader.navigation.goToPlaceHolder" | "reader.navigation.goToTitle" | "reader.navigation.historyNext" | "reader.navigation.historyPrevious" | "reader.navigation.infoTitle" | "reader.navigation.magnifyingGlassButton" | "reader.navigation.openTableOfContentsTitle" | "reader.navigation.page" | "reader.navigation.pdfscalemode" | "reader.navigation.print" | "reader.navigation.settingsTitle" | "reader.notes" | "reader.notes.colors" | "reader.notes.colors.cyan" | "reader.notes.colors.green" | "reader.notes.colors.orange" | "reader.notes.colors.purple" | "reader.notes.colors.red" | "reader.notes.colors.yellow" | "reader.picker" | "reader.picker.search" | "reader.picker.search.founds" | "reader.picker.search.input" | "reader.picker.search.next" | "reader.picker.search.notFound" | "reader.picker.search.previous" | "reader.picker.search.results" | "reader.picker.search.submit" | "reader.picker.searchTitle" | "reader.print" | "reader.print.description" | "reader.print.descriptionLcp" | "reader.print.descriptionLcpLimit" | "reader.print.pageHelpInfo" | "reader.print.pageHelpInfo1" | "reader.print.pageHelpInfo2" | "reader.print.pageHelpInfo3" | "reader.print.pageHelpInfo4" | "reader.print.pages" | "reader.print.print" | "reader.settings" | "reader.settings.column" | "reader.settings.column.auto" | "reader.settings.column.one" | "reader.settings.column.title" | "reader.settings.column.two" | "reader.settings.customFontSelected" | "reader.settings.customizeReader" | "reader.settings.disabled" | "reader.settings.display" | "reader.settings.disposition" | "reader.settings.disposition.title" | "reader.settings.font" | "reader.settings.fontSize" | "reader.settings.infoCustomFont" | "reader.settings.justification" | "reader.settings.justify" | "reader.settings.letterSpacing" | "reader.settings.lineSpacing" | "reader.settings.margin" | "reader.settings.noFootnotes" | "reader.settings.noRTLFlip" | "reader.settings.noRuby" | "reader.settings.noTemporaryNavTargetOutline" | "reader.settings.paginated" | "reader.settings.paraSpacing" | "reader.settings.pdfZoom" | "reader.settings.pdfZoom.name" | "reader.settings.pdfZoom.name.100pct" | "reader.settings.pdfZoom.name.150pct" | "reader.settings.pdfZoom.name.200pct" | "reader.settings.pdfZoom.name.300pct" | "reader.settings.pdfZoom.name.500pct" | "reader.settings.pdfZoom.name.50pct" | "reader.settings.pdfZoom.name.fit" | "reader.settings.pdfZoom.name.width" | "reader.settings.pdfZoom.title" | "reader.settings.preset" | "reader.settings.preset.apply" | "reader.settings.preset.applyDetails" | "reader.settings.preset.detail" | "reader.settings.preset.reset" | "reader.settings.preset.resetDetails" | "reader.settings.preset.save" | "reader.settings.preset.saveDetails" | "reader.settings.preset.title" | "reader.settings.preview" | "reader.settings.reduceMotion" | "reader.settings.scrolled" | "reader.settings.spacing" | "reader.settings.text" | "reader.settings.theme" | "reader.settings.theme.name" | "reader.settings.theme.name.Contrast1" | "reader.settings.theme.name.Contrast2" | "reader.settings.theme.name.Contrast3" | "reader.settings.theme.name.Contrast4" | "reader.settings.theme.name.Neutral" | "reader.settings.theme.name.Night" | "reader.settings.theme.name.Paper" | "reader.settings.theme.name.Sepia" | "reader.settings.theme.title" | "reader.settings.wordSpacing" | "reader.svg" | "reader.svg.left" | "reader.svg.right" | "reader.toc" | "reader.toc.publicationNoToc" | "reader.tts" | "reader.tts.activate" | "reader.tts.language" | "reader.tts.next" | "reader.tts.options" | "reader.tts.pause" | "reader.tts.play" | "reader.tts.previous" | "reader.tts.sentenceDetect" | "reader.tts.sentenceDetectDescription" | "reader.tts.speed" | "reader.tts.stop" | "reader.tts.voice" | "settings" | "settings.annotationCreator" | "settings.annotationCreator.creator" | "settings.annotationCreator.help" | "settings.annotationCreator.name" | "settings.annotationCreator.organization" | "settings.annotationCreator.person" | "settings.annotationCreator.type" | "settings.auth" | "settings.auth.help" | "settings.auth.title" | "settings.auth.wipeData" | "settings.keyboard" | "settings.keyboard.advancedMenu" | "settings.keyboard.cancel" | "settings.keyboard.disclaimer" | "settings.keyboard.editUserJson" | "settings.keyboard.keyboardShortcuts" | "settings.keyboard.list" | "settings.keyboard.list.AddBookmarkWithLabel" | "settings.keyboard.list.AddBookmarkWithLabel.description" | "settings.keyboard.list.AddBookmarkWithLabel.name" | "settings.keyboard.list.AnnotationsCreate" | "settings.keyboard.list.AnnotationsCreate.description" | "settings.keyboard.list.AnnotationsCreate.name" | "settings.keyboard.list.AnnotationsCreateQuick" | "settings.keyboard.list.AnnotationsCreateQuick.description" | "settings.keyboard.list.AnnotationsCreateQuick.name" | "settings.keyboard.list.AnnotationsToggleMargin" | "settings.keyboard.list.AnnotationsToggleMargin.description" | "settings.keyboard.list.AnnotationsToggleMargin.name" | "settings.keyboard.list.AudioNext" | "settings.keyboard.list.AudioNext.description" | "settings.keyboard.list.AudioNext.name" | "settings.keyboard.list.AudioNextAlt" | "settings.keyboard.list.AudioNextAlt.description" | "settings.keyboard.list.AudioNextAlt.name" | "settings.keyboard.list.AudioPlayPause" | "settings.keyboard.list.AudioPlayPause.description" | "settings.keyboard.list.AudioPlayPause.name" | "settings.keyboard.list.AudioPrevious" | "settings.keyboard.list.AudioPrevious.description" | "settings.keyboard.list.AudioPrevious.name" | "settings.keyboard.list.AudioPreviousAlt" | "settings.keyboard.list.AudioPreviousAlt.description" | "settings.keyboard.list.AudioPreviousAlt.name" | "settings.keyboard.list.AudioStop" | "settings.keyboard.list.AudioStop.description" | "settings.keyboard.list.AudioStop.name" | "settings.keyboard.list.CloseReader" | "settings.keyboard.list.CloseReader.description" | "settings.keyboard.list.CloseReader.name" | "settings.keyboard.list.FXLZoomIn" | "settings.keyboard.list.FXLZoomIn.description" | "settings.keyboard.list.FXLZoomIn.name" | "settings.keyboard.list.FXLZoomOut" | "settings.keyboard.list.FXLZoomOut.description" | "settings.keyboard.list.FXLZoomOut.name" | "settings.keyboard.list.FXLZoomReset" | "settings.keyboard.list.FXLZoomReset.description" | "settings.keyboard.list.FXLZoomReset.name" | "settings.keyboard.list.FocusMain" | "settings.keyboard.list.FocusMain.description" | "settings.keyboard.list.FocusMain.name" | "settings.keyboard.list.FocusMainDeep" | "settings.keyboard.list.FocusMainDeep.description" | "settings.keyboard.list.FocusMainDeep.name" | "settings.keyboard.list.FocusReaderGotoPage" | "settings.keyboard.list.FocusReaderGotoPage.description" | "settings.keyboard.list.FocusReaderGotoPage.name" | "settings.keyboard.list.FocusReaderNavigation" | "settings.keyboard.list.FocusReaderNavigation.description" | "settings.keyboard.list.FocusReaderNavigation.name" | "settings.keyboard.list.FocusReaderNavigationAnnotations" | "settings.keyboard.list.FocusReaderNavigationAnnotations.description" | "settings.keyboard.list.FocusReaderNavigationAnnotations.name" | "settings.keyboard.list.FocusReaderNavigationBookmarks" | "settings.keyboard.list.FocusReaderNavigationBookmarks.description" | "settings.keyboard.list.FocusReaderNavigationBookmarks.name" | "settings.keyboard.list.FocusReaderNavigationSearch" | "settings.keyboard.list.FocusReaderNavigationSearch.description" | "settings.keyboard.list.FocusReaderNavigationSearch.name" | "settings.keyboard.list.FocusReaderNavigationTOC" | "settings.keyboard.list.FocusReaderNavigationTOC.description" | "settings.keyboard.list.FocusReaderNavigationTOC.name" | "settings.keyboard.list.FocusReaderSettings" | "settings.keyboard.list.FocusReaderSettings.description" | "settings.keyboard.list.FocusReaderSettings.name" | "settings.keyboard.list.FocusSearch" | "settings.keyboard.list.FocusSearch.description" | "settings.keyboard.list.FocusSearch.name" | "settings.keyboard.list.FocusToolbar" | "settings.keyboard.list.FocusToolbar.description" | "settings.keyboard.list.FocusToolbar.name" | "settings.keyboard.list.NavigateNextChapter" | "settings.keyboard.list.NavigateNextChapter.description" | "settings.keyboard.list.NavigateNextChapter.name" | "settings.keyboard.list.NavigateNextChapterAlt" | "settings.keyboard.list.NavigateNextChapterAlt.description" | "settings.keyboard.list.NavigateNextChapterAlt.name" | "settings.keyboard.list.NavigateNextHistory" | "settings.keyboard.list.NavigateNextHistory.description" | "settings.keyboard.list.NavigateNextHistory.name" | "settings.keyboard.list.NavigateNextLibraryPage" | "settings.keyboard.list.NavigateNextLibraryPage.description" | "settings.keyboard.list.NavigateNextLibraryPage.name" | "settings.keyboard.list.NavigateNextLibraryPageAlt" | "settings.keyboard.list.NavigateNextLibraryPageAlt.description" | "settings.keyboard.list.NavigateNextLibraryPageAlt.name" | "settings.keyboard.list.NavigateNextOPDSPage" | "settings.keyboard.list.NavigateNextOPDSPage.description" | "settings.keyboard.list.NavigateNextOPDSPage.name" | "settings.keyboard.list.NavigateNextOPDSPageAlt" | "settings.keyboard.list.NavigateNextOPDSPageAlt.description" | "settings.keyboard.list.NavigateNextOPDSPageAlt.name" | "settings.keyboard.list.NavigateNextPage" | "settings.keyboard.list.NavigateNextPage.description" | "settings.keyboard.list.NavigateNextPage.name" | "settings.keyboard.list.NavigateNextPageAlt" | "settings.keyboard.list.NavigateNextPageAlt.description" | "settings.keyboard.list.NavigateNextPageAlt.name" | "settings.keyboard.list.NavigatePreviousChapter" | "settings.keyboard.list.NavigatePreviousChapter.description" | "settings.keyboard.list.NavigatePreviousChapter.name" | "settings.keyboard.list.NavigatePreviousChapterAlt" | "settings.keyboard.list.NavigatePreviousChapterAlt.description" | "settings.keyboard.list.NavigatePreviousChapterAlt.name" | "settings.keyboard.list.NavigatePreviousHistory" | "settings.keyboard.list.NavigatePreviousHistory.description" | "settings.keyboard.list.NavigatePreviousHistory.name" | "settings.keyboard.list.NavigatePreviousLibraryPage" | "settings.keyboard.list.NavigatePreviousLibraryPage.description" | "settings.keyboard.list.NavigatePreviousLibraryPage.name" | "settings.keyboard.list.NavigatePreviousLibraryPageAlt" | "settings.keyboard.list.NavigatePreviousLibraryPageAlt.description" | "settings.keyboard.list.NavigatePreviousLibraryPageAlt.name" | "settings.keyboard.list.NavigatePreviousOPDSPage" | "settings.keyboard.list.NavigatePreviousOPDSPage.description" | "settings.keyboard.list.NavigatePreviousOPDSPage.name" | "settings.keyboard.list.NavigatePreviousOPDSPageAlt" | "settings.keyboard.list.NavigatePreviousOPDSPageAlt.description" | "settings.keyboard.list.NavigatePreviousOPDSPageAlt.name" | "settings.keyboard.list.NavigatePreviousPage" | "settings.keyboard.list.NavigatePreviousPage.description" | "settings.keyboard.list.NavigatePreviousPage.name" | "settings.keyboard.list.NavigatePreviousPageAlt" | "settings.keyboard.list.NavigatePreviousPageAlt.description" | "settings.keyboard.list.NavigatePreviousPageAlt.name" | "settings.keyboard.list.NavigateToBegin" | "settings.keyboard.list.NavigateToBegin.description" | "settings.keyboard.list.NavigateToBegin.name" | "settings.keyboard.list.NavigateToEnd" | "settings.keyboard.list.NavigateToEnd.description" | "settings.keyboard.list.NavigateToEnd.name" | "settings.keyboard.list.OpenReaderInfo" | "settings.keyboard.list.OpenReaderInfo.description" | "settings.keyboard.list.OpenReaderInfo.name" | "settings.keyboard.list.OpenReaderInfoWhereAmI" | "settings.keyboard.list.OpenReaderInfoWhereAmI.description" | "settings.keyboard.list.OpenReaderInfoWhereAmI.name" | "settings.keyboard.list.Print" | "settings.keyboard.list.Print.description" | "settings.keyboard.list.Print.name" | "settings.keyboard.list.SearchNext" | "settings.keyboard.list.SearchNext.description" | "settings.keyboard.list.SearchNext.name" | "settings.keyboard.list.SearchNextAlt" | "settings.keyboard.list.SearchNextAlt.description" | "settings.keyboard.list.SearchNextAlt.name" | "settings.keyboard.list.SearchPrevious" | "settings.keyboard.list.SearchPrevious.description" | "settings.keyboard.list.SearchPrevious.name" | "settings.keyboard.list.SearchPreviousAlt" | "settings.keyboard.list.SearchPreviousAlt.description" | "settings.keyboard.list.SearchPreviousAlt.name" | "settings.keyboard.list.SpeakReaderInfoWhereAmI" | "settings.keyboard.list.SpeakReaderInfoWhereAmI.description" | "settings.keyboard.list.SpeakReaderInfoWhereAmI.name" | "settings.keyboard.list.ToggleBookmark" | "settings.keyboard.list.ToggleBookmark.description" | "settings.keyboard.list.ToggleBookmark.name" | "settings.keyboard.list.ToggleReaderFullscreen" | "settings.keyboard.list.ToggleReaderFullscreen.description" | "settings.keyboard.list.ToggleReaderFullscreen.name" | "settings.keyboard.loadUserJson" | "settings.keyboard.noShortcutFound" | "settings.keyboard.resetDefaults" | "settings.keyboard.save" | "settings.keyboard.searchPlaceholder" | "settings.language" | "settings.language.languageChoice" | "settings.library" | "settings.library.enableAPIAPP" | "settings.library.title" | "settings.note" | "settings.note.export" | "settings.note.export.applyDefaultTemplate" | "settings.note.export.enableCheckbox" | "settings.note.export.overrideHTMLTemplate" | "settings.session" | "settings.session.title" | "settings.tabs" | "settings.tabs.appearance" | "settings.tabs.general" | "settings.tabs.keyboardShortcuts" | "settings.theme" | "settings.theme.auto" | "settings.theme.dark" | "settings.theme.description" | "settings.theme.light" | "settings.theme.title" | "tts" | "tts.highlight" | "tts.highlight.mainColor" | "tts.highlight.maskBlockWordOutline" | "tts.highlight.maskBlockWordSolidBackground" | "tts.highlight.maskBlockWordUnderline" | "tts.highlight.maskWordOutline" | "tts.highlight.maskWordSolidBackground" | "tts.highlight.maskWordUnderline" | "tts.highlight.outlineWordOutline" | "tts.highlight.outlineWordSolidBackground" | "tts.highlight.outlineWordUnderline" | "tts.highlight.preview" | "tts.highlight.solidBackgroundWordOutline" | "tts.highlight.solidBackgroundWordSolidBackground" | "tts.highlight.solidBackgroundWordUnderline" | "tts.highlight.style" | "tts.highlight.underlineWordOutline" | "tts.highlight.underlineWordSolidBackground" | "tts.highlight.underlineWordUnderline" | "tts.highlight.wordColor" | "wizard" | "wizard.buttons" | "wizard.buttons.discover" | "wizard.buttons.goToBooks" | "wizard.buttons.next" | "wizard.description" | "wizard.description.annotations" | "wizard.description.catalogs" | "wizard.description.home" | "wizard.description.readingView1" | "wizard.description.readingView2" | "wizard.description.yourBooks" | "wizard.dontShow" | "wizard.tab" | "wizard.tab.annotations" | "wizard.tab.catalogs" | "wizard.tab.home" | "wizard.tab.readingView" | "wizard.tab.yourBooks" | "wizard.title" | "wizard.title.allBooks" | "wizard.title.newFeature" | "wizard.title.welcome"; + type TTranslatorKeyParameter = "accessibility" | "accessibility.bookMenu" | "accessibility.closeDialog" | "accessibility.importFile" | "accessibility.leftSlideButton" | "accessibility.mainContent" | "accessibility.rightSlideButton" | "accessibility.skipLink" | "accessibility.toolbar" | "apiapp" | "apiapp.documentation" | "apiapp.howItWorks" | "apiapp.informations" | "apiapp.noLibraryFound" | "app" | "app.edit" | "app.edit.copy" | "app.edit.cut" | "app.edit.paste" | "app.edit.print" | "app.edit.redo" | "app.edit.selectAll" | "app.edit.title" | "app.edit.undo" | "app.hide" | "app.quit" | "app.session" | "app.session.exit" | "app.session.exit.askBox" | "app.session.exit.askBox.button" | "app.session.exit.askBox.button.no" | "app.session.exit.askBox.button.yes" | "app.session.exit.askBox.help" | "app.session.exit.askBox.message" | "app.session.exit.askBox.title" | "app.update" | "app.update.message" | "app.update.title" | "app.window" | "app.window.showLibrary" | "catalog" | "catalog.about" | "catalog.about.title" | "catalog.addBookToLib" | "catalog.addTags" | "catalog.addTagsButton" | "catalog.allBooks" | "catalog.bookInfo" | "catalog.column" | "catalog.column.ascending" | "catalog.column.descending" | "catalog.column.unsorted" | "catalog.delete" | "catalog.deleteBook" | "catalog.deleteTag" | "catalog.description" | "catalog.emptyTagList" | "catalog.entry" | "catalog.entry.continueReading" | "catalog.entry.lastAdditions" | "catalog.export" | "catalog.exportAnnotation" | "catalog.format" | "catalog.importAnnotation" | "catalog.lang" | "catalog.lastRead" | "catalog.moreInfo" | "catalog.myBooks" | "catalog.noPublicationHelpL1" | "catalog.noPublicationHelpL2" | "catalog.noPublicationHelpL3" | "catalog.noPublicationHelpL4" | "catalog.numberOfPages" | "catalog.opds" | "catalog.opds.auth" | "catalog.opds.auth.cancel" | "catalog.opds.auth.login" | "catalog.opds.auth.password" | "catalog.opds.auth.register" | "catalog.opds.auth.username" | "catalog.opds.info" | "catalog.opds.info.availableSince" | "catalog.opds.info.availableState" | "catalog.opds.info.availableState.available" | "catalog.opds.info.availableState.ready" | "catalog.opds.info.availableState.reserved" | "catalog.opds.info.availableState.unavailable" | "catalog.opds.info.availableState.unknown" | "catalog.opds.info.availableUntil" | "catalog.opds.info.copyAvalaible" | "catalog.opds.info.copyTotal" | "catalog.opds.info.holdPosition" | "catalog.opds.info.holdTotal" | "catalog.opds.info.numberOfItems" | "catalog.opds.info.priveValue" | "catalog.opds.info.state" | "catalog.publisher" | "catalog.readBook" | "catalog.released" | "catalog.sort" | "catalog.tag" | "catalog.tags" | "catalog.update" | "chatbot" | "chatbot.detailedDescTitle" | "chatbot.detailedDescription" | "chatbot.editorDescription" | "chatbot.generateDescription" | "chatbot.generateDescriptionTitle" | "chatbot.inputPlaceholder" | "chatbot.noApiKey" | "chatbot.noDescription" | "chatbot.reset" | "chatbot.sendQuestion" | "chatbot.shortDescTitle" | "chatbot.shortDescription" | "chatbot.systemPromptEditor" | "chatbot.title" | "dialog" | "dialog.annotations" | "dialog.annotations.descAuthor" | "dialog.annotations.descCreator" | "dialog.annotations.descList" | "dialog.annotations.descNewer" | "dialog.annotations.descOlder" | "dialog.annotations.descTitle" | "dialog.annotations.importAll" | "dialog.annotations.importWithoutConflict" | "dialog.annotations.origin" | "dialog.annotations.title" | "dialog.cancel" | "dialog.deleteAnnotations" | "dialog.deleteAnnotationsText" | "dialog.deleteBookmarks" | "dialog.deleteBookmarksText" | "dialog.deleteFeed" | "dialog.deletePublication" | "dialog.import" | "dialog.importError" | "dialog.renew" | "dialog.return" | "dialog.yes" | "error" | "error.errorBox" | "error.errorBox.error" | "error.errorBox.message" | "error.errorBox.title" | "header" | "header.allBooks" | "header.catalogs" | "header.downloads" | "header.fitlerTagTitle" | "header.gridTitle" | "header.home" | "header.homeTitle" | "header.importTitle" | "header.listTitle" | "header.myCatalogs" | "header.refreshTitle" | "header.searchPlaceholder" | "header.searchTitle" | "header.settings" | "header.viewMode" | "library" | "library.lcp" | "library.lcp.hint" | "library.lcp.open" | "library.lcp.password" | "library.lcp.sentence" | "library.lcp.urlHint" | "library.lcp.whatIsLcp?" | "library.lcp.whatIsLcpInfoDetails" | "library.lcp.whatIsLcpInfoDetailsLink" | "message" | "message.annotations" | "message.annotations.alreadyImported" | "message.annotations.emptyFile" | "message.annotations.errorParsing" | "message.annotations.noBelongTo" | "message.annotations.nothing" | "message.annotations.success" | "message.download" | "message.download.error" | "message.import" | "message.import.alreadyImport" | "message.import.fail" | "message.import.success" | "message.open" | "message.open.error" | "opds" | "opds.addForm" | "opds.addForm.addButton" | "opds.addForm.name" | "opds.addForm.namePlaceholder" | "opds.addForm.url" | "opds.addForm.urlPlaceholder" | "opds.addFormApiapp" | "opds.addFormApiapp.title" | "opds.addMenu" | "opds.breadcrumbRoot" | "opds.documentation" | "opds.empty" | "opds.firstPage" | "opds.informations" | "opds.lastPage" | "opds.menu" | "opds.menu.aboutBook" | "opds.menu.addExtract" | "opds.menu.goBuyBook" | "opds.menu.goLoanBook" | "opds.menu.goRevokeLoanBook" | "opds.menu.goSubBook" | "opds.network" | "opds.network.error" | "opds.network.noInternet" | "opds.network.noInternetMessage" | "opds.network.reject" | "opds.network.timeout" | "opds.next" | "opds.previous" | "opds.shelf" | "opds.updateForm" | "opds.updateForm.name" | "opds.updateForm.title" | "opds.updateForm.updateButton" | "opds.updateForm.url" | "opds.whatIsOpds" | "publ-a11y-display-guide" | "publ-a11y-display-guide.accessibility-summary" | "publ-a11y-display-guide.accessibility-summary.accessibility-summary-title" | "publ-a11y-display-guide.additional-accessibility-information" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-aria" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-aria.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-aria.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-audio-descriptions" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-audio-descriptions.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-audio-descriptions.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-braille" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-braille.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-braille.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-full-ruby-annotations" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-full-ruby-annotations.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-full-ruby-annotations.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-foreground-and-background-audio" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-foreground-and-background-audio.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-foreground-and-background-audio.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-text-and-background" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-text-and-background.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-high-contrast-between-text-and-background.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-large-print" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-large-print.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-large-print.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-page-breaks" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-page-breaks.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-page-breaks.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-ruby-annotations" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-ruby-annotations.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-ruby-annotations.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-sign-language" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-sign-language.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-sign-language.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-graphics" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-graphics.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-graphics.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-objects" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-objects.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-tactile-objects.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-text-to-speech-hinting" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-text-to-speech-hinting.compact" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-text-to-speech-hinting.descriptive" | "publ-a11y-display-guide.additional-accessibility-information.additional-accessibility-information-title" | "publ-a11y-display-guide.conformance" | "publ-a11y-display-guide.conformance.conformance-a" | "publ-a11y-display-guide.conformance.conformance-a.compact" | "publ-a11y-display-guide.conformance.conformance-a.descriptive" | "publ-a11y-display-guide.conformance.conformance-aa" | "publ-a11y-display-guide.conformance.conformance-aa.compact" | "publ-a11y-display-guide.conformance.conformance-aa.descriptive" | "publ-a11y-display-guide.conformance.conformance-aaa" | "publ-a11y-display-guide.conformance.conformance-aaa.compact" | "publ-a11y-display-guide.conformance.conformance-aaa.descriptive" | "publ-a11y-display-guide.conformance.conformance-certifier" | "publ-a11y-display-guide.conformance.conformance-certifier.compact" | "publ-a11y-display-guide.conformance.conformance-certifier-credentials" | "publ-a11y-display-guide.conformance.conformance-certifier-credentials.compact" | "publ-a11y-display-guide.conformance.conformance-details-certifier-report" | "publ-a11y-display-guide.conformance.conformance-details-certifier-report.compact" | "publ-a11y-display-guide.conformance.conformance-details-claim" | "publ-a11y-display-guide.conformance.conformance-details-claim.compact" | "publ-a11y-display-guide.conformance.conformance-details-claim.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-0" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-0.compact" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-0.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-1" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-1.compact" | "publ-a11y-display-guide.conformance.conformance-details-epub-accessibility-1-1.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-level-a" | "publ-a11y-display-guide.conformance.conformance-details-level-a.compact" | "publ-a11y-display-guide.conformance.conformance-details-level-a.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-level-aa" | "publ-a11y-display-guide.conformance.conformance-details-level-aa.compact" | "publ-a11y-display-guide.conformance.conformance-details-level-aa.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-level-aaa" | "publ-a11y-display-guide.conformance.conformance-details-level-aaa.compact" | "publ-a11y-display-guide.conformance.conformance-details-level-aaa.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-0" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-0.compact" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-1" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-1.compact" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-1.descriptive" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-2" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-2.compact" | "publ-a11y-display-guide.conformance.conformance-details-wcag-2-2.descriptive" | "publ-a11y-display-guide.conformance.conformance-no" | "publ-a11y-display-guide.conformance.conformance-no.compact" | "publ-a11y-display-guide.conformance.conformance-no.descriptive" | "publ-a11y-display-guide.conformance.conformance-title" | "publ-a11y-display-guide.conformance.conformance-unknown-standard" | "publ-a11y-display-guide.conformance.conformance-unknown-standard.compact" | "publ-a11y-display-guide.conformance.conformance-unknown-standard.descriptive" | "publ-a11y-display-guide.hazards" | "publ-a11y-display-guide.hazards.hazards-flashing" | "publ-a11y-display-guide.hazards.hazards-flashing.compact" | "publ-a11y-display-guide.hazards.hazards-flashing.descriptive" | "publ-a11y-display-guide.hazards.hazards-flashing-none" | "publ-a11y-display-guide.hazards.hazards-flashing-none.compact" | "publ-a11y-display-guide.hazards.hazards-flashing-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-flashing-unknown" | "publ-a11y-display-guide.hazards.hazards-flashing-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-flashing-unknown.descriptive" | "publ-a11y-display-guide.hazards.hazards-motion" | "publ-a11y-display-guide.hazards.hazards-motion.compact" | "publ-a11y-display-guide.hazards.hazards-motion.descriptive" | "publ-a11y-display-guide.hazards.hazards-motion-none" | "publ-a11y-display-guide.hazards.hazards-motion-none.compact" | "publ-a11y-display-guide.hazards.hazards-motion-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-motion-unknown" | "publ-a11y-display-guide.hazards.hazards-motion-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-motion-unknown.descriptive" | "publ-a11y-display-guide.hazards.hazards-no-metadata" | "publ-a11y-display-guide.hazards.hazards-no-metadata.compact" | "publ-a11y-display-guide.hazards.hazards-no-metadata.descriptive" | "publ-a11y-display-guide.hazards.hazards-none" | "publ-a11y-display-guide.hazards.hazards-none.compact" | "publ-a11y-display-guide.hazards.hazards-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-sound" | "publ-a11y-display-guide.hazards.hazards-sound.compact" | "publ-a11y-display-guide.hazards.hazards-sound.descriptive" | "publ-a11y-display-guide.hazards.hazards-sound-none" | "publ-a11y-display-guide.hazards.hazards-sound-none.compact" | "publ-a11y-display-guide.hazards.hazards-sound-none.descriptive" | "publ-a11y-display-guide.hazards.hazards-sound-unknown" | "publ-a11y-display-guide.hazards.hazards-sound-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-sound-unknown.descriptive" | "publ-a11y-display-guide.hazards.hazards-title" | "publ-a11y-display-guide.hazards.hazards-unknown" | "publ-a11y-display-guide.hazards.hazards-unknown.compact" | "publ-a11y-display-guide.hazards.hazards-unknown.descriptive" | "publ-a11y-display-guide.navigation" | "publ-a11y-display-guide.navigation.navigation-index" | "publ-a11y-display-guide.navigation.navigation-index.compact" | "publ-a11y-display-guide.navigation.navigation-index.descriptive" | "publ-a11y-display-guide.navigation.navigation-no-metadata" | "publ-a11y-display-guide.navigation.navigation-no-metadata.compact" | "publ-a11y-display-guide.navigation.navigation-no-metadata.descriptive" | "publ-a11y-display-guide.navigation.navigation-page-navigation" | "publ-a11y-display-guide.navigation.navigation-page-navigation.compact" | "publ-a11y-display-guide.navigation.navigation-page-navigation.descriptive" | "publ-a11y-display-guide.navigation.navigation-structural" | "publ-a11y-display-guide.navigation.navigation-structural.compact" | "publ-a11y-display-guide.navigation.navigation-structural.descriptive" | "publ-a11y-display-guide.navigation.navigation-title" | "publ-a11y-display-guide.navigation.navigation-toc" | "publ-a11y-display-guide.navigation.navigation-toc.compact" | "publ-a11y-display-guide.navigation.navigation-toc.descriptive" | "publ-a11y-display-guide.rich-content" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-latex" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-latex.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-latex.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-mathml" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-mathml.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-chemistry-as-mathml.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-latex" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-latex.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-latex.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-mathml" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-mathml.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-as-mathml.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-described" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-described.compact" | "publ-a11y-display-guide.rich-content.rich-content-accessible-math-described.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-closed-captions" | "publ-a11y-display-guide.rich-content.rich-content-closed-captions.compact" | "publ-a11y-display-guide.rich-content.rich-content-closed-captions.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-extended" | "publ-a11y-display-guide.rich-content.rich-content-extended.compact" | "publ-a11y-display-guide.rich-content.rich-content-extended.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-open-captions" | "publ-a11y-display-guide.rich-content.rich-content-open-captions.compact" | "publ-a11y-display-guide.rich-content.rich-content-open-captions.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-title" | "publ-a11y-display-guide.rich-content.rich-content-transcript" | "publ-a11y-display-guide.rich-content.rich-content-transcript.compact" | "publ-a11y-display-guide.rich-content.rich-content-transcript.descriptive" | "publ-a11y-display-guide.rich-content.rich-content-unknown" | "publ-a11y-display-guide.rich-content.rich-content-unknown.compact" | "publ-a11y-display-guide.rich-content.rich-content-unknown.descriptive" | "publ-a11y-display-guide.ways-of-reading" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-alt-text" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-alt-text.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-alt-text.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-no-metadata" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-no-metadata.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-no-metadata.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-none" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-none.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-none.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-not-fully" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-not-fully.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-not-fully.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-readable" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-readable.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-nonvisual-reading-readable.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-complementary" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-complementary.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-complementary.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-no-metadata" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-no-metadata.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-no-metadata.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-only" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-only.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-only.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-synchronized" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-synchronized.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-prerecorded-audio-synchronized.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-title" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-modifiable" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-modifiable.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-modifiable.descriptive" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-unknown" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-unknown.compact" | "publ-a11y-display-guide.ways-of-reading.ways-of-reading-visual-adjustments-unknown.descriptive" | "publication" | "publication.accessibility" | "publication.accessibility.accessModeSufficient" | "publication.accessibility.accessModeSufficient.textual" | "publication.accessibility.accessibilityFeature" | "publication.accessibility.accessibilityFeature.alternativeText" | "publication.accessibility.accessibilityFeature.displayTransformability" | "publication.accessibility.accessibilityFeature.longDescription" | "publication.accessibility.accessibilityFeature.printPageNumbers" | "publication.accessibility.accessibilityFeature.readingOrder" | "publication.accessibility.accessibilityFeature.synchronizedAudioText" | "publication.accessibility.accessibilityFeature.tableOfContents" | "publication.accessibility.accessibilityHazard" | "publication.accessibility.accessibilityHazard.flashing" | "publication.accessibility.accessibilityHazard.motionSimulation" | "publication.accessibility.accessibilityHazard.name" | "publication.accessibility.accessibilityHazard.noFlashing" | "publication.accessibility.accessibilityHazard.noMotionSimulation" | "publication.accessibility.accessibilityHazard.noSound" | "publication.accessibility.accessibilityHazard.none" | "publication.accessibility.accessibilityHazard.sound" | "publication.accessibility.accessibilityHazard.unknown" | "publication.accessibility.certifierReport" | "publication.accessibility.conformsTo" | "publication.accessibility.moreInformation" | "publication.accessibility.name" | "publication.accessibility.noA11y" | "publication.actions" | "publication.audio" | "publication.audio.tracks" | "publication.author" | "publication.cancelledLcp" | "publication.certificateRevoked" | "publication.certificateSignatureInvalid" | "publication.cover" | "publication.cover.img" | "publication.day" | "publication.days" | "publication.duration" | "publication.duration.title" | "publication.encryptedNoLicense" | "publication.expired" | "publication.expiredLcp" | "publication.incorrectPassphrase" | "publication.lcpEnd" | "publication.lcpRightsCopy" | "publication.lcpRightsPrint" | "publication.lcpStart" | "publication.licenceLCP" | "publication.licenseCertificateDateInvalid" | "publication.licenseOutOfDate" | "publication.licenseSignatureInvalid" | "publication.licensed" | "publication.markAsRead" | "publication.notStarted" | "publication.onGoing" | "publication.progression" | "publication.progression.title" | "publication.read" | "publication.remainingTime" | "publication.renewButton" | "publication.returnButton" | "publication.returnedLcp" | "publication.revokedLcp" | "publication.seeLess" | "publication.seeMore" | "publication.timeLeft" | "publication.title" | "publication.userKeyCheckInvalid" | "reader" | "reader.annotations" | "reader.annotations.Color" | "reader.annotations.addNote" | "reader.annotations.advancedMode" | "reader.annotations.annotationsExport" | "reader.annotations.annotationsExport.description" | "reader.annotations.annotationsExport.title" | "reader.annotations.annotationsOptions" | "reader.annotations.date" | "reader.annotations.export" | "reader.annotations.filter" | "reader.annotations.filter.all" | "reader.annotations.filter.filterByColor" | "reader.annotations.filter.filterByCreator" | "reader.annotations.filter.filterByDrawtype" | "reader.annotations.filter.filterByTag" | "reader.annotations.filter.filterOptions" | "reader.annotations.filter.none" | "reader.annotations.hide" | "reader.annotations.highlight" | "reader.annotations.noSelectionToast" | "reader.annotations.note" | "reader.annotations.quickAnnotations" | "reader.annotations.saveNote" | "reader.annotations.sorting" | "reader.annotations.sorting.lastcreated" | "reader.annotations.sorting.lastmodified" | "reader.annotations.sorting.progression" | "reader.annotations.sorting.sortingOptions" | "reader.annotations.toggleMarginMarks" | "reader.annotations.type" | "reader.annotations.type.outline" | "reader.annotations.type.solid" | "reader.annotations.type.strikethrough" | "reader.annotations.type.underline" | "reader.bookmarks" | "reader.bookmarks.index" | "reader.divina" | "reader.divina.mute" | "reader.divina.unmute" | "reader.fxl" | "reader.fxl.fit" | "reader.goToContent" | "reader.imgViewer" | "reader.imgViewer.title" | "reader.imgViewer.zoomIn" | "reader.imgViewer.zoomOut" | "reader.imgViewer.zoomReset" | "reader.marks" | "reader.marks.annotations" | "reader.marks.bookmarks" | "reader.marks.delete" | "reader.marks.edit" | "reader.marks.goTo" | "reader.marks.landmarks" | "reader.marks.saveMark" | "reader.marks.search" | "reader.marks.searchResult" | "reader.marks.toc" | "reader.media-overlays" | "reader.media-overlays.activate" | "reader.media-overlays.captions" | "reader.media-overlays.captionsDescription" | "reader.media-overlays.disableContinuousPlay" | "reader.media-overlays.disableContinuousPlayDescription" | "reader.media-overlays.ignoreAndUseTTS" | "reader.media-overlays.ignoreAndUseTTSDescription" | "reader.media-overlays.next" | "reader.media-overlays.pause" | "reader.media-overlays.play" | "reader.media-overlays.previous" | "reader.media-overlays.skip" | "reader.media-overlays.skipDescription" | "reader.media-overlays.speed" | "reader.media-overlays.stop" | "reader.media-overlays.title" | "reader.navigation" | "reader.navigation.ZenModeExit" | "reader.navigation.ZenModeTitle" | "reader.navigation.annotationTitle" | "reader.navigation.backHomeTitle" | "reader.navigation.bookmarkTitle" | "reader.navigation.currentPage" | "reader.navigation.currentPageTotal" | "reader.navigation.detachWindowTitle" | "reader.navigation.goTo" | "reader.navigation.goToError" | "reader.navigation.goToPlaceHolder" | "reader.navigation.goToTitle" | "reader.navigation.historyNext" | "reader.navigation.historyPrevious" | "reader.navigation.infoTitle" | "reader.navigation.magnifyingGlassButton" | "reader.navigation.openTableOfContentsTitle" | "reader.navigation.page" | "reader.navigation.pdfscalemode" | "reader.navigation.print" | "reader.navigation.settingsTitle" | "reader.notes" | "reader.notes.colors" | "reader.notes.colors.cyan" | "reader.notes.colors.green" | "reader.notes.colors.orange" | "reader.notes.colors.purple" | "reader.notes.colors.red" | "reader.notes.colors.yellow" | "reader.picker" | "reader.picker.search" | "reader.picker.search.founds" | "reader.picker.search.input" | "reader.picker.search.next" | "reader.picker.search.notFound" | "reader.picker.search.previous" | "reader.picker.search.results" | "reader.picker.search.submit" | "reader.picker.searchTitle" | "reader.print" | "reader.print.description" | "reader.print.descriptionLcp" | "reader.print.descriptionLcpLimit" | "reader.print.pageHelpInfo" | "reader.print.pageHelpInfo1" | "reader.print.pageHelpInfo2" | "reader.print.pageHelpInfo3" | "reader.print.pageHelpInfo4" | "reader.print.pages" | "reader.print.print" | "reader.settings" | "reader.settings.column" | "reader.settings.column.auto" | "reader.settings.column.one" | "reader.settings.column.title" | "reader.settings.column.two" | "reader.settings.customFontSelected" | "reader.settings.customizeReader" | "reader.settings.disabled" | "reader.settings.display" | "reader.settings.disposition" | "reader.settings.disposition.title" | "reader.settings.font" | "reader.settings.fontSize" | "reader.settings.infoCustomFont" | "reader.settings.justification" | "reader.settings.justify" | "reader.settings.letterSpacing" | "reader.settings.lineSpacing" | "reader.settings.margin" | "reader.settings.noFootnotes" | "reader.settings.noRTLFlip" | "reader.settings.noRuby" | "reader.settings.noTemporaryNavTargetOutline" | "reader.settings.paginated" | "reader.settings.paraSpacing" | "reader.settings.pdfZoom" | "reader.settings.pdfZoom.name" | "reader.settings.pdfZoom.name.100pct" | "reader.settings.pdfZoom.name.150pct" | "reader.settings.pdfZoom.name.200pct" | "reader.settings.pdfZoom.name.300pct" | "reader.settings.pdfZoom.name.500pct" | "reader.settings.pdfZoom.name.50pct" | "reader.settings.pdfZoom.name.fit" | "reader.settings.pdfZoom.name.width" | "reader.settings.pdfZoom.title" | "reader.settings.preset" | "reader.settings.preset.apply" | "reader.settings.preset.applyDetails" | "reader.settings.preset.detail" | "reader.settings.preset.reset" | "reader.settings.preset.resetDetails" | "reader.settings.preset.save" | "reader.settings.preset.saveDetails" | "reader.settings.preset.title" | "reader.settings.preview" | "reader.settings.reduceMotion" | "reader.settings.scrolled" | "reader.settings.spacing" | "reader.settings.text" | "reader.settings.theme" | "reader.settings.theme.name" | "reader.settings.theme.name.Contrast1" | "reader.settings.theme.name.Contrast2" | "reader.settings.theme.name.Contrast3" | "reader.settings.theme.name.Contrast4" | "reader.settings.theme.name.Neutral" | "reader.settings.theme.name.Night" | "reader.settings.theme.name.Paper" | "reader.settings.theme.name.Sepia" | "reader.settings.theme.title" | "reader.settings.wordSpacing" | "reader.svg" | "reader.svg.left" | "reader.svg.right" | "reader.toc" | "reader.toc.publicationNoToc" | "reader.tts" | "reader.tts.activate" | "reader.tts.language" | "reader.tts.next" | "reader.tts.options" | "reader.tts.pause" | "reader.tts.play" | "reader.tts.previous" | "reader.tts.sentenceDetect" | "reader.tts.sentenceDetectDescription" | "reader.tts.speed" | "reader.tts.stop" | "reader.tts.voice" | "settings" | "settings.annotationCreator" | "settings.annotationCreator.creator" | "settings.annotationCreator.help" | "settings.annotationCreator.name" | "settings.annotationCreator.organization" | "settings.annotationCreator.person" | "settings.annotationCreator.type" | "settings.apiKey" | "settings.apiKey.help" | "settings.apiKey.howTo1" | "settings.apiKey.howTo2" | "settings.apiKey.howTo3" | "settings.apiKey.keyLabel" | "settings.apiKey.mistral" | "settings.apiKey.openAi" | "settings.apiKey.title" | "settings.auth" | "settings.auth.help" | "settings.auth.title" | "settings.auth.wipeData" | "settings.keyboard" | "settings.keyboard.advancedMenu" | "settings.keyboard.cancel" | "settings.keyboard.disclaimer" | "settings.keyboard.editUserJson" | "settings.keyboard.keyboardShortcuts" | "settings.keyboard.list" | "settings.keyboard.list.AddBookmarkWithLabel" | "settings.keyboard.list.AddBookmarkWithLabel.description" | "settings.keyboard.list.AddBookmarkWithLabel.name" | "settings.keyboard.list.AnnotationsCreate" | "settings.keyboard.list.AnnotationsCreate.description" | "settings.keyboard.list.AnnotationsCreate.name" | "settings.keyboard.list.AnnotationsCreateQuick" | "settings.keyboard.list.AnnotationsCreateQuick.description" | "settings.keyboard.list.AnnotationsCreateQuick.name" | "settings.keyboard.list.AnnotationsToggleMargin" | "settings.keyboard.list.AnnotationsToggleMargin.description" | "settings.keyboard.list.AnnotationsToggleMargin.name" | "settings.keyboard.list.AudioNext" | "settings.keyboard.list.AudioNext.description" | "settings.keyboard.list.AudioNext.name" | "settings.keyboard.list.AudioNextAlt" | "settings.keyboard.list.AudioNextAlt.description" | "settings.keyboard.list.AudioNextAlt.name" | "settings.keyboard.list.AudioPlayPause" | "settings.keyboard.list.AudioPlayPause.description" | "settings.keyboard.list.AudioPlayPause.name" | "settings.keyboard.list.AudioPrevious" | "settings.keyboard.list.AudioPrevious.description" | "settings.keyboard.list.AudioPrevious.name" | "settings.keyboard.list.AudioPreviousAlt" | "settings.keyboard.list.AudioPreviousAlt.description" | "settings.keyboard.list.AudioPreviousAlt.name" | "settings.keyboard.list.AudioStop" | "settings.keyboard.list.AudioStop.description" | "settings.keyboard.list.AudioStop.name" | "settings.keyboard.list.CloseReader" | "settings.keyboard.list.CloseReader.description" | "settings.keyboard.list.CloseReader.name" | "settings.keyboard.list.FXLZoomIn" | "settings.keyboard.list.FXLZoomIn.description" | "settings.keyboard.list.FXLZoomIn.name" | "settings.keyboard.list.FXLZoomOut" | "settings.keyboard.list.FXLZoomOut.description" | "settings.keyboard.list.FXLZoomOut.name" | "settings.keyboard.list.FXLZoomReset" | "settings.keyboard.list.FXLZoomReset.description" | "settings.keyboard.list.FXLZoomReset.name" | "settings.keyboard.list.FocusMain" | "settings.keyboard.list.FocusMain.description" | "settings.keyboard.list.FocusMain.name" | "settings.keyboard.list.FocusMainDeep" | "settings.keyboard.list.FocusMainDeep.description" | "settings.keyboard.list.FocusMainDeep.name" | "settings.keyboard.list.FocusReaderGotoPage" | "settings.keyboard.list.FocusReaderGotoPage.description" | "settings.keyboard.list.FocusReaderGotoPage.name" | "settings.keyboard.list.FocusReaderNavigation" | "settings.keyboard.list.FocusReaderNavigation.description" | "settings.keyboard.list.FocusReaderNavigation.name" | "settings.keyboard.list.FocusReaderNavigationAnnotations" | "settings.keyboard.list.FocusReaderNavigationAnnotations.description" | "settings.keyboard.list.FocusReaderNavigationAnnotations.name" | "settings.keyboard.list.FocusReaderNavigationBookmarks" | "settings.keyboard.list.FocusReaderNavigationBookmarks.description" | "settings.keyboard.list.FocusReaderNavigationBookmarks.name" | "settings.keyboard.list.FocusReaderNavigationSearch" | "settings.keyboard.list.FocusReaderNavigationSearch.description" | "settings.keyboard.list.FocusReaderNavigationSearch.name" | "settings.keyboard.list.FocusReaderNavigationTOC" | "settings.keyboard.list.FocusReaderNavigationTOC.description" | "settings.keyboard.list.FocusReaderNavigationTOC.name" | "settings.keyboard.list.FocusReaderSettings" | "settings.keyboard.list.FocusReaderSettings.description" | "settings.keyboard.list.FocusReaderSettings.name" | "settings.keyboard.list.FocusSearch" | "settings.keyboard.list.FocusSearch.description" | "settings.keyboard.list.FocusSearch.name" | "settings.keyboard.list.FocusToolbar" | "settings.keyboard.list.FocusToolbar.description" | "settings.keyboard.list.FocusToolbar.name" | "settings.keyboard.list.NavigateNextChapter" | "settings.keyboard.list.NavigateNextChapter.description" | "settings.keyboard.list.NavigateNextChapter.name" | "settings.keyboard.list.NavigateNextChapterAlt" | "settings.keyboard.list.NavigateNextChapterAlt.description" | "settings.keyboard.list.NavigateNextChapterAlt.name" | "settings.keyboard.list.NavigateNextHistory" | "settings.keyboard.list.NavigateNextHistory.description" | "settings.keyboard.list.NavigateNextHistory.name" | "settings.keyboard.list.NavigateNextLibraryPage" | "settings.keyboard.list.NavigateNextLibraryPage.description" | "settings.keyboard.list.NavigateNextLibraryPage.name" | "settings.keyboard.list.NavigateNextLibraryPageAlt" | "settings.keyboard.list.NavigateNextLibraryPageAlt.description" | "settings.keyboard.list.NavigateNextLibraryPageAlt.name" | "settings.keyboard.list.NavigateNextOPDSPage" | "settings.keyboard.list.NavigateNextOPDSPage.description" | "settings.keyboard.list.NavigateNextOPDSPage.name" | "settings.keyboard.list.NavigateNextOPDSPageAlt" | "settings.keyboard.list.NavigateNextOPDSPageAlt.description" | "settings.keyboard.list.NavigateNextOPDSPageAlt.name" | "settings.keyboard.list.NavigateNextPage" | "settings.keyboard.list.NavigateNextPage.description" | "settings.keyboard.list.NavigateNextPage.name" | "settings.keyboard.list.NavigateNextPageAlt" | "settings.keyboard.list.NavigateNextPageAlt.description" | "settings.keyboard.list.NavigateNextPageAlt.name" | "settings.keyboard.list.NavigatePreviousChapter" | "settings.keyboard.list.NavigatePreviousChapter.description" | "settings.keyboard.list.NavigatePreviousChapter.name" | "settings.keyboard.list.NavigatePreviousChapterAlt" | "settings.keyboard.list.NavigatePreviousChapterAlt.description" | "settings.keyboard.list.NavigatePreviousChapterAlt.name" | "settings.keyboard.list.NavigatePreviousHistory" | "settings.keyboard.list.NavigatePreviousHistory.description" | "settings.keyboard.list.NavigatePreviousHistory.name" | "settings.keyboard.list.NavigatePreviousLibraryPage" | "settings.keyboard.list.NavigatePreviousLibraryPage.description" | "settings.keyboard.list.NavigatePreviousLibraryPage.name" | "settings.keyboard.list.NavigatePreviousLibraryPageAlt" | "settings.keyboard.list.NavigatePreviousLibraryPageAlt.description" | "settings.keyboard.list.NavigatePreviousLibraryPageAlt.name" | "settings.keyboard.list.NavigatePreviousOPDSPage" | "settings.keyboard.list.NavigatePreviousOPDSPage.description" | "settings.keyboard.list.NavigatePreviousOPDSPage.name" | "settings.keyboard.list.NavigatePreviousOPDSPageAlt" | "settings.keyboard.list.NavigatePreviousOPDSPageAlt.description" | "settings.keyboard.list.NavigatePreviousOPDSPageAlt.name" | "settings.keyboard.list.NavigatePreviousPage" | "settings.keyboard.list.NavigatePreviousPage.description" | "settings.keyboard.list.NavigatePreviousPage.name" | "settings.keyboard.list.NavigatePreviousPageAlt" | "settings.keyboard.list.NavigatePreviousPageAlt.description" | "settings.keyboard.list.NavigatePreviousPageAlt.name" | "settings.keyboard.list.NavigateToBegin" | "settings.keyboard.list.NavigateToBegin.description" | "settings.keyboard.list.NavigateToBegin.name" | "settings.keyboard.list.NavigateToEnd" | "settings.keyboard.list.NavigateToEnd.description" | "settings.keyboard.list.NavigateToEnd.name" | "settings.keyboard.list.OpenReaderInfo" | "settings.keyboard.list.OpenReaderInfo.description" | "settings.keyboard.list.OpenReaderInfo.name" | "settings.keyboard.list.OpenReaderInfoWhereAmI" | "settings.keyboard.list.OpenReaderInfoWhereAmI.description" | "settings.keyboard.list.OpenReaderInfoWhereAmI.name" | "settings.keyboard.list.Print" | "settings.keyboard.list.Print.description" | "settings.keyboard.list.Print.name" | "settings.keyboard.list.SearchNext" | "settings.keyboard.list.SearchNext.description" | "settings.keyboard.list.SearchNext.name" | "settings.keyboard.list.SearchNextAlt" | "settings.keyboard.list.SearchNextAlt.description" | "settings.keyboard.list.SearchNextAlt.name" | "settings.keyboard.list.SearchPrevious" | "settings.keyboard.list.SearchPrevious.description" | "settings.keyboard.list.SearchPrevious.name" | "settings.keyboard.list.SearchPreviousAlt" | "settings.keyboard.list.SearchPreviousAlt.description" | "settings.keyboard.list.SearchPreviousAlt.name" | "settings.keyboard.list.SpeakReaderInfoWhereAmI" | "settings.keyboard.list.SpeakReaderInfoWhereAmI.description" | "settings.keyboard.list.SpeakReaderInfoWhereAmI.name" | "settings.keyboard.list.ToggleBookmark" | "settings.keyboard.list.ToggleBookmark.description" | "settings.keyboard.list.ToggleBookmark.name" | "settings.keyboard.list.ToggleReaderFullscreen" | "settings.keyboard.list.ToggleReaderFullscreen.description" | "settings.keyboard.list.ToggleReaderFullscreen.name" | "settings.keyboard.loadUserJson" | "settings.keyboard.noShortcutFound" | "settings.keyboard.resetDefaults" | "settings.keyboard.save" | "settings.keyboard.searchPlaceholder" | "settings.language" | "settings.language.languageChoice" | "settings.library" | "settings.library.enableAPIAPP" | "settings.library.title" | "settings.note" | "settings.note.export" | "settings.note.export.applyDefaultTemplate" | "settings.note.export.enableCheckbox" | "settings.note.export.overrideHTMLTemplate" | "settings.session" | "settings.session.title" | "settings.tabs" | "settings.tabs.aiKeyManager" | "settings.tabs.appearance" | "settings.tabs.general" | "settings.tabs.keyboardShortcuts" | "settings.theme" | "settings.theme.auto" | "settings.theme.dark" | "settings.theme.description" | "settings.theme.light" | "settings.theme.title" | "tts" | "tts.highlight" | "tts.highlight.mainColor" | "tts.highlight.maskBlockWordOutline" | "tts.highlight.maskBlockWordSolidBackground" | "tts.highlight.maskBlockWordUnderline" | "tts.highlight.maskWordOutline" | "tts.highlight.maskWordSolidBackground" | "tts.highlight.maskWordUnderline" | "tts.highlight.outlineWordOutline" | "tts.highlight.outlineWordSolidBackground" | "tts.highlight.outlineWordUnderline" | "tts.highlight.preview" | "tts.highlight.solidBackgroundWordOutline" | "tts.highlight.solidBackgroundWordSolidBackground" | "tts.highlight.solidBackgroundWordUnderline" | "tts.highlight.style" | "tts.highlight.underlineWordOutline" | "tts.highlight.underlineWordSolidBackground" | "tts.highlight.underlineWordUnderline" | "tts.highlight.wordColor" | "wizard" | "wizard.buttons" | "wizard.buttons.discover" | "wizard.buttons.goToBooks" | "wizard.buttons.next" | "wizard.description" | "wizard.description.annotations" | "wizard.description.catalogs" | "wizard.description.home" | "wizard.description.readingView1" | "wizard.description.readingView2" | "wizard.description.yourBooks" | "wizard.dontShow" | "wizard.tab" | "wizard.tab.annotations" | "wizard.tab.catalogs" | "wizard.tab.home" | "wizard.tab.readingView" | "wizard.tab.yourBooks" | "wizard.title" | "wizard.title.allBooks" | "wizard.title.newFeature" | "wizard.title.welcome"; } export = typed_i18n_keys; \ No newline at end of file diff --git a/src/typings/en.translation.d.ts b/src/typings/en.translation.d.ts index b6d0be8514..fe81abf9ab 100644 --- a/src/typings/en.translation.d.ts +++ b/src/typings/en.translation.d.ts @@ -297,6 +297,36 @@ declare namespace typed_i18n { (_: "catalog.sort", __?: {}): string; (_: "catalog.tag", __?: {}): string; (_: "catalog.tags", __?: {}): string; (_: "catalog.update", __?: {}): string; + (_: "chatbot", __?: {}): { + readonly "detailedDescTitle": string, + readonly "detailedDescription": string, + readonly "editorDescription": string, + readonly "generateDescription": string, + readonly "generateDescriptionTitle": string, + readonly "inputPlaceholder": string, + readonly "noApiKey": string, + readonly "noDescription": string, + readonly "reset": string, + readonly "sendQuestion": string, + readonly "shortDescTitle": string, + readonly "shortDescription": string, + readonly "systemPromptEditor": string, + readonly "title": string +}; + (_: "chatbot.detailedDescTitle", __?: {}): string; + (_: "chatbot.detailedDescription", __?: {}): string; + (_: "chatbot.editorDescription", __?: {}): string; + (_: "chatbot.generateDescription", __?: {}): string; + (_: "chatbot.generateDescriptionTitle", __?: {}): string; + (_: "chatbot.inputPlaceholder", __?: {}): string; + (_: "chatbot.noApiKey", __?: {}): string; + (_: "chatbot.noDescription", __?: {}): string; + (_: "chatbot.reset", __?: {}): string; + (_: "chatbot.sendQuestion", __?: {}): string; + (_: "chatbot.shortDescTitle", __?: {}): string; + (_: "chatbot.shortDescription", __?: {}): string; + (_: "chatbot.systemPromptEditor", __?: {}): string; + (_: "chatbot.title", __?: {}): string; (_: "dialog", __?: {}): { readonly "annotations": { readonly "descAuthor": string, @@ -2271,6 +2301,16 @@ declare namespace typed_i18n { readonly "person": string, readonly "type": string }, + readonly "apiKey": { + readonly "help": string, + readonly "howTo1": string, + readonly "howTo2": string, + readonly "howTo3": string, + readonly "keyLabel": string, + readonly "mistral": string, + readonly "openAi": string, + readonly "title": string + }, readonly "auth": { readonly "help": string, readonly "title": string, @@ -2524,6 +2564,7 @@ declare namespace typed_i18n { }, readonly "session": { readonly "title": string }, readonly "tabs": { + readonly "aiKeyManager": string, readonly "appearance": string, readonly "general": string, readonly "keyboardShortcuts": string @@ -2550,6 +2591,24 @@ declare namespace typed_i18n { (_: "settings.annotationCreator.organization", __?: {}): string; (_: "settings.annotationCreator.person", __?: {}): string; (_: "settings.annotationCreator.type", __?: {}): string; + (_: "settings.apiKey", __?: {}): { + readonly "help": string, + readonly "howTo1": string, + readonly "howTo2": string, + readonly "howTo3": string, + readonly "keyLabel": string, + readonly "mistral": string, + readonly "openAi": string, + readonly "title": string +}; + (_: "settings.apiKey.help", __?: {}): string; + (_: "settings.apiKey.howTo1", __?: {}): string; + (_: "settings.apiKey.howTo2", __?: {}): string; + (_: "settings.apiKey.howTo3", __?: {}): string; + (_: "settings.apiKey.keyLabel", __?: {}): string; + (_: "settings.apiKey.mistral", __?: {}): string; + (_: "settings.apiKey.openAi", __?: {}): string; + (_: "settings.apiKey.title", __?: {}): string; (_: "settings.auth", __?: {}): { readonly "help": string, readonly "title": string, @@ -3212,10 +3271,12 @@ declare namespace typed_i18n { (_: "settings.session", __?: {}): { readonly "title": string }; (_: "settings.session.title", __?: {}): string; (_: "settings.tabs", __?: {}): { + readonly "aiKeyManager": string, readonly "appearance": string, readonly "general": string, readonly "keyboardShortcuts": string }; + (_: "settings.tabs.aiKeyManager", __?: {}): string; (_: "settings.tabs.appearance", __?: {}): string; (_: "settings.tabs.general", __?: {}): string; (_: "settings.tabs.keyboardShortcuts", __?: {}): string; diff --git a/webpack.config-preprocessor-directives.js b/webpack.config-preprocessor-directives.js index 1d734c53eb..9d51393006 100644 --- a/webpack.config-preprocessor-directives.js +++ b/webpack.config-preprocessor-directives.js @@ -39,6 +39,8 @@ const telemetrySecret = process.env.THORIUM_TELEMETRY_SECRET || ""; // const USE_HTTP_STREAMER = false; +const isAIFeatureEnabled = false; + const data = { __APP_VERSION__: JSON.stringify(version), __PACK_NAME__: JSON.stringify(name), // EDRLab.ThoriumReader @@ -58,6 +60,7 @@ const data = { __TELEMETRY_URL__: JSON.stringify(telemetryUrl), __TELEMETRY_SECRET__: JSON.stringify(telemetrySecret), // __USE_HTTP_STREAMER__: JSON.stringify(USE_HTTP_STREAMER), + __AI_FEATURE__: JSON.stringify(isAIFeatureEnabled), }; // we do not replace "process.env.NODE_ENV" at build-time,