Skip to content

Commit e8d6938

Browse files
authored
Feat(webui): dashboard and console qol improvements (#7215)
* feat: support native fullscreen for log console * feat: skip welcome page for configured users * feat: display plugin names under pinned icons * fix: refine container styles
1 parent 206973e commit e8d6938

File tree

4 files changed

+107
-18
lines changed

4 files changed

+107
-18
lines changed

dashboard/src/components/extension/PinnedPluginItem.vue

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,26 @@ const authorDisplay = computed(() => {
6565
>
6666
<v-menu offset-y>
6767
<template #activator="{ props: menuProps }">
68-
<v-avatar
69-
v-bind="menuProps"
70-
size="72"
71-
class="pinned-avatar activator-avatar"
72-
:title="plugin.display_name || plugin.name"
73-
>
74-
<img
75-
:src="(typeof plugin.logo === 'string' && plugin.logo.trim()) ? plugin.logo : defaultPluginIcon"
76-
:alt="plugin.name"
77-
@error="handlePinnedImgError"
78-
/>
79-
</v-avatar>
68+
<div class="d-flex flex-column align-center" style="cursor: pointer; width: 80px;">
69+
<v-avatar
70+
v-bind="menuProps"
71+
size="72"
72+
class="pinned-avatar activator-avatar mb-1"
73+
:title="plugin.display_name || plugin.name"
74+
>
75+
<img
76+
:src="(typeof plugin.logo === 'string' && plugin.logo.trim()) ? plugin.logo : defaultPluginIcon"
77+
:alt="plugin.name"
78+
@error="handlePinnedImgError"
79+
/>
80+
</v-avatar>
81+
<span
82+
class="text-caption text-center text-truncate"
83+
style="width: 100%; font-size: 0.75rem; opacity: 0.9; line-height: 1.2;"
84+
>
85+
{{ plugin.display_name || plugin.name }}
86+
</span>
87+
</div>
8088
</template>
8189

8290
<v-card>
@@ -182,8 +190,7 @@ const authorDisplay = computed(() => {
182190
.pinned-card-wrapper {
183191
position: relative;
184192
display: inline-block;
185-
width: 72px;
186-
height: 72px;
193+
width: 80px;
187194
}
188195
189196
.pinned-item {

dashboard/src/components/shared/ConsoleDisplayer.vue

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
55
</script>
66

77
<template>
8-
<div>
8+
<div class="console-displayer-wrapper" id="console-wrapper">
99
<div class="filter-controls mb-2" v-if="showLevelBtns">
1010
<v-chip-group v-model="selectedLevels" column multiple>
1111
<v-chip v-for="level in logLevels" :key="level" :color="getLevelColor(level)" filter variant="flat" size="small"
1212
:text-color="level === 'DEBUG' || level === 'INFO' ? 'black' : 'white'" class="font-weight-medium">
1313
{{ level }}
1414
</v-chip>
1515
</v-chip-group>
16+
<v-spacer></v-spacer>
17+
<v-btn
18+
:icon="isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'"
19+
variant="text"
20+
density="compact"
21+
class="me-4 fullscreen-btn"
22+
@click="toggleFullscreen"
23+
></v-btn>
1624
</div>
1725

1826
<div id="term" style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:auto; height: 100%">
@@ -26,6 +34,7 @@ export default {
2634
data() {
2735
return {
2836
autoScroll: true,
37+
isFullscreen: false,
2938
logColorAnsiMap: {
3039
'\u001b[1;34m': 'color: #39C5BB; font-weight: bold;',
3140
'\u001b[1;36m': 'color: #00FFFF; font-weight: bold;',
@@ -80,8 +89,10 @@ export default {
8089
async mounted() {
8190
await this.fetchLogHistory();
8291
this.connectSSE();
92+
document.addEventListener('fullscreenchange', this.handleFullscreenChange);
8393
},
8494
beforeUnmount() {
95+
document.removeEventListener('fullscreenchange', this.handleFullscreenChange);
8596
if (this.eventSource) {
8697
this.eventSource.close();
8798
this.eventSource = null;
@@ -253,6 +264,21 @@ export default {
253264
this.autoScroll = !this.autoScroll;
254265
},
255266
267+
toggleFullscreen() {
268+
const container = document.getElementById('console-wrapper');
269+
if (!document.fullscreenElement) {
270+
container.requestFullscreen().catch(err => {
271+
console.error(`Error attempting to enable full-screen mode: ${err.message}`);
272+
});
273+
} else {
274+
document.exitFullscreen();
275+
}
276+
},
277+
278+
handleFullscreenChange() {
279+
this.isFullscreen = !!document.fullscreenElement;
280+
},
281+
256282
printLog(log) {
257283
let ele = document.getElementById('term')
258284
if (!ele) {
@@ -282,14 +308,30 @@ export default {
282308
</script>
283309

284310
<style scoped>
311+
.console-displayer-wrapper {
312+
height: 100%;
313+
display: flex;
314+
flex-direction: column;
315+
}
316+
317+
#console-wrapper:fullscreen {
318+
background-color: #1e1e1e;
319+
padding: 20px;
320+
}
321+
285322
.filter-controls {
286323
display: flex;
324+
align-items: center;
287325
flex-wrap: wrap;
288326
gap: 8px;
289327
margin-bottom: 8px;
290328
margin-left: 20px;
291329
}
292330
331+
.fullscreen-btn {
332+
color: rgba(255, 255, 255, 0.7) !important; /* 提高在深色背景下的对比度 */
333+
}
334+
293335
:deep(.console-log-line) {
294336
display: block;
295337
margin-bottom: 2px;

dashboard/src/stores/auth.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,47 @@ export const useAuthStore = defineStore({
2525
localStorage.setItem('user', this.username);
2626
localStorage.setItem('token', res.data.data.token);
2727
localStorage.setItem('change_pwd_hint', res.data.data?.change_pwd_hint);
28+
29+
const onboardingCompleted = await this.checkOnboardingCompleted();
2830
this.returnUrl = null;
29-
router.push('/welcome');
31+
if (onboardingCompleted) {
32+
router.push('/dashboard/default');
33+
} else {
34+
router.push('/welcome');
35+
}
3036
} catch (error) {
3137
return Promise.reject(error);
3238
}
3339
},
40+
async checkOnboardingCompleted(): Promise<boolean> {
41+
try {
42+
// 1. 检查平台配置
43+
const platformRes = await axios.get('/api/config/get');
44+
const hasPlatform = (platformRes.data.data.config.platform || []).length > 0;
45+
if (!hasPlatform) return false;
46+
47+
// 2. 检查提供者配置
48+
const providerRes = await axios.get('/api/config/provider/template');
49+
const providers = providerRes.data.data?.providers || [];
50+
const sources = providerRes.data.data?.provider_sources || [];
51+
const sourceMap = new Map();
52+
sources.forEach((s: any) => sourceMap.set(s.id, s.provider_type));
53+
54+
const hasProvider = providers.some((provider: any) => {
55+
if (provider.provider_type) return provider.provider_type === 'chat_completion';
56+
if (provider.provider_source_id) {
57+
const type = sourceMap.get(provider.provider_source_id);
58+
if (type === 'chat_completion') return true;
59+
}
60+
return String(provider.type || '').includes('chat_completion');
61+
});
62+
63+
return hasProvider;
64+
} catch (e) {
65+
console.error('Failed to check onboarding status:', e);
66+
return false;
67+
}
68+
},
3469
logout() {
3570
this.username = '';
3671
localStorage.removeItem('user');

dashboard/src/views/authentication/auth/LoginPage.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ function toggleTheme() {
2222
theme.global.name.value = newTheme;
2323
}
2424
25-
onMounted(() => {
25+
onMounted(async () => {
2626
// 检查用户是否已登录,如果已登录则重定向
2727
if (authStore.has_token()) {
28-
router.push('/welcome');
28+
const onboardingCompleted = await authStore.checkOnboardingCompleted();
29+
if (onboardingCompleted) {
30+
router.push('/dashboard/default');
31+
} else {
32+
router.push('/welcome');
33+
}
2934
return;
3035
}
3136

0 commit comments

Comments
 (0)