This commit is contained in:
JamJan05 2026-05-21 12:48:31 +02:00
parent 091f2689a5
commit 9b209dea82
5 changed files with 80 additions and 45 deletions

2
.gitignore vendored
View file

@ -4,3 +4,5 @@ main.js
.DS_Store
Thumbs.db
data.json
.codex/
.agents/

View file

@ -36,7 +36,9 @@ export class GPTSettingsTab extends PluginSettingTab {
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: t("settings_title") });
new Setting(containerEl)
.setName(t("settings_title"))
.setHeading();
this.renderLanguage(containerEl);
this.renderKeyWarning(containerEl);
@ -156,7 +158,9 @@ export class GPTSettingsTab extends PluginSettingTab {
const keysStoredLocal = !keysInSync && extEnabled;
const keysLocation = keysStoredLocal ? t("settings_key_local") : t("settings_key_sync");
el.createEl("h3", { text: t("settings_openai_title") });
new Setting(el)
.setName(t("settings_openai_title"))
.setHeading();
new Setting(el)
.setName(t("settings_openai_key_name"))
@ -190,7 +194,9 @@ export class GPTSettingsTab extends PluginSettingTab {
}
private renderOpenAISettings(el: HTMLElement): void {
el.createEl("h3", { text: "⚙️ " + t("settings_openai_title") + " — " + t("settings_thinking_name") });
new Setting(el)
.setName("⚙️ " + t("settings_openai_title") + " — " + t("settings_thinking_name"))
.setHeading();
new Setting(el)
.setName(t("settings_thinking_name"))
@ -205,7 +211,9 @@ export class GPTSettingsTab extends PluginSettingTab {
}),
);
el.createEl("h4", { text: t("settings_max_tokens_title") });
new Setting(el)
.setName(t("settings_max_tokens_title"))
.setHeading();
new Setting(el)
.setName(t("settings_max_tokens_fast_name"))
@ -213,7 +221,7 @@ export class GPTSettingsTab extends PluginSettingTab {
.addText(txt => {
txt.inputEl.type = "number";
txt.inputEl.min = "256";
txt.inputEl.style.width = "90px";
txt.inputEl.addClass("gpt-settings-input-compact");
txt.setValue(String(this.plugin.settings.maxTokensFast ?? 4096))
.onChange(async (v: string) => {
const n = parseInt(v, 10);
@ -230,7 +238,7 @@ export class GPTSettingsTab extends PluginSettingTab {
.addText(txt => {
txt.inputEl.type = "number";
txt.inputEl.min = "256";
txt.inputEl.style.width = "90px";
txt.inputEl.addClass("gpt-settings-input-compact");
txt.setValue(String(this.plugin.settings.maxTokensNormal ?? 8192))
.onChange(async (v: string) => {
const n = parseInt(v, 10);
@ -247,7 +255,7 @@ export class GPTSettingsTab extends PluginSettingTab {
.addText(txt => {
txt.inputEl.type = "number";
txt.inputEl.min = "256";
txt.inputEl.style.width = "90px";
txt.inputEl.addClass("gpt-settings-input-compact");
txt.setValue(String(this.plugin.settings.maxTokensThink ?? 16000))
.onChange(async (v: string) => {
const n = parseInt(v, 10);
@ -282,7 +290,9 @@ export class GPTSettingsTab extends PluginSettingTab {
}
private renderContext(el: HTMLElement): void {
el.createEl("h3", { text: "💬 " + t("settings_context_title") });
new Setting(el)
.setName("💬 " + t("settings_context_title"))
.setHeading();
new Setting(el)
.setName(t("settings_context_name"))
@ -290,7 +300,7 @@ export class GPTSettingsTab extends PluginSettingTab {
.addText(txt => {
txt.inputEl.type = "number";
txt.inputEl.min = "0";
txt.inputEl.style.width = "90px";
txt.inputEl.addClass("gpt-settings-input-compact");
txt.setValue(String(this.plugin.settings.maxContextMessages ?? 0))
.onChange(async (v: string) => {
const n = parseInt(v, 10);
@ -308,7 +318,9 @@ export class GPTSettingsTab extends PluginSettingTab {
const keysStoredLocal = !keysInSync && extEnabled;
const keysLocation = keysStoredLocal ? t("settings_key_local") : t("settings_key_sync");
el.createEl("h3", { text: t("settings_claude_title") });
new Setting(el)
.setName(t("settings_claude_title"))
.setHeading();
new Setting(el)
.setName(t("settings_claude_key_name"))
@ -338,7 +350,9 @@ export class GPTSettingsTab extends PluginSettingTab {
}
private renderRAG(el: HTMLElement): void {
el.createEl("h3", { text: t("settings_rag_title") });
new Setting(el)
.setName(t("settings_rag_title"))
.setHeading();
new Setting(el)
.setName(t("settings_rag_enable_name"))
@ -387,7 +401,9 @@ export class GPTSettingsTab extends PluginSettingTab {
}
private renderStorage(el: HTMLElement): void {
el.createEl("h3", { text: t("settings_storage_title") });
new Setting(el)
.setName(t("settings_storage_title"))
.setHeading();
const isDesktop = this.plugin.externalStorage.isDesktop;
const isActive = this.plugin.externalStorage.isEnabled;
@ -408,8 +424,7 @@ export class GPTSettingsTab extends PluginSettingTab {
info.createEl("strong", { text: "Location:" });
info.createEl("br");
const pathEl = info.createEl("code", { text: currentPath });
pathEl.style.fontSize = "11px";
pathEl.style.wordBreak = "break-all";
pathEl.addClass("gpt-settings-storage-path");
} else {
this.renderSafeInlineMarkup(info, t("settings_storage_inactive_html"));
}
@ -438,7 +453,7 @@ export class GPTSettingsTab extends PluginSettingTab {
txt.setPlaceholder("/path/to/folder (empty = auto)")
.setValue(this.plugin.settings.externalStoragePath ?? "")
.setDisabled(!isDesktop);
txt.inputEl.style.width = "100%";
txt.inputEl.addClass("gpt-settings-input-full");
txt.onChange(async (v: string) => {
this.plugin.settings.externalStoragePath = v.trim();
await this.plugin.saveSettings();

View file

@ -183,11 +183,11 @@ export class GPTChatView extends ItemView {
if (!this.ragStatusEl) return;
this.ragStatusEl.textContent = text;
this.ragStatusEl.className = `gpt-rag-status gpt-rag-${state}`;
this.ragStatusEl.style.display = "block";
this.ragStatusEl.removeClass("gpt-ctx-hidden");
}
private hideRagStatus(): void {
if (this.ragStatusEl) this.ragStatusEl.style.display = "none";
if (this.ragStatusEl) this.ragStatusEl.addClass("gpt-ctx-hidden");
}
// ── Build UI ────────────────────────────────────────────────────────────────
@ -265,8 +265,7 @@ export class GPTChatView extends ItemView {
}
private buildRagStatus(root: HTMLElement): void {
this.ragStatusEl = root.createEl("div", { cls: "gpt-rag-status" });
this.ragStatusEl.style.display = "none";
this.ragStatusEl = root.createEl("div", { cls: "gpt-rag-status gpt-ctx-hidden" });
}
private buildManualBar(root: HTMLElement): void {
@ -375,7 +374,7 @@ export class GPTChatView extends ItemView {
const row = list.createEl("label", { cls: "gpt-modal-row" });
const cb = row.createEl("input", { attr: { type: "checkbox" } }) as HTMLInputElement;
cb.checked = selected.has(f.path);
cb.style.accentColor = "var(--interactive-accent)";
cb.setCssProps({ "accent-color": "var(--interactive-accent)" });
cb.onchange = () => { selected.has(f.path) ? selected.delete(f.path) : selected.add(f.path); };
const icon = f.extension === "canvas" ? "🗂️ " : "";
row.createEl("span", { cls: "gpt-modal-row-label", text: icon + f.basename });
@ -431,7 +430,11 @@ export class GPTChatView extends ItemView {
updateRagBadge(): void {
if (!this.ragBadge) return;
this.ragBadge.textContent = this.settings.ragEnabled ? "RAG" : "";
this.ragBadge.style.display = this.settings.ragEnabled ? "inline-block" : "none";
if (this.settings.ragEnabled) {
this.ragBadge.removeClass("gpt-ctx-hidden");
} else {
this.ragBadge.addClass("gpt-ctx-hidden");
}
}
updateProjectBar(): void {
@ -446,8 +449,10 @@ export class GPTChatView extends ItemView {
if (!proj) { this.projectBar.addClass("gpt-ctx-hidden"); return; }
this.projectBar.removeClass("gpt-ctx-hidden");
this.projectBar.style.background = `color-mix(in srgb,${proj.color} 10%,var(--background-secondary))`;
this.projectBar.style.borderBottomColor = `color-mix(in srgb,${proj.color} 25%,transparent)`;
this.projectBar.setCssProps({
background: `color-mix(in srgb,${proj.color} 10%,var(--background-secondary))`,
"border-bottom-color": `color-mix(in srgb,${proj.color} 25%,transparent)`,
});
const sessions = this.plugin.projects.getProjectSessions(proj.id);
const promptBadge = proj.systemPrompt ? " " + t("projects_custom_prompt_badge") : "";
@ -639,8 +644,10 @@ export class GPTChatView extends ItemView {
// Attach to document.body — avoids CSS transform issues on Obsidian panels
document.body.appendChild(picker);
const rect = this.modelSelectorBtn.getBoundingClientRect();
picker.style.top = `${rect.bottom + 4}px`;
picker.style.left = `${rect.left}px`;
picker.setCssProps({
top: `${rect.bottom + 4}px`,
left: `${rect.left}px`,
});
// Close on click outside the picker
this.pickerCloseHandler = (e: MouseEvent): void => {
@ -978,11 +985,11 @@ export class GPTChatView extends ItemView {
this.abortController?.abort();
new Notice("⏹ Generation stopped");
};
this.sendBtn.style.display = "none";
this.sendBtn.addClass("gpt-ctx-hidden");
} else {
this.stopBtn?.remove();
this.stopBtn = null;
this.sendBtn.style.display = "";
this.sendBtn.removeClass("gpt-ctx-hidden");
}
}

View file

@ -98,11 +98,10 @@ export class GPTProjectsView extends ItemView {
if (!proj) return;
const bar = root.createEl("div", { cls: "gpt-projects-active-bar" });
bar.style.background = `color-mix(in srgb,${proj.color} 12%,var(--background-secondary))`;
bar.style.borderBottomColor = `color-mix(in srgb,${proj.color} 25%,transparent)`;
bar.setCssProps({ "--gpt-project-color": proj.color });
const dot = bar.createEl("span", { cls: "gpt-projects-active-dot" });
dot.style.background = proj.color;
dot.setCssProps({ "--gpt-project-color": proj.color });
bar.createEl("span", {
cls: "gpt-projects-active-name",
@ -137,16 +136,16 @@ export class GPTProjectsView extends ItemView {
const isActive = this.plugin.activeProjectId === proj.id;
const card = list.createEl("div", { cls: "gpt-projects-card" });
card.setCssProps({ "--gpt-project-color": proj.color });
if (isActive) {
card.style.borderColor = proj.color;
card.style.background = `color-mix(in srgb,${proj.color} 8%,var(--background-secondary))`;
card.addClass("gpt-projects-card--active");
}
// ── Card header ─────────────────────────────────────────────────────────
const top = card.createEl("div", { cls: "gpt-projects-card-top" });
const dot = top.createEl("span", { cls: "gpt-projects-card-dot" });
dot.style.background = proj.color;
dot.setCssProps({ "--gpt-project-color": proj.color });
top.createEl("span", { cls: "gpt-projects-card-name", text: proj.name });
@ -154,8 +153,7 @@ export class GPTProjectsView extends ItemView {
cls: "gpt-projects-card-badge",
text: t("projects_chat_count", sessions.length),
});
badge.style.background = `color-mix(in srgb,${proj.color} 15%,var(--background-primary))`;
badge.style.color = proj.color;
badge.setCssProps({ "--gpt-project-color": proj.color });
// Edit button
const editBtn = top.createEl("button", {
@ -182,9 +180,7 @@ export class GPTProjectsView extends ItemView {
// Custom prompt — badge
if (proj.systemPrompt) {
const tag = card.createEl("div", { cls: "gpt-projects-card-prompt-tag" });
tag.style.background = `color-mix(in srgb,${proj.color} 10%,var(--background-primary))`;
tag.style.color = proj.color;
tag.style.borderColor = `color-mix(in srgb,${proj.color} 20%,transparent)`;
tag.setCssProps({ "--gpt-project-color": proj.color });
this.setIconText(tag, "pencil", t("projects_own_prompt_btn"));
}
@ -272,8 +268,9 @@ export class GPTProjectsView extends ItemView {
cls: "gpt-modal-ok",
text: isEdit ? "Zapisz" : t("projects_create_btn"),
});
ok.onclick = async () => { const name = nameInput.value.trim();
if (!name) { nameInput.style.borderColor = "var(--color-red)"; return; }
ok.onclick = async () => {
const name = nameInput.value.trim();
if (!name) { nameInput.addClass("gpt-modal-input-error"); return; }
if (isEdit) {
await this.plugin.projects.updateProject(editProject!.id, {
@ -299,6 +296,7 @@ export class GPTProjectsView extends ItemView {
nameInput.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.key === "Enter") void (ok.onclick as (() => Promise<void>) | null)?.();
});
nameInput.addEventListener("input", () => nameInput.removeClass("gpt-modal-input-error"));
overlay.appendChild(box);
this.containerEl.appendChild(overlay);

View file

@ -425,6 +425,7 @@
font-size: 13px; outline: none;
}
.gpt-modal-input:focus { border-color: var(--gpt-color-openai); }
.gpt-modal-input.gpt-modal-input-error { border-color: var(--color-red, var(--gpt-color-error)); }
.gpt-modal-list {
max-height: 260px; overflow-y: auto;
display: flex; flex-direction: column; gap: 3px;
@ -551,19 +552,20 @@
/* ─── Projects view ──────────────────────────────────────────────────────────── */
.gpt-projects-list { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 6px; scrollbar-width: thin; }
.gpt-projects-active-bar { display: flex; align-items: center; gap: 8px; padding: 8px 14px; border-bottom: 1px solid; flex-shrink: 0; font-size: 11px; }
.gpt-projects-active-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.gpt-projects-active-bar { display: flex; align-items: center; gap: 8px; padding: 8px 14px; border-bottom: 1px solid color-mix(in srgb, var(--gpt-project-color, var(--interactive-accent)) 25%, transparent); background: color-mix(in srgb, var(--gpt-project-color, var(--interactive-accent)) 12%, var(--background-secondary)); flex-shrink: 0; font-size: 11px; }
.gpt-projects-active-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--gpt-project-color, var(--interactive-accent)); }
.gpt-projects-active-name { font-size: 11px; font-weight: 600; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gpt-projects-empty { color: var(--text-muted); font-size: 12px; text-align: center; margin-top: 24px; padding: 0 12px; line-height: 1.6; }
.gpt-projects-empty-hint { font-size: 11px; opacity: .7; margin-top: 4px; }
.gpt-projects-card { padding: 11px 13px; border-radius: 10px; cursor: pointer; border: 2px solid transparent; background: var(--background-secondary); transition: all .15s; }
.gpt-projects-card:hover { background: var(--background-modifier-hover); }
.gpt-projects-card--active { border-color: var(--gpt-project-color, var(--interactive-accent)); background: color-mix(in srgb, var(--gpt-project-color, var(--interactive-accent)) 8%, var(--background-secondary)); }
.gpt-projects-card-top { display: flex; align-items: center; gap: 8px; }
.gpt-projects-card-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.gpt-projects-card-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; background: var(--gpt-project-color, var(--interactive-accent)); }
.gpt-projects-card-name { font-size: 12px; font-weight: 700; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-normal); }
.gpt-projects-card-badge { font-size: 10px; padding: 1px 7px; border-radius: 10px; font-weight: 600; flex-shrink: 0; }
.gpt-projects-card-badge { font-size: 10px; padding: 1px 7px; border-radius: 10px; font-weight: 600; flex-shrink: 0; background: color-mix(in srgb, var(--gpt-project-color, var(--interactive-accent)) 15%, var(--background-primary)); color: var(--gpt-project-color, var(--interactive-accent)); }
.gpt-projects-card-desc { font-size: 11px; color: var(--text-muted); margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gpt-projects-card-prompt-tag { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: 600; border: 1px solid; }
.gpt-projects-card-prompt-tag { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: 600; border: 1px solid color-mix(in srgb, var(--gpt-project-color, var(--interactive-accent)) 20%, transparent); background: color-mix(in srgb, var(--gpt-project-color, var(--interactive-accent)) 10%, var(--background-primary)); color: var(--gpt-project-color, var(--interactive-accent)); }
.gpt-projects-card-chats { margin-top: 8px; display: flex; flex-direction: column; gap: 3px; }
.gpt-projects-card-chat { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 6px; font-size: 11px; color: var(--text-muted); cursor: pointer; transition: background .1s; }
.gpt-projects-card-chat:hover { background: var(--background-modifier-hover); }
@ -625,3 +627,14 @@
font-size: 12px; line-height: 1.6;
border: 1px solid var(--background-modifier-border);
}
.gpt-settings-input-compact {
width: 90px;
max-width: 100%;
}
.gpt-settings-input-full {
width: 100%;
}
.gpt-settings-storage-path {
font-size: 11px;
word-break: break-all;
}