diff --git a/backend/internal/web/embed_on.go b/backend/internal/web/embed_on.go index 41ce4d48..ffca98a5 100644 --- a/backend/internal/web/embed_on.go +++ b/backend/internal/web/embed_on.go @@ -180,7 +180,37 @@ func (s *FrontendServer) injectSettings(settingsJSON []byte) []byte { // Inject before headClose := []byte("") - return bytes.Replace(s.baseHTML, headClose, append(script, headClose...), 1) + result := bytes.Replace(s.baseHTML, headClose, append(script, headClose...), 1) + + // Replace with custom site name so the browser tab shows it immediately + result = injectSiteTitle(result, settingsJSON) + + return result +} + +// injectSiteTitle replaces the static <title> in HTML with the configured site name. +// This ensures the browser tab shows the correct title before JS executes. +func injectSiteTitle(html, settingsJSON []byte) []byte { + var cfg struct { + SiteName string `json:"site_name"` + } + if err := json.Unmarshal(settingsJSON, &cfg); err != nil || cfg.SiteName == "" { + return html + } + + // Find and replace the existing <title>... + titleStart := bytes.Index(html, []byte("")) + titleEnd := bytes.Index(html, []byte("")) + if titleStart == -1 || titleEnd == -1 || titleEnd <= titleStart { + return html + } + + newTitle := []byte("" + cfg.SiteName + " - AI API Gateway") + var buf bytes.Buffer + buf.Write(html[:titleStart]) + buf.Write(newTitle) + buf.Write(html[titleEnd+len(""):]) + return buf.Bytes() } // replaceNoncePlaceholder replaces the nonce placeholder with actual nonce value diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 4fc6a7c8..7485aa1a 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,6 +3,7 @@ import { RouterView, useRouter, useRoute } from 'vue-router' import { onMounted, onBeforeUnmount, watch } from 'vue' import Toast from '@/components/common/Toast.vue' import NavigationProgress from '@/components/common/NavigationProgress.vue' +import { resolveDocumentTitle } from '@/router/title' import AnnouncementPopup from '@/components/common/AnnouncementPopup.vue' import { useAppStore, useAuthStore, useSubscriptionStore, useAnnouncementStore } from '@/stores' import { getSetupStatus } from '@/api/setup' @@ -104,6 +105,9 @@ onMounted(async () => { // Load public settings into appStore (will be cached for other components) await appStore.fetchPublicSettings() + + // Re-resolve document title now that siteName is available + document.title = resolveDocumentTitle(route.meta.title, appStore.siteName, route.meta.titleKey as string) })