refactor(web): align route paths, page dirs and i18n keys with sidebar labels

- Rename route paths to match sidebar tab labels:
  models→providers, search-providers→web-search,
  memory-providers→memory, tts-providers→speech,
  email-providers→email, browser-contexts→browser,
  settings(profile)→profile
- Rename page directories accordingly
- Rename i18n keys: sidebar.models→providers, searchProvider→webSearch,
  memoryProvider→memory, ttsProvider→speech, emailProvider→email,
  browserContext→browser
- Fix bot detail tab value 'settings' → 'general' to match label
- Fix ZH bots.tabs.general untranslated ("General" → "通用")
- Align usage page title with sidebar label
This commit is contained in:
Acbox
2026-03-28 23:34:53 +08:00
parent eb99f75c37
commit c0057b5c54
46 changed files with 211 additions and 211 deletions
@@ -96,33 +96,33 @@ const navItems = computed(() => [
icon: ['fas', 'robot'],
},
{
title: t('sidebar.models'),
name: 'models',
title: t('sidebar.providers'),
name: 'providers',
icon: ['fas', 'cubes'],
},
{
title: t('sidebar.searchProvider'),
name: 'search-providers',
title: t('sidebar.webSearch'),
name: 'web-search',
icon: ['fas', 'globe'],
},
{
title: t('sidebar.memoryProvider'),
name: 'memory-providers',
title: t('sidebar.memory'),
name: 'memory',
icon: ['fas', 'brain'],
},
{
title: t('sidebar.ttsProvider'),
name: 'tts-providers',
title: t('sidebar.speech'),
name: 'speech',
icon: ['fas', 'volume-high'],
},
{
title: t('sidebar.emailProvider'),
name: 'email-providers',
title: t('sidebar.email'),
name: 'email',
icon: ['fas', 'envelope'],
},
{
title: t('sidebar.browserContexts'),
name: 'browser-contexts',
title: t('sidebar.browser'),
name: 'browser',
icon: ['fas', 'window-maximize'],
},
{
@@ -132,7 +132,7 @@ const navItems = computed(() => [
},
{
title: t('sidebar.settings'),
name: 'settings',
name: 'profile',
icon: ['fas', 'gear'],
},
])
+1 -1
View File
@@ -62,7 +62,7 @@
<SidebarMenuButton
:tooltip="displayTitle"
class="h-10 px-2.5"
@click="router.push({ name: 'settings' })"
@click="router.push({ name: 'profile' })"
>
<div class="size-9 shrink-0 rounded-full border border-border bg-accent overflow-hidden p-[1.385px]">
<img
+13 -13
View File
@@ -58,17 +58,17 @@
"session": "Session",
"chat": "Chat",
"bots": "Bots",
"models": "Providers",
"searchProvider": "Web Search",
"memoryProvider": "Memory",
"ttsProvider": "Speech",
"emailProvider": "Email",
"providers": "Providers",
"webSearch": "Web Search",
"memory": "Memory",
"speech": "Speech",
"email": "Email",
"settings": "Settings",
"home": "Home",
"mcp": "MCP",
"platform": "Platform",
"usage": "Usage",
"browserContexts": "Browser"
"browser": "Browser"
},
"breadcrumb": {
"main": "Home"
@@ -179,7 +179,7 @@
"searchSessionPlaceholder": "Search"
},
"models": {
"title": "Providers",
"title": "Models",
"searchPlaceholder": "Search providers…",
"addModel": "Add Model",
"editModel": "Edit Model",
@@ -260,7 +260,7 @@
}
}
},
"searchProvider": {
"webSearch": {
"title": "Web Search",
"add": "Add Web Search",
"empty": "No search providers",
@@ -289,7 +289,7 @@
"yandex": "Yandex"
}
},
"memoryProvider": {
"memory": {
"title": "Memory Providers",
"add": "Add Memory Provider",
"empty": "No memory providers",
@@ -337,7 +337,7 @@
"openviking": "OpenViking"
}
},
"ttsProvider": {
"speech": {
"title": "Speech",
"add": "Add Speech Provider",
"providerType": "Provider Type",
@@ -373,7 +373,7 @@
"failed": "Synthesis failed"
}
},
"emailProvider": {
"email": {
"title": "Email",
"add": "Add Email",
"providerType": "Provider Type",
@@ -416,7 +416,7 @@
"logoutFailed": "Failed to revoke authorization"
}
},
"browserContext": {
"browser": {
"title": "Browser Contexts",
"add": "Add Browser Context",
"searchPlaceholder": "Search browser contexts...",
@@ -1088,7 +1088,7 @@
}
},
"usage": {
"title": "Token Usage",
"title": "Usage",
"selectBot": "Select Bot",
"selectBotPlaceholder": "Choose a bot to view usage",
"timeRange": "Time Range",
+13 -13
View File
@@ -58,17 +58,17 @@
"session":"会话",
"chat": "对话",
"bots": "Bots",
"models": "模型管理",
"searchProvider": "搜索提供方",
"memoryProvider": "记忆",
"ttsProvider": "语音合成",
"emailProvider": "邮件提供方",
"providers": "模型管理",
"webSearch": "搜索提供方",
"memory": "记忆",
"speech": "语音合成",
"email": "邮件提供方",
"settings": "设置",
"home": "首页",
"mcp": "MCP",
"platform": "接入平台",
"usage": "用量统计",
"browserContexts": "浏览器"
"browser": "浏览器"
},
"breadcrumb": {
"main": "主页"
@@ -256,7 +256,7 @@
}
}
},
"searchProvider": {
"webSearch": {
"title": "搜索提供方",
"add": "添加搜索提供方",
"empty": "暂无搜索提供方",
@@ -285,7 +285,7 @@
"yandex": "Yandex"
}
},
"memoryProvider": {
"memory": {
"title": "记忆提供方",
"add": "添加记忆提供方",
"empty": "暂无记忆提供方",
@@ -333,7 +333,7 @@
"openviking": "OpenViking"
}
},
"ttsProvider": {
"speech": {
"title": "语音合成",
"add": "添加语音合成提供方",
"providerType": "提供方类型",
@@ -369,7 +369,7 @@
"failed": "合成失败"
}
},
"emailProvider": {
"email": {
"title": "邮件提供方",
"add": "添加邮件提供方",
"providerType": "提供方类型",
@@ -412,7 +412,7 @@
"logoutFailed": "撤销授权失败"
}
},
"browserContext": {
"browser": {
"title": "浏览器上下文",
"add": "添加浏览器上下文",
"searchPlaceholder": "搜索浏览器上下文...",
@@ -598,7 +598,7 @@
},
"tabs": {
"overview": "概览",
"general": "General",
"general": "通用",
"memory": "记忆",
"channels": "平台",
"container": "容器",
@@ -1084,7 +1084,7 @@
}
},
"usage": {
"title": "Token 用量",
"title": "用量统计",
"selectBot": "选择 Bot",
"selectBotPlaceholder": "选择一个 Bot 查看用量",
"timeRange": "时间范围",
@@ -40,7 +40,7 @@
class="rounded-md border border-border bg-card px-3 py-2 text-xs text-muted-foreground"
>
{{ $t('bots.settings.memoryModePreview', {
mode: $t(`memoryProvider.modeNames.${selectedBuiltinMemoryMode}`),
mode: $t(`memory.modeNames.${selectedBuiltinMemoryMode}`),
}) }}
</div>
<div
@@ -4,9 +4,9 @@
:options="options"
:placeholder="placeholder || ''"
:aria-label="placeholder || 'Select browser context'"
:search-placeholder="$t('browserContext.searchPlaceholder')"
:search-placeholder="$t('browser.searchPlaceholder')"
search-aria-label="Search browser contexts"
:empty-text="$t('browserContext.emptyTitle')"
:empty-text="$t('browser.emptyTitle')"
:show-group-headers="false"
>
<template #trigger="{ open, displayLabel }">
@@ -4,9 +4,9 @@
:options="options"
:placeholder="placeholder || ''"
:aria-label="placeholder || 'Select memory provider'"
:search-placeholder="$t('memoryProvider.searchPlaceholder')"
:search-placeholder="$t('memory.searchPlaceholder')"
search-aria-label="Search memory providers"
:empty-text="$t('memoryProvider.empty')"
:empty-text="$t('memory.empty')"
:show-group-headers="false"
>
<template #trigger="{ open, displayLabel }">
@@ -83,7 +83,7 @@ const options = computed<SearchableSelectOption[]>(() => {
value: provider.id || '',
label: provider.name || provider.id || '',
description: provider.provider === 'builtin'
? t(`memoryProvider.modeNames.${provider.config?.memory_mode || 'off'}`)
? t(`memory.modeNames.${provider.config?.memory_mode || 'off'}`)
: provider.provider,
keywords: [
provider.name ?? '',
@@ -4,9 +4,9 @@
:options="options"
:placeholder="placeholder || ''"
:aria-label="placeholder || 'Select search provider'"
:search-placeholder="$t('searchProvider.searchPlaceholder')"
:search-placeholder="$t('webSearch.searchPlaceholder')"
search-aria-label="Search providers"
:empty-text="$t('searchProvider.empty')"
:empty-text="$t('webSearch.empty')"
:show-group-headers="false"
>
<template #trigger="{ open, displayLabel }">
@@ -4,9 +4,9 @@
:options="options"
:placeholder="placeholder || ''"
:aria-label="placeholder || 'Select TTS model'"
:search-placeholder="$t('ttsProvider.searchPlaceholder')"
:search-placeholder="$t('speech.searchPlaceholder')"
search-aria-label="Search TTS models"
:empty-text="$t('ttsProvider.emptyTitle')"
:empty-text="$t('speech.emptyTitle')"
>
<template #trigger="{ open, displayLabel }">
<Button
@@ -4,9 +4,9 @@
:options="options"
:placeholder="placeholder || ''"
:aria-label="placeholder || 'Select TTS provider'"
:search-placeholder="$t('ttsProvider.searchPlaceholder')"
:search-placeholder="$t('speech.searchPlaceholder')"
search-aria-label="Search TTS providers"
:empty-text="$t('ttsProvider.emptyTitle')"
:empty-text="$t('speech.emptyTitle')"
:show-group-headers="false"
>
<template #trigger="{ open, displayLabel }">
+1 -1
View File
@@ -276,7 +276,7 @@ const tabList = computed(() => {
{
value: 'overview', label: 'bots.tabs.overview', component: BotOverview, params: {}
},
{ value: 'settings', label: 'bots.tabs.general', component: BotSettings, params: { 'bot-id': bot_id, 'bot-type': bot.value?.type } },
{ value: 'general', label: 'bots.tabs.general', component: BotSettings, params: { 'bot-id': bot_id, 'bot-type': bot.value?.type } },
{ value: 'container', label: 'bots.tabs.container', component: BotContainer, params: {} },
{ value: 'memory', label: 'bots.tabs.memory', component: BotMemory, params: { 'bot-id': bot_id } },
{ value: 'channels', label: 'bots.tabs.channels', component: BotChannels, params: { 'bot-id': bot_id } },
@@ -2,9 +2,9 @@
<section>
<FormDialogShell
v-model:open="open"
:title="$t('browserContext.add')"
:title="$t('browser.add')"
:cancel-text="$t('common.cancel')"
:submit-text="$t('browserContext.add')"
:submit-text="$t('browser.add')"
:submit-disabled="(form.meta.value.valid === false) || isLoading"
:loading="isLoading"
@submit="handleCreate"
@@ -17,7 +17,7 @@
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('browserContext.add') }}
/> {{ $t('browser.add') }}
</Button>
</template>
<template #body>
@@ -28,13 +28,13 @@
>
<FormItem>
<Label :for="componentField.id || 'browser-context-name'">
{{ $t('browserContext.name') }}
{{ $t('browser.name') }}
</Label>
<FormControl>
<Input
:id="componentField.id || 'browser-context-name'"
type="text"
:placeholder="$t('browserContext.namePlaceholder')"
:placeholder="$t('browser.namePlaceholder')"
v-bind="componentField"
/>
</FormControl>
@@ -8,7 +8,7 @@
/>
<div>
<h2 class="text-sm font-semibold">
{{ curContext?.name || $t('browserContext.title') }}
{{ curContext?.name || $t('browser.title') }}
</h2>
<p class="text-xs text-muted-foreground">
{{ curContext?.id }}
@@ -25,11 +25,11 @@
name="name"
>
<FormItem>
<Label>{{ $t('browserContext.name') }}</Label>
<Label>{{ $t('browser.name') }}</Label>
<FormControl>
<Input
type="text"
:placeholder="$t('browserContext.namePlaceholder')"
:placeholder="$t('browser.namePlaceholder')"
v-bind="componentField"
/>
</FormControl>
@@ -38,7 +38,7 @@
<Separator class="my-4" />
<h3 class="text-xs font-medium text-foreground">
{{ $t('browserContext.config') }}
{{ $t('browser.config') }}
</h3>
<FormField
@@ -46,7 +46,7 @@
name="core"
>
<FormItem>
<Label>{{ $t('browserContext.core') }}</Label>
<Label>{{ $t('browser.core') }}</Label>
<FormControl>
<div class="flex gap-3">
<button
@@ -59,7 +59,7 @@
: 'border-border bg-card text-muted-foreground hover:bg-accent'"
@click="handleChange(c)"
>
{{ $t(`browserContext.${c}`) }}
{{ $t(`browser.${c}`) }}
</button>
</div>
</FormControl>
@@ -72,7 +72,7 @@
name="viewportWidth"
>
<FormItem>
<Label>{{ $t('browserContext.viewportWidth') }}</Label>
<Label>{{ $t('browser.viewportWidth') }}</Label>
<FormControl>
<Input
type="number"
@@ -87,7 +87,7 @@
name="viewportHeight"
>
<FormItem>
<Label>{{ $t('browserContext.viewportHeight') }}</Label>
<Label>{{ $t('browser.viewportHeight') }}</Label>
<FormControl>
<Input
type="number"
@@ -103,11 +103,11 @@
name="userAgent"
>
<FormItem>
<Label>{{ $t('browserContext.userAgent') }}</Label>
<Label>{{ $t('browser.userAgent') }}</Label>
<FormControl>
<Input
type="text"
:placeholder="$t('browserContext.userAgentPlaceholder')"
:placeholder="$t('browser.userAgentPlaceholder')"
v-bind="componentField"
/>
</FormControl>
@@ -120,7 +120,7 @@
name="deviceScaleFactor"
>
<FormItem>
<Label>{{ $t('browserContext.deviceScaleFactor') }}</Label>
<Label>{{ $t('browser.deviceScaleFactor') }}</Label>
<FormControl>
<Input
type="number"
@@ -136,11 +136,11 @@
name="locale"
>
<FormItem>
<Label>{{ $t('browserContext.locale') }}</Label>
<Label>{{ $t('browser.locale') }}</Label>
<FormControl>
<Input
type="text"
:placeholder="$t('browserContext.localePlaceholder')"
:placeholder="$t('browser.localePlaceholder')"
v-bind="componentField"
/>
</FormControl>
@@ -153,11 +153,11 @@
name="timezoneId"
>
<FormItem>
<Label>{{ $t('browserContext.timezoneId') }}</Label>
<Label>{{ $t('browser.timezoneId') }}</Label>
<FormControl>
<TimezoneSelect
:model-value="value || emptyTimezoneValue"
:placeholder="$t('browserContext.timezonePlaceholder')"
:placeholder="$t('browser.timezonePlaceholder')"
allow-empty
:empty-label="$t('common.optional')"
@update:model-value="(val) => handleChange(val === emptyTimezoneValue ? '' : val)"
@@ -178,7 +178,7 @@
@update:model-value="handleChange"
/>
</FormControl>
<Label class="mt-0!">{{ $t('browserContext.isMobile') }}</Label>
<Label class="mt-0!">{{ $t('browser.isMobile') }}</Label>
</FormItem>
</FormField>
@@ -193,7 +193,7 @@
@update:model-value="handleChange"
/>
</FormControl>
<Label class="mt-0!">{{ $t('browserContext.ignoreHTTPSErrors') }}</Label>
<Label class="mt-0!">{{ $t('browser.ignoreHTTPSErrors') }}</Label>
</FormItem>
</FormField>
</div>
@@ -203,7 +203,7 @@
<div class="flex gap-2 items-center justify-between">
<ConfirmPopover
:title="$t('browserContext.deleteConfirm')"
:title="$t('browser.deleteConfirm')"
:confirm-text="$t('common.delete')"
@confirm="handleDelete"
>
@@ -363,7 +363,7 @@ const handleSave = form.handleSubmit(async (values) => {
() => updateMutation({ id, name: values.name, config }),
{
fallbackMessage: t('common.saveFailed'),
onSuccess: () => toast.success(t('browserContext.saveSuccess')),
onSuccess: () => toast.success(t('browser.saveSuccess')),
},
)
})
@@ -373,7 +373,7 @@ async function handleDelete() {
if (!id) return
try {
await deleteMutation(id)
toast.success(t('browserContext.deleteSuccess'))
toast.success(t('browser.deleteSuccess'))
} catch (err) {
toast.error(resolveApiErrorMessage(err, t('common.deleteFailed')))
}
@@ -108,8 +108,8 @@ const openStatus = reactive({
<FontAwesomeIcon :icon="['fas', 'window-maximize']" />
</EmptyMedia>
</EmptyHeader>
<EmptyTitle>{{ $t('browserContext.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('browserContext.emptyDescription') }}</EmptyDescription>
<EmptyTitle>{{ $t('browser.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('browser.emptyDescription') }}</EmptyDescription>
<EmptyContent>
<Button
variant="outline"
@@ -118,7 +118,7 @@ const openStatus = reactive({
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('browserContext.add') }}
/> {{ $t('browser.add') }}
</Button>
</EmptyContent>
</Empty>
@@ -2,9 +2,9 @@
<section>
<FormDialogShell
v-model:open="open"
:title="$t('emailProvider.add')"
:title="$t('email.add')"
:cancel-text="$t('common.cancel')"
:submit-text="$t('emailProvider.add')"
:submit-text="$t('email.add')"
:submit-disabled="(form.meta.value.valid === false) || isLoading"
:loading="isLoading"
@submit="handleCreate"
@@ -17,7 +17,7 @@
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('emailProvider.add') }}
/> {{ $t('email.add') }}
</Button>
</template>
<template #body>
@@ -46,7 +46,7 @@
>
<FormItem>
<Label :for="componentField.id || 'email-provider-type'">
{{ $t('emailProvider.providerType') }}
{{ $t('email.providerType') }}
</Label>
<FormControl>
<Select v-bind="componentField">
@@ -50,7 +50,7 @@
class="space-y-2"
>
<Label :for="field.type === 'bool' || field.type === 'enum' ? undefined : `email-field-${field.key}`">
{{ $te(`emailProvider.fields.${field.key}`) ? $t(`emailProvider.fields.${field.key}`) : (field.title || field.key) }}
{{ $te(`email.fields.${field.key}`) ? $t(`email.fields.${field.key}`) : (field.title || field.key) }}
<span
v-if="!field.required"
class="text-xs text-muted-foreground ml-1"
@@ -136,29 +136,29 @@
<div class="flex flex-wrap items-start justify-between gap-4">
<div class="flex-1 min-w-[220px]">
<p class="text-xs font-medium">
{{ $t('emailProvider.oauth.title') }}
{{ $t('email.oauth.title') }}
</p>
<p class="text-xs text-muted-foreground mt-0.5">
{{ $t('emailProvider.oauth.description') }}
{{ $t('email.oauth.description') }}
</p>
<p
class="text-xs mt-2"
:class="oauthTokenExpired ? 'text-destructive' : 'text-muted-foreground'"
>
<template v-if="oauthStatusLoading">
{{ $t('emailProvider.oauth.status.checking') }}
{{ $t('email.oauth.status.checking') }}
</template>
<template v-else-if="oauthStatus && !oauthStatus.configured">
{{ $t('emailProvider.oauth.status.notConfigured') }}
{{ $t('email.oauth.status.notConfigured') }}
</template>
<template v-else-if="oauthTokenExpired">
{{ $t('emailProvider.oauth.status.expired') }}
{{ $t('email.oauth.status.expired') }}
</template>
<template v-else-if="oauthStatus && oauthStatus.has_token">
{{ oauthStatus.email_address ? $t('emailProvider.oauth.status.authorized', { email: oauthStatus.email_address }) : $t('emailProvider.oauth.status.authorizedUnknown') }}
{{ oauthStatus.email_address ? $t('email.oauth.status.authorized', { email: oauthStatus.email_address }) : $t('email.oauth.status.authorizedUnknown') }}
</template>
<template v-else>
{{ $t('emailProvider.oauth.status.missing') }}
{{ $t('email.oauth.status.missing') }}
</template>
</p>
</div>
@@ -174,7 +174,7 @@
:icon="['fas', 'key']"
class="mr-1.5"
/>
{{ $t('emailProvider.oauth.authorize') }}
{{ $t('email.oauth.authorize') }}
</LoadingButton>
<LoadingButton
v-if="hasOAuthToken"
@@ -183,7 +183,7 @@
:loading="revokeLoading"
@click="handleRevoke"
>
{{ $t('emailProvider.oauth.logout') }}
{{ $t('email.oauth.logout') }}
</LoadingButton>
</div>
</div>
@@ -191,7 +191,7 @@
<section class="flex justify-end mt-6 gap-4">
<ConfirmPopover
:message="$t('emailProvider.deleteConfirm')"
:message="$t('email.deleteConfirm')"
:loading="deleteLoading"
@confirm="handleDelete"
>
@@ -378,12 +378,12 @@ async function handleAuthorize() {
path: { id: curProviderId.value },
})
if (error || !data?.auth_url) {
throw new Error(t('emailProvider.oauth.authorizeFailed'))
throw new Error(t('email.oauth.authorizeFailed'))
}
window.open(data.auth_url, '_blank', 'noopener,noreferrer')
toast.success(t('emailProvider.oauth.authorizeOpened'))
toast.success(t('email.oauth.authorizeOpened'))
} catch (e: unknown) {
toast.error(e instanceof Error ? e.message : t('emailProvider.oauth.authorizeFailed'))
toast.error(e instanceof Error ? e.message : t('email.oauth.authorizeFailed'))
} finally {
authorizeLoading.value = false
}
@@ -419,10 +419,10 @@ async function handleRevoke() {
path: { id: curProviderId.value },
})
if (error) throw error
toast.success(t('emailProvider.oauth.logoutSuccess'))
toast.success(t('email.oauth.logoutSuccess'))
await fetchOAuthStatus()
} catch (error: unknown) {
toast.error(error instanceof Error ? error.message : t('emailProvider.oauth.logoutFailed'))
toast.error(error instanceof Error ? error.message : t('email.oauth.logoutFailed'))
} finally {
revokeLoading.value = false
}
@@ -103,8 +103,8 @@ const openStatus = reactive({ addOpen: false })
<FontAwesomeIcon :icon="['fas', 'envelope']" />
</EmptyMedia>
</EmptyHeader>
<EmptyTitle>{{ $t('emailProvider.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('emailProvider.emptyDescription') }}</EmptyDescription>
<EmptyTitle>{{ $t('email.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('email.emptyDescription') }}</EmptyDescription>
<EmptyContent>
<Button
variant="outline"
@@ -113,7 +113,7 @@ const openStatus = reactive({ addOpen: false })
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('emailProvider.add') }}
/> {{ $t('email.add') }}
</Button>
</EmptyContent>
</Empty>
@@ -9,23 +9,23 @@
:icon="['fas', 'plus']"
class="mr-2"
/>
{{ $t('memoryProvider.add') }}
{{ $t('memory.add') }}
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-md">
<DialogHeader>
<DialogTitle>{{ $t('memoryProvider.add') }}</DialogTitle>
<DialogTitle>{{ $t('memory.add') }}</DialogTitle>
</DialogHeader>
<div class="space-y-4 py-4">
<div class="space-y-2">
<Label>{{ $t('memoryProvider.name') }}</Label>
<Label>{{ $t('memory.name') }}</Label>
<Input
v-model="form.name"
:placeholder="$t('memoryProvider.namePlaceholder')"
:placeholder="$t('memory.namePlaceholder')"
/>
</div>
<div class="space-y-2">
<Label>{{ $t('memoryProvider.provider') }}</Label>
<Label>{{ $t('memory.provider') }}</Label>
<Select v-model:model-value="form.provider">
<SelectTrigger>
<SelectValue />
@@ -33,13 +33,13 @@
<SelectContent>
<SelectGroup>
<SelectItem value="builtin">
{{ $t('memoryProvider.providerNames.builtin') }}
{{ $t('memory.providerNames.builtin') }}
</SelectItem>
<SelectItem value="mem0">
{{ $t('memoryProvider.providerNames.mem0') }}
{{ $t('memory.providerNames.mem0') }}
</SelectItem>
<SelectItem value="openviking">
{{ $t('memoryProvider.providerNames.openviking') }}
{{ $t('memory.providerNames.openviking') }}
</SelectItem>
</SelectGroup>
</SelectContent>
@@ -115,7 +115,7 @@ async function handleCreate() {
},
throwOnError: true,
})
toast.success(t('memoryProvider.saveSuccess'))
toast.success(t('memory.saveSuccess'))
queryCache.invalidateQueries({ key: ['memory-providers'] })
open.value = false
form.name = ''
@@ -9,11 +9,11 @@
{{ curProvider.name }}
</h3>
<p class="text-xs text-muted-foreground mt-0.5">
{{ $t(`memoryProvider.providerNames.${curProvider.provider}`, curProvider.provider) }}
{{ $t(`memory.providerNames.${curProvider.provider}`, curProvider.provider) }}
</p>
</div>
<ConfirmPopover
:message="$t('memoryProvider.deleteConfirm')"
:message="$t('memory.deleteConfirm')"
@confirm="handleDelete"
>
<template #trigger>
@@ -36,19 +36,19 @@
<!-- Name -->
<div class="space-y-2">
<Label>{{ $t('memoryProvider.name') }}</Label>
<Label>{{ $t('memory.name') }}</Label>
<Input
v-model="form.name"
:placeholder="$t('memoryProvider.namePlaceholder')"
:placeholder="$t('memory.namePlaceholder')"
/>
</div>
<!-- Builtin Config (model selectors) -->
<template v-if="curProvider.provider === 'builtin'">
<div class="space-y-2">
<Label>{{ $t('memoryProvider.builtinMode') }}</Label>
<Label>{{ $t('memory.builtinMode') }}</Label>
<p class="text-xs text-muted-foreground">
{{ $t('memoryProvider.builtinModeDescription') }}
{{ $t('memory.builtinModeDescription') }}
</p>
<div class="inline-flex rounded-xl border border-border bg-muted/70 p-1">
<div class="relative grid grid-cols-3">
@@ -62,7 +62,7 @@
:class="builtinModeButtonClass('off')"
@click="handleBuiltinModeChange('off')"
>
{{ $t('memoryProvider.modeNames.off') }}
{{ $t('memory.modeNames.off') }}
</button>
<button
type="button"
@@ -70,7 +70,7 @@
:class="builtinModeButtonClass('sparse')"
@click="handleBuiltinModeChange('sparse')"
>
{{ $t('memoryProvider.modeNames.sparse') }}
{{ $t('memory.modeNames.sparse') }}
</button>
<button
type="button"
@@ -78,7 +78,7 @@
:class="builtinModeButtonClass('dense')"
@click="handleBuiltinModeChange('dense')"
>
{{ $t('memoryProvider.modeNames.dense') }}
{{ $t('memory.modeNames.dense') }}
</button>
</div>
</div>
@@ -89,10 +89,10 @@
class="rounded-lg border border-border bg-card p-4 space-y-2"
>
<h4 class="text-xs font-medium">
{{ $t('memoryProvider.modeNames.off') }}
{{ $t('memory.modeNames.off') }}
</h4>
<p class="text-xs text-muted-foreground">
{{ $t('memoryProvider.modeDescriptions.off') }}
{{ $t('memory.modeDescriptions.off') }}
</p>
</div>
@@ -102,15 +102,15 @@
>
<div class="space-y-1">
<h4 class="text-xs font-medium">
{{ $t('memoryProvider.sparseSectionTitle') }}
{{ $t('memory.sparseSectionTitle') }}
</h4>
<p class="text-xs text-muted-foreground">
{{ $t('memoryProvider.modeDescriptions.sparse') }}
{{ $t('memory.modeDescriptions.sparse') }}
</p>
</div>
<div class="rounded-md border border-border bg-background px-3 py-2 text-xs text-muted-foreground">
{{ $t('memoryProvider.sparseInstallHint') }}
{{ $t('memory.sparseInstallHint') }}
</div>
</div>
@@ -120,29 +120,29 @@
>
<div class="space-y-1">
<h4 class="text-xs font-medium">
{{ $t('memoryProvider.denseSectionTitle') }}
{{ $t('memory.denseSectionTitle') }}
</h4>
<p class="text-xs text-muted-foreground">
{{ $t('memoryProvider.modeDescriptions.dense') }}
{{ $t('memory.modeDescriptions.dense') }}
</p>
</div>
<div class="space-y-2">
<Label>{{ $t('memoryProvider.denseEmbeddingModel') }}</Label>
<Label>{{ $t('memory.denseEmbeddingModel') }}</Label>
<p class="text-xs text-muted-foreground">
{{ $t('memoryProvider.denseEmbeddingModelDescription') }}
{{ $t('memory.denseEmbeddingModelDescription') }}
</p>
<ModelSelect
v-model="configForm.embedding_model_id"
:models="models"
:providers="providers"
model-type="embedding"
:placeholder="$t('memoryProvider.denseEmbeddingModel')"
:placeholder="$t('memory.denseEmbeddingModel')"
/>
</div>
<div class="rounded-md border border-border bg-background px-3 py-2 text-xs text-muted-foreground">
{{ $t('memoryProvider.denseQdrantHint') }}
{{ $t('memory.denseQdrantHint') }}
</div>
</div>
@@ -163,17 +163,17 @@
class="text-xs"
:class="collection.qdrant?.ok ? 'text-foreground' : 'text-destructive'"
>
{{ collection.qdrant?.ok ? $t('memoryProvider.collectionHealthy') : $t('memoryProvider.collectionUnavailable') }}
{{ collection.qdrant?.ok ? $t('memory.collectionHealthy') : $t('memory.collectionUnavailable') }}
</span>
</div>
<p class="text-2xl font-semibold text-foreground">
{{ collection.points ?? 0 }}
</p>
<p class="text-xs text-muted-foreground">
{{ $t('memoryProvider.collectionPoints') }}
{{ $t('memory.collectionPoints') }}
</p>
<p class="text-xs text-muted-foreground">
{{ collection.exists ? $t('memoryProvider.collectionExists') : $t('memoryProvider.collectionMissing') }}
{{ collection.exists ? $t('memory.collectionExists') : $t('memory.collectionMissing') }}
</p>
</div>
</div>
@@ -351,7 +351,7 @@ async function handleSave() {
if (curProvider?.value && data) {
Object.assign(curProvider.value, data)
}
toast.success(t('memoryProvider.saveSuccess'))
toast.success(t('memory.saveSuccess'))
queryCache.invalidateQueries({ key: ['memory-providers'] })
} catch (error) {
console.error('Failed to save:', error)
@@ -369,11 +369,11 @@ async function handleDelete() {
path: { id: curProvider.value.id! },
throwOnError: true,
})
toast.success(t('memoryProvider.deleteSuccess'))
toast.success(t('memory.deleteSuccess'))
queryCache.invalidateQueries({ key: ['memory-providers'] })
} catch (error) {
console.error('Failed to delete:', error)
toast.error(t('memoryProvider.deleteFailed'))
toast.error(t('memory.deleteFailed'))
} finally {
deleteLoading.value = false
}
@@ -100,8 +100,8 @@ const openStatus = reactive({ addOpen: false })
<FontAwesomeIcon :icon="['fas', 'brain']" />
</EmptyMedia>
</EmptyHeader>
<EmptyTitle>{{ $t('memoryProvider.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('memoryProvider.emptyDescription') }}</EmptyDescription>
<EmptyTitle>{{ $t('memory.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('memory.emptyDescription') }}</EmptyDescription>
<EmptyContent>
<Button
variant="outline"
@@ -112,7 +112,7 @@ const openStatus = reactive({ addOpen: false })
:icon="['fas', 'plus']"
class="mr-2"
/>
{{ $t('memoryProvider.add') }}
{{ $t('memory.add') }}
</Button>
</EmptyContent>
</Empty>
@@ -1,16 +1,16 @@
<template>
<FormDialogShell
v-model:open="open"
:title="$t('ttsProvider.addModel')"
:title="$t('speech.addModel')"
:cancel-text="$t('common.cancel')"
:submit-text="$t('ttsProvider.addModel')"
:submit-text="$t('speech.addModel')"
:submit-disabled="(form.meta.value.valid === false) || isLoading"
:loading="isLoading"
@submit="handleCreate"
>
<template #trigger>
<Button variant="default">
{{ $t('ttsProvider.addModel') }}
{{ $t('speech.addModel') }}
</Button>
</template>
<template #body>
@@ -21,13 +21,13 @@
>
<FormItem>
<Label :for="componentField.id || 'tts-model-id'">
{{ $t('ttsProvider.modelId') }}
{{ $t('speech.modelId') }}
</Label>
<FormControl>
<Input
:id="componentField.id || 'tts-model-id'"
type="text"
:placeholder="$t('ttsProvider.modelIdPlaceholder')"
:placeholder="$t('speech.modelIdPlaceholder')"
v-bind="componentField"
/>
</FormControl>
@@ -2,9 +2,9 @@
<section>
<FormDialogShell
v-model:open="open"
:title="$t('ttsProvider.add')"
:title="$t('speech.add')"
:cancel-text="$t('common.cancel')"
:submit-text="$t('ttsProvider.add')"
:submit-text="$t('speech.add')"
:submit-disabled="(form.meta.value.valid === false) || isLoading"
:loading="isLoading"
@submit="handleCreate"
@@ -17,7 +17,7 @@
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('ttsProvider.add') }}
/> {{ $t('speech.add') }}
</Button>
</template>
<template #body>
@@ -46,7 +46,7 @@
>
<FormItem>
<Label :for="componentField.id || 'tts-provider-type'">
{{ $t('ttsProvider.providerType') }}
{{ $t('speech.providerType') }}
</Label>
<FormControl>
<Select v-bind="componentField">
@@ -3,7 +3,7 @@
<template v-if="caps">
<!-- Language -->
<div class="space-y-2">
<Label for="tts-lang">{{ $t('ttsProvider.fields.language') }}</Label>
<Label for="tts-lang">{{ $t('speech.fields.language') }}</Label>
<Select
:model-value="configData.voice_lang ?? ''"
@update:model-value="onLangChange"
@@ -12,7 +12,7 @@
id="tts-lang"
class="w-full"
>
<SelectValue :placeholder="$t('ttsProvider.fields.languagePlaceholder')" />
<SelectValue :placeholder="$t('speech.fields.languagePlaceholder')" />
</SelectTrigger>
<SelectContent class="max-h-60">
<SelectItem
@@ -28,7 +28,7 @@
<!-- Voice -->
<div class="space-y-2">
<Label for="tts-voice">{{ $t('ttsProvider.fields.voice') }}</Label>
<Label for="tts-voice">{{ $t('speech.fields.voice') }}</Label>
<Select
:model-value="configData.voice_id ?? ''"
@update:model-value="(val) => configData.voice_id = val"
@@ -37,7 +37,7 @@
id="tts-voice"
class="w-full"
>
<SelectValue :placeholder="$t('ttsProvider.fields.voicePlaceholder')" />
<SelectValue :placeholder="$t('speech.fields.voicePlaceholder')" />
</SelectTrigger>
<SelectContent class="max-h-60">
<SelectItem
@@ -56,7 +56,7 @@
v-if="caps.formats && caps.formats.length > 0"
class="space-y-2"
>
<Label for="tts-format">{{ $t('ttsProvider.fields.format') }}</Label>
<Label for="tts-format">{{ $t('speech.fields.format') }}</Label>
<Select
:model-value="configData.format ?? ''"
@update:model-value="(val) => configData.format = val"
@@ -65,7 +65,7 @@
id="tts-format"
class="w-full"
>
<SelectValue :placeholder="$t('ttsProvider.fields.formatPlaceholder')" />
<SelectValue :placeholder="$t('speech.fields.formatPlaceholder')" />
</SelectTrigger>
<SelectContent>
<SelectItem
@@ -84,9 +84,9 @@
v-if="caps.speed"
class="space-y-2"
>
<Label>{{ $t('ttsProvider.fields.speed') }}</Label>
<Label>{{ $t('speech.fields.speed') }}</Label>
<p class="text-xs text-muted-foreground">
{{ $t('ttsProvider.fields.speedDescription', { default: caps.speed.default ?? 1 }) }}
{{ $t('speech.fields.speedDescription', { default: caps.speed.default ?? 1 }) }}
</p>
<div v-if="caps.speed.options && caps.speed.options.length > 0">
<Select
@@ -130,9 +130,9 @@
v-if="caps.pitch"
class="space-y-2"
>
<Label>{{ $t('ttsProvider.fields.pitch') }}</Label>
<Label>{{ $t('speech.fields.pitch') }}</Label>
<p class="text-xs text-muted-foreground">
{{ $t('ttsProvider.fields.pitchDescription', { default: caps.pitch.default ?? 0 }) }}
{{ $t('speech.fields.pitchDescription', { default: caps.pitch.default ?? 0 }) }}
</p>
<div
v-if="caps.pitch.options && caps.pitch.options.length > 0"
@@ -178,7 +178,7 @@
v-else
class="text-xs text-muted-foreground"
>
{{ $t('ttsProvider.noCapabilities') }}
{{ $t('speech.noCapabilities') }}
</div>
<Separator class="my-3" />
@@ -186,12 +186,12 @@
<!-- Test Synthesis -->
<div class="space-y-3">
<h4 class="text-xs font-medium">
{{ $t('ttsProvider.test.title') }}
{{ $t('speech.test.title') }}
</h4>
<div class="relative">
<Textarea
v-model="testText"
:placeholder="$t('ttsProvider.test.placeholder')"
:placeholder="$t('speech.test.placeholder')"
:maxlength="maxTestTextLen"
rows="2"
class="resize-none"
@@ -213,7 +213,7 @@
:icon="['fas', 'play']"
class="mr-1.5"
/>
{{ $t('ttsProvider.test.generate') }}
{{ $t('speech.test.generate') }}
</LoadingButton>
<span
v-if="testError"
@@ -398,7 +398,7 @@ async function handleTest() {
await new Promise<void>((resolve) => setTimeout(resolve, 50))
audioEl.value?.play()
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : t('ttsProvider.test.failed')
const msg = e instanceof Error ? e.message : t('speech.test.failed')
testError.value = msg
toast.error(msg)
} finally {
@@ -47,7 +47,7 @@
<section>
<div class="flex justify-between items-center mb-4">
<h3 class="text-xs font-medium">
{{ $t('ttsProvider.models') }}
{{ $t('speech.models') }}
</h3>
<div
v-if="curProviderId"
@@ -61,7 +61,7 @@
@click="handleImportModels"
>
<FontAwesomeIcon :icon="['fas', 'file-import']" />
{{ $t('ttsProvider.importModels') }}
{{ $t('speech.importModels') }}
</LoadingButton>
<AddTtsModel
:provider-id="curProviderId"
@@ -74,7 +74,7 @@
v-if="providerModels.length === 0"
class="text-xs text-muted-foreground py-4 text-center"
>
{{ $t('ttsProvider.noModels') }}
{{ $t('speech.noModels') }}
</div>
<div
@@ -119,7 +119,7 @@
<section class="flex justify-end mt-6 gap-4">
<ConfirmPopover
:message="$t('ttsProvider.deleteConfirm')"
:message="$t('speech.deleteConfirm')"
:loading="deleteLoading"
@confirm="handleDelete"
>
@@ -289,11 +289,11 @@ async function handleImportModels() {
headers: authHeaders(),
})
if (!resp.ok) throw new Error('Import failed')
toast.success(t('ttsProvider.importSuccess'))
toast.success(t('speech.importSuccess'))
refreshModels()
queryCache.invalidateQueries({ key: ['tts-models'] })
} catch (e: unknown) {
toast.error(e instanceof Error ? e.message : t('ttsProvider.importFailed'))
toast.error(e instanceof Error ? e.message : t('speech.importFailed'))
} finally {
importLoading.value = false
}
@@ -103,8 +103,8 @@ const openStatus = reactive({ addOpen: false })
<FontAwesomeIcon :icon="['fas', 'volume-high']" />
</EmptyMedia>
</EmptyHeader>
<EmptyTitle>{{ $t('ttsProvider.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('ttsProvider.emptyDescription') }}</EmptyDescription>
<EmptyTitle>{{ $t('speech.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('speech.emptyDescription') }}</EmptyDescription>
<EmptyContent>
<Button
variant="outline"
@@ -113,7 +113,7 @@ const openStatus = reactive({ addOpen: false })
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('ttsProvider.add') }}
/> {{ $t('speech.add') }}
</Button>
</EmptyContent>
</Empty>
@@ -2,9 +2,9 @@
<section>
<FormDialogShell
v-model:open="open"
:title="$t('searchProvider.add')"
:title="$t('webSearch.add')"
:cancel-text="$t('common.cancel')"
:submit-text="$t('searchProvider.add')"
:submit-text="$t('webSearch.add')"
:submit-disabled="(form.meta.value.valid === false) || isLoading"
:loading="isLoading"
@submit="handleCreate"
@@ -17,7 +17,7 @@
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('searchProvider.add') }}
/> {{ $t('webSearch.add') }}
</Button>
</template>
<template #body>
@@ -53,14 +53,14 @@
class="mb-2"
:for="componentField.id || 'search-provider-create-type'"
>
{{ $t('searchProvider.provider') }}
{{ $t('webSearch.provider') }}
</Label>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger
:id="componentField.id || 'search-provider-create-type'"
class="w-full"
:aria-label="$t('searchProvider.provider')"
:aria-label="$t('webSearch.provider')"
>
<SelectValue :placeholder="$t('common.typePlaceholder')" />
</SelectTrigger>
@@ -71,7 +71,7 @@
:key="type"
:value="type"
>
{{ $t(`searchProvider.providerNames.${type}`, type) }}
{{ $t(`webSearch.providerNames.${type}`, type) }}
</SelectItem>
</SelectGroup>
</SelectContent>
@@ -82,13 +82,13 @@
v-else-if="form.values.provider"
class="text-xs text-muted-foreground"
>
{{ $t('searchProvider.unsupportedProvider') }}
{{ $t('webSearch.unsupportedProvider') }}
</div>
</div>
<section class="flex justify-end mt-4 gap-4">
<ConfirmPopover
:message="$t('searchProvider.deleteConfirm')"
:message="$t('webSearch.deleteConfirm')"
:loading="deleteLoading"
@confirm="deleteProvider"
>
@@ -114,8 +114,8 @@ const openStatus = reactive({
<FontAwesomeIcon :icon="['fas', 'globe']" />
</EmptyMedia>
</EmptyHeader>
<EmptyTitle>{{ $t('searchProvider.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('searchProvider.emptyDescription') }}</EmptyDescription>
<EmptyTitle>{{ $t('webSearch.emptyTitle') }}</EmptyTitle>
<EmptyDescription>{{ $t('webSearch.emptyDescription') }}</EmptyDescription>
<EmptyContent>
<Button
variant="outline"
@@ -124,7 +124,7 @@ const openStatus = reactive({
<FontAwesomeIcon
:icon="['fas', 'plus']"
class="mr-1"
/> {{ $t('searchProvider.add') }}
/> {{ $t('webSearch.add') }}
</Button>
</EmptyContent>
</Empty>
+25 -25
View File
@@ -58,51 +58,51 @@ const routes = [
],
},
{
name: 'models',
path: 'models',
component: () => import('@/pages/models/index.vue'),
name: 'providers',
path: 'providers',
component: () => import('@/pages/providers/index.vue'),
meta: {
breadcrumb: i18nRef('sidebar.models'),
breadcrumb: i18nRef('sidebar.providers'),
},
},
{
name: 'search-providers',
path: 'search-providers',
component: () => import('@/pages/search-providers/index.vue'),
name: 'web-search',
path: 'web-search',
component: () => import('@/pages/web-search/index.vue'),
meta: {
breadcrumb: i18nRef('sidebar.searchProvider'),
breadcrumb: i18nRef('sidebar.webSearch'),
},
},
{
name: 'memory-providers',
path: 'memory-providers',
component: () => import('@/pages/memory-providers/index.vue'),
name: 'memory',
path: 'memory',
component: () => import('@/pages/memory/index.vue'),
meta: {
breadcrumb: i18nRef('sidebar.memoryProvider'),
breadcrumb: i18nRef('sidebar.memory'),
},
},
{
name: 'tts-providers',
path: 'tts-providers',
component: () => import('@/pages/tts-providers/index.vue'),
name: 'speech',
path: 'speech',
component: () => import('@/pages/speech/index.vue'),
meta: {
breadcrumb: i18nRef('sidebar.ttsProvider'),
breadcrumb: i18nRef('sidebar.speech'),
},
},
{
name: 'email-providers',
path: 'email-providers',
component: () => import('@/pages/email-providers/index.vue'),
name: 'email',
path: 'email',
component: () => import('@/pages/email/index.vue'),
meta: {
breadcrumb: i18nRef('sidebar.emailProvider'),
breadcrumb: i18nRef('sidebar.email'),
},
},
{
name: 'browser-contexts',
path: 'browser-contexts',
component: () => import('@/pages/browser-contexts/index.vue'),
name: 'browser',
path: 'browser',
component: () => import('@/pages/browser/index.vue'),
meta: {
breadcrumb: i18nRef('sidebar.browserContexts'),
breadcrumb: i18nRef('sidebar.browser'),
},
},
{
@@ -114,7 +114,7 @@ const routes = [
},
},
{
name: 'settings',
name: 'profile',
path: 'profile',
component: () => import('@/pages/settings/index.vue'),
meta: {