mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat(web): add timezone setup for each bot
This commit is contained in:
@@ -39,7 +39,8 @@
|
||||
"no": "No",
|
||||
"details": "Details",
|
||||
"pin": "Pin",
|
||||
"unpin": "Unpin"
|
||||
"unpin": "Unpin",
|
||||
"searchTimezone": "Search timezone"
|
||||
},
|
||||
"auth": {
|
||||
"welcome": "Welcome Back",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"no": "否",
|
||||
"details": "详情",
|
||||
"pin": "置顶",
|
||||
"unpin": "取消置顶"
|
||||
"unpin": "取消置顶",
|
||||
"searchTimezone": "搜索时区"
|
||||
},
|
||||
"auth": {
|
||||
"welcome": "欢迎回来",
|
||||
|
||||
@@ -212,6 +212,19 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>{{ $t('bots.timezone') }}</Label>
|
||||
<TimezoneSelect
|
||||
v-model="form.timezone"
|
||||
allow-empty
|
||||
:placeholder="defaultTimezone"
|
||||
:empty-label="timezoneEmptyLabel"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ $t('bots.timezoneInheritedHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Language -->
|
||||
@@ -262,10 +275,10 @@
|
||||
<!-- Save -->
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
:disabled="!hasChanges || isLoading"
|
||||
:disabled="!hasChanges || saveLoading"
|
||||
@click="handleSave"
|
||||
>
|
||||
<Spinner v-if="isLoading" />
|
||||
<Spinner v-if="saveLoading" />
|
||||
{{ $t('bots.settings.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -322,6 +335,7 @@ import { useRouter } from 'vue-router'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import TimezoneSelect from '@/components/timezone-select/index.vue'
|
||||
import ModelSelect from './model-select.vue'
|
||||
import ReasoningEffortSelect from './reasoning-effort-select.vue'
|
||||
import { EFFORT_LABELS, EFFORT_OPACITY } from './reasoning-effort'
|
||||
@@ -330,10 +344,11 @@ import MemoryProviderSelect from './memory-provider-select.vue'
|
||||
import TtsModelSelect from './tts-model-select.vue'
|
||||
import BrowserContextSelect from './browser-context-select.vue'
|
||||
import { useQuery, useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { getBotsByBotIdSettings, putBotsByBotIdSettings, deleteBotsById, getModels, getProviders, getSearchProviders, getMemoryProviders, getSpeechProviders, getSpeechModels, getBrowserContexts, getBotsByBotIdMemoryStatus, postBotsByBotIdMemoryRebuild } from '@memohai/sdk'
|
||||
import { getBotsById, putBotsById, getBotsByBotIdSettings, putBotsByBotIdSettings, deleteBotsById, getModels, getProviders, getSearchProviders, getMemoryProviders, getSpeechProviders, getSpeechModels, getBrowserContexts, getBotsByBotIdMemoryStatus, postBotsByBotIdMemoryRebuild } from '@memohai/sdk'
|
||||
import type { SettingsSettings } from '@memohai/sdk'
|
||||
import type { Ref } from 'vue'
|
||||
import { resolveApiErrorMessage } from '@/utils/api-error'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
const props = defineProps<{
|
||||
botId: string
|
||||
@@ -341,8 +356,13 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const botIdRef = computed(() => props.botId) as Ref<string>
|
||||
const defaultTimezone = computed(() => userStore.userInfo.timezone || 'UTC')
|
||||
const timezoneEmptyLabel = computed(() =>
|
||||
`${t('bots.timezoneInherited')} (${defaultTimezone.value})`,
|
||||
)
|
||||
|
||||
// ---- Data ----
|
||||
const queryCache = useQueryCache()
|
||||
@@ -356,6 +376,15 @@ const { data: settings } = useQuery({
|
||||
enabled: () => !!botIdRef.value,
|
||||
})
|
||||
|
||||
const { data: bot } = useQuery({
|
||||
key: () => ['bot', botIdRef.value],
|
||||
query: async () => {
|
||||
const { data } = await getBotsById({ path: { id: botIdRef.value }, throwOnError: true })
|
||||
return data
|
||||
},
|
||||
enabled: () => !!botIdRef.value,
|
||||
})
|
||||
|
||||
const { data: modelData } = useQuery({
|
||||
key: ['all-models'],
|
||||
query: async () => {
|
||||
@@ -424,6 +453,21 @@ const { mutateAsync: updateSettings, isLoading } = useMutation({
|
||||
onSettled: () => queryCache.invalidateQueries({ key: ['bot-settings', botIdRef.value] }),
|
||||
})
|
||||
|
||||
const { mutateAsync: updateBot, isLoading: isUpdatingBot } = useMutation({
|
||||
mutation: async (timezone: string) => {
|
||||
const { data } = await putBotsById({
|
||||
path: { id: botIdRef.value },
|
||||
body: { timezone },
|
||||
throwOnError: true,
|
||||
})
|
||||
return data
|
||||
},
|
||||
onSettled: () => {
|
||||
queryCache.invalidateQueries({ key: ['bot', botIdRef.value] })
|
||||
queryCache.invalidateQueries({ key: ['bots'] })
|
||||
},
|
||||
})
|
||||
|
||||
const { mutateAsync: deleteBot, isLoading: deleteLoading } = useMutation({
|
||||
mutation: async () => {
|
||||
await deleteBotsById({ path: { id: botIdRef.value }, throwOnError: true })
|
||||
@@ -455,6 +499,7 @@ const form = reactive({
|
||||
memory_provider_id: '',
|
||||
tts_model_id: '',
|
||||
browser_context_id: '',
|
||||
timezone: '',
|
||||
language: '',
|
||||
reasoning_enabled: false,
|
||||
reasoning_effort: 'medium',
|
||||
@@ -599,10 +644,14 @@ watch(settings, (val) => {
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const hasChanges = computed(() => {
|
||||
watch(bot, (val) => {
|
||||
form.timezone = val?.timezone ?? ''
|
||||
}, { immediate: true })
|
||||
|
||||
const hasSettingsChanges = computed(() => {
|
||||
if (!settings.value) return true
|
||||
const s = settings.value
|
||||
let changed =
|
||||
return (
|
||||
form.chat_model_id !== (s.chat_model_id ?? '')
|
||||
|| form.title_model_id !== (s.title_model_id ?? '')
|
||||
|| form.image_model_id !== (s.image_model_id ?? '')
|
||||
@@ -613,15 +662,25 @@ const hasChanges = computed(() => {
|
||||
|| form.language !== (s.language ?? '')
|
||||
|| form.reasoning_enabled !== (s.reasoning_enabled ?? false)
|
||||
|| form.reasoning_effort !== (s.reasoning_effort || 'medium')
|
||||
return changed
|
||||
)
|
||||
})
|
||||
|
||||
const hasTimezoneChanges = computed(() => form.timezone !== (bot.value?.timezone ?? ''))
|
||||
const hasChanges = computed(() => hasSettingsChanges.value || hasTimezoneChanges.value)
|
||||
const saveLoading = computed(() => isLoading.value || isUpdatingBot.value)
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
await updateSettings({ ...form })
|
||||
if (hasSettingsChanges.value) {
|
||||
const { timezone: _timezone, ...settingsPayload } = form
|
||||
await updateSettings(settingsPayload)
|
||||
}
|
||||
if (hasTimezoneChanges.value) {
|
||||
await updateBot(form.timezone)
|
||||
}
|
||||
toast.success(t('bots.settings.saveSuccess'))
|
||||
} catch {
|
||||
return
|
||||
} catch (error) {
|
||||
toast.error(resolveApiErrorMessage(error, t('common.saveFailed')))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user