make app more intuitive
This commit is contained in:
+68
-15
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="hero-grid">
|
<div class="hero-grid">
|
||||||
<article class="hero-panel">
|
<article class="hero-panel">
|
||||||
<p>Log in to continue your chats. Personas, shared presets, and theme changes are available after sign-in.</p>
|
<p>Sign in to start a chat immediately, then add personas when you want richer roleplay.</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,10 +80,11 @@
|
|||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Chats</p>
|
<p class="eyebrow">Chats</p>
|
||||||
<h2>Stories & chats</h2>
|
<h2>Conversations</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-header-actions">
|
<div class="sidebar-header-actions action-pair">
|
||||||
<button id="newChat" class="secondary-action" type="button">New chat</button>
|
<button id="newChat" class="secondary-action" type="button">New chat</button>
|
||||||
|
<button id="startRoleplay" class="secondary-action" type="button">Start roleplay</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="field compact-field">
|
<label class="field compact-field">
|
||||||
@@ -92,14 +93,26 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="chatList" class="chat-list"></div>
|
<div id="chatList" class="chat-list"></div>
|
||||||
|
<div id="chatListLoading" class="chat-loading hidden" role="status" aria-live="polite">
|
||||||
|
<span class="chat-loading-dot" aria-hidden="true"></span>
|
||||||
|
<span id="chatListLoadingText">Loading chats...</span>
|
||||||
|
</div>
|
||||||
<div class="sidebar-footer sidebar-footer-stack">
|
<div class="sidebar-footer sidebar-footer-stack">
|
||||||
<a href="/market" class="secondary-action">Persona Market</a>
|
<details id="toolsDetails" class="tools-details">
|
||||||
|
<summary>Tools</summary>
|
||||||
|
<div class="tools-links">
|
||||||
|
<a href="/market" class="secondary-action">Browse personas</a>
|
||||||
<a href="/settings" class="secondary-action">Settings</a>
|
<a href="/settings" class="secondary-action">Settings</a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details id="accountDetails" class="tools-details">
|
||||||
|
<summary>Account</summary>
|
||||||
<div class="session-chip">
|
<div class="session-chip">
|
||||||
<span class="session-chip-label">Signed in as</span>
|
<span class="session-chip-label">Signed in as</span>
|
||||||
<strong id="sessionUser">-</strong>
|
<strong id="sessionUser">-</strong>
|
||||||
</div>
|
</div>
|
||||||
<button id="logout" type="button">Logout</button>
|
<button id="logout" type="button">Logout</button>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -109,6 +122,12 @@
|
|||||||
<div class="chat-title">
|
<div class="chat-title">
|
||||||
<p class="eyebrow">Conversation</p>
|
<p class="eyebrow">Conversation</p>
|
||||||
<h2 id="activeChatTitle">New chat</h2>
|
<h2 id="activeChatTitle">New chat</h2>
|
||||||
|
<div class="chat-context">
|
||||||
|
<button id="contextChipButton" class="context-chip-button" type="button" aria-expanded="false" aria-controls="contextPopover">
|
||||||
|
<span>Context</span>
|
||||||
|
<strong id="contextSummaryLabel">Assistant</strong>
|
||||||
|
</button>
|
||||||
|
<div id="contextPopover" class="persona-popover hidden" role="dialog">
|
||||||
<div id="chatParticipants" class="chat-participants">
|
<div id="chatParticipants" class="chat-participants">
|
||||||
<span id="chatModePill" class="context-pill chat-mode-pill">Assistant mode</span>
|
<span id="chatModePill" class="context-pill chat-mode-pill">Assistant mode</span>
|
||||||
<span id="chatCharacterPill" class="context-pill hidden">Character: none</span>
|
<span id="chatCharacterPill" class="context-pill hidden">Character: none</span>
|
||||||
@@ -116,10 +135,7 @@
|
|||||||
<span id="chatScenePill" class="context-pill hidden">Scene: none</span>
|
<span id="chatScenePill" class="context-pill hidden">Scene: none</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-toolbar">
|
</div>
|
||||||
<button id="renameChat" class="icon-button" type="button">Rename</button>
|
|
||||||
<button id="clearChat" class="icon-button" type="button">Clear</button>
|
|
||||||
<button id="exportChat" class="icon-button" type="button">Export</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-header-actions">
|
<div class="chat-header-actions">
|
||||||
@@ -172,6 +188,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="chat-actions-menu">
|
||||||
|
<button id="chatActionsMenuButton" class="secondary-action" type="button" aria-expanded="false" aria-controls="chatActionsPopover">
|
||||||
|
More
|
||||||
|
</button>
|
||||||
|
<div id="chatActionsPopover" class="persona-popover hidden" role="dialog">
|
||||||
|
<div class="persona-section">
|
||||||
|
<div class="persona-section-header">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Conversation</p>
|
||||||
|
<h2>Actions</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="persona-item-actions">
|
||||||
|
<button id="renameChat" class="icon-button" type="button">Rename</button>
|
||||||
|
<button id="clearChat" class="icon-button" type="button">Clear</button>
|
||||||
|
<button id="exportChat" class="icon-button" type="button">Export</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -179,7 +215,7 @@
|
|||||||
|
|
||||||
<div class="input-shell input-shell-focus">
|
<div class="input-shell input-shell-focus">
|
||||||
<div class="input-meta">
|
<div class="input-meta">
|
||||||
<p class="composer-hint">Enter sends. Shift+Enter adds a new line.</p>
|
<p class="composer-hint">Enter to send · Shift+Enter for new line</p>
|
||||||
<p id="appNotice" class="status inline-status hidden" aria-live="polite"></p>
|
<p id="appNotice" class="status inline-status hidden" aria-live="polite"></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="quickPrompts" class="quick-prompts" aria-label="Suggested starters"></div>
|
<div id="quickPrompts" class="quick-prompts" aria-label="Suggested starters"></div>
|
||||||
@@ -263,31 +299,30 @@
|
|||||||
<button id="roleplayStarterClose" type="button" class="icon-button">Close</button>
|
<button id="roleplayStarterClose" type="button" class="icon-button">Close</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="roleplay-starter-intro">
|
<div class="roleplay-starter-intro">
|
||||||
<p class="persona-helper">Pick a character, optionally choose who you are in the scene, and the character will open the conversation with the first message.</p>
|
<p class="persona-helper">Pick a character, choose who you are in the scene, and generate an opening message in one step.</p>
|
||||||
</div>
|
</div>
|
||||||
<p id="roleplayStarterNotice" class="status hidden"></p>
|
<p id="roleplayStarterNotice" class="status hidden"></p>
|
||||||
<div class="roleplay-starter-actions">
|
<div class="roleplay-starter-actions">
|
||||||
<button id="roleplayNewPersona" class="secondary-action" type="button">Create AI Character</button>
|
<button id="roleplayNewPersona" class="secondary-action" type="button">Create AI Character</button>
|
||||||
<a href="/settings" class="secondary-action">Settings</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="persona-form-grid">
|
<div class="persona-form-grid">
|
||||||
<label class="field field-wide">
|
<label class="field field-wide">
|
||||||
<span>Character</span>
|
<span>1. Character</span>
|
||||||
<select id="roleplayCharacterSelect"></select>
|
<select id="roleplayCharacterSelect"></select>
|
||||||
</label>
|
</label>
|
||||||
<label class="field field-wide">
|
<label class="field field-wide">
|
||||||
<span>User persona</span>
|
<span>2. You in the scene</span>
|
||||||
<select id="roleplayUserPersonaSelect"></select>
|
<select id="roleplayUserPersonaSelect"></select>
|
||||||
</label>
|
</label>
|
||||||
<label class="field field-wide">
|
<label class="field field-wide">
|
||||||
<span>Scenario seed</span>
|
<span>3. Optional opening direction</span>
|
||||||
<textarea id="roleplayScenarioInput" rows="3" placeholder="Optional. Describe the kind of opening scene you want, such as a setting, conflict, relationship dynamic, or mood."></textarea>
|
<textarea id="roleplayScenarioInput" rows="3" placeholder="Optional. Describe the kind of opening scene you want, such as a setting, conflict, relationship dynamic, or mood."></textarea>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="roleplayStarterSuggestions" class="quick-prompts" aria-label="Scenario suggestions"></div>
|
<div id="roleplayStarterSuggestions" class="quick-prompts" aria-label="Scenario suggestions"></div>
|
||||||
<div class="popup-actions">
|
<div class="popup-actions">
|
||||||
<button id="roleplayStarterCancel" type="button" class="secondary-action">Cancel</button>
|
<button id="roleplayStarterCancel" type="button" class="secondary-action">Cancel</button>
|
||||||
<button id="roleplayStarterConfirm" type="button">Start roleplay</button>
|
<button id="roleplayStarterConfirm" type="button">Create opening scene</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -315,6 +350,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="onboardingModal" class="modal-overlay hidden">
|
||||||
|
<div class="modal-card onboarding-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="persona-modal-heading">
|
||||||
|
<p class="eyebrow">Get started</p>
|
||||||
|
<h3 id="onboardingTitle">What do you want to do?</h3>
|
||||||
|
</div>
|
||||||
|
<button id="onboardingSkip" type="button" class="icon-button">Skip</button>
|
||||||
|
</div>
|
||||||
|
<p id="onboardingSubtitle" class="subtitle">Pick one path to set up your first successful chat.</p>
|
||||||
|
<div id="onboardingChoices" class="onboarding-choices"></div>
|
||||||
|
<div class="popup-actions">
|
||||||
|
<button id="onboardingBack" type="button" class="secondary-action hidden">Back</button>
|
||||||
|
<button id="onboardingContinue" type="button">Continue</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
|
||||||
<script src="script.js" defer></script>
|
<script src="script.js" defer></script>
|
||||||
|
|||||||
+6
-19
@@ -15,7 +15,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Shared personas</p>
|
<p class="eyebrow">Shared personas</p>
|
||||||
<h1><span data-theme-name>FakeGPT</span> Market</h1>
|
<h1><span data-theme-name>FakeGPT</span> Market</h1>
|
||||||
<p class="subtitle">Browse shared AI Characters and User Personas, or manage your own AI Characters.</p>
|
<p class="subtitle">Find characters and personas for roleplay, then start with an explicit persona choice.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="market-header-actions">
|
<div class="market-header-actions">
|
||||||
<button id="refreshMarket" class="secondary-action" type="button">Refresh</button>
|
<button id="refreshMarket" class="secondary-action" type="button">Refresh</button>
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
<aside class="settings-nav" aria-label="Market sections">
|
<aside class="settings-nav" aria-label="Market sections">
|
||||||
<button class="settings-nav-item active" type="button" data-market-view="ai-characters">AI Characters</button>
|
<button class="settings-nav-item active" type="button" data-market-view="ai-characters">AI Characters</button>
|
||||||
<button class="settings-nav-item" type="button" data-market-view="user-personas">User Personas</button>
|
<button class="settings-nav-item" type="button" data-market-view="user-personas">User Personas</button>
|
||||||
<button class="settings-nav-item" type="button" data-market-view="my-personas">My Personas</button>
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
@@ -45,8 +44,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Discover</p>
|
<p class="eyebrow">Discover</p>
|
||||||
<h2>AI Characters</h2>
|
<h2>AI Characters</h2>
|
||||||
<p class="subtitle">Find shared characters to start roleplay chats with.</p>
|
<p class="subtitle">Pick a character, choose your persona, and start roleplay.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="marketCreateAiCharacter" type="button" class="secondary-action">Create AI Character</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="marketPersonaList" class="persona-list"></div>
|
<div id="marketPersonaList" class="persona-list"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,26 +58,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Discover</p>
|
<p class="eyebrow">Discover</p>
|
||||||
<h2>User Personas</h2>
|
<h2>User Personas</h2>
|
||||||
<p class="subtitle">Browse shared user personas you can collect for roleplay context.</p>
|
<p class="subtitle">Collect personas you can use as your identity in roleplay scenes.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="marketCreateUserPersona" type="button" class="secondary-action">Create User Persona</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="marketUserPersonaList" class="persona-list"></div>
|
<div id="marketUserPersonaList" class="persona-list"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="settings-view" data-market-panel="my-personas">
|
|
||||||
<div class="settings-panel">
|
|
||||||
<div class="settings-section-header">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Manage</p>
|
|
||||||
<h2>My Personas</h2>
|
|
||||||
<p class="subtitle">Create and manage your own AI Characters.</p>
|
|
||||||
</div>
|
|
||||||
<button id="marketCreatePersona" type="button" class="secondary-action">Create persona</button>
|
|
||||||
</div>
|
|
||||||
<div id="marketOwnedPersonaList" class="persona-list"></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +105,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="market-preview-actions">
|
<div class="market-preview-actions">
|
||||||
<label id="marketPreviewUserPersonaField" class="field market-preview-user-field hidden">
|
<label id="marketPreviewUserPersonaField" class="field market-preview-user-field hidden">
|
||||||
<span>Talk as</span>
|
<span>You in this roleplay</span>
|
||||||
<select id="marketPreviewUserPersonaSelect"></select>
|
<select id="marketPreviewUserPersonaSelect"></select>
|
||||||
</label>
|
</label>
|
||||||
<button id="marketPreviewConfirm" type="button" class="primary-action">Take persona</button>
|
<button id="marketPreviewConfirm" type="button" class="primary-action">Take persona</button>
|
||||||
|
|||||||
+64
-106
@@ -1,13 +1,13 @@
|
|||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
const marketPersonaList = $("marketPersonaList");
|
const marketPersonaList = $("marketPersonaList");
|
||||||
const marketUserPersonaList = $("marketUserPersonaList");
|
const marketUserPersonaList = $("marketUserPersonaList");
|
||||||
const marketOwnedPersonaList = $("marketOwnedPersonaList");
|
|
||||||
const refreshMarketBtn = $("refreshMarket");
|
const refreshMarketBtn = $("refreshMarket");
|
||||||
const marketStatus = $("marketStatus");
|
const marketStatus = $("marketStatus");
|
||||||
const marketSearchInput = $("marketSearch");
|
const marketSearchInput = $("marketSearch");
|
||||||
const marketViewButtons = document.querySelectorAll("[data-market-view]");
|
const marketViewButtons = document.querySelectorAll("[data-market-view]");
|
||||||
const marketPanels = document.querySelectorAll("[data-market-panel]");
|
const marketPanels = document.querySelectorAll("[data-market-panel]");
|
||||||
const marketCreatePersonaBtn = $("marketCreatePersona");
|
const marketCreateAiCharacterBtn = $("marketCreateAiCharacter");
|
||||||
|
const marketCreateUserPersonaBtn = $("marketCreateUserPersona");
|
||||||
const marketPreviewModal = $("marketPreviewModal");
|
const marketPreviewModal = $("marketPreviewModal");
|
||||||
const marketPreviewName = $("marketPreviewName");
|
const marketPreviewName = $("marketPreviewName");
|
||||||
const marketPreviewMeta = $("marketPreviewMeta");
|
const marketPreviewMeta = $("marketPreviewMeta");
|
||||||
@@ -45,9 +45,10 @@ const themes = {
|
|||||||
let allMarketPersonas = [];
|
let allMarketPersonas = [];
|
||||||
let assistantPersonas = [];
|
let assistantPersonas = [];
|
||||||
let userPersonas = [];
|
let userPersonas = [];
|
||||||
let publishedPersonaIds = new Set();
|
|
||||||
let pendingMarketPersona = null;
|
let pendingMarketPersona = null;
|
||||||
let editingPersonaId = null;
|
let editingPersonaId = null;
|
||||||
|
let activeUserPersonaId = null;
|
||||||
|
let activeMarketView = "ai-characters";
|
||||||
|
|
||||||
async function request(url, data, method = "POST") {
|
async function request(url, data, method = "POST") {
|
||||||
try {
|
try {
|
||||||
@@ -65,7 +66,6 @@ async function request(url, data, method = "POST") {
|
|||||||
const get = (url) => request(url, null, "GET");
|
const get = (url) => request(url, null, "GET");
|
||||||
const post = (url, data) => request(url, data, "POST");
|
const post = (url, data) => request(url, data, "POST");
|
||||||
const put = (url, data) => request(url, data, "PUT");
|
const put = (url, data) => request(url, data, "PUT");
|
||||||
const del = (url) => request(url, null, "DELETE");
|
|
||||||
|
|
||||||
function applyTheme(themeKey) {
|
function applyTheme(themeKey) {
|
||||||
const nextTheme = themes[themeKey] ? themeKey : "fakegpt";
|
const nextTheme = themes[themeKey] ? themeKey : "fakegpt";
|
||||||
@@ -92,11 +92,14 @@ function setPersonaFormNotice(message = "", state = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setMarketView(view) {
|
function setMarketView(view) {
|
||||||
marketViewButtons.forEach((item) => item.classList.toggle("active", item.dataset.marketView === view));
|
const normalizedView = view === "user-personas" ? "user-personas" : "ai-characters";
|
||||||
marketPanels.forEach((panel) => panel.classList.toggle("active", panel.dataset.marketPanel === view));
|
activeMarketView = normalizedView;
|
||||||
|
marketViewButtons.forEach((item) => item.classList.toggle("active", item.dataset.marketView === normalizedView));
|
||||||
|
marketPanels.forEach((panel) => panel.classList.toggle("active", panel.dataset.marketPanel === normalizedView));
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
params.set("view", view);
|
params.set("view", normalizedView);
|
||||||
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
||||||
|
applyMarketFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPersonaMeta(persona) {
|
function buildPersonaMeta(persona) {
|
||||||
@@ -113,7 +116,6 @@ function closeMarketPreview() {
|
|||||||
marketPreviewModal.classList.add("hidden");
|
marketPreviewModal.classList.add("hidden");
|
||||||
pendingMarketPersona = null;
|
pendingMarketPersona = null;
|
||||||
marketPreviewUserPersonaSelect.innerHTML = "";
|
marketPreviewUserPersonaSelect.innerHTML = "";
|
||||||
marketPreviewConfirm.classList.add("hidden");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateUserPersonaChoices() {
|
function populateUserPersonaChoices() {
|
||||||
@@ -121,7 +123,7 @@ function populateUserPersonaChoices() {
|
|||||||
|
|
||||||
const promptOption = document.createElement("option");
|
const promptOption = document.createElement("option");
|
||||||
promptOption.value = "";
|
promptOption.value = "";
|
||||||
promptOption.textContent = "Choose who you are";
|
promptOption.textContent = "Choose who you are in this roleplay";
|
||||||
promptOption.disabled = true;
|
promptOption.disabled = true;
|
||||||
promptOption.selected = true;
|
promptOption.selected = true;
|
||||||
marketPreviewUserPersonaSelect.appendChild(promptOption);
|
marketPreviewUserPersonaSelect.appendChild(promptOption);
|
||||||
@@ -135,6 +137,7 @@ function populateUserPersonaChoices() {
|
|||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = String(persona.id);
|
option.value = String(persona.id);
|
||||||
option.textContent = persona.name;
|
option.textContent = persona.name;
|
||||||
|
option.selected = persona.id === activeUserPersonaId;
|
||||||
marketPreviewUserPersonaSelect.appendChild(option);
|
marketPreviewUserPersonaSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -156,9 +159,13 @@ function openMarketPreview(persona) {
|
|||||||
|
|
||||||
const isAssistant = persona.persona_type === "assistant";
|
const isAssistant = persona.persona_type === "assistant";
|
||||||
marketPreviewUserPersonaField.classList.toggle("hidden", !isAssistant);
|
marketPreviewUserPersonaField.classList.toggle("hidden", !isAssistant);
|
||||||
marketPreviewConfirm.textContent = isAssistant ? "Start chat" : "Collect as user persona";
|
marketPreviewConfirm.textContent = isAssistant ? "Start roleplay" : "Collect persona";
|
||||||
marketPreviewConfirm.classList.toggle("hidden", isAssistant);
|
if (isAssistant) {
|
||||||
if (isAssistant) populateUserPersonaChoices();
|
populateUserPersonaChoices();
|
||||||
|
marketPreviewConfirm.classList.add("hidden");
|
||||||
|
} else {
|
||||||
|
marketPreviewConfirm.classList.remove("hidden");
|
||||||
|
}
|
||||||
marketPreviewModal.classList.remove("hidden");
|
marketPreviewModal.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,9 +217,19 @@ function renderMarketList(items, listElement, type) {
|
|||||||
meta.textContent = buildPersonaMeta(persona);
|
meta.textContent = buildPersonaMeta(persona);
|
||||||
|
|
||||||
detailsBtn.type = "button";
|
detailsBtn.type = "button";
|
||||||
detailsBtn.textContent = "View";
|
detailsBtn.textContent = "Preview";
|
||||||
detailsBtn.addEventListener("click", () => openMarketPreview(persona));
|
detailsBtn.addEventListener("click", () => openMarketPreview(persona));
|
||||||
|
|
||||||
|
if (type !== "assistant") {
|
||||||
|
const quickActionBtn = document.createElement("button");
|
||||||
|
quickActionBtn.type = "button";
|
||||||
|
quickActionBtn.textContent = "Collect";
|
||||||
|
quickActionBtn.addEventListener("click", () => {
|
||||||
|
void collectMarketPersona(persona.id);
|
||||||
|
});
|
||||||
|
actions.appendChild(quickActionBtn);
|
||||||
|
}
|
||||||
|
|
||||||
titleRow.append(title, tag);
|
titleRow.append(title, tag);
|
||||||
actions.appendChild(detailsBtn);
|
actions.appendChild(detailsBtn);
|
||||||
item.append(titleRow, meta, actions);
|
item.append(titleRow, meta, actions);
|
||||||
@@ -220,58 +237,6 @@ function renderMarketList(items, listElement, type) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOwnedPersonaList() {
|
|
||||||
marketOwnedPersonaList.innerHTML = "";
|
|
||||||
if (!assistantPersonas.length) {
|
|
||||||
const empty = document.createElement("p");
|
|
||||||
empty.className = "status persona-status";
|
|
||||||
empty.textContent = "No AI Characters yet. Create one to get started.";
|
|
||||||
marketOwnedPersonaList.appendChild(empty);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assistantPersonas.forEach((persona) => {
|
|
||||||
const item = document.createElement("div");
|
|
||||||
const titleRow = document.createElement("div");
|
|
||||||
const title = document.createElement("h4");
|
|
||||||
const tag = document.createElement("span");
|
|
||||||
const meta = document.createElement("p");
|
|
||||||
const actions = document.createElement("div");
|
|
||||||
|
|
||||||
item.className = "persona-item";
|
|
||||||
titleRow.className = "persona-title-row";
|
|
||||||
actions.className = "persona-item-actions";
|
|
||||||
tag.className = "persona-role-tag ai";
|
|
||||||
tag.textContent = "AI Character";
|
|
||||||
title.textContent = persona.name;
|
|
||||||
meta.textContent = formatPersonaField(persona.pronouns) === "Not provided." ? "Pronouns: n/a" : `Pronouns: ${persona.pronouns}`;
|
|
||||||
|
|
||||||
[
|
|
||||||
["Edit", () => openPersonaForm(persona)],
|
|
||||||
[publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id)],
|
|
||||||
["Delete", () => deletePersona(persona.id)]
|
|
||||||
].forEach(([label, handler]) => {
|
|
||||||
const button = document.createElement("button");
|
|
||||||
button.type = "button";
|
|
||||||
button.textContent = label;
|
|
||||||
button.addEventListener("click", handler);
|
|
||||||
actions.appendChild(button);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publishedPersonaIds.has(persona.id)) {
|
|
||||||
const unpublish = document.createElement("button");
|
|
||||||
unpublish.type = "button";
|
|
||||||
unpublish.textContent = "Unpublish";
|
|
||||||
unpublish.addEventListener("click", () => unpublishPersona(persona.id));
|
|
||||||
actions.appendChild(unpublish);
|
|
||||||
}
|
|
||||||
|
|
||||||
titleRow.append(title, tag);
|
|
||||||
item.append(titleRow, meta, actions);
|
|
||||||
marketOwnedPersonaList.appendChild(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterMarketPersonas() {
|
function filterMarketPersonas() {
|
||||||
const query = marketSearchInput.value.trim().toLowerCase();
|
const query = marketSearchInput.value.trim().toLowerCase();
|
||||||
if (!query) return allMarketPersonas;
|
if (!query) return allMarketPersonas;
|
||||||
@@ -283,15 +248,24 @@ function filterMarketPersonas() {
|
|||||||
|
|
||||||
function applyMarketFilter() {
|
function applyMarketFilter() {
|
||||||
const filtered = filterMarketPersonas();
|
const filtered = filterMarketPersonas();
|
||||||
renderMarketList(filtered.filter((persona) => persona.persona_type === "assistant"), marketPersonaList, "assistant");
|
const assistantItems = filtered.filter((persona) => persona.persona_type === "assistant");
|
||||||
renderMarketList(filtered.filter((persona) => persona.persona_type === "user"), marketUserPersonaList, "user");
|
const userItems = filtered.filter((persona) => persona.persona_type === "user");
|
||||||
renderOwnedPersonaList();
|
renderMarketList(assistantItems, marketPersonaList, "assistant");
|
||||||
|
renderMarketList(userItems, marketUserPersonaList, "user");
|
||||||
|
|
||||||
if (marketSearchInput.value.trim() && !filtered.length) {
|
if (marketSearchInput.value.trim() && !filtered.length) {
|
||||||
setMarketStatus("No personas match your search.", "error");
|
setMarketStatus("No personas match your search.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMarketStatus(`${filtered.length} persona${filtered.length === 1 ? "" : "s"} shown.`);
|
if (activeMarketView === "ai-characters") {
|
||||||
|
setMarketStatus(`${assistantItems.length} AI Character${assistantItems.length === 1 ? "" : "s"} ready for roleplay.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activeMarketView === "user-personas") {
|
||||||
|
setMarketStatus(`${userItems.length} User Persona${userItems.length === 1 ? "" : "s"} available to collect.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMarketStatus(`${assistantItems.length + userItems.length} persona${assistantItems.length + userItems.length === 1 ? "" : "s"} shown.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadOwnedPersonas() {
|
async function loadOwnedPersonas() {
|
||||||
@@ -299,7 +273,7 @@ async function loadOwnedPersonas() {
|
|||||||
if (res.error) return false;
|
if (res.error) return false;
|
||||||
assistantPersonas = res.assistantPersonas || [];
|
assistantPersonas = res.assistantPersonas || [];
|
||||||
userPersonas = res.userPersonas || [];
|
userPersonas = res.userPersonas || [];
|
||||||
publishedPersonaIds = new Set(res.publishedPersonaIds || []);
|
activeUserPersonaId = res.activeUserPersonaId || null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,8 +304,12 @@ async function collectMarketPersona(marketId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startMarketPersonaChat(marketId, userPersonaSelection) {
|
async function startMarketPersonaChat(marketId, userPersonaSelection) {
|
||||||
|
if (!userPersonaSelection) {
|
||||||
|
setMarketStatus("Choose who you are before starting roleplay.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (userPersonaSelection === "create_new") {
|
if (userPersonaSelection === "create_new") {
|
||||||
window.location.href = "/settings?view=my-personas&create=user";
|
window.location.href = "/settings?view=my-roleplay-companions&create=user";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,40 +350,10 @@ async function savePersona() {
|
|||||||
|
|
||||||
await loadMarketPersonas();
|
await loadMarketPersonas();
|
||||||
closePersonaForm();
|
closePersonaForm();
|
||||||
setMarketView("my-personas");
|
setMarketView("ai-characters");
|
||||||
setMarketStatus(editingPersonaId ? "AI Character updated." : "AI Character created.", "success");
|
setMarketStatus(editingPersonaId ? "AI Character updated." : "AI Character created.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deletePersona(id) {
|
|
||||||
const res = await del(`/personas/${id}`);
|
|
||||||
if (res.error) {
|
|
||||||
setMarketStatus(res.error, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await loadMarketPersonas();
|
|
||||||
setMarketStatus("AI Character deleted.", "success");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function publishPersona(id) {
|
|
||||||
const res = await post(`/personas/${id}/publish`, {});
|
|
||||||
if (res.error) {
|
|
||||||
setMarketStatus(res.error, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await loadMarketPersonas();
|
|
||||||
setMarketStatus("AI Character published.", "success");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unpublishPersona(id) {
|
|
||||||
const res = await post(`/personas/${id}/unpublish`, {});
|
|
||||||
if (res.error) {
|
|
||||||
setMarketStatus(res.error, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await loadMarketPersonas();
|
|
||||||
setMarketStatus("AI Character unpublished.", "success");
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshMarketBtn.addEventListener("click", () => {
|
refreshMarketBtn.addEventListener("click", () => {
|
||||||
void loadMarketPersonas();
|
void loadMarketPersonas();
|
||||||
});
|
});
|
||||||
@@ -413,7 +361,14 @@ marketSearchInput.addEventListener("input", applyMarketFilter);
|
|||||||
marketViewButtons.forEach((button) => {
|
marketViewButtons.forEach((button) => {
|
||||||
button.addEventListener("click", () => setMarketView(button.dataset.marketView));
|
button.addEventListener("click", () => setMarketView(button.dataset.marketView));
|
||||||
});
|
});
|
||||||
marketCreatePersonaBtn.addEventListener("click", () => openPersonaForm());
|
if (marketCreateAiCharacterBtn) {
|
||||||
|
marketCreateAiCharacterBtn.addEventListener("click", () => openPersonaForm());
|
||||||
|
}
|
||||||
|
if (marketCreateUserPersonaBtn) {
|
||||||
|
marketCreateUserPersonaBtn.addEventListener("click", () => {
|
||||||
|
window.location.href = "/settings?view=my-roleplay-companions&create=user";
|
||||||
|
});
|
||||||
|
}
|
||||||
marketPreviewClose.addEventListener("click", closeMarketPreview);
|
marketPreviewClose.addEventListener("click", closeMarketPreview);
|
||||||
marketPreviewModal.addEventListener("click", (event) => {
|
marketPreviewModal.addEventListener("click", (event) => {
|
||||||
if (event.target === marketPreviewModal) closeMarketPreview();
|
if (event.target === marketPreviewModal) closeMarketPreview();
|
||||||
@@ -421,7 +376,7 @@ marketPreviewModal.addEventListener("click", (event) => {
|
|||||||
marketPreviewUserPersonaSelect.addEventListener("change", () => {
|
marketPreviewUserPersonaSelect.addEventListener("change", () => {
|
||||||
if (!pendingMarketPersona || pendingMarketPersona.persona_type !== "assistant") return;
|
if (!pendingMarketPersona || pendingMarketPersona.persona_type !== "assistant") return;
|
||||||
if (marketPreviewUserPersonaSelect.value === "create_new") {
|
if (marketPreviewUserPersonaSelect.value === "create_new") {
|
||||||
window.location.href = "/settings?view=my-personas&create=user";
|
window.location.href = "/settings?view=my-roleplay-companions&create=user";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
marketPreviewConfirm.classList.toggle("hidden", !marketPreviewUserPersonaSelect.value);
|
marketPreviewConfirm.classList.toggle("hidden", !marketPreviewUserPersonaSelect.value);
|
||||||
@@ -430,7 +385,7 @@ marketPreviewConfirm.addEventListener("click", async () => {
|
|||||||
if (!pendingMarketPersona) return;
|
if (!pendingMarketPersona) return;
|
||||||
const marketId = pendingMarketPersona.id;
|
const marketId = pendingMarketPersona.id;
|
||||||
if (pendingMarketPersona.persona_type === "assistant") {
|
if (pendingMarketPersona.persona_type === "assistant") {
|
||||||
await startMarketPersonaChat(marketId, marketPreviewUserPersonaSelect.value || "");
|
await startMarketPersonaChat(marketId, marketPreviewUserPersonaSelect.value || "self");
|
||||||
closeMarketPreview();
|
closeMarketPreview();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -451,5 +406,8 @@ window.addEventListener("load", async () => {
|
|||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
setMarketView(params.get("view") || "ai-characters");
|
setMarketView(params.get("view") || "ai-characters");
|
||||||
await loadMarketPersonas();
|
await loadMarketPersonas();
|
||||||
if (params.get("create") === "assistant") openPersonaForm();
|
if (params.get("create") === "assistant") {
|
||||||
|
setMarketView("ai-characters");
|
||||||
|
openPersonaForm();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+327
-18
@@ -9,8 +9,11 @@ const loginUsernameInput = $("loginUsername"), loginPasswordInput = $("loginPass
|
|||||||
const registerUsernameInput = $("registerUsername"), registerPasswordInput = $("registerPassword");
|
const registerUsernameInput = $("registerUsername"), registerPasswordInput = $("registerPassword");
|
||||||
const loginSubmit = $("loginSubmit"), registerSubmit = $("registerSubmit");
|
const loginSubmit = $("loginSubmit"), registerSubmit = $("registerSubmit");
|
||||||
const chatList = $("chatList"), chatSearchInput = $("chatSearch"), newChatBtn = $("newChat");
|
const chatList = $("chatList"), chatSearchInput = $("chatSearch"), newChatBtn = $("newChat");
|
||||||
|
const startRoleplayBtn = $("startRoleplay");
|
||||||
const renameChatBtn = $("renameChat"), clearChatBtn = $("clearChat"), exportChatBtn = $("exportChat");
|
const renameChatBtn = $("renameChat"), clearChatBtn = $("clearChat"), exportChatBtn = $("exportChat");
|
||||||
const activeChatTitle = $("activeChatTitle"), sessionUser = $("sessionUser");
|
const activeChatTitle = $("activeChatTitle"), sessionUser = $("sessionUser");
|
||||||
|
const contextChipButton = $("contextChipButton"), contextPopover = $("contextPopover"), contextSummaryLabel = $("contextSummaryLabel");
|
||||||
|
const chatActionsMenuButton = $("chatActionsMenuButton"), chatActionsPopover = $("chatActionsPopover");
|
||||||
const chatModePill = $("chatModePill"), chatCharacterPill = $("chatCharacterPill"), chatUserPersonaPill = $("chatUserPersonaPill"), chatScenePill = $("chatScenePill");
|
const chatModePill = $("chatModePill"), chatCharacterPill = $("chatCharacterPill"), chatUserPersonaPill = $("chatUserPersonaPill"), chatScenePill = $("chatScenePill");
|
||||||
const personaList = $("personaList"), userPersonaList = $("userPersonaList");
|
const personaList = $("personaList"), userPersonaList = $("userPersonaList");
|
||||||
const personaForm = $("personaForm"), personaFormTitle = $("personaFormTitle"), personaTypeSelect = $("personaType");
|
const personaForm = $("personaForm"), personaFormTitle = $("personaFormTitle"), personaTypeSelect = $("personaType");
|
||||||
@@ -28,8 +31,11 @@ const popupModal = $("popupModal"), popupCloseBtn = $("popupClose"), popupCancel
|
|||||||
const popupTitle = $("popupTitle"), popupEyebrow = $("popupEyebrow"), popupDescription = $("popupDescription"), popupField = $("popupField");
|
const popupTitle = $("popupTitle"), popupEyebrow = $("popupEyebrow"), popupDescription = $("popupDescription"), popupField = $("popupField");
|
||||||
const popupInputLabel = $("popupInputLabel"), popupInput = $("popupInput");
|
const popupInputLabel = $("popupInputLabel"), popupInput = $("popupInput");
|
||||||
const appNotice = $("appNotice"), quickPrompts = $("quickPrompts");
|
const appNotice = $("appNotice"), quickPrompts = $("quickPrompts");
|
||||||
|
const onboardingModal = $("onboardingModal"), onboardingTitle = $("onboardingTitle"), onboardingSubtitle = $("onboardingSubtitle");
|
||||||
|
const onboardingChoices = $("onboardingChoices"), onboardingBack = $("onboardingBack"), onboardingContinue = $("onboardingContinue"), onboardingSkip = $("onboardingSkip");
|
||||||
const authScreens = document.querySelectorAll(".auth-screen"), toggleButtons = document.querySelectorAll(".auth-toggle .toggle");
|
const authScreens = document.querySelectorAll(".auth-screen"), toggleButtons = document.querySelectorAll(".auth-toggle .toggle");
|
||||||
const themeNameTargets = document.querySelectorAll("[data-theme-name]"), themeLogoTargets = document.querySelectorAll("[data-theme-logo]");
|
const themeNameTargets = document.querySelectorAll("[data-theme-name]"), themeLogoTargets = document.querySelectorAll("[data-theme-logo]");
|
||||||
|
const modelMenu = document.querySelector(".model-menu"), personaMenu = document.querySelector(".persona-menu");
|
||||||
const requestedChatId = Number(new URLSearchParams(window.location.search).get("chat")) || null;
|
const requestedChatId = Number(new URLSearchParams(window.location.search).get("chat")) || null;
|
||||||
|
|
||||||
let isProcessing = false, activeChatId = null, editingPersonaId = null, editingPersonaType = "assistant", currentUsername = "";
|
let isProcessing = false, activeChatId = null, editingPersonaId = null, editingPersonaType = "assistant", currentUsername = "";
|
||||||
@@ -38,6 +44,14 @@ let activeUserPersonaId = null, currentSummary = null, publishedPersonaIds = new
|
|||||||
let popupResolver = null, popupMode = null, popupLastFocus = null;
|
let popupResolver = null, popupMode = null, popupLastFocus = null;
|
||||||
let roleplayPresetCharacterId = null;
|
let roleplayPresetCharacterId = null;
|
||||||
let noticeTimer = null;
|
let noticeTimer = null;
|
||||||
|
let workspaceMode = localStorage.getItem("krishd-workspace-mode") || "basic";
|
||||||
|
let onboardingStep = 1, onboardingIntent = "ask";
|
||||||
|
|
||||||
|
const onboardingPrompts = {
|
||||||
|
ask: ["Explain this topic in simple terms.", "Compare these options and recommend one."],
|
||||||
|
brainstorm: ["Give me 10 practical ideas for this problem.", "Suggest 3 creative directions and tradeoffs."],
|
||||||
|
roleplay: ["Start a roleplay scene with immediate tension.", "Give me a dramatic opening with clear stakes."]
|
||||||
|
};
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
"fakegpt": {name: "FakeGPT", short: "FG"},
|
"fakegpt": {name: "FakeGPT", short: "FG"},
|
||||||
@@ -98,6 +112,90 @@ function setPersonaFormNotice(message = "", state = "") {
|
|||||||
personaFormNotice.textContent = message;
|
personaFormNotice.textContent = message;
|
||||||
personaFormNotice.className = state ? `status ${state}` : "status";
|
personaFormNotice.className = state ? `status ${state}` : "status";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyWorkspaceMode(nextMode, persist = true) {
|
||||||
|
workspaceMode = nextMode === "advanced" ? "advanced" : "basic";
|
||||||
|
document.body.dataset.workspaceMode = workspaceMode;
|
||||||
|
if (persist) localStorage.setItem("krishd-workspace-mode", workspaceMode);
|
||||||
|
if (workspaceMode === "basic") {
|
||||||
|
closeModelPopover();
|
||||||
|
closePersonaPopover();
|
||||||
|
}
|
||||||
|
if (modelMenu) modelMenu.classList.toggle("hidden", workspaceMode === "basic");
|
||||||
|
if (personaMenu) personaMenu.classList.toggle("hidden", workspaceMode === "basic");
|
||||||
|
updateComposerPlaceholder();
|
||||||
|
renderQuickPrompts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCompletedOnboarding() {
|
||||||
|
return localStorage.getItem("krishd-onboarding-complete") === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
function markOnboardingCompleted() {
|
||||||
|
localStorage.setItem("krishd-onboarding-complete", "true");
|
||||||
|
closeOnboarding();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOnboarding() {
|
||||||
|
if (!onboardingModal) return;
|
||||||
|
onboardingModal.classList.add("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOnboardingStep() {
|
||||||
|
if (!onboardingChoices || !onboardingTitle || !onboardingSubtitle) return;
|
||||||
|
onboardingChoices.innerHTML = "";
|
||||||
|
onboardingBack.classList.toggle("hidden", onboardingStep === 1);
|
||||||
|
onboardingContinue.classList.toggle("hidden", onboardingStep === 2);
|
||||||
|
if (onboardingStep === 1) {
|
||||||
|
onboardingTitle.textContent = "What do you want to do?";
|
||||||
|
onboardingSubtitle.textContent = "Choose one path. You can switch anytime.";
|
||||||
|
[["ask", "Ask questions"], ["brainstorm", "Brainstorm ideas"], ["roleplay", "Roleplay with a character"]].forEach(([key, label]) => {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = `secondary-action${onboardingIntent === key ? " active" : ""}`;
|
||||||
|
button.textContent = label;
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
onboardingIntent = key;
|
||||||
|
renderOnboardingStep();
|
||||||
|
});
|
||||||
|
onboardingChoices.appendChild(button);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onboardingTitle.textContent = "Choose a starter";
|
||||||
|
onboardingSubtitle.textContent = "Pick one template and send your first message.";
|
||||||
|
onboardingPrompts[onboardingIntent].forEach((template) => {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = "secondary-action";
|
||||||
|
button.textContent = template;
|
||||||
|
button.addEventListener("click", async () => {
|
||||||
|
if (onboardingIntent === "roleplay") {
|
||||||
|
closeOnboarding();
|
||||||
|
if (assistantPersonas.length) {
|
||||||
|
openRoleplayStarter();
|
||||||
|
} else {
|
||||||
|
openPersonaForm(null, "assistant");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!activeChatId) await createNewChat();
|
||||||
|
msgInput.value = template;
|
||||||
|
msgInput.dispatchEvent(new Event("input"));
|
||||||
|
msgInput.focus();
|
||||||
|
closeOnboarding();
|
||||||
|
});
|
||||||
|
onboardingChoices.appendChild(button);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeShowOnboarding() {
|
||||||
|
if (!onboardingModal || hasCompletedOnboarding()) return;
|
||||||
|
onboardingStep = 1;
|
||||||
|
onboardingIntent = "ask";
|
||||||
|
renderOnboardingStep();
|
||||||
|
onboardingModal.classList.remove("hidden");
|
||||||
|
}
|
||||||
function setMessageContent(el, content) {
|
function setMessageContent(el, content) {
|
||||||
const markdownParser = window.marked;
|
const markdownParser = window.marked;
|
||||||
const purifier = window.DOMPurify;
|
const purifier = window.DOMPurify;
|
||||||
@@ -163,9 +261,10 @@ function renderQuickPrompts() {
|
|||||||
if (!quickPrompts) return;
|
if (!quickPrompts) return;
|
||||||
const activeChat = getChatById(activeChatId);
|
const activeChat = getChatById(activeChatId);
|
||||||
const prompts = getQuickPromptsForChat(activeChat);
|
const prompts = getQuickPromptsForChat(activeChat);
|
||||||
|
const visiblePrompts = workspaceMode === "basic" ? prompts.slice(0, 3) : prompts;
|
||||||
quickPrompts.innerHTML = "";
|
quickPrompts.innerHTML = "";
|
||||||
quickPrompts.classList.toggle("hidden", prompts.length === 0);
|
quickPrompts.classList.toggle("hidden", visiblePrompts.length === 0);
|
||||||
prompts.forEach((prompt) => {
|
visiblePrompts.forEach((prompt) => {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.className = "prompt-chip";
|
button.className = "prompt-chip";
|
||||||
@@ -180,9 +279,20 @@ function renderQuickPrompts() {
|
|||||||
}
|
}
|
||||||
function updateComposerPlaceholder() {
|
function updateComposerPlaceholder() {
|
||||||
const activeChat = getChatById(activeChatId);
|
const activeChat = getChatById(activeChatId);
|
||||||
msgInput.placeholder = activeChat?.assistant_persona_id
|
if (!activeChat) {
|
||||||
? "Reply in the scene..."
|
msgInput.placeholder = "Create a chat to begin...";
|
||||||
: "Type your message...";
|
return;
|
||||||
|
}
|
||||||
|
if (activeChat.assistant_persona_id) {
|
||||||
|
msgInput.placeholder = "Continue the scene...";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgInput.placeholder = workspaceMode === "basic" ? "Ask anything..." : "Type your message...";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSendState() {
|
||||||
|
const hasText = Boolean(msgInput.value.trim());
|
||||||
|
sendBtn.disabled = isProcessing || !hasText;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMessage(content, isUser = false, isLoading = false) {
|
function addMessage(content, isUser = false, isLoading = false) {
|
||||||
@@ -216,13 +326,78 @@ function addMessage(content, isUser = false, isLoading = false) {
|
|||||||
|
|
||||||
function renderMessages() {
|
function renderMessages() {
|
||||||
messagesDiv.innerHTML = "";
|
messagesDiv.innerHTML = "";
|
||||||
|
const chat = getChatById(activeChatId);
|
||||||
|
if (!currentMessages.length && (!chat || chat.assistant_persona_id)) {
|
||||||
|
renderMessageEmptyState();
|
||||||
|
} else {
|
||||||
currentMessages.forEach((msg) => addMessage(msg.content, msg.role === "user"));
|
currentMessages.forEach((msg) => addMessage(msg.content, msg.role === "user"));
|
||||||
|
}
|
||||||
updateChatParticipants();
|
updateChatParticipants();
|
||||||
updateChatActionState();
|
updateChatActionState();
|
||||||
renderQuickPrompts();
|
renderQuickPrompts();
|
||||||
updateComposerPlaceholder();
|
updateComposerPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createStarterAction(label, className, handler) {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = className;
|
||||||
|
button.textContent = label;
|
||||||
|
button.addEventListener("click", handler);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMessageStarterPrompt(label, prompt) {
|
||||||
|
return createStarterAction(label, "secondary-action", async () => {
|
||||||
|
if (!activeChatId) await createNewChat();
|
||||||
|
msgInput.value = prompt;
|
||||||
|
msgInput.dispatchEvent(new Event("input"));
|
||||||
|
msgInput.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMessageEmptyState() {
|
||||||
|
const empty = document.createElement("section");
|
||||||
|
const title = document.createElement("h3");
|
||||||
|
const description = document.createElement("p");
|
||||||
|
const actions = document.createElement("div");
|
||||||
|
const chat = getChatById(activeChatId);
|
||||||
|
|
||||||
|
empty.className = "messages-empty";
|
||||||
|
title.className = "messages-empty-title";
|
||||||
|
description.className = "messages-empty-copy";
|
||||||
|
actions.className = "messages-empty-actions";
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
title.textContent = "Start your first conversation";
|
||||||
|
description.textContent = "Create a chat, or jump directly into roleplay.";
|
||||||
|
actions.append(
|
||||||
|
createStarterAction("New chat", "primary-action", () => { void createNewChat(); }),
|
||||||
|
createStarterAction("Start roleplay", "secondary-action", () => {
|
||||||
|
if (assistantPersonas.length) {
|
||||||
|
openRoleplayStarter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openPersonaForm(null, "assistant");
|
||||||
|
}),
|
||||||
|
createMessageStarterPrompt("Try a starter prompt", "Help me plan my day in 3 steps.")
|
||||||
|
);
|
||||||
|
} else if (chat.assistant_persona_id) {
|
||||||
|
title.textContent = "Your scene is ready";
|
||||||
|
description.textContent = "Send your first line, or use a prompt to set the tone.";
|
||||||
|
actions.append(
|
||||||
|
createMessageStarterPrompt("Add tension", "Open with a tense line that raises the stakes immediately."),
|
||||||
|
createMessageStarterPrompt("Set the scene", "Describe what your character notices first in this scene."),
|
||||||
|
createStarterAction("Pick another roleplay setup", "secondary-action", () => openRoleplayStarter())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty.append(title, description, actions);
|
||||||
|
messagesDiv.appendChild(empty);
|
||||||
|
}
|
||||||
|
|
||||||
function updateWorkspaceCopy() {
|
function updateWorkspaceCopy() {
|
||||||
const activeChat = getChatById(activeChatId);
|
const activeChat = getChatById(activeChatId);
|
||||||
if (activeChatTitle) {
|
if (activeChatTitle) {
|
||||||
@@ -255,6 +430,7 @@ function updateChatParticipants() {
|
|||||||
chatCharacterPill.classList.add("hidden");
|
chatCharacterPill.classList.add("hidden");
|
||||||
chatUserPersonaPill.classList.add("hidden");
|
chatUserPersonaPill.classList.add("hidden");
|
||||||
chatScenePill.classList.add("hidden");
|
chatScenePill.classList.add("hidden");
|
||||||
|
if (contextSummaryLabel) contextSummaryLabel.textContent = "None";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (chat.assistant_persona_id) {
|
if (chat.assistant_persona_id) {
|
||||||
@@ -274,12 +450,14 @@ function updateChatParticipants() {
|
|||||||
} else {
|
} else {
|
||||||
chatScenePill.classList.add("hidden");
|
chatScenePill.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
if (contextSummaryLabel) contextSummaryLabel.textContent = chat.assistant_persona_name || "Roleplay";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chatModePill.textContent = "Assistant mode";
|
chatModePill.textContent = "Assistant mode";
|
||||||
chatCharacterPill.classList.add("hidden");
|
chatCharacterPill.classList.add("hidden");
|
||||||
chatUserPersonaPill.classList.add("hidden");
|
chatUserPersonaPill.classList.add("hidden");
|
||||||
chatScenePill.classList.add("hidden");
|
chatScenePill.classList.add("hidden");
|
||||||
|
if (contextSummaryLabel) contextSummaryLabel.textContent = "Assistant";
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateContextRail() {
|
function updateContextRail() {
|
||||||
@@ -367,6 +545,14 @@ function updateChatActionState() {
|
|||||||
exportChatBtn.disabled = !hasChat || currentMessages.length === 0;
|
exportChatBtn.disabled = !hasChat || currentMessages.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function maybeShowPersonaNudge() {
|
||||||
|
if (localStorage.getItem("krishd-persona-nudge-shown") === "true") return;
|
||||||
|
const assistantOnlyChats = chatSessions.filter((chat) => !chat.assistant_persona_id).length;
|
||||||
|
if (assistantOnlyChats < 3) return;
|
||||||
|
localStorage.setItem("krishd-persona-nudge-shown", "true");
|
||||||
|
setNotice("Want to personalize responses? Try personas in Tools.", "success");
|
||||||
|
}
|
||||||
|
|
||||||
function renderChatList() {
|
function renderChatList() {
|
||||||
const query = chatSearchInput.value.trim().toLowerCase();
|
const query = chatSearchInput.value.trim().toLowerCase();
|
||||||
const filtered = chatSessions.filter((chat) => !query || chat.title.toLowerCase().includes(query));
|
const filtered = chatSessions.filter((chat) => !query || chat.title.toLowerCase().includes(query));
|
||||||
@@ -437,9 +623,31 @@ function openPersonaForm(persona = null, personaType = "assistant") {
|
|||||||
|
|
||||||
function closePersonaForm() { personaModal.classList.add("hidden"); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; setPersonaFormNotice(""); }
|
function closePersonaForm() { personaModal.classList.add("hidden"); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; setPersonaFormNotice(""); }
|
||||||
function closePersonaPopover() { personaPopover.classList.add("hidden"); personaMenuButton.setAttribute("aria-expanded", "false"); }
|
function closePersonaPopover() { personaPopover.classList.add("hidden"); personaMenuButton.setAttribute("aria-expanded", "false"); }
|
||||||
|
function closeContextPopover() {
|
||||||
|
if (!contextPopover || !contextChipButton) return;
|
||||||
|
contextPopover.classList.add("hidden");
|
||||||
|
contextChipButton.setAttribute("aria-expanded", "false");
|
||||||
|
}
|
||||||
|
function closeChatActionsPopover() {
|
||||||
|
if (!chatActionsPopover || !chatActionsMenuButton) return;
|
||||||
|
chatActionsPopover.classList.add("hidden");
|
||||||
|
chatActionsMenuButton.setAttribute("aria-expanded", "false");
|
||||||
|
}
|
||||||
|
function closeAuxiliaryPopovers() {
|
||||||
|
closeModelPopover();
|
||||||
|
closePersonaPopover();
|
||||||
|
closeContextPopover();
|
||||||
|
closeChatActionsPopover();
|
||||||
|
}
|
||||||
function populateRoleplayStarter() {
|
function populateRoleplayStarter() {
|
||||||
if (!roleplayCharacterSelect || !roleplayUserPersonaSelect) return;
|
if (!roleplayCharacterSelect || !roleplayUserPersonaSelect) return;
|
||||||
roleplayCharacterSelect.innerHTML = "";
|
roleplayCharacterSelect.innerHTML = "";
|
||||||
|
const placeholder = document.createElement("option");
|
||||||
|
placeholder.value = "";
|
||||||
|
placeholder.textContent = "Choose AI Character";
|
||||||
|
placeholder.disabled = true;
|
||||||
|
placeholder.selected = true;
|
||||||
|
roleplayCharacterSelect.appendChild(placeholder);
|
||||||
assistantPersonas.forEach((persona) => {
|
assistantPersonas.forEach((persona) => {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = String(persona.id);
|
option.value = String(persona.id);
|
||||||
@@ -447,10 +655,13 @@ function populateRoleplayStarter() {
|
|||||||
option.selected = persona.id === roleplayPresetCharacterId;
|
option.selected = persona.id === roleplayPresetCharacterId;
|
||||||
roleplayCharacterSelect.appendChild(option);
|
roleplayCharacterSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
if (roleplayPresetCharacterId) {
|
||||||
|
roleplayCharacterSelect.value = String(roleplayPresetCharacterId);
|
||||||
|
}
|
||||||
roleplayUserPersonaSelect.innerHTML = "";
|
roleplayUserPersonaSelect.innerHTML = "";
|
||||||
const noneOption = document.createElement("option");
|
const noneOption = document.createElement("option");
|
||||||
noneOption.value = "";
|
noneOption.value = "";
|
||||||
noneOption.textContent = "No user persona";
|
noneOption.textContent = "Myself (no user persona)";
|
||||||
roleplayUserPersonaSelect.appendChild(noneOption);
|
roleplayUserPersonaSelect.appendChild(noneOption);
|
||||||
userPersonas.forEach((persona) => {
|
userPersonas.forEach((persona) => {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
@@ -459,11 +670,17 @@ function populateRoleplayStarter() {
|
|||||||
option.selected = persona.id === activeUserPersonaId;
|
option.selected = persona.id === activeUserPersonaId;
|
||||||
roleplayUserPersonaSelect.appendChild(option);
|
roleplayUserPersonaSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
if (assistantPersonas.length === 0) {
|
||||||
|
setRoleplayStarterNotice("Create an AI Character first, then start roleplay.", "error");
|
||||||
|
roleplayStarterConfirm.disabled = true;
|
||||||
|
} else {
|
||||||
|
setRoleplayStarterNotice("");
|
||||||
|
roleplayStarterConfirm.disabled = false;
|
||||||
|
}
|
||||||
updateRoleplaySuggestions();
|
updateRoleplaySuggestions();
|
||||||
}
|
}
|
||||||
function openRoleplayStarter(characterId = null) {
|
function openRoleplayStarter(characterId = null) {
|
||||||
closePersonaPopover();
|
closeAuxiliaryPopovers();
|
||||||
closeModelPopover();
|
|
||||||
roleplayPresetCharacterId = characterId;
|
roleplayPresetCharacterId = characterId;
|
||||||
populateRoleplayStarter();
|
populateRoleplayStarter();
|
||||||
if (roleplayScenarioInput) roleplayScenarioInput.value = "";
|
if (roleplayScenarioInput) roleplayScenarioInput.value = "";
|
||||||
@@ -480,6 +697,7 @@ function closeRoleplayStarter() {
|
|||||||
setRoleplayStarterNotice("");
|
setRoleplayStarterNotice("");
|
||||||
}
|
}
|
||||||
function closeModelPopover() {
|
function closeModelPopover() {
|
||||||
|
if (!modelPopover || !modelMenuButton) return;
|
||||||
modelPopover.classList.add("hidden");
|
modelPopover.classList.add("hidden");
|
||||||
modelMenuButton.setAttribute("aria-expanded", "false");
|
modelMenuButton.setAttribute("aria-expanded", "false");
|
||||||
}
|
}
|
||||||
@@ -653,13 +871,15 @@ async function publishPersona(id) { const res = await post(`/personas/${id}/publ
|
|||||||
async function unpublishPersona(id) { const res = await post(`/personas/${id}/unpublish`, {}); if (res.error) return setNotice(res.error, "error"); await loadPersonas(); setNotice("Persona unpublished.", "success"); }
|
async function unpublishPersona(id) { const res = await post(`/personas/${id}/unpublish`, {}); if (res.error) return setNotice(res.error, "error"); await loadPersonas(); setNotice("Persona unpublished.", "success"); }
|
||||||
|
|
||||||
function setLoadingState(loading) {
|
function setLoadingState(loading) {
|
||||||
isProcessing = loading; sendBtn.disabled = loading; msgInput.disabled = loading;
|
isProcessing = loading;
|
||||||
|
msgInput.disabled = loading;
|
||||||
sendBtn.classList.toggle("loading", loading);
|
sendBtn.classList.toggle("loading", loading);
|
||||||
if (loading) {
|
if (loading) {
|
||||||
msgInput.placeholder = "Processing response...";
|
msgInput.placeholder = "Processing response...";
|
||||||
} else {
|
} else {
|
||||||
updateComposerPlaceholder();
|
updateComposerPlaceholder();
|
||||||
}
|
}
|
||||||
|
updateSendState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAuthScreen(target) {
|
function showAuthScreen(target) {
|
||||||
@@ -676,6 +896,7 @@ async function setActiveChat(id) {
|
|||||||
const existing = getChatById(id);
|
const existing = getChatById(id);
|
||||||
if (res.chat && existing) Object.assign(existing, res.chat);
|
if (res.chat && existing) Object.assign(existing, res.chat);
|
||||||
currentMessages = res.messages || []; renderMessages();
|
currentMessages = res.messages || []; renderMessages();
|
||||||
|
updateSendState();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadChatSessions() {
|
async function loadChatSessions() {
|
||||||
@@ -686,13 +907,19 @@ async function loadChatSessions() {
|
|||||||
const preferredChatId = (requestedChatId && getChatById(requestedChatId)) ? requestedChatId : activeChatId && getChatById(activeChatId) ? activeChatId : chatSessions[0].id;
|
const preferredChatId = (requestedChatId && getChatById(requestedChatId)) ? requestedChatId : activeChatId && getChatById(activeChatId) ? activeChatId : chatSessions[0].id;
|
||||||
return setActiveChat(preferredChatId);
|
return setActiveChat(preferredChatId);
|
||||||
}
|
}
|
||||||
return createNewChat();
|
activeChatId = null;
|
||||||
|
currentMessages = [];
|
||||||
|
renderMessages();
|
||||||
|
updateWorkspaceCopy();
|
||||||
|
updateSendState();
|
||||||
|
return setNotice("Pick a starting action to begin.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNewChat() {
|
async function createNewChat() {
|
||||||
const res = await post("/chats", {title: "New chat"});
|
const res = await post("/chats", {title: "New chat"});
|
||||||
if (res.error || !res.chat) return setNotice(res.error || "Unable to create chat.", "error");
|
if (res.error || !res.chat) return setNotice(res.error || "Unable to create chat.", "error");
|
||||||
chatSessions.unshift(res.chat); await loadSummary(); renderChatList(); await setActiveChat(res.chat.id); setNotice("New chat created.", "success");
|
chatSessions.unshift(res.chat); await loadSummary(); renderChatList(); await setActiveChat(res.chat.id); setNotice("New chat created.", "success");
|
||||||
|
msgInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startRoleplay() {
|
async function startRoleplay() {
|
||||||
@@ -807,6 +1034,7 @@ async function checkSession() {
|
|||||||
if (!res.user) return false;
|
if (!res.user) return false;
|
||||||
currentUsername = res.user; authDiv.style.display = "none"; chatDiv.style.display = "block";
|
currentUsername = res.user; authDiv.style.display = "none"; chatDiv.style.display = "block";
|
||||||
await Promise.all([displayModels(), loadSummary(), loadChatSessions(), loadPersonas()]);
|
await Promise.all([displayModels(), loadSummary(), loadChatSessions(), loadPersonas()]);
|
||||||
|
maybeShowOnboarding();
|
||||||
msgInput.focus(); return true;
|
msgInput.focus(); return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,6 +1048,7 @@ async function handleAuth(endpoint, credentials) {
|
|||||||
if (endpoint === "login") {
|
if (endpoint === "login") {
|
||||||
currentUsername = username; authDiv.style.display = "none"; chatDiv.style.display = "block";
|
currentUsername = username; authDiv.style.display = "none"; chatDiv.style.display = "block";
|
||||||
await Promise.all([displayModels(), loadSummary(), loadChatSessions(), loadPersonas()]);
|
await Promise.all([displayModels(), loadSummary(), loadChatSessions(), loadPersonas()]);
|
||||||
|
maybeShowOnboarding();
|
||||||
setNotice("Ready.", "success"); msgInput.focus();
|
setNotice("Ready.", "success"); msgInput.focus();
|
||||||
} else {
|
} else {
|
||||||
setAuthMessage("Registration successful. You can now log in.", "success");
|
setAuthMessage("Registration successful. You can now log in.", "success");
|
||||||
@@ -844,7 +1073,12 @@ async function sendMessage() {
|
|||||||
const res = await post("/chat", {message, model: modelSelect.value, chatId: activeChatId});
|
const res = await post("/chat", {message, model: modelSelect.value, chatId: activeChatId});
|
||||||
if (messagesDiv.contains(loadingMsg)) messagesDiv.removeChild(loadingMsg);
|
if (messagesDiv.contains(loadingMsg)) messagesDiv.removeChild(loadingMsg);
|
||||||
if (res.reply) {
|
if (res.reply) {
|
||||||
currentMessages.push({role: "bot", content: res.reply}); addMessage(res.reply); await loadSummary(); setNotice("Reply received.", "success");
|
currentMessages.push({role: "bot", content: res.reply});
|
||||||
|
addMessage(res.reply);
|
||||||
|
await loadSummary();
|
||||||
|
markOnboardingCompleted();
|
||||||
|
maybeShowPersonaNudge();
|
||||||
|
setNotice("Reply received.", "success");
|
||||||
} else {
|
} else {
|
||||||
addMessage(`[Error: ${res.error}]`); setNotice(res.error || "Failed to generate reply.", "error");
|
addMessage(`[Error: ${res.error}]`); setNotice(res.error || "Failed to generate reply.", "error");
|
||||||
}
|
}
|
||||||
@@ -859,6 +1093,8 @@ async function sendMessage() {
|
|||||||
function togglePersonaPopover(event) {
|
function togglePersonaPopover(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
closeModelPopover();
|
closeModelPopover();
|
||||||
|
closeContextPopover();
|
||||||
|
closeChatActionsPopover();
|
||||||
const isHidden = personaPopover.classList.contains("hidden");
|
const isHidden = personaPopover.classList.contains("hidden");
|
||||||
personaPopover.classList.toggle("hidden", !isHidden);
|
personaPopover.classList.toggle("hidden", !isHidden);
|
||||||
personaMenuButton.setAttribute("aria-expanded", String(isHidden));
|
personaMenuButton.setAttribute("aria-expanded", String(isHidden));
|
||||||
@@ -867,11 +1103,35 @@ function togglePersonaPopover(event) {
|
|||||||
function toggleModelPopover(event) {
|
function toggleModelPopover(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
closePersonaPopover();
|
closePersonaPopover();
|
||||||
|
closeContextPopover();
|
||||||
|
closeChatActionsPopover();
|
||||||
const isHidden = modelPopover.classList.contains("hidden");
|
const isHidden = modelPopover.classList.contains("hidden");
|
||||||
modelPopover.classList.toggle("hidden", !isHidden);
|
modelPopover.classList.toggle("hidden", !isHidden);
|
||||||
modelMenuButton.setAttribute("aria-expanded", String(isHidden));
|
modelMenuButton.setAttribute("aria-expanded", String(isHidden));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleContextPopover(event) {
|
||||||
|
if (!contextPopover || !contextChipButton) return;
|
||||||
|
event.stopPropagation();
|
||||||
|
closeModelPopover();
|
||||||
|
closePersonaPopover();
|
||||||
|
closeChatActionsPopover();
|
||||||
|
const isHidden = contextPopover.classList.contains("hidden");
|
||||||
|
contextPopover.classList.toggle("hidden", !isHidden);
|
||||||
|
contextChipButton.setAttribute("aria-expanded", String(isHidden));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleChatActionsPopover(event) {
|
||||||
|
if (!chatActionsPopover || !chatActionsMenuButton) return;
|
||||||
|
event.stopPropagation();
|
||||||
|
closeModelPopover();
|
||||||
|
closePersonaPopover();
|
||||||
|
closeContextPopover();
|
||||||
|
const isHidden = chatActionsPopover.classList.contains("hidden");
|
||||||
|
chatActionsPopover.classList.toggle("hidden", !isHidden);
|
||||||
|
chatActionsMenuButton.setAttribute("aria-expanded", String(isHidden));
|
||||||
|
}
|
||||||
|
|
||||||
toggleButtons.forEach((btn) => btn.addEventListener("click", () => showAuthScreen(btn.dataset.target)));
|
toggleButtons.forEach((btn) => btn.addEventListener("click", () => showAuthScreen(btn.dataset.target)));
|
||||||
loginForm.addEventListener("submit", (event) => { event.preventDefault(); void handleAuth("login", {username: loginUsernameInput.value.trim(), password: loginPasswordInput.value.trim()}); });
|
loginForm.addEventListener("submit", (event) => { event.preventDefault(); void handleAuth("login", {username: loginUsernameInput.value.trim(), password: loginPasswordInput.value.trim()}); });
|
||||||
registerForm.addEventListener("submit", (event) => { event.preventDefault(); void handleAuth("register", {username: registerUsernameInput.value.trim(), password: registerPasswordInput.value.trim()}); });
|
registerForm.addEventListener("submit", (event) => { event.preventDefault(); void handleAuth("register", {username: registerUsernameInput.value.trim(), password: registerPasswordInput.value.trim()}); });
|
||||||
@@ -879,12 +1139,23 @@ $("logout").addEventListener("click", async () => {
|
|||||||
await post("/logout", {}); chatDiv.style.display = "none"; authDiv.style.display = "grid";
|
await post("/logout", {}); chatDiv.style.display = "none"; authDiv.style.display = "grid";
|
||||||
activeChatId = null; currentUsername = ""; currentSummary = null; chatSessions = []; currentMessages = []; assistantPersonas = []; userPersonas = [];
|
activeChatId = null; currentUsername = ""; currentSummary = null; chatSessions = []; currentMessages = []; assistantPersonas = []; userPersonas = [];
|
||||||
activeUserPersonaId = null; publishedPersonaIds = new Set(); messagesDiv.innerHTML = ""; renderChatList(); updateChatActionState();
|
activeUserPersonaId = null; publishedPersonaIds = new Set(); messagesDiv.innerHTML = ""; renderChatList(); updateChatActionState();
|
||||||
|
updateComposerPlaceholder();
|
||||||
|
updateSendState();
|
||||||
showAuthScreen("login"); setAuthMessage("Logged out.", "success"); setNotice("Ready.");
|
showAuthScreen("login"); setAuthMessage("Logged out.", "success"); setNotice("Ready.");
|
||||||
});
|
});
|
||||||
sendBtn.addEventListener("click", () => { void sendMessage(); });
|
sendBtn.addEventListener("click", () => { void sendMessage(); });
|
||||||
msgInput.addEventListener("keydown", (event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); void sendMessage(); } });
|
msgInput.addEventListener("keydown", (event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); void sendMessage(); } });
|
||||||
msgInput.addEventListener("input", function () { this.style.height = "auto"; this.style.height = `${Math.min(this.scrollHeight, 180)}px`; });
|
msgInput.addEventListener("input", function () {
|
||||||
|
this.style.height = "auto";
|
||||||
|
this.style.height = `${Math.min(this.scrollHeight, 180)}px`;
|
||||||
|
updateSendState();
|
||||||
|
});
|
||||||
newChatBtn.addEventListener("click", () => { void createNewChat(); });
|
newChatBtn.addEventListener("click", () => { void createNewChat(); });
|
||||||
|
if (startRoleplayBtn) {
|
||||||
|
startRoleplayBtn.addEventListener("click", () => {
|
||||||
|
window.location.href = "/market?view=ai-characters";
|
||||||
|
});
|
||||||
|
}
|
||||||
renameChatBtn.addEventListener("click", () => { if (activeChatId) void renameChat(activeChatId); });
|
renameChatBtn.addEventListener("click", () => { if (activeChatId) void renameChat(activeChatId); });
|
||||||
clearChatBtn.addEventListener("click", () => { if (activeChatId) void clearChat(activeChatId); }); exportChatBtn.addEventListener("click", exportCurrentChat);
|
clearChatBtn.addEventListener("click", () => { if (activeChatId) void clearChat(activeChatId); }); exportChatBtn.addEventListener("click", exportCurrentChat);
|
||||||
chatSearchInput.addEventListener("input", renderChatList); modelSelect.addEventListener("change", () => { updateModelHelp(); updateContextRail(); });
|
chatSearchInput.addEventListener("input", renderChatList); modelSelect.addEventListener("change", () => { updateModelHelp(); updateContextRail(); });
|
||||||
@@ -919,19 +1190,57 @@ popupInput.addEventListener("keydown", (event) => {
|
|||||||
closePopup(popupInput.value);
|
closePopup(popupInput.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
modelMenuButton.addEventListener("click", toggleModelPopover); modelPopover.addEventListener("click", (event) => event.stopPropagation());
|
if (modelMenuButton && modelPopover) {
|
||||||
personaMenuButton.addEventListener("click", togglePersonaPopover); personaPopover.addEventListener("click", (event) => event.stopPropagation());
|
modelMenuButton.addEventListener("click", toggleModelPopover);
|
||||||
document.addEventListener("click", () => { closeModelPopover(); closePersonaPopover(); });
|
modelPopover.addEventListener("click", (event) => event.stopPropagation());
|
||||||
|
}
|
||||||
|
if (personaMenuButton && personaPopover) {
|
||||||
|
personaMenuButton.addEventListener("click", togglePersonaPopover);
|
||||||
|
personaPopover.addEventListener("click", (event) => event.stopPropagation());
|
||||||
|
}
|
||||||
|
if (contextChipButton && contextPopover) {
|
||||||
|
contextChipButton.addEventListener("click", toggleContextPopover);
|
||||||
|
contextPopover.addEventListener("click", (event) => event.stopPropagation());
|
||||||
|
}
|
||||||
|
if (chatActionsMenuButton && chatActionsPopover) {
|
||||||
|
chatActionsMenuButton.addEventListener("click", toggleChatActionsPopover);
|
||||||
|
chatActionsPopover.addEventListener("click", (event) => event.stopPropagation());
|
||||||
|
}
|
||||||
|
if (onboardingContinue) {
|
||||||
|
onboardingContinue.addEventListener("click", () => {
|
||||||
|
onboardingStep = 2;
|
||||||
|
renderOnboardingStep();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (onboardingBack) {
|
||||||
|
onboardingBack.addEventListener("click", () => {
|
||||||
|
onboardingStep = 1;
|
||||||
|
renderOnboardingStep();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (onboardingSkip) {
|
||||||
|
onboardingSkip.addEventListener("click", markOnboardingCompleted);
|
||||||
|
}
|
||||||
|
if (onboardingModal) {
|
||||||
|
onboardingModal.addEventListener("click", (event) => {
|
||||||
|
if (event.target === onboardingModal) markOnboardingCompleted();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener("click", () => { closeAuxiliaryPopovers(); });
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
closeModelPopover();
|
closeAuxiliaryPopovers();
|
||||||
closePersonaPopover();
|
|
||||||
closeRoleplayStarter();
|
closeRoleplayStarter();
|
||||||
closePersonaForm();
|
closePersonaForm();
|
||||||
closePopup(false);
|
closePopup(false);
|
||||||
|
closeOnboarding();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("load", async () => {
|
window.addEventListener("load", async () => {
|
||||||
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false);
|
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false);
|
||||||
setNotice("Ready."); updateChatActionState(); await checkSession();
|
applyWorkspaceMode(localStorage.getItem("krishd-workspace-mode") || "basic", false);
|
||||||
|
setNotice("Ready.");
|
||||||
|
updateChatActionState();
|
||||||
|
updateSendState();
|
||||||
|
await checkSession();
|
||||||
});
|
});
|
||||||
|
|||||||
+15
-22
@@ -25,16 +25,10 @@
|
|||||||
|
|
||||||
<p id="settingsNotice" class="status">Loading settings...</p>
|
<p id="settingsNotice" class="status">Loading settings...</p>
|
||||||
|
|
||||||
<section class="settings-quick-actions">
|
|
||||||
<button id="quickCreateAiCharacter" type="button" class="primary-action">Create AI Character</button>
|
|
||||||
<button id="quickCreateUserPersona" type="button" class="secondary-action">Create User Persona</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="settings-layout">
|
<section class="settings-layout">
|
||||||
<aside class="settings-nav" aria-label="Settings sections">
|
<aside class="settings-nav" aria-label="Settings sections">
|
||||||
<button class="settings-nav-item active" type="button" data-settings-view="personal">Personal</button>
|
<button class="settings-nav-item active" type="button" data-settings-view="personal">Personal</button>
|
||||||
<button class="settings-nav-item" type="button" data-settings-view="my-ai-characters">My AI Characters</button>
|
<button class="settings-nav-item" type="button" data-settings-view="my-roleplay-companions">My Roleplay Companions</button>
|
||||||
<button class="settings-nav-item" type="button" data-settings-view="my-personas">My Personas</button>
|
|
||||||
<button class="settings-nav-item" type="button" data-settings-view="design">Design</button>
|
<button class="settings-nav-item" type="button" data-settings-view="design">Design</button>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -89,30 +83,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="settings-view" data-settings-panel="my-ai-characters">
|
<section class="settings-view" data-settings-panel="my-roleplay-companions">
|
||||||
<div class="settings-panel">
|
<div class="settings-panel">
|
||||||
<div class="settings-section-header">
|
<div class="settings-section-header">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Create & manage</p>
|
<p class="eyebrow">Create & manage</p>
|
||||||
<h2>My AI Characters</h2>
|
<h2>My Roleplay Companions</h2>
|
||||||
<p class="subtitle">Build characters for roleplay chats and publish them to the market when they are ready.</p>
|
<p class="subtitle">Build characters for roleplay chats and publish them to the market when they are ready.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="market-header-actions action-pair">
|
||||||
<button id="createAiCharacter" type="button" class="secondary-action">Create AI Character</button>
|
<button id="createAiCharacter" type="button" class="secondary-action">Create AI Character</button>
|
||||||
|
<button id="createUserPersona" type="button" class="secondary-action">Create User Persona</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>AI Characters</h3>
|
||||||
<div id="settingsAiCharacterList" class="persona-list"></div>
|
<div id="settingsAiCharacterList" class="persona-list"></div>
|
||||||
</div>
|
<h3>User Personas</h3>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="settings-view" data-settings-panel="my-personas">
|
|
||||||
<div class="settings-panel">
|
|
||||||
<div class="settings-section-header">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Create & manage</p>
|
|
||||||
<h2>My Personas</h2>
|
|
||||||
<p class="subtitle">Create the personas you want to speak as, then set one as default for faster roleplay setup.</p>
|
|
||||||
</div>
|
|
||||||
<button id="createUserPersona" type="button" class="secondary-action">Create user persona</button>
|
|
||||||
</div>
|
|
||||||
<div id="settingsUserPersonaList" class="persona-list"></div>
|
<div id="settingsUserPersonaList" class="persona-list"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -138,6 +124,13 @@
|
|||||||
<option value="confusity">Confusity</option>
|
<option value="confusity">Confusity</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Workspace mode</span>
|
||||||
|
<select id="workspaceModeSelect">
|
||||||
|
<option value="basic">Basic</option>
|
||||||
|
<option value="advanced">Advanced</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+14
-16
@@ -1,6 +1,7 @@
|
|||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
const settingsNotice = $("settingsNotice");
|
const settingsNotice = $("settingsNotice");
|
||||||
const settingsThemeSelect = $("settingsThemeSelect");
|
const settingsThemeSelect = $("settingsThemeSelect");
|
||||||
|
const workspaceModeSelect = $("workspaceModeSelect");
|
||||||
const currentUsernameInput = $("currentUsername");
|
const currentUsernameInput = $("currentUsername");
|
||||||
const newUsernameInput = $("newUsername");
|
const newUsernameInput = $("newUsername");
|
||||||
const usernamePasswordInput = $("usernamePassword");
|
const usernamePasswordInput = $("usernamePassword");
|
||||||
@@ -13,8 +14,6 @@ const settingsNavItems = document.querySelectorAll("[data-settings-view]");
|
|||||||
const settingsPanels = document.querySelectorAll("[data-settings-panel]");
|
const settingsPanels = document.querySelectorAll("[data-settings-panel]");
|
||||||
const createAiCharacterBtn = $("createAiCharacter");
|
const createAiCharacterBtn = $("createAiCharacter");
|
||||||
const createUserPersonaBtn = $("createUserPersona");
|
const createUserPersonaBtn = $("createUserPersona");
|
||||||
const quickCreateAiCharacterBtn = $("quickCreateAiCharacter");
|
|
||||||
const quickCreateUserPersonaBtn = $("quickCreateUserPersona");
|
|
||||||
const settingsAiCharacterList = $("settingsAiCharacterList");
|
const settingsAiCharacterList = $("settingsAiCharacterList");
|
||||||
const settingsUserPersonaList = $("settingsUserPersonaList");
|
const settingsUserPersonaList = $("settingsUserPersonaList");
|
||||||
const settingsPersonaModal = $("settingsPersonaModal");
|
const settingsPersonaModal = $("settingsPersonaModal");
|
||||||
@@ -88,10 +87,11 @@ function applyTheme(themeKey, persist = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSettingsView(view) {
|
function setSettingsView(view) {
|
||||||
settingsNavItems.forEach((item) => item.classList.toggle("active", item.dataset.settingsView === view));
|
const normalizedView = ["my-ai-characters", "my-personas"].includes(view) ? "my-roleplay-companions" : view;
|
||||||
settingsPanels.forEach((panel) => panel.classList.toggle("active", panel.dataset.settingsPanel === view));
|
settingsNavItems.forEach((item) => item.classList.toggle("active", item.dataset.settingsView === normalizedView));
|
||||||
|
settingsPanels.forEach((panel) => panel.classList.toggle("active", panel.dataset.settingsPanel === normalizedView));
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
params.set("view", view);
|
params.set("view", normalizedView);
|
||||||
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ async function savePersona() {
|
|||||||
|
|
||||||
await loadPersonas();
|
await loadPersonas();
|
||||||
closePersonaModal();
|
closePersonaModal();
|
||||||
setSettingsView(payload.personaType === "assistant" ? "my-ai-characters" : "my-personas");
|
setSettingsView("my-roleplay-companions");
|
||||||
setNotice(wasEditing ? "Persona updated." : "Persona created.", "success");
|
setNotice(wasEditing ? "Persona updated." : "Persona created.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,16 +329,13 @@ settingsThemeSelect.addEventListener("change", (event) => {
|
|||||||
applyTheme(event.target.value);
|
applyTheme(event.target.value);
|
||||||
setNotice("Theme updated.", "success");
|
setNotice("Theme updated.", "success");
|
||||||
});
|
});
|
||||||
|
workspaceModeSelect.addEventListener("change", (event) => {
|
||||||
|
const mode = event.target.value === "advanced" ? "advanced" : "basic";
|
||||||
|
localStorage.setItem("krishd-workspace-mode", mode);
|
||||||
|
setNotice(`Workspace mode set to ${mode}.`, "success");
|
||||||
|
});
|
||||||
|
|
||||||
settingsNavItems.forEach((item) => item.addEventListener("click", () => setSettingsView(item.dataset.settingsView)));
|
settingsNavItems.forEach((item) => item.addEventListener("click", () => setSettingsView(item.dataset.settingsView)));
|
||||||
quickCreateAiCharacterBtn.addEventListener("click", () => {
|
|
||||||
setSettingsView("my-ai-characters");
|
|
||||||
openPersonaModal(null, "assistant");
|
|
||||||
});
|
|
||||||
quickCreateUserPersonaBtn.addEventListener("click", () => {
|
|
||||||
setSettingsView("my-personas");
|
|
||||||
openPersonaModal(null, "user");
|
|
||||||
});
|
|
||||||
createAiCharacterBtn.addEventListener("click", () => openPersonaModal(null, "assistant"));
|
createAiCharacterBtn.addEventListener("click", () => openPersonaModal(null, "assistant"));
|
||||||
createUserPersonaBtn.addEventListener("click", () => openPersonaModal(null, "user"));
|
createUserPersonaBtn.addEventListener("click", () => openPersonaModal(null, "user"));
|
||||||
settingsPersonaClose.addEventListener("click", closePersonaModal);
|
settingsPersonaClose.addEventListener("click", closePersonaModal);
|
||||||
@@ -353,6 +350,7 @@ settingsPersonaForm.addEventListener("submit", (event) => {
|
|||||||
|
|
||||||
window.addEventListener("load", async () => {
|
window.addEventListener("load", async () => {
|
||||||
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false);
|
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false);
|
||||||
|
workspaceModeSelect.value = localStorage.getItem("krishd-workspace-mode") || "basic";
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const view = params.get("view") || "personal";
|
const view = params.get("view") || "personal";
|
||||||
setSettingsView(view);
|
setSettingsView(view);
|
||||||
@@ -361,10 +359,10 @@ window.addEventListener("load", async () => {
|
|||||||
|
|
||||||
const create = params.get("create");
|
const create = params.get("create");
|
||||||
if (create === "assistant") {
|
if (create === "assistant") {
|
||||||
setSettingsView("my-ai-characters");
|
setSettingsView("my-roleplay-companions");
|
||||||
openPersonaModal(null, "assistant");
|
openPersonaModal(null, "assistant");
|
||||||
} else if (create === "user") {
|
} else if (create === "user") {
|
||||||
setSettingsView("my-personas");
|
setSettingsView("my-roleplay-companions");
|
||||||
openPersonaModal(null, "user");
|
openPersonaModal(null, "user");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+152
-1
@@ -631,6 +631,26 @@ select:focus {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-loading {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(24, 32, 31, 0.08);
|
||||||
|
background: rgba(24, 32, 31, 0.04);
|
||||||
|
color: var(--text-soft);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-loading-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--brand);
|
||||||
|
animation: pulse 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-item,
|
.chat-item,
|
||||||
.persona-item {
|
.persona-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -704,6 +724,24 @@ select:focus {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary-action.active {
|
||||||
|
background: rgba(164, 85, 45, 0.18);
|
||||||
|
border-color: rgba(164, 85, 45, 0.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-pair {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-pair .secondary-action,
|
||||||
|
.action-pair button {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.danger {
|
.danger {
|
||||||
background: linear-gradient(145deg, #b34b40, #8f3026);
|
background: linear-gradient(145deg, #b34b40, #8f3026);
|
||||||
}
|
}
|
||||||
@@ -717,6 +755,30 @@ select:focus {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tools-details {
|
||||||
|
border: 1px solid rgba(24, 32, 31, 0.08);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(24, 32, 31, 0.04);
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-links {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-details .session-chip,
|
||||||
|
.tools-details button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.session-chip {
|
.session-chip {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -818,6 +880,34 @@ select:focus {
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-context {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-chip-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(24, 32, 31, 0.06);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid rgba(24, 32, 31, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-chip-button span {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-chip-button strong {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-header-actions {
|
.sidebar-header-actions {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -829,6 +919,10 @@ select:focus {
|
|||||||
align-self: end;
|
align-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-actions-menu {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-header {
|
.chat-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
@@ -1066,6 +1160,37 @@ select:focus {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.messages-empty {
|
||||||
|
margin: auto 0;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: var(--surface-strong);
|
||||||
|
border: 1px dashed var(--line-strong);
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-empty-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-empty-copy {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-soft);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-empty-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-empty-actions .primary-action,
|
||||||
|
.messages-empty-actions .secondary-action {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.msg {
|
.msg {
|
||||||
max-width: min(78%, 780px);
|
max-width: min(78%, 780px);
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
@@ -1283,7 +1408,8 @@ select:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-header-actions .model-menu,
|
.chat-header-actions .model-menu,
|
||||||
.chat-header-actions .persona-menu {
|
.chat-header-actions .persona-menu,
|
||||||
|
.chat-header-actions .chat-actions-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
@@ -1391,6 +1517,17 @@ select:focus {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-card {
|
||||||
|
width: min(640px, 94vw);
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-choices {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -2395,6 +2532,16 @@ body[data-theme="beta-ai"] .persona-form .field {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.messages-empty-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-empty-actions .primary-action,
|
||||||
|
.messages-empty-actions .secondary-action {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
@@ -2433,6 +2580,10 @@ body[data-theme="beta-ai"] .persona-form .field {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-pair {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.model-popover {
|
.model-popover {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: auto;
|
right: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user