116116 </v-card >
117117 </v-col >
118118 </v-row >
119+
120+ <v-row v-if =" showAnnouncement" class =" px-4 mb-4" >
121+ <v-col cols =" 12" >
122+ <v-card class =" welcome-card pa-6" elevation =" 0" border >
123+ <div class =" mb-4 text-h3 font-weight-bold" >
124+ {{ tm('announcement.title') }}
125+ </div >
126+ <MarkdownRender
127+ :content =" welcomeAnnouncement"
128+ :typewriter =" false"
129+ class =" welcome-announcement-markdown markdown-content"
130+ />
131+ </v-card >
132+ </v-col >
133+ </v-row >
119134 </v-container >
120135
121136 <AddNewPlatform v-model:show =" showAddPlatformDialog" :metadata =" platformMetadata" :config_data =" platformConfigData"
@@ -129,12 +144,16 @@ import { computed, ref, watch, onMounted } from 'vue';
129144import axios from ' axios' ;
130145import AddNewPlatform from ' @/components/platform/AddNewPlatform.vue' ;
131146import ProviderConfigDialog from ' @/components/chat/ProviderConfigDialog.vue' ;
132- import { useModuleI18n } from ' @/i18n/composables' ;
147+ import { useI18n , useModuleI18n } from ' @/i18n/composables' ;
133148import { useToast } from ' @/utils/toast' ;
149+ import { MarkdownRender } from ' markstream-vue' ;
150+ import ' markstream-vue/index.css' ;
151+ import ' highlight.js/styles/github.css' ;
134152
135153type StepState = ' pending' | ' completed' | ' skipped' ;
136154
137155const { tm } = useModuleI18n (' features/welcome' );
156+ const { locale } = useI18n ();
138157const { success : showSuccess, error : showError } = useToast ();
139158
140159const showAddPlatformDialog = ref (false );
@@ -148,6 +167,38 @@ const providerCountBeforeOpen = ref(0);
148167
149168const platformStepState = ref <StepState >(' pending' );
150169const providerStepState = ref <StepState >(' pending' );
170+ const welcomeAnnouncementRaw = ref <unknown >(null );
171+
172+ function resolveWelcomeAnnouncement(raw : unknown , currentLocale : string ) {
173+ if (typeof raw === ' string' ) {
174+ return raw .trim ();
175+ }
176+
177+ if (! raw || typeof raw !== ' object' || Array .isArray (raw )) {
178+ return ' ' ;
179+ }
180+
181+ const localeMap = raw as Record <string , unknown >;
182+ const normalized = currentLocale .replace (' -' , ' _' );
183+ const preferredKeys =
184+ normalized .startsWith (' zh' )
185+ ? [normalized , ' zh_CN' , ' zh-CN' , ' zh' , ' en_US' , ' en-US' , ' en' ]
186+ : [normalized , ' en_US' , ' en-US' , ' en' , ' zh_CN' , ' zh-CN' , ' zh' ];
187+
188+ for (const key of preferredKeys ) {
189+ const value = localeMap [key ];
190+ if (typeof value === ' string' && value .trim ().length > 0 ) {
191+ return value .trim ();
192+ }
193+ }
194+
195+ return ' ' ;
196+ }
197+
198+ const welcomeAnnouncement = computed (() =>
199+ resolveWelcomeAnnouncement (welcomeAnnouncementRaw .value , locale .value )
200+ );
201+ const showAnnouncement = computed (() => welcomeAnnouncement .value .length > 0 );
151202
152203const springFestivalDates: Record <number , string > = {
153204 2025 : ' 01-29' ,
@@ -285,7 +336,19 @@ async function syncDefaultConfigProviderIfNeeded() {
285336 showSuccess (tm (' onboard.providerDefaultUpdated' , { id: targetProviderId }));
286337}
287338
339+ async function loadWelcomeAnnouncement() {
340+ try {
341+ const res = await axios .get (' https://cloud.astrbot.app/api/v1/announcement' );
342+ welcomeAnnouncementRaw .value = res ?.data ?.data ?.notice ?.welcome_page ?? null ;
343+ } catch (e ) {
344+ welcomeAnnouncementRaw .value = null ;
345+ console .error (e );
346+ }
347+ }
348+
288349onMounted (async () => {
350+ await loadWelcomeAnnouncement ();
351+
289352 try {
290353 await loadPlatformConfigBase ();
291354 if ((platformConfigData .value .platform || []).length > 0 ) {
@@ -363,4 +426,8 @@ watch(showProviderDialog, async (visible, wasVisible) => {
363426.welcome-card {
364427 border-radius : 16px ;
365428}
429+
430+ .welcome-announcement-markdown {
431+ line-height : 1.7 ;
432+ }
366433 </style >
0 commit comments