Mayo commited on
refactor: remove openai-compatible feature
Browse files- ui/components/canvas/CanvasToolbar.tsx +13 -63
- ui/components/panels/LlmControls.tsx +20 -70
- ui/components/panels/TextBlocksPanel.tsx +2 -13
- ui/lib/openai.ts +0 -97
- ui/lib/store.ts +12 -142
- ui/public/locales/en-US/translation.json +0 -9
- ui/public/locales/ja-JP/translation.json +0 -9
- ui/public/locales/zh-CN/translation.json +0 -9
- ui/public/locales/zh-TW/translation.json +0 -9
ui/components/canvas/CanvasToolbar.tsx
CHANGED
|
@@ -14,11 +14,8 @@ import {
|
|
| 14 |
import { Separator } from '@/components/ui/separator'
|
| 15 |
import { useAppStore } from '@/lib/store'
|
| 16 |
import { useTextBlocks } from '@/hooks/useTextBlocks'
|
| 17 |
-
import { OPENAI_COMPATIBLE_MODEL_ID } from '@/lib/openai'
|
| 18 |
import { RenderEffect, RgbaColor, TextStyle } from '@/types'
|
| 19 |
import { Button } from '@/components/ui/button'
|
| 20 |
-
import { Input } from '@/components/ui/input'
|
| 21 |
-
import { Textarea } from '@/components/ui/textarea'
|
| 22 |
import { ColorPicker } from '@/components/ui/color-picker'
|
| 23 |
import {
|
| 24 |
Tooltip,
|
|
@@ -326,20 +323,11 @@ function LlmStatusPopover() {
|
|
| 326 |
llmSetSelectedLanguage,
|
| 327 |
llmToggleLoadUnload,
|
| 328 |
llmCheckReady,
|
| 329 |
-
llmOpenAIEndpoint,
|
| 330 |
-
llmOpenAIApiKey,
|
| 331 |
-
llmOpenAIPrompt,
|
| 332 |
-
llmOpenAIModel,
|
| 333 |
-
setLlmOpenAIEndpoint,
|
| 334 |
-
setLlmOpenAIApiKey,
|
| 335 |
-
setLlmOpenAIPrompt,
|
| 336 |
-
setLlmOpenAIModel,
|
| 337 |
} = useAppStore()
|
| 338 |
const { t } = useTranslation()
|
| 339 |
|
| 340 |
const activeLanguages =
|
| 341 |
llmModels.find((model) => model.id === llmSelectedModel)?.languages ?? []
|
| 342 |
-
const isOpenAICompatible = llmSelectedModel === OPENAI_COMPATIBLE_MODEL_ID
|
| 343 |
|
| 344 |
useEffect(() => {
|
| 345 |
llmList()
|
|
@@ -386,9 +374,7 @@ function LlmStatusPopover() {
|
|
| 386 |
<SelectContent>
|
| 387 |
{llmModels.map((model) => (
|
| 388 |
<SelectItem key={model.id} value={model.id}>
|
| 389 |
-
{model.id
|
| 390 |
-
? t('llm.openaiCompatible')
|
| 391 |
-
: model.id}
|
| 392 |
</SelectItem>
|
| 393 |
))}
|
| 394 |
</SelectContent>
|
|
@@ -413,55 +399,19 @@ function LlmStatusPopover() {
|
|
| 413 |
</Select>
|
| 414 |
)}
|
| 415 |
|
| 416 |
-
{/* OpenAI compatible settings */}
|
| 417 |
-
{isOpenAICompatible && (
|
| 418 |
-
<div className='space-y-1.5 rounded border p-1.5'>
|
| 419 |
-
<Input
|
| 420 |
-
type='text'
|
| 421 |
-
value={llmOpenAIEndpoint}
|
| 422 |
-
placeholder={t('llm.openaiEndpointPlaceholder')}
|
| 423 |
-
onChange={(event) => setLlmOpenAIEndpoint(event.target.value)}
|
| 424 |
-
className='h-6 text-xs'
|
| 425 |
-
/>
|
| 426 |
-
<Input
|
| 427 |
-
type='password'
|
| 428 |
-
value={llmOpenAIApiKey}
|
| 429 |
-
placeholder={t('llm.openaiApiKeyPlaceholder')}
|
| 430 |
-
autoComplete='off'
|
| 431 |
-
onChange={(event) => setLlmOpenAIApiKey(event.target.value)}
|
| 432 |
-
className='h-6 text-xs'
|
| 433 |
-
/>
|
| 434 |
-
<Input
|
| 435 |
-
value={llmOpenAIModel}
|
| 436 |
-
placeholder={t('llm.openaiModelPlaceholder')}
|
| 437 |
-
onChange={(event) => setLlmOpenAIModel(event.target.value)}
|
| 438 |
-
className='h-6 text-xs'
|
| 439 |
-
/>
|
| 440 |
-
<Textarea
|
| 441 |
-
value={llmOpenAIPrompt}
|
| 442 |
-
placeholder={t('llm.openaiPromptLabel')}
|
| 443 |
-
rows={2}
|
| 444 |
-
onChange={(event) => setLlmOpenAIPrompt(event.target.value)}
|
| 445 |
-
className='min-h-0 px-2 py-1 text-xs'
|
| 446 |
-
/>
|
| 447 |
-
</div>
|
| 448 |
-
)}
|
| 449 |
-
|
| 450 |
{/* Load/Unload button */}
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
</Button>
|
| 464 |
-
)}
|
| 465 |
</div>
|
| 466 |
</PopoverContent>
|
| 467 |
</Popover>
|
|
|
|
| 14 |
import { Separator } from '@/components/ui/separator'
|
| 15 |
import { useAppStore } from '@/lib/store'
|
| 16 |
import { useTextBlocks } from '@/hooks/useTextBlocks'
|
|
|
|
| 17 |
import { RenderEffect, RgbaColor, TextStyle } from '@/types'
|
| 18 |
import { Button } from '@/components/ui/button'
|
|
|
|
|
|
|
| 19 |
import { ColorPicker } from '@/components/ui/color-picker'
|
| 20 |
import {
|
| 21 |
Tooltip,
|
|
|
|
| 323 |
llmSetSelectedLanguage,
|
| 324 |
llmToggleLoadUnload,
|
| 325 |
llmCheckReady,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
} = useAppStore()
|
| 327 |
const { t } = useTranslation()
|
| 328 |
|
| 329 |
const activeLanguages =
|
| 330 |
llmModels.find((model) => model.id === llmSelectedModel)?.languages ?? []
|
|
|
|
| 331 |
|
| 332 |
useEffect(() => {
|
| 333 |
llmList()
|
|
|
|
| 374 |
<SelectContent>
|
| 375 |
{llmModels.map((model) => (
|
| 376 |
<SelectItem key={model.id} value={model.id}>
|
| 377 |
+
{model.id}
|
|
|
|
|
|
|
| 378 |
</SelectItem>
|
| 379 |
))}
|
| 380 |
</SelectContent>
|
|
|
|
| 399 |
</Select>
|
| 400 |
)}
|
| 401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
{/* Load/Unload button */}
|
| 403 |
+
<Button
|
| 404 |
+
variant='outline'
|
| 405 |
+
size='sm'
|
| 406 |
+
onClick={llmToggleLoadUnload}
|
| 407 |
+
disabled={!llmSelectedModel || llmLoading}
|
| 408 |
+
className='w-full gap-1.5 text-xs'
|
| 409 |
+
>
|
| 410 |
+
{llmLoading && (
|
| 411 |
+
<LoaderCircleIcon className='size-3.5 animate-spin' />
|
| 412 |
+
)}
|
| 413 |
+
{!llmReady ? t('llm.load') : t('llm.unload')}
|
| 414 |
+
</Button>
|
|
|
|
|
|
|
| 415 |
</div>
|
| 416 |
</PopoverContent>
|
| 417 |
</Popover>
|
ui/components/panels/LlmControls.tsx
CHANGED
|
@@ -3,11 +3,8 @@
|
|
| 3 |
import { useEffect, useState } from 'react'
|
| 4 |
import { useTranslation } from 'react-i18next'
|
| 5 |
import { LoaderCircleIcon, SparklesIcon } from 'lucide-react'
|
| 6 |
-
import { OPENAI_COMPATIBLE_MODEL_ID } from '@/lib/openai'
|
| 7 |
import { useAppStore } from '@/lib/store'
|
| 8 |
import { Button } from '@/components/ui/button'
|
| 9 |
-
import { Input } from '@/components/ui/input'
|
| 10 |
-
import { Textarea } from '@/components/ui/textarea'
|
| 11 |
import {
|
| 12 |
Tooltip,
|
| 13 |
TooltipContent,
|
|
@@ -34,21 +31,12 @@ export function LlmControls() {
|
|
| 34 |
llmToggleLoadUnload,
|
| 35 |
llmGenerate,
|
| 36 |
llmCheckReady,
|
| 37 |
-
llmOpenAIEndpoint,
|
| 38 |
-
llmOpenAIApiKey,
|
| 39 |
-
llmOpenAIPrompt,
|
| 40 |
-
llmOpenAIModel,
|
| 41 |
-
setLlmOpenAIEndpoint,
|
| 42 |
-
setLlmOpenAIApiKey,
|
| 43 |
-
setLlmOpenAIPrompt,
|
| 44 |
-
setLlmOpenAIModel,
|
| 45 |
} = useAppStore()
|
| 46 |
const [generating, setGenerating] = useState(false)
|
| 47 |
const { t } = useTranslation()
|
| 48 |
|
| 49 |
const activeLanguages =
|
| 50 |
llmModels.find((model) => model.id === llmSelectedModel)?.languages ?? []
|
| 51 |
-
const isOpenAICompatible = llmSelectedModel === OPENAI_COMPATIBLE_MODEL_ID
|
| 52 |
|
| 53 |
useEffect(() => {
|
| 54 |
llmList()
|
|
@@ -68,9 +56,7 @@ export function LlmControls() {
|
|
| 68 |
<SelectContent>
|
| 69 |
{llmModels.map((model) => (
|
| 70 |
<SelectItem key={model.id} value={model.id}>
|
| 71 |
-
{model.id
|
| 72 |
-
? t('llm.openaiCompatible')
|
| 73 |
-
: model.id}
|
| 74 |
</SelectItem>
|
| 75 |
))}
|
| 76 |
</SelectContent>
|
|
@@ -97,63 +83,27 @@ export function LlmControls() {
|
|
| 97 |
</Select>
|
| 98 |
)}
|
| 99 |
|
| 100 |
-
{/* OpenAI compatible settings */}
|
| 101 |
-
{isOpenAICompatible && (
|
| 102 |
-
<div className='bg-card/50 space-y-1.5 rounded p-1.5'>
|
| 103 |
-
<Input
|
| 104 |
-
type='text'
|
| 105 |
-
value={llmOpenAIEndpoint}
|
| 106 |
-
placeholder={t('llm.openaiEndpointPlaceholder')}
|
| 107 |
-
onChange={(event) => setLlmOpenAIEndpoint(event.target.value)}
|
| 108 |
-
className='h-7 text-xs'
|
| 109 |
-
/>
|
| 110 |
-
<Input
|
| 111 |
-
type='password'
|
| 112 |
-
value={llmOpenAIApiKey}
|
| 113 |
-
placeholder={t('llm.openaiApiKeyPlaceholder')}
|
| 114 |
-
autoComplete='off'
|
| 115 |
-
onChange={(event) => setLlmOpenAIApiKey(event.target.value)}
|
| 116 |
-
className='h-7 text-xs'
|
| 117 |
-
/>
|
| 118 |
-
<Input
|
| 119 |
-
value={llmOpenAIModel}
|
| 120 |
-
placeholder={t('llm.openaiModelPlaceholder')}
|
| 121 |
-
onChange={(event) => setLlmOpenAIModel(event.target.value)}
|
| 122 |
-
className='h-7 text-xs'
|
| 123 |
-
/>
|
| 124 |
-
<Textarea
|
| 125 |
-
value={llmOpenAIPrompt}
|
| 126 |
-
placeholder={t('llm.openaiPromptLabel')}
|
| 127 |
-
rows={2}
|
| 128 |
-
onChange={(event) => setLlmOpenAIPrompt(event.target.value)}
|
| 129 |
-
className='min-h-0 px-2 py-1.5 text-xs'
|
| 130 |
-
/>
|
| 131 |
-
</div>
|
| 132 |
-
)}
|
| 133 |
-
|
| 134 |
{/* Action buttons */}
|
| 135 |
<div className='flex gap-1'>
|
| 136 |
-
|
| 137 |
-
<
|
| 138 |
-
<
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
</Tooltip>
|
| 156 |
-
)}
|
| 157 |
<Tooltip>
|
| 158 |
<TooltipTrigger asChild>
|
| 159 |
<Button
|
|
|
|
| 3 |
import { useEffect, useState } from 'react'
|
| 4 |
import { useTranslation } from 'react-i18next'
|
| 5 |
import { LoaderCircleIcon, SparklesIcon } from 'lucide-react'
|
|
|
|
| 6 |
import { useAppStore } from '@/lib/store'
|
| 7 |
import { Button } from '@/components/ui/button'
|
|
|
|
|
|
|
| 8 |
import {
|
| 9 |
Tooltip,
|
| 10 |
TooltipContent,
|
|
|
|
| 31 |
llmToggleLoadUnload,
|
| 32 |
llmGenerate,
|
| 33 |
llmCheckReady,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
} = useAppStore()
|
| 35 |
const [generating, setGenerating] = useState(false)
|
| 36 |
const { t } = useTranslation()
|
| 37 |
|
| 38 |
const activeLanguages =
|
| 39 |
llmModels.find((model) => model.id === llmSelectedModel)?.languages ?? []
|
|
|
|
| 40 |
|
| 41 |
useEffect(() => {
|
| 42 |
llmList()
|
|
|
|
| 56 |
<SelectContent>
|
| 57 |
{llmModels.map((model) => (
|
| 58 |
<SelectItem key={model.id} value={model.id}>
|
| 59 |
+
{model.id}
|
|
|
|
|
|
|
| 60 |
</SelectItem>
|
| 61 |
))}
|
| 62 |
</SelectContent>
|
|
|
|
| 83 |
</Select>
|
| 84 |
)}
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
{/* Action buttons */}
|
| 87 |
<div className='flex gap-1'>
|
| 88 |
+
<Tooltip>
|
| 89 |
+
<TooltipTrigger asChild>
|
| 90 |
+
<Button
|
| 91 |
+
variant='outline'
|
| 92 |
+
size='sm'
|
| 93 |
+
onClick={llmToggleLoadUnload}
|
| 94 |
+
disabled={!llmSelectedModel || llmLoading}
|
| 95 |
+
className='flex-1 gap-1.5 text-xs'
|
| 96 |
+
>
|
| 97 |
+
{llmLoading && (
|
| 98 |
+
<LoaderCircleIcon className='size-3.5 animate-spin' />
|
| 99 |
+
)}
|
| 100 |
+
{!llmReady ? t('llm.load') : t('llm.unload')}
|
| 101 |
+
</Button>
|
| 102 |
+
</TooltipTrigger>
|
| 103 |
+
<TooltipContent side='bottom' sideOffset={4}>
|
| 104 |
+
{!llmReady ? t('llm.loadTooltip') : t('llm.unloadTooltip')}
|
| 105 |
+
</TooltipContent>
|
| 106 |
+
</Tooltip>
|
|
|
|
|
|
|
| 107 |
<Tooltip>
|
| 108 |
<TooltipTrigger asChild>
|
| 109 |
<Button
|
ui/components/panels/TextBlocksPanel.tsx
CHANGED
|
@@ -6,7 +6,6 @@ import { motion } from 'motion/react'
|
|
| 6 |
import { TextBlock } from '@/types'
|
| 7 |
import { Languages, LoaderCircleIcon } from 'lucide-react'
|
| 8 |
import { useTextBlocks } from '@/hooks/useTextBlocks'
|
| 9 |
-
import { isOpenAIConfigured, OPENAI_COMPATIBLE_MODEL_ID } from '@/lib/openai'
|
| 10 |
import { useAppStore } from '@/lib/store'
|
| 11 |
import {
|
| 12 |
Accordion,
|
|
@@ -32,18 +31,8 @@ export function TextBlocksPanel() {
|
|
| 32 |
replaceBlock,
|
| 33 |
} = useTextBlocks()
|
| 34 |
const { t } = useTranslation()
|
| 35 |
-
const {
|
| 36 |
-
llmGenerate,
|
| 37 |
-
llmReady,
|
| 38 |
-
llmSelectedModel,
|
| 39 |
-
llmOpenAIEndpoint,
|
| 40 |
-
llmOpenAIApiKey,
|
| 41 |
-
} = useAppStore()
|
| 42 |
const [generatingIndex, setGeneratingIndex] = useState<number | null>(null)
|
| 43 |
-
const llmAvailable =
|
| 44 |
-
llmSelectedModel === OPENAI_COMPATIBLE_MODEL_ID
|
| 45 |
-
? isOpenAIConfigured(llmOpenAIEndpoint, llmOpenAIApiKey)
|
| 46 |
-
: llmReady
|
| 47 |
|
| 48 |
if (!document) {
|
| 49 |
return (
|
|
@@ -101,7 +90,7 @@ export function TextBlocksPanel() {
|
|
| 101 |
onChange={(updates) => void replaceBlock(index, updates)}
|
| 102 |
onGenerate={() => void handleGenerate(index)}
|
| 103 |
generating={generatingIndex === index}
|
| 104 |
-
llmReady={
|
| 105 |
/>
|
| 106 |
))}
|
| 107 |
</Accordion>
|
|
|
|
| 6 |
import { TextBlock } from '@/types'
|
| 7 |
import { Languages, LoaderCircleIcon } from 'lucide-react'
|
| 8 |
import { useTextBlocks } from '@/hooks/useTextBlocks'
|
|
|
|
| 9 |
import { useAppStore } from '@/lib/store'
|
| 10 |
import {
|
| 11 |
Accordion,
|
|
|
|
| 31 |
replaceBlock,
|
| 32 |
} = useTextBlocks()
|
| 33 |
const { t } = useTranslation()
|
| 34 |
+
const { llmGenerate, llmReady } = useAppStore()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
const [generatingIndex, setGeneratingIndex] = useState<number | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
if (!document) {
|
| 38 |
return (
|
|
|
|
| 90 |
onChange={(updates) => void replaceBlock(index, updates)}
|
| 91 |
onGenerate={() => void handleGenerate(index)}
|
| 92 |
generating={generatingIndex === index}
|
| 93 |
+
llmReady={llmReady}
|
| 94 |
/>
|
| 95 |
))}
|
| 96 |
</Accordion>
|
ui/lib/openai.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
| 1 |
-
export type LlmModelInfo = {
|
| 2 |
-
id: string
|
| 3 |
-
languages: string[]
|
| 4 |
-
}
|
| 5 |
-
|
| 6 |
-
export const OPENAI_COMPATIBLE_MODEL_ID = 'openai-compatible'
|
| 7 |
-
export const OPENAI_DEFAULT_MODEL = 'gpt-4o-mini'
|
| 8 |
-
|
| 9 |
-
export const OPENAI_COMPATIBLE_MODEL: LlmModelInfo = {
|
| 10 |
-
id: OPENAI_COMPATIBLE_MODEL_ID,
|
| 11 |
-
languages: [],
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
export const isOpenAIModel = (modelId?: string) =>
|
| 15 |
-
modelId === OPENAI_COMPATIBLE_MODEL_ID
|
| 16 |
-
|
| 17 |
-
export const isOpenAIConfigured = (endpoint: string, apiKey: string) =>
|
| 18 |
-
endpoint.trim().length > 0 || apiKey.trim().length > 0
|
| 19 |
-
|
| 20 |
-
const normalizeOpenAIEndpoint = (endpoint: string) => {
|
| 21 |
-
const baseEndpoint = endpoint.trim() || 'https://api.openai.com/v1/'
|
| 22 |
-
|
| 23 |
-
const parsed = new URL(baseEndpoint)
|
| 24 |
-
if (!parsed.pathname.endsWith('/chat/completions')) {
|
| 25 |
-
const normalizedPath = parsed.pathname.replace(/\/$/, '')
|
| 26 |
-
parsed.pathname = `${normalizedPath}/chat/completions`
|
| 27 |
-
}
|
| 28 |
-
return parsed.toString()
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
type OpenAIChoice = {
|
| 32 |
-
message?: { content?: string }
|
| 33 |
-
text?: string
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
const parseOpenAIResponse = (payload: { choices?: OpenAIChoice[] }) => {
|
| 37 |
-
const choice = payload?.choices?.[0]
|
| 38 |
-
const content = choice?.message?.content ?? choice?.text
|
| 39 |
-
if (!content) {
|
| 40 |
-
throw new Error('OpenAI compatible response missing content')
|
| 41 |
-
}
|
| 42 |
-
return content.trim()
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
export const callOpenAICompletion = async ({
|
| 46 |
-
endpoint,
|
| 47 |
-
apiKey,
|
| 48 |
-
prompt,
|
| 49 |
-
content,
|
| 50 |
-
model,
|
| 51 |
-
}: {
|
| 52 |
-
endpoint: string
|
| 53 |
-
apiKey: string
|
| 54 |
-
prompt: string
|
| 55 |
-
content: string
|
| 56 |
-
model?: string
|
| 57 |
-
}) => {
|
| 58 |
-
const resolvedEndpoint = endpoint.trim() || 'https://api.openai.com/v1/'
|
| 59 |
-
const url = normalizeOpenAIEndpoint(resolvedEndpoint)
|
| 60 |
-
if (resolvedEndpoint === 'https://api.openai.com/v1/' && !apiKey.trim()) {
|
| 61 |
-
throw new Error('OpenAI compatible API key is required')
|
| 62 |
-
}
|
| 63 |
-
const parsed = new URL(url)
|
| 64 |
-
const selectedModel =
|
| 65 |
-
model?.trim() || parsed.searchParams.get('model') || OPENAI_DEFAULT_MODEL
|
| 66 |
-
parsed.searchParams.delete('model')
|
| 67 |
-
|
| 68 |
-
const headers: Record<string, string> = {
|
| 69 |
-
'Content-Type': 'application/json',
|
| 70 |
-
}
|
| 71 |
-
if (apiKey && apiKey.trim()) {
|
| 72 |
-
headers.Authorization = `Bearer ${apiKey}`
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
const response = await fetch(parsed.toString(), {
|
| 76 |
-
method: 'POST',
|
| 77 |
-
headers,
|
| 78 |
-
body: JSON.stringify({
|
| 79 |
-
model: selectedModel,
|
| 80 |
-
messages: [
|
| 81 |
-
{ role: 'system', content: prompt },
|
| 82 |
-
{ role: 'user', content },
|
| 83 |
-
],
|
| 84 |
-
temperature: 0.2,
|
| 85 |
-
}),
|
| 86 |
-
})
|
| 87 |
-
|
| 88 |
-
if (!response.ok) {
|
| 89 |
-
const message = await response.text()
|
| 90 |
-
throw new Error(
|
| 91 |
-
`OpenAI compatible request failed (${response.status}): ${message}`,
|
| 92 |
-
)
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
const payload = (await response.json()) as { choices?: OpenAIChoice[] }
|
| 96 |
-
return parseOpenAIResponse(payload)
|
| 97 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ui/lib/store.ts
CHANGED
|
@@ -10,15 +10,10 @@ import {
|
|
| 10 |
ToolMode,
|
| 11 |
} from '@/types'
|
| 12 |
import { createOperationSlice, type OperationSlice } from '@/lib/operations'
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
OPENAI_COMPATIBLE_MODEL,
|
| 18 |
-
OPENAI_DEFAULT_MODEL,
|
| 19 |
-
type LlmModelInfo,
|
| 20 |
-
} from '@/lib/openai'
|
| 21 |
-
import { t } from 'i18next'
|
| 22 |
|
| 23 |
type ProcessAction = 'detect' | 'ocr' | 'inpaint' | 'llmGenerate' | 'render'
|
| 24 |
|
|
@@ -166,10 +161,6 @@ type AppState = OperationSlice & {
|
|
| 166 |
llmSelectedLanguage?: string
|
| 167 |
llmReady: boolean
|
| 168 |
llmLoading: boolean
|
| 169 |
-
llmOpenAIEndpoint: string
|
| 170 |
-
llmOpenAIApiKey: string
|
| 171 |
-
llmOpenAIPrompt: string
|
| 172 |
-
llmOpenAIModel: string
|
| 173 |
// ui + actions
|
| 174 |
setTotalPages: (count: number) => void
|
| 175 |
fetchCurrentDocument: () => Promise<void>
|
|
@@ -239,66 +230,9 @@ type AppState = OperationSlice & {
|
|
| 239 |
index?: number,
|
| 240 |
text_block_index?: number,
|
| 241 |
) => Promise<void>
|
| 242 |
-
setLlmOpenAIEndpoint: (endpoint: string) => void
|
| 243 |
-
setLlmOpenAIApiKey: (apiKey: string) => void
|
| 244 |
-
setLlmOpenAIPrompt: (prompt: string) => void
|
| 245 |
-
setLlmOpenAIModel: (model: string) => void
|
| 246 |
}
|
| 247 |
|
| 248 |
export const useAppStore = create<AppState>((set, get) => {
|
| 249 |
-
const isOpenAICompatible = () => isOpenAIModel(get().llmSelectedModel)
|
| 250 |
-
|
| 251 |
-
const syncOpenAIReady = () => {
|
| 252 |
-
if (!isOpenAICompatible()) return false
|
| 253 |
-
const { llmOpenAIEndpoint, llmOpenAIApiKey } = get()
|
| 254 |
-
const ready = isOpenAIConfigured(llmOpenAIEndpoint, llmOpenAIApiKey)
|
| 255 |
-
set({ llmReady: ready, llmLoading: false })
|
| 256 |
-
return true
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
const generateWithOpenAI = async (
|
| 260 |
-
textBlockIndex?: number,
|
| 261 |
-
): Promise<TextBlock[] | void> => {
|
| 262 |
-
const {
|
| 263 |
-
currentDocument,
|
| 264 |
-
llmOpenAIEndpoint,
|
| 265 |
-
llmOpenAIApiKey,
|
| 266 |
-
llmOpenAIPrompt,
|
| 267 |
-
llmOpenAIModel,
|
| 268 |
-
} = get()
|
| 269 |
-
if (!currentDocument) return
|
| 270 |
-
await textBlockSyncer.flush()
|
| 271 |
-
|
| 272 |
-
const blocks = currentDocument.textBlocks ?? []
|
| 273 |
-
const hasSingle = typeof textBlockIndex === 'number' && textBlockIndex >= 0
|
| 274 |
-
const sourceText = hasSingle
|
| 275 |
-
? (blocks[textBlockIndex!]?.text ?? '')
|
| 276 |
-
: blocks.map((block) => block.text ?? '').join('\n')
|
| 277 |
-
if (!sourceText.trim()) return
|
| 278 |
-
|
| 279 |
-
const completion = await callOpenAICompletion({
|
| 280 |
-
endpoint: llmOpenAIEndpoint,
|
| 281 |
-
apiKey: llmOpenAIApiKey,
|
| 282 |
-
prompt: llmOpenAIPrompt.trim(),
|
| 283 |
-
content: sourceText,
|
| 284 |
-
model: llmOpenAIModel,
|
| 285 |
-
})
|
| 286 |
-
|
| 287 |
-
const translatedLines = hasSingle ? null : completion.split(/\r?\n/)
|
| 288 |
-
const updatedBlocks = blocks.map((block, i) => {
|
| 289 |
-
if (hasSingle) {
|
| 290 |
-
return i === textBlockIndex
|
| 291 |
-
? { ...block, translation: completion }
|
| 292 |
-
: block
|
| 293 |
-
}
|
| 294 |
-
const translated = translatedLines?.[i]
|
| 295 |
-
if (translated === undefined) return block
|
| 296 |
-
return { ...block, translation: translated }
|
| 297 |
-
})
|
| 298 |
-
|
| 299 |
-
return updatedBlocks
|
| 300 |
-
}
|
| 301 |
-
|
| 302 |
return {
|
| 303 |
...createOperationSlice(set),
|
| 304 |
totalPages: 0,
|
|
@@ -322,10 +256,6 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 322 |
llmSelectedLanguage: undefined,
|
| 323 |
llmReady: false,
|
| 324 |
llmLoading: false,
|
| 325 |
-
llmOpenAIEndpoint: '',
|
| 326 |
-
llmOpenAIApiKey: '',
|
| 327 |
-
llmOpenAIPrompt: t('llm.openaiPromptPlaceholder'),
|
| 328 |
-
llmOpenAIModel: OPENAI_DEFAULT_MODEL,
|
| 329 |
operation: undefined,
|
| 330 |
setTotalPages: (count: number) => {
|
| 331 |
set((state) => ({
|
|
@@ -564,10 +494,7 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 564 |
},
|
| 565 |
llmList: async () => {
|
| 566 |
try {
|
| 567 |
-
const models = [
|
| 568 |
-
...(await invoke<LlmModelInfo[]>('llm_list')),
|
| 569 |
-
OPENAI_COMPATIBLE_MODEL,
|
| 570 |
-
]
|
| 571 |
set({ llmModels: models })
|
| 572 |
const currentModel = get().llmSelectedModel
|
| 573 |
const currentLanguage = get().llmSelectedLanguage
|
|
@@ -584,7 +511,6 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 584 |
llmSelectedModel: nextModel,
|
| 585 |
llmSelectedLanguage: nextLanguage,
|
| 586 |
})
|
| 587 |
-
syncOpenAIReady()
|
| 588 |
} catch (_) {}
|
| 589 |
},
|
| 590 |
llmSetSelectedModel: async (id: string) => {
|
|
@@ -600,7 +526,6 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 600 |
llmLoading: false,
|
| 601 |
llmReady: false,
|
| 602 |
})
|
| 603 |
-
syncOpenAIReady()
|
| 604 |
},
|
| 605 |
llmSetSelectedLanguage: (language: string) => {
|
| 606 |
const languages = findModelLanguages(
|
|
@@ -611,11 +536,6 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 611 |
set({ llmSelectedLanguage: language })
|
| 612 |
},
|
| 613 |
llmToggleLoadUnload: async () => {
|
| 614 |
-
if (isOpenAICompatible()) {
|
| 615 |
-
syncOpenAIReady()
|
| 616 |
-
return
|
| 617 |
-
}
|
| 618 |
-
|
| 619 |
// unload
|
| 620 |
if (get().llmReady) {
|
| 621 |
await invoke('llm_offload')
|
|
@@ -658,7 +578,6 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 658 |
}
|
| 659 |
},
|
| 660 |
llmCheckReady: async () => {
|
| 661 |
-
if (syncOpenAIReady()) return
|
| 662 |
if (get().llmReady) return
|
| 663 |
if (llmReadyCheckInFlight) {
|
| 664 |
await llmReadyCheckInFlight
|
|
@@ -685,27 +604,11 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 685 |
: languages[0]
|
| 686 |
: undefined
|
| 687 |
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
}
|
| 694 |
-
const updatedBlocks = await generateWithOpenAI(textBlockIndex)
|
| 695 |
-
if (updatedBlocks) {
|
| 696 |
-
// Save the updated blocks to the server
|
| 697 |
-
await invoke('update_text_blocks', {
|
| 698 |
-
index,
|
| 699 |
-
textBlocks: updatedBlocks,
|
| 700 |
-
})
|
| 701 |
-
}
|
| 702 |
-
} else {
|
| 703 |
-
await invoke('llm_generate', {
|
| 704 |
-
index,
|
| 705 |
-
textBlockIndex,
|
| 706 |
-
language,
|
| 707 |
-
})
|
| 708 |
-
}
|
| 709 |
|
| 710 |
if (index === get().currentDocumentIndex) {
|
| 711 |
await get().refreshCurrentDocument()
|
|
@@ -716,26 +619,8 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 716 |
void get().renderTextBlock(undefined, index, textBlockIndex)
|
| 717 |
}
|
| 718 |
},
|
| 719 |
-
setLlmOpenAIEndpoint: (endpoint: string) => {
|
| 720 |
-
set({ llmOpenAIEndpoint: endpoint })
|
| 721 |
-
syncOpenAIReady()
|
| 722 |
-
},
|
| 723 |
-
setLlmOpenAIApiKey: (apiKey: string) => {
|
| 724 |
-
set({ llmOpenAIApiKey: apiKey })
|
| 725 |
-
syncOpenAIReady()
|
| 726 |
-
},
|
| 727 |
-
setLlmOpenAIPrompt: (prompt: string) => {
|
| 728 |
-
set({ llmOpenAIPrompt: prompt })
|
| 729 |
-
syncOpenAIReady()
|
| 730 |
-
},
|
| 731 |
-
setLlmOpenAIModel: (model: string) => {
|
| 732 |
-
set({ llmOpenAIModel: model })
|
| 733 |
-
syncOpenAIReady()
|
| 734 |
-
},
|
| 735 |
-
|
| 736 |
// batch proceeses
|
| 737 |
processImage: async (_, index, options) => {
|
| 738 |
-
const openAISelected = isOpenAICompatible()
|
| 739 |
const normalizedOptions: ProcessImageOptionsObject =
|
| 740 |
typeof options === 'function'
|
| 741 |
? { onProgress: options, skipOperationTracking: false }
|
|
@@ -746,16 +631,9 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 746 |
const operation = get().operation
|
| 747 |
const isBatchRun = operation?.type === 'process-all'
|
| 748 |
|
| 749 |
-
if (!get().llmReady
|
| 750 |
await get().llmList()
|
| 751 |
await get().llmToggleLoadUnload()
|
| 752 |
-
} else if (openAISelected) {
|
| 753 |
-
syncOpenAIReady()
|
| 754 |
-
if (!get().llmReady) {
|
| 755 |
-
throw new Error(
|
| 756 |
-
'OpenAI compatible endpoint and API key are required before processing.',
|
| 757 |
-
)
|
| 758 |
-
}
|
| 759 |
}
|
| 760 |
|
| 761 |
index = index ?? get().currentDocumentIndex
|
|
@@ -844,18 +722,10 @@ export const useAppStore = create<AppState>((set, get) => {
|
|
| 844 |
processAllImages: async () => {
|
| 845 |
const total = get().totalPages
|
| 846 |
if (!total) return
|
| 847 |
-
const openAISelected = isOpenAICompatible()
|
| 848 |
|
| 849 |
-
if (!get().llmReady
|
| 850 |
await get().llmList()
|
| 851 |
await get().llmToggleLoadUnload()
|
| 852 |
-
} else if (openAISelected) {
|
| 853 |
-
syncOpenAIReady()
|
| 854 |
-
if (!get().llmReady) {
|
| 855 |
-
throw new Error(
|
| 856 |
-
'OpenAI compatible endpoint and API key are required before processing.',
|
| 857 |
-
)
|
| 858 |
-
}
|
| 859 |
}
|
| 860 |
|
| 861 |
get().startOperation({
|
|
|
|
| 10 |
ToolMode,
|
| 11 |
} from '@/types'
|
| 12 |
import { createOperationSlice, type OperationSlice } from '@/lib/operations'
|
| 13 |
+
type LlmModelInfo = {
|
| 14 |
+
id: string
|
| 15 |
+
languages: string[]
|
| 16 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
type ProcessAction = 'detect' | 'ocr' | 'inpaint' | 'llmGenerate' | 'render'
|
| 19 |
|
|
|
|
| 161 |
llmSelectedLanguage?: string
|
| 162 |
llmReady: boolean
|
| 163 |
llmLoading: boolean
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
// ui + actions
|
| 165 |
setTotalPages: (count: number) => void
|
| 166 |
fetchCurrentDocument: () => Promise<void>
|
|
|
|
| 230 |
index?: number,
|
| 231 |
text_block_index?: number,
|
| 232 |
) => Promise<void>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
}
|
| 234 |
|
| 235 |
export const useAppStore = create<AppState>((set, get) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
return {
|
| 237 |
...createOperationSlice(set),
|
| 238 |
totalPages: 0,
|
|
|
|
| 256 |
llmSelectedLanguage: undefined,
|
| 257 |
llmReady: false,
|
| 258 |
llmLoading: false,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
operation: undefined,
|
| 260 |
setTotalPages: (count: number) => {
|
| 261 |
set((state) => ({
|
|
|
|
| 494 |
},
|
| 495 |
llmList: async () => {
|
| 496 |
try {
|
| 497 |
+
const models = await invoke<LlmModelInfo[]>('llm_list')
|
|
|
|
|
|
|
|
|
|
| 498 |
set({ llmModels: models })
|
| 499 |
const currentModel = get().llmSelectedModel
|
| 500 |
const currentLanguage = get().llmSelectedLanguage
|
|
|
|
| 511 |
llmSelectedModel: nextModel,
|
| 512 |
llmSelectedLanguage: nextLanguage,
|
| 513 |
})
|
|
|
|
| 514 |
} catch (_) {}
|
| 515 |
},
|
| 516 |
llmSetSelectedModel: async (id: string) => {
|
|
|
|
| 526 |
llmLoading: false,
|
| 527 |
llmReady: false,
|
| 528 |
})
|
|
|
|
| 529 |
},
|
| 530 |
llmSetSelectedLanguage: (language: string) => {
|
| 531 |
const languages = findModelLanguages(
|
|
|
|
| 536 |
set({ llmSelectedLanguage: language })
|
| 537 |
},
|
| 538 |
llmToggleLoadUnload: async () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
// unload
|
| 540 |
if (get().llmReady) {
|
| 541 |
await invoke('llm_offload')
|
|
|
|
| 578 |
}
|
| 579 |
},
|
| 580 |
llmCheckReady: async () => {
|
|
|
|
| 581 |
if (get().llmReady) return
|
| 582 |
if (llmReadyCheckInFlight) {
|
| 583 |
await llmReadyCheckInFlight
|
|
|
|
| 604 |
: languages[0]
|
| 605 |
: undefined
|
| 606 |
|
| 607 |
+
await invoke('llm_generate', {
|
| 608 |
+
index,
|
| 609 |
+
textBlockIndex,
|
| 610 |
+
language,
|
| 611 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
|
| 613 |
if (index === get().currentDocumentIndex) {
|
| 614 |
await get().refreshCurrentDocument()
|
|
|
|
| 619 |
void get().renderTextBlock(undefined, index, textBlockIndex)
|
| 620 |
}
|
| 621 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
// batch proceeses
|
| 623 |
processImage: async (_, index, options) => {
|
|
|
|
| 624 |
const normalizedOptions: ProcessImageOptionsObject =
|
| 625 |
typeof options === 'function'
|
| 626 |
? { onProgress: options, skipOperationTracking: false }
|
|
|
|
| 631 |
const operation = get().operation
|
| 632 |
const isBatchRun = operation?.type === 'process-all'
|
| 633 |
|
| 634 |
+
if (!get().llmReady) {
|
| 635 |
await get().llmList()
|
| 636 |
await get().llmToggleLoadUnload()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
}
|
| 638 |
|
| 639 |
index = index ?? get().currentDocumentIndex
|
|
|
|
| 722 |
processAllImages: async () => {
|
| 723 |
const total = get().totalPages
|
| 724 |
if (!total) return
|
|
|
|
| 725 |
|
| 726 |
+
if (!get().llmReady) {
|
| 727 |
await get().llmList()
|
| 728 |
await get().llmToggleLoadUnload()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
}
|
| 730 |
|
| 731 |
get().startOperation({
|
ui/public/locales/en-US/translation.json
CHANGED
|
@@ -85,15 +85,6 @@
|
|
| 85 |
"idle": "Idle",
|
| 86 |
"selectPlaceholder": "Select model",
|
| 87 |
"modelLabel": "Model",
|
| 88 |
-
"openaiCompatible": "OpenAI Compatible",
|
| 89 |
-
"openaiEndpointLabel": "OpenAI compatible endpoint",
|
| 90 |
-
"openaiEndpointPlaceholder": "https://api.openai.com/v1",
|
| 91 |
-
"openaiApiKeyLabel": "API key",
|
| 92 |
-
"openaiApiKeyPlaceholder": "sk-...",
|
| 93 |
-
"openaiModelLabel": "Model name",
|
| 94 |
-
"openaiModelPlaceholder": "gpt-4o-mini",
|
| 95 |
-
"openaiPromptLabel": "Prompt",
|
| 96 |
-
"openaiPromptPlaceholder": "Translate the following text to Chinese, retain formatting, do not add extra explanations:\n",
|
| 97 |
"languageLabel": "Target language",
|
| 98 |
"languagePlaceholder": "Select language",
|
| 99 |
"load": "Load",
|
|
|
|
| 85 |
"idle": "Idle",
|
| 86 |
"selectPlaceholder": "Select model",
|
| 87 |
"modelLabel": "Model",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
"languageLabel": "Target language",
|
| 89 |
"languagePlaceholder": "Select language",
|
| 90 |
"load": "Load",
|
ui/public/locales/ja-JP/translation.json
CHANGED
|
@@ -85,15 +85,6 @@
|
|
| 85 |
"idle": "待機中",
|
| 86 |
"selectPlaceholder": "モデルを選択",
|
| 87 |
"modelLabel": "モデル",
|
| 88 |
-
"openaiCompatible": "OpenAI 互換サービス",
|
| 89 |
-
"openaiEndpointLabel": "OpenAI 端点",
|
| 90 |
-
"openaiEndpointPlaceholder": "https://api.openai.com/v1",
|
| 91 |
-
"openaiApiKeyLabel": "API キー",
|
| 92 |
-
"openaiApiKeyPlaceholder": "sk-...",
|
| 93 |
-
"openaiModelLabel": "モデル名",
|
| 94 |
-
"openaiModelPlaceholder": "gpt-4o-mini",
|
| 95 |
-
"openaiPromptLabel": "プロンプト",
|
| 96 |
-
"openaiPromptPlaceholder": "以下のテキストを日本語に翻訳し、フォーマットを保持し、追加の説明を加えないでください:\n",
|
| 97 |
"languageLabel": "翻訳先の言語",
|
| 98 |
"languagePlaceholder": "言語を選択",
|
| 99 |
"load": "読み込み",
|
|
|
|
| 85 |
"idle": "待機中",
|
| 86 |
"selectPlaceholder": "モデルを選択",
|
| 87 |
"modelLabel": "モデル",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
"languageLabel": "翻訳先の言語",
|
| 89 |
"languagePlaceholder": "言語を選択",
|
| 90 |
"load": "読み込み",
|
ui/public/locales/zh-CN/translation.json
CHANGED
|
@@ -85,15 +85,6 @@
|
|
| 85 |
"idle": "空闲",
|
| 86 |
"selectPlaceholder": "选择模型",
|
| 87 |
"modelLabel": "模型",
|
| 88 |
-
"openaiCompatible": "OpenAI 兼容服务",
|
| 89 |
-
"openaiEndpointLabel": "OpenAI 端点",
|
| 90 |
-
"openaiEndpointPlaceholder": "https://api.openai.com/v1",
|
| 91 |
-
"openaiApiKeyLabel": "API 密钥",
|
| 92 |
-
"openaiApiKeyPlaceholder": "sk-...",
|
| 93 |
-
"openaiModelLabel": "模型名称",
|
| 94 |
-
"openaiModelPlaceholder": "gpt-4o-mini",
|
| 95 |
-
"openaiPromptLabel": "提示词",
|
| 96 |
-
"openaiPromptPlaceholder": "将下列文本翻译为中文,保留格式,不要添加额外说明:\n",
|
| 97 |
"languageLabel": "目标语言",
|
| 98 |
"languagePlaceholder": "选择语言",
|
| 99 |
"load": "加载",
|
|
|
|
| 85 |
"idle": "空闲",
|
| 86 |
"selectPlaceholder": "选择模型",
|
| 87 |
"modelLabel": "模型",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
"languageLabel": "目标语言",
|
| 89 |
"languagePlaceholder": "选择语言",
|
| 90 |
"load": "加载",
|
ui/public/locales/zh-TW/translation.json
CHANGED
|
@@ -85,15 +85,6 @@
|
|
| 85 |
"idle": "閒置",
|
| 86 |
"selectPlaceholder": "選擇模型",
|
| 87 |
"modelLabel": "模型",
|
| 88 |
-
"openaiCompatible": "OpenAI 兼容服務",
|
| 89 |
-
"openaiEndpointLabel": "OpenAI 端點",
|
| 90 |
-
"openaiEndpointPlaceholder": "https://api.openai.com/v1",
|
| 91 |
-
"openaiApiKeyLabel": "API 密鑰",
|
| 92 |
-
"openaiApiKeyPlaceholder": "sk-...",
|
| 93 |
-
"openaiModelLabel": "模型名稱",
|
| 94 |
-
"openaiModelPlaceholder": "gpt-4o-mini",
|
| 95 |
-
"openaiPromptLabel": "提示詞",
|
| 96 |
-
"openaiPromptPlaceholder": "將下列文本翻譯為繁體中文,保留格式,不要添加額外說明:\n",
|
| 97 |
"languageLabel": "目標語言",
|
| 98 |
"languagePlaceholder": "選擇語言",
|
| 99 |
"load": "載入",
|
|
|
|
| 85 |
"idle": "閒置",
|
| 86 |
"selectPlaceholder": "選擇模型",
|
| 87 |
"modelLabel": "模型",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
"languageLabel": "目標語言",
|
| 89 |
"languagePlaceholder": "選擇語言",
|
| 90 |
"load": "載入",
|