ux/ui fix
This commit is contained in:
		
							parent
							
								
									d62dad6992
								
							
						
					
					
						commit
						f18fae6c90
					
				@ -381,11 +381,10 @@ class ModelStateService extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    async removeApiKey(provider) {
 | 
			
		||||
        if (this.state.apiKeys[provider]) {
 | 
			
		||||
            delete this.state.apiKeys[provider];
 | 
			
		||||
            this._saveState();
 | 
			
		||||
            
 | 
			
		||||
            this.state.apiKeys[provider] = null;
 | 
			
		||||
            await providerSettingsRepository.remove(provider);
 | 
			
		||||
            await this._saveState();
 | 
			
		||||
            this._autoSelectAvailableModels([]);
 | 
			
		||||
            
 | 
			
		||||
            this._broadcastToAllWindows('model-state:updated', this.state);
 | 
			
		||||
            this._broadcastToAllWindows('settings-updated');
 | 
			
		||||
            return true;
 | 
			
		||||
@ -527,7 +526,10 @@ class ModelStateService extends EventEmitter {
 | 
			
		||||
        
 | 
			
		||||
        // Auto warm-up for Ollama models
 | 
			
		||||
        if (type === 'llm' && modelId && modelId !== previousModelId) {
 | 
			
		||||
            this._autoWarmUpOllamaModel(modelId, previousModelId);
 | 
			
		||||
            const provider = this.getProviderForModel('llm', modelId);
 | 
			
		||||
            if (provider === 'ollama') {
 | 
			
		||||
                this._autoWarmUpOllamaModel(modelId, previousModelId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._broadcastToAllWindows('model-state:updated', this.state);
 | 
			
		||||
 | 
			
		||||
@ -532,6 +532,7 @@ async function handleFirebaseAuthCallback(params) {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // 1. Sync user data to local DB
 | 
			
		||||
        userRepository.setAuthService(authService);
 | 
			
		||||
        userRepository.findOrCreate(firebaseUser);
 | 
			
		||||
        console.log('[Auth] User data synced with local DB.');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,18 +1,20 @@
 | 
			
		||||
import './MainHeader.js';
 | 
			
		||||
import './ApiKeyHeader.js';
 | 
			
		||||
import './PermissionHeader.js';
 | 
			
		||||
import './WelcomeHeader.js';
 | 
			
		||||
 | 
			
		||||
class HeaderTransitionManager {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.headerContainer      = document.getElementById('header-container');
 | 
			
		||||
        this.currentHeaderType    = null;   // 'apikey' | 'main' | 'permission'
 | 
			
		||||
        this.currentHeaderType    = null;   // 'welcome' | 'apikey' | 'main' | 'permission'
 | 
			
		||||
        this.welcomeHeader        = null;
 | 
			
		||||
        this.apiKeyHeader         = null;
 | 
			
		||||
        this.mainHeader            = null;
 | 
			
		||||
        this.permissionHeader      = null;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * only one header window is allowed
 | 
			
		||||
         * @param {'apikey'|'main'|'permission'} type
 | 
			
		||||
         * @param {'welcome'|'apikey'|'main'|'permission'} type
 | 
			
		||||
         */
 | 
			
		||||
        this.ensureHeader = (type) => {
 | 
			
		||||
            console.log('[HeaderController] ensureHeader: Ensuring header of type:', type);
 | 
			
		||||
@ -23,14 +25,25 @@ class HeaderTransitionManager {
 | 
			
		||||
 | 
			
		||||
            this.headerContainer.innerHTML = '';
 | 
			
		||||
            
 | 
			
		||||
            this.welcomeHeader = null;
 | 
			
		||||
            this.apiKeyHeader = null;
 | 
			
		||||
            this.mainHeader = null;
 | 
			
		||||
            this.permissionHeader = null;
 | 
			
		||||
 | 
			
		||||
            // Create new header element
 | 
			
		||||
            if (type === 'apikey') {
 | 
			
		||||
            if (type === 'welcome') {
 | 
			
		||||
                this.welcomeHeader = document.createElement('welcome-header');
 | 
			
		||||
                this.welcomeHeader.loginCallback = () => this.handleLoginOption();
 | 
			
		||||
                this.welcomeHeader.apiKeyCallback = () => this.handleApiKeyOption();
 | 
			
		||||
                this.headerContainer.appendChild(this.welcomeHeader);
 | 
			
		||||
                console.log('[HeaderController] ensureHeader: Header of type:', type, 'created.');
 | 
			
		||||
            } else if (type === 'apikey') {
 | 
			
		||||
                this.apiKeyHeader = document.createElement('apikey-header');
 | 
			
		||||
                this.apiKeyHeader.stateUpdateCallback = (userState) => this.handleStateUpdate(userState);
 | 
			
		||||
                this.apiKeyHeader.backCallback = () => this.transitionToWelcomeHeader();
 | 
			
		||||
                this.apiKeyHeader.addEventListener('request-resize', e => {
 | 
			
		||||
                    this._resizeForApiKey(e.detail.height);
 | 
			
		||||
                });
 | 
			
		||||
                this.headerContainer.appendChild(this.apiKeyHeader);
 | 
			
		||||
                console.log('[HeaderController] ensureHeader: Header of type:', type, 'created.');
 | 
			
		||||
            } else if (type === 'permission') {
 | 
			
		||||
@ -49,6 +62,10 @@ class HeaderTransitionManager {
 | 
			
		||||
 | 
			
		||||
        console.log('[HeaderController] Manager initialized');
 | 
			
		||||
 | 
			
		||||
        // WelcomeHeader 콜백 메서드들
 | 
			
		||||
        this.handleLoginOption = this.handleLoginOption.bind(this);
 | 
			
		||||
        this.handleApiKeyOption = this.handleApiKeyOption.bind(this);
 | 
			
		||||
 | 
			
		||||
        this._bootstrap();
 | 
			
		||||
 | 
			
		||||
        if (window.api) {
 | 
			
		||||
@ -66,8 +83,14 @@ class HeaderTransitionManager {
 | 
			
		||||
            });
 | 
			
		||||
            window.api.headerController.onForceShowApiKeyHeader(async () => {
 | 
			
		||||
                console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
 | 
			
		||||
                await this._resizeForApiKey();
 | 
			
		||||
                this.ensureHeader('apikey');
 | 
			
		||||
                const isConfigured = await window.api.apiKeyHeader.areProvidersConfigured();
 | 
			
		||||
                if (!isConfigured) {
 | 
			
		||||
                    await this._resizeForWelcome();
 | 
			
		||||
                    this.ensureHeader('welcome');
 | 
			
		||||
                } else {
 | 
			
		||||
                    await this._resizeForApiKey();
 | 
			
		||||
                    this.ensureHeader('apikey');
 | 
			
		||||
                }
 | 
			
		||||
            });            
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -88,7 +111,7 @@ class HeaderTransitionManager {
 | 
			
		||||
            this.handleStateUpdate(userState);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Fallback for non-electron environment (testing/web)
 | 
			
		||||
            this.ensureHeader('apikey');
 | 
			
		||||
            this.ensureHeader('welcome');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -110,10 +133,38 @@ class HeaderTransitionManager {
 | 
			
		||||
                this.transitionToMainHeader();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            await this._resizeForApiKey();
 | 
			
		||||
            this.ensureHeader('apikey');
 | 
			
		||||
            // 프로바이더가 설정되지 않았으면 WelcomeHeader 먼저 표시
 | 
			
		||||
            await this._resizeForWelcome();
 | 
			
		||||
            this.ensureHeader('welcome');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // WelcomeHeader 콜백 메서드들
 | 
			
		||||
    async handleLoginOption() {
 | 
			
		||||
        console.log('[HeaderController] Login option selected');
 | 
			
		||||
        if (window.api) {
 | 
			
		||||
            await window.api.common.startFirebaseAuth();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async handleApiKeyOption() {
 | 
			
		||||
        console.log('[HeaderController] API key option selected');
 | 
			
		||||
        await this._resizeForApiKey(400);
 | 
			
		||||
        this.ensureHeader('apikey');
 | 
			
		||||
        // ApiKeyHeader에 뒤로가기 콜백 설정
 | 
			
		||||
        if (this.apiKeyHeader) {
 | 
			
		||||
            this.apiKeyHeader.backCallback = () => this.transitionToWelcomeHeader();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async transitionToWelcomeHeader() {
 | 
			
		||||
        if (this.currentHeaderType === 'welcome') {
 | 
			
		||||
            return this._resizeForWelcome();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this._resizeForWelcome();
 | 
			
		||||
        this.ensureHeader('welcome');
 | 
			
		||||
    }
 | 
			
		||||
    //////// after_modelStateService ////////
 | 
			
		||||
 | 
			
		||||
    async transitionToPermissionHeader() {
 | 
			
		||||
@ -161,15 +212,13 @@ class HeaderTransitionManager {
 | 
			
		||||
    async _resizeForMain() {
 | 
			
		||||
        if (!window.api) return;
 | 
			
		||||
        console.log('[HeaderController] _resizeForMain: Resizing window to 353x47');
 | 
			
		||||
        return window.api.headerController.resizeHeaderWindow({ width: 353, height: 47 })
 | 
			
		||||
            .catch(() => {});
 | 
			
		||||
        return window.api.headerController.resizeHeaderWindow({ width: 353, height: 47 }).catch(() => {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _resizeForApiKey() {
 | 
			
		||||
    async _resizeForApiKey(height = 370) {
 | 
			
		||||
        if (!window.api) return;
 | 
			
		||||
        console.log('[HeaderController] _resizeForApiKey: Resizing window to 350x300');
 | 
			
		||||
        return window.api.headerController.resizeHeaderWindow({ width: 350, height: 300 })
 | 
			
		||||
            .catch(() => {});
 | 
			
		||||
        console.log(`[HeaderController] _resizeForApiKey: Resizing window to 456x${height}`);
 | 
			
		||||
        return window.api.headerController.resizeHeaderWindow({ width: 456, height: height }).catch(() => {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _resizeForPermissionHeader() {
 | 
			
		||||
@ -178,6 +227,13 @@ class HeaderTransitionManager {
 | 
			
		||||
            .catch(() => {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _resizeForWelcome() {
 | 
			
		||||
        if (!window.api) return;
 | 
			
		||||
        console.log('[HeaderController] _resizeForWelcome: Resizing window to 456x370');
 | 
			
		||||
        return window.api.headerController.resizeHeaderWindow({ width: 456, height: 364 })
 | 
			
		||||
            .catch(() => {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async checkPermissions() {
 | 
			
		||||
        if (!window.api) {
 | 
			
		||||
            return { success: true };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										234
									
								
								src/ui/app/WelcomeHeader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/ui/app/WelcomeHeader.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,234 @@
 | 
			
		||||
import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
 | 
			
		||||
 | 
			
		||||
export class WelcomeHeader extends LitElement {
 | 
			
		||||
    static styles = css`
 | 
			
		||||
        :host {
 | 
			
		||||
            display: block;
 | 
			
		||||
            font-family:
 | 
			
		||||
                'Inter',
 | 
			
		||||
                -apple-system,
 | 
			
		||||
                BlinkMacSystemFont,
 | 
			
		||||
                'Segoe UI',
 | 
			
		||||
                Roboto,
 | 
			
		||||
                sans-serif;
 | 
			
		||||
        }
 | 
			
		||||
        .container {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            box-sizing: border-box;
 | 
			
		||||
            height: auto;
 | 
			
		||||
            padding: 24px 16px;
 | 
			
		||||
            background: rgba(0, 0, 0, 0.64);
 | 
			
		||||
            box-shadow: 0px 0px 0px 1.5px rgba(255, 255, 255, 0.64) inset;
 | 
			
		||||
            border-radius: 16px;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            justify-content: flex-start;
 | 
			
		||||
            align-items: flex-start;
 | 
			
		||||
            gap: 32px;
 | 
			
		||||
            display: inline-flex;
 | 
			
		||||
            -webkit-app-region: drag;
 | 
			
		||||
        }
 | 
			
		||||
        .close-button {
 | 
			
		||||
            -webkit-app-region: no-drag;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 16px;
 | 
			
		||||
            right: 16px;
 | 
			
		||||
            width: 20px;
 | 
			
		||||
            height: 20px;
 | 
			
		||||
            background: rgba(255, 255, 255, 0.1);
 | 
			
		||||
            border: none;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            color: rgba(255, 255, 255, 0.7);
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
            transition: all 0.15s ease;
 | 
			
		||||
            z-index: 10;
 | 
			
		||||
            font-size: 16px;
 | 
			
		||||
            line-height: 1;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
        }
 | 
			
		||||
        .close-button:hover {
 | 
			
		||||
            background: rgba(255, 255, 255, 0.2);
 | 
			
		||||
            color: rgba(255, 255, 255, 0.9);
 | 
			
		||||
        }
 | 
			
		||||
        .header-section {
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            justify-content: flex-start;
 | 
			
		||||
            align-items: flex-start;
 | 
			
		||||
            gap: 4px;
 | 
			
		||||
            display: flex;
 | 
			
		||||
        }
 | 
			
		||||
        .title {
 | 
			
		||||
            color: white;
 | 
			
		||||
            font-size: 18px;
 | 
			
		||||
            font-weight: 700;
 | 
			
		||||
        }
 | 
			
		||||
        .subtitle {
 | 
			
		||||
            color: white;
 | 
			
		||||
            font-size: 14px;
 | 
			
		||||
            font-weight: 500;
 | 
			
		||||
        }
 | 
			
		||||
        .option-card {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            justify-content: flex-start;
 | 
			
		||||
            align-items: flex-start;
 | 
			
		||||
            gap: 8px;
 | 
			
		||||
            display: inline-flex;
 | 
			
		||||
        }
 | 
			
		||||
        .divider {
 | 
			
		||||
            width: 1px;
 | 
			
		||||
            align-self: stretch;
 | 
			
		||||
            position: relative;
 | 
			
		||||
            background: #bebebe;
 | 
			
		||||
            border-radius: 2px;
 | 
			
		||||
        }
 | 
			
		||||
        .option-content {
 | 
			
		||||
            flex: 1 1 0;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            justify-content: flex-start;
 | 
			
		||||
            align-items: flex-start;
 | 
			
		||||
            gap: 8px;
 | 
			
		||||
            display: inline-flex;
 | 
			
		||||
            min-width: 0;
 | 
			
		||||
        }
 | 
			
		||||
        .option-title {
 | 
			
		||||
            color: white;
 | 
			
		||||
            font-size: 14px;
 | 
			
		||||
            font-weight: 700;
 | 
			
		||||
        }
 | 
			
		||||
        .option-description {
 | 
			
		||||
            color: #dcdcdc;
 | 
			
		||||
            font-size: 12px;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            line-height: 18px;
 | 
			
		||||
            letter-spacing: 0.12px;
 | 
			
		||||
            white-space: nowrap;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            text-overflow: ellipsis;
 | 
			
		||||
        }
 | 
			
		||||
        .action-button {
 | 
			
		||||
            -webkit-app-region: no-drag;
 | 
			
		||||
            padding: 8px 10px;
 | 
			
		||||
            background: rgba(132.6, 132.6, 132.6, 0.8);
 | 
			
		||||
            box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.16);
 | 
			
		||||
            border-radius: 16px;
 | 
			
		||||
            border: 1px solid rgba(255, 255, 255, 0.5);
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            gap: 6px;
 | 
			
		||||
            display: flex;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            transition: background-color 0.2s;
 | 
			
		||||
        }
 | 
			
		||||
        .action-button:hover {
 | 
			
		||||
            background: rgba(150, 150, 150, 0.9);
 | 
			
		||||
        }
 | 
			
		||||
        .button-text {
 | 
			
		||||
            color: white;
 | 
			
		||||
            font-size: 12px;
 | 
			
		||||
            font-weight: 600;
 | 
			
		||||
        }
 | 
			
		||||
        .button-icon {
 | 
			
		||||
            width: 12px;
 | 
			
		||||
            height: 12px;
 | 
			
		||||
            position: relative;
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
        }
 | 
			
		||||
        .arrow-icon {
 | 
			
		||||
            border: solid white;
 | 
			
		||||
            border-width: 0 1.2px 1.2px 0;
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            padding: 3px;
 | 
			
		||||
            transform: rotate(-45deg);
 | 
			
		||||
            -webkit-transform: rotate(-45deg);
 | 
			
		||||
        }
 | 
			
		||||
        .footer {
 | 
			
		||||
            align-self: stretch;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            color: #dcdcdc;
 | 
			
		||||
            font-size: 12px;
 | 
			
		||||
            font-weight: 500;
 | 
			
		||||
            line-height: 19.2px;
 | 
			
		||||
        }
 | 
			
		||||
        .footer-link {
 | 
			
		||||
            text-decoration: underline;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
        }
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    static properties = {
 | 
			
		||||
        loginCallback: { type: Function },
 | 
			
		||||
        apiKeyCallback: { type: Function },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        this.loginCallback = () => {};
 | 
			
		||||
        this.apiKeyCallback = () => {};
 | 
			
		||||
        this.handleClose = this.handleClose.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updated(changedProperties) {
 | 
			
		||||
        super.updated(changedProperties);
 | 
			
		||||
        this.dispatchEvent(new CustomEvent('content-changed', { bubbles: true, composed: true }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleClose() {
 | 
			
		||||
        if (window.require) {
 | 
			
		||||
            window.require('electron').ipcRenderer.invoke('quit-application');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
                <button class="close-button" @click=${this.handleClose}>×</button>
 | 
			
		||||
                <div class="header-section">
 | 
			
		||||
                    <div class="title">Welcome to Glass</div>
 | 
			
		||||
                    <div class="subtitle">Choose how to connect your AI model</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="option-card">
 | 
			
		||||
                    <div class="divider"></div>
 | 
			
		||||
                    <div class="option-content">
 | 
			
		||||
                        <div class="option-title">Quick start with default API key</div>
 | 
			
		||||
                        <div class="option-description">
 | 
			
		||||
                            100% free with Pickle's OpenAI key<br/>No personal data collected<br/>Sign up with Google in seconds
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <button class="action-button" @click=${this.loginCallback}>
 | 
			
		||||
                        <div class="button-text">Open Browser to Log in</div>
 | 
			
		||||
                        <div class="button-icon"><div class="arrow-icon"></div></div>
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="option-card">
 | 
			
		||||
                    <div class="divider"></div>
 | 
			
		||||
                    <div class="option-content">
 | 
			
		||||
                        <div class="option-title">Use Personal API keys</div>
 | 
			
		||||
                        <div class="option-description">
 | 
			
		||||
                            Costs may apply based on your API usage<br/>No personal data collected<br/>Use your own API keys (OpenAI, Gemini, etc.)
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <button class="action-button" @click=${this.apiKeyCallback}>
 | 
			
		||||
                        <div class="button-text">Enter Your API Key</div>
 | 
			
		||||
                        <div class="button-icon"><div class="arrow-icon"></div></div>
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="footer">
 | 
			
		||||
                    Glass does not collect your personal data —
 | 
			
		||||
                    <span class="footer-link" @click=${this.openPrivacyPolicy}>See details</span>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    openPrivacyPolicy() {
 | 
			
		||||
        if (window.require) {
 | 
			
		||||
            window.require('electron').shell.openExternal('https://pickleglass.com/privacy');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('welcome-header', WelcomeHeader);
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user