完善一些提示性内容

This commit is contained in:
kura 2026-05-02 16:48:32 +08:00
parent 6eb2fc18b3
commit 5f5255830f
2 changed files with 76 additions and 10 deletions

View File

@ -67,17 +67,20 @@ const translationConfig = ref<TranslationConfig>({
apiBase: localStorage.getItem('llm.apiBase') ?? 'https://open.bigmodel.cn/api/paas/v4', apiBase: localStorage.getItem('llm.apiBase') ?? 'https://open.bigmodel.cn/api/paas/v4',
apiKey: localStorage.getItem('llm.apiKey') ?? '', apiKey: localStorage.getItem('llm.apiKey') ?? '',
model: localStorage.getItem('llm.model') ?? 'GLM-4.7-Flash', model: localStorage.getItem('llm.model') ?? 'GLM-4.7-Flash',
batchSize: Number(localStorage.getItem('llm.batchSize') ?? '60'), batchSize: Number(localStorage.getItem('llm.batchSize') ?? '12'),
contextSize: Number(localStorage.getItem('llm.contextSize') ?? '5'), contextSize: Number(localStorage.getItem('llm.contextSize') ?? '5'),
}) })
const pending = ref(false) const pending = ref(false)
const feedback = ref('') const feedback = ref('')
const feedbackTone = ref<'normal' | 'error'>('normal')
const showAdvanced = ref(false) const showAdvanced = ref(false)
const isDragging = ref(false) const isDragging = ref(false)
const showApiKeyDialog = ref(false) const showApiKeyDialog = ref(false)
const apiKeyUrlCopied = ref(false) const apiKeyUrlCopied = ref(false)
const authorUrlCopied = ref(false)
let unlistenMenuAction: UnlistenFn | null = null let unlistenMenuAction: UnlistenFn | null = null
let dragDropUnlistenFn: UnlistenFn | null = null let dragDropUnlistenFn: UnlistenFn | null = null
let authorCopyTimer: ReturnType<typeof setTimeout> | null = null
const selectedTask = computed(() => taskStore.selectedTask) const selectedTask = computed(() => taskStore.selectedTask)
const hasTranslationKey = computed(() => translationConfig.value.apiKey.trim().length > 0) const hasTranslationKey = computed(() => translationConfig.value.apiKey.trim().length > 0)
@ -98,10 +101,29 @@ onUnmounted(() => {
dragDropUnlistenFn() dragDropUnlistenFn()
dragDropUnlistenFn = null dragDropUnlistenFn = null
} }
if (authorCopyTimer) {
clearTimeout(authorCopyTimer)
authorCopyTimer = null
}
}) })
watch(locale, (newLocale) => { watch(locale, (newLocale) => {
localStorage.setItem('locale', newLocale) localStorage.setItem('locale', newLocale)
if (outputMode.value === 'translate' && !hasTranslationKey.value) {
showMissingApiKeyFeedback()
}
})
watch([outputMode, hasTranslationKey], ([mode, hasKey]) => {
if (mode === 'translate' && !hasKey) {
showMissingApiKeyFeedback()
return
}
if (feedbackTone.value === 'error' && feedback.value === t('app.feedback.noApiKey')) {
feedbackTone.value = 'normal'
feedback.value = ''
}
}) })
function persistTranslationConfig() { function persistTranslationConfig() {
@ -115,9 +137,15 @@ function persistTranslationConfig() {
function resetModelPaths() { function resetModelPaths() {
whisperModelPath.value = defaultModelPaths.value?.whisperModelPath ?? '' whisperModelPath.value = defaultModelPaths.value?.whisperModelPath ?? ''
vadModelPath.value = defaultModelPaths.value?.vadModelPath ?? '' vadModelPath.value = defaultModelPaths.value?.vadModelPath ?? ''
feedbackTone.value = 'normal'
feedback.value = t('app.feedback.restoredDefaults') feedback.value = t('app.feedback.restoredDefaults')
} }
function showMissingApiKeyFeedback() {
feedbackTone.value = 'error'
feedback.value = t('app.feedback.noApiKey')
}
function freeApiKeyHint() { function freeApiKeyHint() {
if (locale.value === 'zh-CN') { if (locale.value === 'zh-CN') {
return '开始翻译任务前需要先填写 LLM API Key。你可以选择下面任意一种方式。' return '开始翻译任务前需要先填写 LLM API Key。你可以选择下面任意一种方式。'
@ -127,7 +155,7 @@ function freeApiKeyHint() {
} }
function openApiKeyDialog() { function openApiKeyDialog() {
feedback.value = t('app.feedback.noApiKey') showMissingApiKeyFeedback()
apiKeyUrlCopied.value = false apiKeyUrlCopied.value = false
showApiKeyDialog.value = true showApiKeyDialog.value = true
} }
@ -137,6 +165,18 @@ async function copyFreeApiKeyUrl() {
apiKeyUrlCopied.value = true apiKeyUrlCopied.value = true
} }
async function copyAuthorUrl() {
await navigator.clipboard.writeText('https://kuraa.cc')
authorUrlCopied.value = true
if (authorCopyTimer) {
clearTimeout(authorCopyTimer)
}
authorCopyTimer = setTimeout(() => {
authorUrlCopied.value = false
authorCopyTimer = null
}, 1400)
}
function acknowledgeApiKeyDialog() { function acknowledgeApiKeyDialog() {
showApiKeyDialog.value = false showApiKeyDialog.value = false
outputMode.value = 'source' outputMode.value = 'source'
@ -202,6 +242,7 @@ async function submitFiles(filePaths: string[]) {
} }
pending.value = true pending.value = true
feedbackTone.value = 'normal'
feedback.value = '' feedback.value = ''
try { try {
@ -227,6 +268,7 @@ async function submitFiles(filePaths: string[]) {
async function handlePickFiles() { async function handlePickFiles() {
try { try {
feedbackTone.value = 'normal'
feedback.value = '' feedback.value = ''
persistTranslationConfig() persistTranslationConfig()
const selected = await open({ const selected = await open({
@ -336,11 +378,13 @@ async function handleExport(format: 'srt' | 'vtt' | 'ass') {
async function handleRetryTranslate(taskId: string) { async function handleRetryTranslate(taskId: string) {
persistTranslationConfig() persistTranslationConfig()
if (!translationConfig.value.apiKey.trim()) { if (!translationConfig.value.apiKey.trim()) {
feedbackTone.value = 'error'
feedback.value = t('app.feedback.noApiKey') feedback.value = t('app.feedback.noApiKey')
return return
} }
try { try {
await taskStore.retryTranslation(taskId, translationConfig.value) await taskStore.retryTranslation(taskId, translationConfig.value)
feedbackTone.value = 'normal'
feedback.value = t('app.feedback.translationStarted') feedback.value = t('app.feedback.translationStarted')
} catch (error) { } catch (error) {
feedback.value = error instanceof Error ? error.message : t('app.feedback.translationFailed') feedback.value = error instanceof Error ? error.message : t('app.feedback.translationFailed')
@ -388,8 +432,8 @@ async function handleTranslateFromEditor() {
<span> <span>
{{ locale === 'zh-CN' ? '推荐免费的' : 'Recommended free option:' }} {{ locale === 'zh-CN' ? '推荐免费的' : 'Recommended free option:' }}
<button class="api-key-link" type="button" @click="copyFreeApiKeyUrl">GLM-4.7-Flash</button> <button class="api-key-link" type="button" @click="copyFreeApiKeyUrl">GLM-4.7-Flash</button>
<span v-if="apiKeyUrlCopied" class="copy-hint"> <span class="copy-hint" :class="{ copied: apiKeyUrlCopied }">
{{ locale === 'zh-CN' ? '已复制链接' : 'Link copied' }} {{ apiKeyUrlCopied ? (locale === 'zh-CN' ? '已复制链接' : 'Link copied') : (locale === 'zh-CN' ? '点击复制申请链接' : 'click to copy link') }}
</span> </span>
</span> </span>
</li> </li>
@ -399,7 +443,7 @@ async function handleTranslateFromEditor() {
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<button class="button" type="button" @click="acknowledgeApiKeyDialog"> <button class="button" type="button" @click="acknowledgeApiKeyDialog">
{{ locale === 'zh-CN' ? '明白了' : 'Got it' }} {{ locale === 'zh-CN' ? '明白了,先转原文' : 'Got it, use source mode' }}
</button> </button>
</div> </div>
</section> </section>
@ -410,7 +454,8 @@ async function handleTranslateFromEditor() {
<div class="toolbar-title"> <div class="toolbar-title">
<strong>CrossSubtitle</strong> <strong>CrossSubtitle</strong>
<span class="credit-line"> <span class="credit-line">
by <a href="https://kuraa.cc" target="_blank" rel="noreferrer">kuraa</a> by <button class="author-link" type="button" @click="copyAuthorUrl">kuraa</button>
<span v-if="authorUrlCopied" class="author-copy-hint">🎉</span>
</span> </span>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
@ -460,7 +505,7 @@ async function handleTranslateFromEditor() {
</div> </div>
</div> </div>
<p v-if="feedback" class="feedback status-text">{{ feedback }}</p> <p v-if="feedback" class="feedback status-text" :class="{ error: feedbackTone === 'error' }">{{ feedback }}</p>
</div> </div>
<div v-if="showAdvanced" class="advanced-shell"> <div v-if="showAdvanced" class="advanced-shell">

View File

@ -157,17 +157,27 @@ textarea {
align-items: center; align-items: center;
} }
.credit-line a { .credit-line a,
.author-link {
color: var(--c-text); color: var(--c-text);
background: transparent;
border: 0;
padding: 0;
cursor: pointer;
text-decoration: none; text-decoration: none;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
transition: border-color var(--transition); transition: border-color var(--transition);
} }
.credit-line a:hover { .credit-line a:hover,
.author-link:hover {
border-bottom-color: var(--c-text); border-bottom-color: var(--c-text);
} }
.author-copy-hint {
line-height: 1;
}
.workspace-toolbar, .workspace-toolbar,
.advanced-shell { .advanced-shell {
margin-top: 16px; margin-top: 16px;
@ -332,6 +342,11 @@ textarea {
margin: 12px 0 0; margin: 12px 0 0;
} }
.status-text.error {
color: var(--c-error);
font-weight: 500;
}
.content-grid { .content-grid {
display: grid; display: grid;
grid-template-columns: 300px minmax(0, 1fr); grid-template-columns: 300px minmax(0, 1fr);
@ -950,6 +965,8 @@ textarea {
.api-key-dialog { .api-key-dialog {
width: min(520px, 100%); width: min(520px, 100%);
max-height: calc(100vh - 40px);
overflow: auto;
padding: 20px; padding: 20px;
border: 1px solid var(--c-border); border: 1px solid var(--c-border);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
@ -1109,10 +1126,14 @@ textarea {
} }
.copy-hint { .copy-hint {
color: var(--c-success); color: var(--c-text-tertiary);
font-size: 12px; font-size: 12px;
} }
.copy-hint.copied {
color: var(--c-success);
}
.dialog-actions { .dialog-actions {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;