-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Feat(webui): dashboard and console qol improvements #7215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,14 +5,22 @@ import { EventSourcePolyfill } from 'event-source-polyfill'; | |
| </script> | ||
|
|
||
| <template> | ||
| <div> | ||
| <div class="console-displayer-wrapper" id="console-wrapper"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| <div class="filter-controls mb-2" v-if="showLevelBtns"> | ||
| <v-chip-group v-model="selectedLevels" column multiple> | ||
| <v-chip v-for="level in logLevels" :key="level" :color="getLevelColor(level)" filter variant="flat" size="small" | ||
| :text-color="level === 'DEBUG' || level === 'INFO' ? 'black' : 'white'" class="font-weight-medium"> | ||
| {{ level }} | ||
| </v-chip> | ||
| </v-chip-group> | ||
| <v-spacer></v-spacer> | ||
| <v-btn | ||
| :icon="isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'" | ||
| variant="text" | ||
| density="compact" | ||
| class="me-4 fullscreen-btn" | ||
| @click="toggleFullscreen" | ||
| ></v-btn> | ||
| </div> | ||
|
|
||
| <div id="term" style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:auto; height: 100%"> | ||
|
|
@@ -26,6 +34,7 @@ export default { | |
| data() { | ||
| return { | ||
| autoScroll: true, | ||
| isFullscreen: false, | ||
| logColorAnsiMap: { | ||
| '\u001b[1;34m': 'color: #39C5BB; font-weight: bold;', | ||
| '\u001b[1;36m': 'color: #00FFFF; font-weight: bold;', | ||
|
|
@@ -80,8 +89,10 @@ export default { | |
| async mounted() { | ||
| await this.fetchLogHistory(); | ||
| this.connectSSE(); | ||
| document.addEventListener('fullscreenchange', this.handleFullscreenChange); | ||
| }, | ||
| beforeUnmount() { | ||
| document.removeEventListener('fullscreenchange', this.handleFullscreenChange); | ||
| if (this.eventSource) { | ||
| this.eventSource.close(); | ||
| this.eventSource = null; | ||
|
|
@@ -253,6 +264,21 @@ export default { | |
| this.autoScroll = !this.autoScroll; | ||
| }, | ||
|
|
||
| toggleFullscreen() { | ||
| const container = document.getElementById('console-wrapper'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if (!document.fullscreenElement) { | ||
|
Comment on lines
+267
to
+269
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Guard against a missing If const container = document.getElementById('console-wrapper');
if (!container) {
console.warn('Console wrapper element not found for fullscreen');
return;
} |
||
| container.requestFullscreen().catch(err => { | ||
| console.error(`Error attempting to enable full-screen mode: ${err.message}`); | ||
| }); | ||
| } else { | ||
| document.exitFullscreen(); | ||
| } | ||
| }, | ||
|
|
||
| handleFullscreenChange() { | ||
| this.isFullscreen = !!document.fullscreenElement; | ||
| }, | ||
|
|
||
| printLog(log) { | ||
| let ele = document.getElementById('term') | ||
| if (!ele) { | ||
|
|
@@ -282,14 +308,30 @@ export default { | |
| </script> | ||
|
|
||
| <style scoped> | ||
| .console-displayer-wrapper { | ||
| height: 100%; | ||
| display: flex; | ||
| flex-direction: column; | ||
| } | ||
|
|
||
| #console-wrapper:fullscreen { | ||
| background-color: #1e1e1e; | ||
| padding: 20px; | ||
| } | ||
|
|
||
| .filter-controls { | ||
| display: flex; | ||
| align-items: center; | ||
| flex-wrap: wrap; | ||
| gap: 8px; | ||
| margin-bottom: 8px; | ||
| margin-left: 20px; | ||
| } | ||
|
|
||
| .fullscreen-btn { | ||
| color: rgba(255, 255, 255, 0.7) !important; /* 提高在深色背景下的对比度 */ | ||
| } | ||
|
|
||
| :deep(.console-log-line) { | ||
| display: block; | ||
| margin-bottom: 2px; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,12 +25,47 @@ export const useAuthStore = defineStore({ | |
| localStorage.setItem('user', this.username); | ||
| localStorage.setItem('token', res.data.data.token); | ||
| localStorage.setItem('change_pwd_hint', res.data.data?.change_pwd_hint); | ||
|
|
||
| const onboardingCompleted = await this.checkOnboardingCompleted(); | ||
| this.returnUrl = null; | ||
| router.push('/welcome'); | ||
| if (onboardingCompleted) { | ||
| router.push('/dashboard/default'); | ||
| } else { | ||
| router.push('/welcome'); | ||
| } | ||
| } catch (error) { | ||
| return Promise.reject(error); | ||
| } | ||
| }, | ||
| async checkOnboardingCompleted(): Promise<boolean> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (complexity): Consider extracting the onboarding check logic into a dedicated service that also centralizes provider type resolution to keep the auth store focused and easier to maintain. You can keep the new behavior but reduce complexity and coupling by:
For example, create a dedicated onboarding service: // services/onboardingService.ts
import axios from 'axios';
async function fetchPlatformHasConfig(): Promise<boolean> {
const res = await axios.get('/api/config/get');
return (res.data.data.config.platform || []).length > 0;
}
function resolveProviderType(
provider: any,
sourceTypeById: Record<string, string>,
): string | null {
if (provider.provider_type) return provider.provider_type;
if (provider.provider_source_id) {
return sourceTypeById[provider.provider_source_id] || null;
}
const legacyType = String(provider.type || '');
return legacyType || null;
}
async function fetchHasChatCompletionProvider(): Promise<boolean> {
const res = await axios.get('/api/config/provider/template');
const providers = res.data.data?.providers || [];
const sources = res.data.data?.provider_sources || [];
const sourceTypeById: Record<string, string> = {};
sources.forEach((s: any) => {
sourceTypeById[s.id] = s.provider_type;
});
return providers.some((p: any) => {
const type = resolveProviderType(p, sourceTypeById);
return type === 'chat_completion' || String(type).includes('chat_completion');
});
}
export async function isOnboardingCompleted(): Promise<boolean> {
try {
const hasPlatform = await fetchPlatformHasConfig();
if (!hasPlatform) return false;
return await fetchHasChatCompletionProvider();
} catch (e) {
console.error('Failed to check onboarding status:', e);
return false; // preserve existing behavior
}
}Then the store stays focused and orchestration is clearer: // in auth store
import { isOnboardingCompleted } from '@/services/onboardingService';
async login(username: string, password: string) {
try {
const res = await axios.post('/api/login', { username, password });
// ... existing login logic ...
const onboardingCompleted = await isOnboardingCompleted();
this.returnUrl = null;
router.push(onboardingCompleted ? '/dashboard/default' : '/welcome');
} catch (error) {
return Promise.reject(error);
}
}This keeps functionality identical while:
|
||
| try { | ||
| // 1. 检查平台配置 | ||
| const platformRes = await axios.get('/api/config/get'); | ||
| const hasPlatform = (platformRes.data.data.config.platform || []).length > 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if (!hasPlatform) return false; | ||
|
|
||
| // 2. 检查提供者配置 | ||
| const providerRes = await axios.get('/api/config/provider/template'); | ||
| const providers = providerRes.data.data?.providers || []; | ||
| const sources = providerRes.data.data?.provider_sources || []; | ||
| const sourceMap = new Map(); | ||
| sources.forEach((s: any) => sourceMap.set(s.id, s.provider_type)); | ||
|
|
||
| const hasProvider = providers.some((provider: any) => { | ||
| if (provider.provider_type) return provider.provider_type === 'chat_completion'; | ||
| if (provider.provider_source_id) { | ||
| const type = sourceMap.get(provider.provider_source_id); | ||
| if (type === 'chat_completion') return true; | ||
| } | ||
| return String(provider.type || '').includes('chat_completion'); | ||
| }); | ||
|
|
||
| return hasProvider; | ||
| } catch (e) { | ||
| console.error('Failed to check onboarding status:', e); | ||
| return false; | ||
| } | ||
| }, | ||
| logout() { | ||
| this.username = ''; | ||
| localStorage.removeItem('user'); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.