ux/ui fix
This commit is contained in:
parent
d62dad6992
commit
f18fae6c90
@ -381,11 +381,10 @@ class ModelStateService extends EventEmitter {
|
|||||||
|
|
||||||
async removeApiKey(provider) {
|
async removeApiKey(provider) {
|
||||||
if (this.state.apiKeys[provider]) {
|
if (this.state.apiKeys[provider]) {
|
||||||
delete this.state.apiKeys[provider];
|
this.state.apiKeys[provider] = null;
|
||||||
this._saveState();
|
await providerSettingsRepository.remove(provider);
|
||||||
|
await this._saveState();
|
||||||
this._autoSelectAvailableModels([]);
|
this._autoSelectAvailableModels([]);
|
||||||
|
|
||||||
this._broadcastToAllWindows('model-state:updated', this.state);
|
this._broadcastToAllWindows('model-state:updated', this.state);
|
||||||
this._broadcastToAllWindows('settings-updated');
|
this._broadcastToAllWindows('settings-updated');
|
||||||
return true;
|
return true;
|
||||||
@ -527,7 +526,10 @@ class ModelStateService extends EventEmitter {
|
|||||||
|
|
||||||
// Auto warm-up for Ollama models
|
// Auto warm-up for Ollama models
|
||||||
if (type === 'llm' && modelId && modelId !== previousModelId) {
|
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);
|
this._broadcastToAllWindows('model-state:updated', this.state);
|
||||||
|
@ -532,6 +532,7 @@ async function handleFirebaseAuthCallback(params) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 1. Sync user data to local DB
|
// 1. Sync user data to local DB
|
||||||
|
userRepository.setAuthService(authService);
|
||||||
userRepository.findOrCreate(firebaseUser);
|
userRepository.findOrCreate(firebaseUser);
|
||||||
console.log('[Auth] User data synced with local DB.');
|
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 './MainHeader.js';
|
||||||
import './ApiKeyHeader.js';
|
import './ApiKeyHeader.js';
|
||||||
import './PermissionHeader.js';
|
import './PermissionHeader.js';
|
||||||
|
import './WelcomeHeader.js';
|
||||||
|
|
||||||
class HeaderTransitionManager {
|
class HeaderTransitionManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.headerContainer = document.getElementById('header-container');
|
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.apiKeyHeader = null;
|
||||||
this.mainHeader = null;
|
this.mainHeader = null;
|
||||||
this.permissionHeader = null;
|
this.permissionHeader = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* only one header window is allowed
|
* only one header window is allowed
|
||||||
* @param {'apikey'|'main'|'permission'} type
|
* @param {'welcome'|'apikey'|'main'|'permission'} type
|
||||||
*/
|
*/
|
||||||
this.ensureHeader = (type) => {
|
this.ensureHeader = (type) => {
|
||||||
console.log('[HeaderController] ensureHeader: Ensuring header of type:', type);
|
console.log('[HeaderController] ensureHeader: Ensuring header of type:', type);
|
||||||
@ -23,14 +25,25 @@ class HeaderTransitionManager {
|
|||||||
|
|
||||||
this.headerContainer.innerHTML = '';
|
this.headerContainer.innerHTML = '';
|
||||||
|
|
||||||
|
this.welcomeHeader = null;
|
||||||
this.apiKeyHeader = null;
|
this.apiKeyHeader = null;
|
||||||
this.mainHeader = null;
|
this.mainHeader = null;
|
||||||
this.permissionHeader = null;
|
this.permissionHeader = null;
|
||||||
|
|
||||||
// Create new header element
|
// 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 = document.createElement('apikey-header');
|
||||||
this.apiKeyHeader.stateUpdateCallback = (userState) => this.handleStateUpdate(userState);
|
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);
|
this.headerContainer.appendChild(this.apiKeyHeader);
|
||||||
console.log('[HeaderController] ensureHeader: Header of type:', type, 'created.');
|
console.log('[HeaderController] ensureHeader: Header of type:', type, 'created.');
|
||||||
} else if (type === 'permission') {
|
} else if (type === 'permission') {
|
||||||
@ -49,6 +62,10 @@ class HeaderTransitionManager {
|
|||||||
|
|
||||||
console.log('[HeaderController] Manager initialized');
|
console.log('[HeaderController] Manager initialized');
|
||||||
|
|
||||||
|
// WelcomeHeader 콜백 메서드들
|
||||||
|
this.handleLoginOption = this.handleLoginOption.bind(this);
|
||||||
|
this.handleApiKeyOption = this.handleApiKeyOption.bind(this);
|
||||||
|
|
||||||
this._bootstrap();
|
this._bootstrap();
|
||||||
|
|
||||||
if (window.api) {
|
if (window.api) {
|
||||||
@ -66,8 +83,14 @@ class HeaderTransitionManager {
|
|||||||
});
|
});
|
||||||
window.api.headerController.onForceShowApiKeyHeader(async () => {
|
window.api.headerController.onForceShowApiKeyHeader(async () => {
|
||||||
console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
|
console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
|
||||||
await this._resizeForApiKey();
|
const isConfigured = await window.api.apiKeyHeader.areProvidersConfigured();
|
||||||
this.ensureHeader('apikey');
|
if (!isConfigured) {
|
||||||
|
await this._resizeForWelcome();
|
||||||
|
this.ensureHeader('welcome');
|
||||||
|
} else {
|
||||||
|
await this._resizeForApiKey();
|
||||||
|
this.ensureHeader('apikey');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +111,7 @@ class HeaderTransitionManager {
|
|||||||
this.handleStateUpdate(userState);
|
this.handleStateUpdate(userState);
|
||||||
} else {
|
} else {
|
||||||
// Fallback for non-electron environment (testing/web)
|
// Fallback for non-electron environment (testing/web)
|
||||||
this.ensureHeader('apikey');
|
this.ensureHeader('welcome');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +133,38 @@ class HeaderTransitionManager {
|
|||||||
this.transitionToMainHeader();
|
this.transitionToMainHeader();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this._resizeForApiKey();
|
// 프로바이더가 설정되지 않았으면 WelcomeHeader 먼저 표시
|
||||||
this.ensureHeader('apikey');
|
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 ////////
|
//////// after_modelStateService ////////
|
||||||
|
|
||||||
async transitionToPermissionHeader() {
|
async transitionToPermissionHeader() {
|
||||||
@ -161,15 +212,13 @@ class HeaderTransitionManager {
|
|||||||
async _resizeForMain() {
|
async _resizeForMain() {
|
||||||
if (!window.api) return;
|
if (!window.api) return;
|
||||||
console.log('[HeaderController] _resizeForMain: Resizing window to 353x47');
|
console.log('[HeaderController] _resizeForMain: Resizing window to 353x47');
|
||||||
return window.api.headerController.resizeHeaderWindow({ width: 353, height: 47 })
|
return window.api.headerController.resizeHeaderWindow({ width: 353, height: 47 }).catch(() => {});
|
||||||
.catch(() => {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _resizeForApiKey() {
|
async _resizeForApiKey(height = 370) {
|
||||||
if (!window.api) return;
|
if (!window.api) return;
|
||||||
console.log('[HeaderController] _resizeForApiKey: Resizing window to 350x300');
|
console.log(`[HeaderController] _resizeForApiKey: Resizing window to 456x${height}`);
|
||||||
return window.api.headerController.resizeHeaderWindow({ width: 350, height: 300 })
|
return window.api.headerController.resizeHeaderWindow({ width: 456, height: height }).catch(() => {});
|
||||||
.catch(() => {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _resizeForPermissionHeader() {
|
async _resizeForPermissionHeader() {
|
||||||
@ -178,6 +227,13 @@ class HeaderTransitionManager {
|
|||||||
.catch(() => {});
|
.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() {
|
async checkPermissions() {
|
||||||
if (!window.api) {
|
if (!window.api) {
|
||||||
return { success: true };
|
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