moved js files in public into js and css into css folder
This commit is contained in:
+81
-62
@@ -1,69 +1,88 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sign in · Elsewhere</title>
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sign in · Elsewhere</title>
|
||||
<link rel="icon" href="img/favicon.svg">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="site-page account-page">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="account-layout page-content">
|
||||
<section class="account-intro">
|
||||
<p class="eyebrow">Welcome to elsewhere</p>
|
||||
<h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1>
|
||||
<p>A space for your ideas, your characters, and the conversations worth coming back to.</p>
|
||||
<a href="/">← Back to Elsewhere</a>
|
||||
</section>
|
||||
<section class="account-box">
|
||||
<h2 id="accountTitle">Sign in</h2>
|
||||
<p id="accountDescription" class="muted">Welcome back. Your conversations are waiting.</p>
|
||||
<form id="accountForm">
|
||||
<label class="field">
|
||||
<span>Username</span>
|
||||
<input name="username" autocomplete="username" required minlength="3">
|
||||
</label>
|
||||
<div class="field">
|
||||
<label for="accountPassword">Password</label>
|
||||
<div class="password-input">
|
||||
<input id="accountPassword" name="password" type="password" autocomplete="current-password" required minlength="6">
|
||||
<button type="button" id="showPassword" class="icon-button password-toggle" aria-label="Show password" title="Show password" aria-controls="accountPassword" aria-pressed="false">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/><path class="eye-slash" d="m3 3 18 18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label id="inviteField" class="field hidden">
|
||||
<span>Invite code</span>
|
||||
<input name="inviteCode" autocomplete="off">
|
||||
</label>
|
||||
<label id="consentField" class="consent-container hidden">
|
||||
<input name="consent" type="checkbox">
|
||||
<span>I agree to the <a href="/tos.html" target="_blank" rel="noopener">Terms</a> and <a href="/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.</span>
|
||||
</label>
|
||||
<div id="accountStatus" class="account-status hidden" role="status" aria-live="polite" aria-atomic="true" tabindex="-1">
|
||||
<svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v6m0 3v1"/></svg>
|
||||
<span id="accountStatusText"></span>
|
||||
</div>
|
||||
<button id="accountSubmit" class="primary-action" type="submit">Sign in</button>
|
||||
</form>
|
||||
<p id="accountSwitch">
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<a class="site-brand" href="/">elsewhere</a>
|
||||
<span>A space for every conversation.</span>
|
||||
<a href="/privacy.html">Privacy</a>
|
||||
<a href="/tos.html">Terms</a>
|
||||
</footer>
|
||||
<script type="module" src="/pages.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
<!-- site-header -->
|
||||
<main id="main" class="account-layout page-content">
|
||||
<section class="account-intro">
|
||||
<p class="eyebrow">Welcome to elsewhere</p>
|
||||
<h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1>
|
||||
<p>A space for your ideas, your characters, and the conversations worth coming back to.</p>
|
||||
<a href="/">← Back to Elsewhere</a>
|
||||
</section>
|
||||
<section class="account-box">
|
||||
<h2 id="accountTitle">Sign in</h2>
|
||||
<p id="accountDescription" class="muted">Welcome back. Your conversations are waiting.</p>
|
||||
<form id="accountForm">
|
||||
<label class="field">
|
||||
<span>Username</span>
|
||||
<input name="username" autocomplete="username" required minlength="3">
|
||||
</label>
|
||||
<div class="field">
|
||||
<label for="accountPassword">Password</label>
|
||||
<div class="password-input">
|
||||
<input id="accountPassword" name="password" type="password" autocomplete="current-password"
|
||||
required minlength="6">
|
||||
<button type="button" id="showPassword" class="icon-button password-toggle"
|
||||
aria-label="Show password" title="Show password" aria-controls="accountPassword"
|
||||
aria-pressed="false">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
|
||||
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path class="eye-slash" d="m3 3 18 18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label id="inviteField" class="field hidden">
|
||||
<span>Invite code</span>
|
||||
<input name="inviteCode" autocomplete="off">
|
||||
</label>
|
||||
<label id="consentField" class="consent-container hidden">
|
||||
<input name="consent" type="checkbox">
|
||||
<span>I agree to the <a href="/tos.html" target="_blank" rel="noopener">Terms</a> and <a
|
||||
href="/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.</span>
|
||||
</label>
|
||||
<div id="accountStatus" class="account-status hidden" role="status" aria-live="polite"
|
||||
aria-atomic="true" tabindex="-1">
|
||||
<svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none"
|
||||
stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 7v6m0 3v1" />
|
||||
</svg>
|
||||
<span id="accountStatusText"></span>
|
||||
</div>
|
||||
<button id="accountSubmit" class="primary-action" type="submit">Sign in</button>
|
||||
</form>
|
||||
<p id="accountSwitch">
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<a class="site-brand" href="/">elsewhere</a>
|
||||
<span>A space for every conversation.</span>
|
||||
<a href="/privacy.html">Privacy</a>
|
||||
<a href="/tos.html">Terms</a>
|
||||
</footer>
|
||||
<script type="module" src="js/pages.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+418
-320
@@ -1,369 +1,467 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="img/favicon.svg">
|
||||
<title>Elsewhere</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260920-web-search-composer"/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260920-web-search-composer" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls"><link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
</head>
|
||||
<body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light">
|
||||
<!-- site-header -->
|
||||
<div class="app-shell">
|
||||
<section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace">
|
||||
<div class="chat-layout chat-layout-focus">
|
||||
<aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history">
|
||||
<div class="sidebar-top">
|
||||
<div class="sidebar-workspace-card"><h2>Conversations</h2><button id="newChat" class="primary-action" type="button">New chat</button></div>
|
||||
<label class="field compact-field">
|
||||
<span>Search</span>
|
||||
<input id="chatSearch" type="search" placeholder="Search chats"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="sidebar-middle">
|
||||
<div id="chatList" class="chat-list chat-list-grouped"></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>
|
||||
</aside>
|
||||
|
||||
<section id="main" class="chat-main chat-main-focus" aria-label="Active conversation">
|
||||
<div class="chat-heading"><p id="workspaceNotice" class="status error hidden" role="status" aria-live="polite"></p><header class="chat-header chat-header-focus">
|
||||
<button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button" aria-controls="chatSidebar" aria-expanded="false" aria-label="Open conversation history">History</button>
|
||||
<div class="chat-title-block">
|
||||
<div class="chat-title">
|
||||
<h2 id="activeChatTitle">New chat</h2>
|
||||
<body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light">
|
||||
<!-- site-header -->
|
||||
<div class="app-shell">
|
||||
<section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace">
|
||||
<div class="chat-layout chat-layout-focus">
|
||||
<aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history">
|
||||
<div class="sidebar-top">
|
||||
<div class="sidebar-workspace-card">
|
||||
<h2>Conversations</h2><button id="newChat" class="primary-action" type="button">New
|
||||
chat</button>
|
||||
</div>
|
||||
<div class="chat-header-context" aria-label="Current chat setup">
|
||||
<button id="headerModelButton" class="chat-context-item chat-context-button secondary-action" type="button" aria-controls="chatDrawer" aria-label="Change AI model"><span>Model</span><strong id="headerModelName">Loading...</strong></button>
|
||||
<span class="chat-context-item"><span>Persona</span><strong id="headerPersonaName">None</strong></span>
|
||||
<label class="field compact-field">
|
||||
<span>Search</span>
|
||||
<input id="chatSearch" type="search" placeholder="Search chats" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="sidebar-middle">
|
||||
<div id="chatList" class="chat-list chat-list-grouped"></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>
|
||||
<div class="chat-header-actions">
|
||||
<div class="chat-actions-menu">
|
||||
<button id="chatDrawerButton" class="secondary-action overflow-trigger" type="button" aria-expanded="false" aria-controls="chatDrawer">
|
||||
<span class="overflow-trigger-label">Chat settings</span>
|
||||
</button>
|
||||
<div id="chatDrawer" class="chat-drawer hidden" role="dialog" aria-modal="true" aria-labelledby="chatDrawerTitle">
|
||||
<div class="chat-drawer-panel">
|
||||
<div class="chat-drawer-header">
|
||||
<div>
|
||||
<p class="eyebrow">Conversation</p>
|
||||
<h2 id="chatDrawerTitle">Settings</h2>
|
||||
</div>
|
||||
<button id="chatDrawerClose" class="icon-button" type="button" aria-label="Close chat settings">×</button>
|
||||
</div>
|
||||
<div class="chat-drawer-body">
|
||||
<div class="chat-drawer-summary">
|
||||
<div class="chat-drawer-pills">
|
||||
<span id="chatCharacterPill" class="context-pill hidden">Character: none</span>
|
||||
<span id="chatUserPersonaPill" class="context-pill hidden">User persona: none</span>
|
||||
</aside>
|
||||
|
||||
<section id="main" class="chat-main chat-main-focus" aria-label="Active conversation">
|
||||
<div class="chat-heading">
|
||||
<p id="workspaceNotice" class="status error hidden" role="status" aria-live="polite"></p>
|
||||
<header class="chat-header chat-header-focus">
|
||||
<button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button"
|
||||
aria-controls="chatSidebar" aria-expanded="false"
|
||||
aria-label="Open conversation history">History</button>
|
||||
<div class="chat-title-block">
|
||||
<div class="chat-title">
|
||||
<h2 id="activeChatTitle">New chat</h2>
|
||||
</div>
|
||||
<div class="chat-header-context" aria-label="Current chat setup">
|
||||
<button id="headerModelButton"
|
||||
class="chat-context-item chat-context-button secondary-action" type="button"
|
||||
aria-controls="chatDrawer"
|
||||
aria-label="Change AI model"><span>Model</span><strong
|
||||
id="headerModelName">Loading...</strong></button>
|
||||
<span class="chat-context-item"><span>Persona</span><strong
|
||||
id="headerPersonaName">None</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-header-actions">
|
||||
<div class="chat-actions-menu">
|
||||
<button id="chatDrawerButton" class="secondary-action overflow-trigger"
|
||||
type="button" aria-expanded="false" aria-controls="chatDrawer">
|
||||
<span class="overflow-trigger-label">Chat settings</span>
|
||||
</button>
|
||||
<div id="chatDrawer" class="chat-drawer hidden" role="dialog" aria-modal="true"
|
||||
aria-labelledby="chatDrawerTitle">
|
||||
<div class="chat-drawer-panel">
|
||||
<div class="chat-drawer-header">
|
||||
<div>
|
||||
<p class="eyebrow">Conversation</p>
|
||||
<h2 id="chatDrawerTitle">Settings</h2>
|
||||
</div>
|
||||
<button id="chatDrawerClose" class="icon-button" type="button"
|
||||
aria-label="Close chat settings">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<details class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--model" open>
|
||||
<summary class="chat-drawer-row">
|
||||
<span class="chat-drawer-row-label">
|
||||
<span class="eyebrow">AI model</span>
|
||||
<strong>Model</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-meta">
|
||||
<strong id="modelBadgeName">Loading...</strong>
|
||||
<span class="chat-drawer-row-hint">Change</span>
|
||||
</span>
|
||||
</summary>
|
||||
<div id="modelPanel" class="model-panel chat-drawer-disclosure-panel" data-static-panel="true">
|
||||
<div class="model-select">
|
||||
<div class="model-select-header">
|
||||
<label for="model">Model</label>
|
||||
<span id="modelCount" class="model-count">Loading</span>
|
||||
</div>
|
||||
<div class="model-select-control">
|
||||
<select id="model">
|
||||
<option disabled selected>Loading models...</option>
|
||||
</select>
|
||||
<span class="model-select-chevron" aria-hidden="true"></span>
|
||||
<div class="chat-drawer-body">
|
||||
<div class="chat-drawer-summary">
|
||||
<div class="chat-drawer-pills">
|
||||
<span id="chatCharacterPill"
|
||||
class="context-pill hidden">Character: none</span>
|
||||
<span id="chatUserPersonaPill" class="context-pill hidden">User
|
||||
persona: none</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modelHelp" class="model-help" aria-live="polite">
|
||||
<div class="model-help-header">
|
||||
<strong id="modelHelpTitle">Choose a model</strong>
|
||||
<span id="modelHelpBadge" class="model-help-badge">Waiting</span>
|
||||
</div>
|
||||
<p id="modelHelpSummary" class="model-help-summary">Pick a model to see what it is best suited for.</p>
|
||||
</div>
|
||||
<div class="generation-settings-grid">
|
||||
<label class="field"><span>Temperature</span><input id="chatTemperature" type="number" min="0" max="2" step="0.1" placeholder="Default"/></label>
|
||||
<label class="field"><span>Context length</span><input id="chatContextLength" type="number" min="256" max="131072" placeholder="Default"/></label>
|
||||
<label class="field"><span>Response length</span><input id="chatResponseLength" type="number" min="1" max="32768" placeholder="Default"/></label>
|
||||
<label class="field field-wide"><span>System instruction</span><textarea id="chatSystemInstruction" rows="4" maxlength="8000" placeholder="Optional instructions for this chat"></textarea></label>
|
||||
<button id="saveGenerationSettings" type="button">Save generation settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--persona">
|
||||
<summary class="chat-drawer-row">
|
||||
<span class="chat-drawer-row-label">
|
||||
<span class="eyebrow">Your identity</span>
|
||||
<strong>Persona</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-meta">
|
||||
<strong id="chatDrawerPersonaValue">None</strong>
|
||||
<span class="chat-drawer-row-hint">Change</span>
|
||||
</span>
|
||||
</summary>
|
||||
<div id="personaPanel" class="persona-panel chat-drawer-disclosure-panel" data-static-panel="true">
|
||||
<div class="persona-section">
|
||||
<div class="persona-section-header">
|
||||
<div>
|
||||
<h2>Your persona</h2>
|
||||
<details
|
||||
class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--model"
|
||||
open>
|
||||
<summary class="chat-drawer-row">
|
||||
<span class="chat-drawer-row-label">
|
||||
<span class="eyebrow">AI model</span>
|
||||
<strong>Model</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-meta">
|
||||
<strong id="modelBadgeName">Loading...</strong>
|
||||
<span class="chat-drawer-row-hint">Change</span>
|
||||
</span>
|
||||
</summary>
|
||||
<div id="modelPanel"
|
||||
class="model-panel chat-drawer-disclosure-panel"
|
||||
data-static-panel="true">
|
||||
<div class="model-select">
|
||||
<div class="model-select-header">
|
||||
<label for="model">Model</label>
|
||||
<span id="modelCount" class="model-count">Loading</span>
|
||||
</div>
|
||||
<div class="model-select-control">
|
||||
<select id="model">
|
||||
<option disabled selected>Loading models...</option>
|
||||
</select>
|
||||
<span class="model-select-chevron"
|
||||
aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="persona-section-actions">
|
||||
<button id="clearUserPersona" class="secondary-action" type="button">Clear</button>
|
||||
<a href="/app/personas" class="secondary-action">Manage</a>
|
||||
<div id="modelHelp" class="model-help" aria-live="polite">
|
||||
<div class="model-help-header">
|
||||
<strong id="modelHelpTitle">Choose a model</strong>
|
||||
<span id="modelHelpBadge"
|
||||
class="model-help-badge">Waiting</span>
|
||||
</div>
|
||||
<p id="modelHelpSummary" class="model-help-summary">Pick a
|
||||
model to see what it is best suited for.</p>
|
||||
</div>
|
||||
<div class="generation-settings-grid">
|
||||
<label class="field"><span>Temperature</span><input
|
||||
id="chatTemperature" type="number" min="0" max="2"
|
||||
step="0.1" placeholder="Default" /></label>
|
||||
<label class="field"><span>Context length</span><input
|
||||
id="chatContextLength" type="number" min="256"
|
||||
max="131072" placeholder="Default" /></label>
|
||||
<label class="field"><span>Response length</span><input
|
||||
id="chatResponseLength" type="number" min="1"
|
||||
max="32768" placeholder="Default" /></label>
|
||||
<label class="field field-wide"><span>System
|
||||
instruction</span><textarea
|
||||
id="chatSystemInstruction" rows="4" maxlength="8000"
|
||||
placeholder="Optional instructions for this chat"></textarea></label>
|
||||
<button id="saveGenerationSettings" type="button">Save
|
||||
generation settings</button>
|
||||
</div>
|
||||
</div>
|
||||
<p id="activeUserPersonaStatus" class="status persona-status">No default user persona set.</p>
|
||||
<div id="userPersonaList" class="persona-list"></div>
|
||||
</details>
|
||||
<details
|
||||
class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--persona">
|
||||
<summary class="chat-drawer-row">
|
||||
<span class="chat-drawer-row-label">
|
||||
<span class="eyebrow">Your identity</span>
|
||||
<strong>Persona</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-meta">
|
||||
<strong id="chatDrawerPersonaValue">None</strong>
|
||||
<span class="chat-drawer-row-hint">Change</span>
|
||||
</span>
|
||||
</summary>
|
||||
<div id="personaPanel"
|
||||
class="persona-panel chat-drawer-disclosure-panel"
|
||||
data-static-panel="true">
|
||||
<div class="persona-section">
|
||||
<div class="persona-section-header">
|
||||
<div>
|
||||
<h2>Your persona</h2>
|
||||
</div>
|
||||
<div class="persona-section-actions">
|
||||
<button id="clearUserPersona"
|
||||
class="secondary-action"
|
||||
type="button">Clear</button>
|
||||
<a href="/app/personas"
|
||||
class="secondary-action">Manage</a>
|
||||
</div>
|
||||
</div>
|
||||
<p id="activeUserPersonaStatus"
|
||||
class="status persona-status">No default user persona
|
||||
set.</p>
|
||||
<div id="userPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="chat-drawer-section">
|
||||
<p class="dropdown-section-label">Chat</p>
|
||||
<div class="chat-drawer-action-list">
|
||||
<button id="renameChat"
|
||||
class="secondary-action chat-drawer-action" type="button"
|
||||
data-chat-action="rename">Rename chat</button>
|
||||
<button id="pinChat" class="secondary-action chat-drawer-action"
|
||||
type="button" data-chat-action="pin">Pin</button>
|
||||
<button id="archiveChat"
|
||||
class="secondary-action chat-drawer-action" type="button"
|
||||
data-chat-action="archive">Archive</button>
|
||||
<button id="exportChat"
|
||||
class="secondary-action chat-drawer-action" type="button"
|
||||
data-chat-action="export">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="chat-drawer-section">
|
||||
<p class="dropdown-section-label">Chat</p>
|
||||
<div class="chat-drawer-action-list">
|
||||
<button id="renameChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="rename">Rename chat</button>
|
||||
<button id="pinChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="pin">Pin</button>
|
||||
<button id="archiveChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="archive">Archive</button>
|
||||
<button id="exportChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="export">Export</button>
|
||||
<details class="chat-drawer-section chat-drawer-more">
|
||||
<summary class="chat-drawer-row chat-drawer-row--compact">
|
||||
<span class="chat-drawer-row-label">
|
||||
<strong>Tools & workspace</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-hint">Show</span>
|
||||
</summary>
|
||||
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
|
||||
<button id="moveChatFolder"
|
||||
class="secondary-action chat-drawer-action" type="button"
|
||||
data-chat-action="folder">Move to folder</button>
|
||||
<button id="manageChatMemory"
|
||||
class="secondary-action chat-drawer-action" type="button"
|
||||
data-chat-action="memory">Memory facts</button>
|
||||
<button id="managePersonaState"
|
||||
class="secondary-action chat-drawer-action" type="button"
|
||||
data-chat-action="personaState">Roleplay state</button>
|
||||
<button id="backupWorkspace"
|
||||
class="secondary-action chat-drawer-action"
|
||||
type="button">Backup workspace</button>
|
||||
<button id="importWorkspace"
|
||||
class="secondary-action chat-drawer-action"
|
||||
type="button">Import workspace</button>
|
||||
<input id="importWorkspaceFile" class="hidden" type="file"
|
||||
accept="application/json,.json" />
|
||||
</div>
|
||||
</details>
|
||||
<details class="chat-drawer-section chat-drawer-danger">
|
||||
<summary class="chat-drawer-row chat-drawer-row--compact">
|
||||
<span class="chat-drawer-row-label">
|
||||
<strong>Clear conversation</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-hint">Show</span>
|
||||
</summary>
|
||||
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
|
||||
<button id="clearChat"
|
||||
class="secondary-action chat-drawer-action chat-drawer-action-danger"
|
||||
type="button" data-chat-action="clear">Clear chat</button>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
<details class="chat-drawer-section chat-drawer-more">
|
||||
<summary class="chat-drawer-row chat-drawer-row--compact">
|
||||
<span class="chat-drawer-row-label">
|
||||
<strong>Tools & workspace</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-hint">Show</span>
|
||||
</summary>
|
||||
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
|
||||
<button id="moveChatFolder" class="secondary-action chat-drawer-action" type="button" data-chat-action="folder">Move to folder</button>
|
||||
<button id="manageChatMemory" class="secondary-action chat-drawer-action" type="button" data-chat-action="memory">Memory facts</button>
|
||||
<button id="managePersonaState" class="secondary-action chat-drawer-action" type="button" data-chat-action="personaState">Roleplay state</button>
|
||||
<button id="backupWorkspace" class="secondary-action chat-drawer-action" type="button">Backup workspace</button>
|
||||
<button id="importWorkspace" class="secondary-action chat-drawer-action" type="button">Import workspace</button>
|
||||
<input id="importWorkspaceFile" class="hidden" type="file" accept="application/json,.json"/>
|
||||
</div>
|
||||
</details>
|
||||
<details class="chat-drawer-section chat-drawer-danger">
|
||||
<summary class="chat-drawer-row chat-drawer-row--compact">
|
||||
<span class="chat-drawer-row-label">
|
||||
<strong>Clear conversation</strong>
|
||||
</span>
|
||||
<span class="chat-drawer-row-hint">Show</span>
|
||||
</summary>
|
||||
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
|
||||
<button id="clearChat" class="secondary-action chat-drawer-action chat-drawer-action-danger" type="button" data-chat-action="clear">Clear chat</button>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div id="chatActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite"
|
||||
aria-atomic="true">
|
||||
<div class="chat-activity-card">
|
||||
<div class="chat-activity-mark" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<p id="chatActivityEyebrow" class="eyebrow">Please wait</p>
|
||||
<h3 id="chatActivityTitle">Preparing chat</h3>
|
||||
<p id="chatActivityDetail" class="subtitle">The assistant is still working.</p>
|
||||
</div>
|
||||
</div>
|
||||
</header></div>
|
||||
|
||||
<div id="chatActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite" aria-atomic="true">
|
||||
<div class="chat-activity-card">
|
||||
<div class="chat-activity-mark" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<div id="messages" class="messages-panel messages-panel-focus" role="log" aria-live="polite"
|
||||
aria-relevant="additions text"></div>
|
||||
|
||||
<div class="input-shell input-shell-focus">
|
||||
<div class="input-meta">
|
||||
<p class="composer-hint">Enter to send · Shift+Enter for new line</p>
|
||||
</div>
|
||||
<div id="uploadPreviews" class="upload-previews" aria-live="polite" hidden></div>
|
||||
<div class="input-container">
|
||||
<input id="imagePicker" type="file" accept="image/png,image/jpeg,image/webp" multiple
|
||||
hidden>
|
||||
<button id="attachImage" class="icon-button" type="button" aria-label="Attach image"
|
||||
title="Attach image">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.6" aria-hidden="true">
|
||||
<rect x="3" y="3" width="18" height="18" rx="3" />
|
||||
<circle cx="8" cy="8" r="1.5" />
|
||||
<path d="m3 17 5-5 4 4 4-6 5 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="webSearch" class="icon-button" type="button" aria-label="Search the web"
|
||||
title="Search the web" aria-pressed="false">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.6" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<ellipse cx="12" cy="12" rx="4" ry="9" />
|
||||
<path d="M3 12h18" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning"
|
||||
title="Enable reasoning" aria-pressed="false" disabled>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M12 18V5a3 3 0 0 0-5.8-1A4 4 0 0 0 3 10a4 4 0 0 0 1 7.8A4 4 0 0 0 12 18ZM12 5a3 3 0 0 1 5.8-1A4 4 0 0 1 21 10a4 4 0 0 1-1 7.8A4 4 0 0 1 12 18M8 8a4 4 0 0 1-5 2m13-2a4 4 0 0 0 5 2M8 14a4 4 0 0 0-4 4m12-4a4 4 0 0 1 4 4" />
|
||||
</svg>
|
||||
</button>
|
||||
<label class="visually-hidden" for="msgInput">Message</label>
|
||||
<textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea>
|
||||
<button id="send" type="button" aria-label="Send message"></button>
|
||||
</div>
|
||||
<p id="chatActivityEyebrow" class="eyebrow">Please wait</p>
|
||||
<h3 id="chatActivityTitle">Preparing chat</h3>
|
||||
<p id="chatActivityDetail" class="subtitle">The assistant is still working.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="messages" class="messages-panel messages-panel-focus" role="log" aria-live="polite" aria-relevant="additions text"></div>
|
||||
|
||||
<div class="input-shell input-shell-focus">
|
||||
<div class="input-meta">
|
||||
<p class="composer-hint">Enter to send · Shift+Enter for new line</p>
|
||||
</div>
|
||||
<div id="uploadPreviews" class="upload-previews" aria-live="polite" hidden></div>
|
||||
<div class="input-container">
|
||||
<input id="imagePicker" type="file" accept="image/png,image/jpeg,image/webp" multiple hidden>
|
||||
<button id="attachImage" class="icon-button" type="button" aria-label="Attach image" title="Attach image">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8" cy="8" r="1.5"/><path d="m3 17 5-5 4 4 4-6 5 7"/></svg>
|
||||
</button>
|
||||
<button id="webSearch" class="icon-button" type="button" aria-label="Search the web" title="Search the web" aria-pressed="false">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/></svg>
|
||||
</button>
|
||||
<button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning" title="Enable reasoning" aria-pressed="false" disabled>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 18V5a3 3 0 0 0-5.8-1A4 4 0 0 0 3 10a4 4 0 0 0 1 7.8A4 4 0 0 0 12 18ZM12 5a3 3 0 0 1 5.8-1A4 4 0 0 1 21 10a4 4 0 0 1-1 7.8A4 4 0 0 1 12 18M8 8a4 4 0 0 1-5 2m13-2a4 4 0 0 0 5 2M8 14a4 4 0 0 0-4 4m12-4a4 4 0 0 1 4 4"/></svg>
|
||||
</button>
|
||||
<label class="visually-hidden" for="msgInput">Message</label>
|
||||
<textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea>
|
||||
<button id="send" type="button" aria-label="Send message"></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="personaModal" class="modal-overlay side-panel-overlay hidden">
|
||||
<div class="modal-card persona-modal-card side-panel-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Persona editor</p>
|
||||
<h3 id="personaFormTitle">Create persona</h3>
|
||||
</section>
|
||||
</div>
|
||||
<button id="personaClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<form id="personaForm" class="persona-form">
|
||||
<div class="persona-form-intro">
|
||||
<p class="persona-helper">AI Characters shape who you talk to. User personas add context for who you are in the scene.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="personaModal" class="modal-overlay side-panel-overlay hidden">
|
||||
<div class="modal-card persona-modal-card side-panel-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Persona editor</p>
|
||||
<h3 id="personaFormTitle">Create persona</h3>
|
||||
</div>
|
||||
<button id="personaClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<p id="personaFormNotice" class="status hidden"></p>
|
||||
<div class="persona-form-grid">
|
||||
<label class="field field-wide">
|
||||
<span>Persona for</span>
|
||||
<select id="personaType">
|
||||
<option value="assistant">AI Character</option>
|
||||
<option value="user">Your persona</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Persona name</span>
|
||||
<input id="personaName" placeholder="Persona name" required/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Persona pronouns</span>
|
||||
<input id="personaPronouns" placeholder="e.g. she/her, they/them"/>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Appearance</span>
|
||||
<textarea id="personaAppearance" rows="3" placeholder="Describe their look"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Background</span>
|
||||
<textarea id="personaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Details</span>
|
||||
<textarea id="personaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
|
||||
</label>
|
||||
<label id="personaExamplesField" class="field field-wide hidden">
|
||||
<span>Example dialogues</span>
|
||||
<div class="dialogue-format-card" aria-hidden="true">
|
||||
<p class="dialogue-format-label">Format</p>
|
||||
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
|
||||
<form id="personaForm" class="persona-form">
|
||||
<div class="persona-form-intro">
|
||||
<p class="persona-helper">AI Characters shape who you talk to. User personas add context for who you
|
||||
are in the scene.</p>
|
||||
</div>
|
||||
<p id="personaFormNotice" class="status hidden"></p>
|
||||
<div class="persona-form-grid">
|
||||
<label class="field field-wide">
|
||||
<span>Persona for</span>
|
||||
<select id="personaType">
|
||||
<option value="assistant">AI Character</option>
|
||||
<option value="user">Your persona</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Persona name</span>
|
||||
<input id="personaName" placeholder="Persona name" required />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Persona pronouns</span>
|
||||
<input id="personaPronouns" placeholder="e.g. she/her, they/them" />
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Appearance</span>
|
||||
<textarea id="personaAppearance" rows="3" placeholder="Describe their look"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Background</span>
|
||||
<textarea id="personaBackground" rows="3"
|
||||
placeholder="Backstory, history, role, or origins"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Details</span>
|
||||
<textarea id="personaDetails" rows="4"
|
||||
placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
|
||||
</label>
|
||||
<label id="personaExamplesField" class="field field-wide hidden">
|
||||
<span>Example dialogues</span>
|
||||
<div class="dialogue-format-card" aria-hidden="true">
|
||||
<p class="dialogue-format-label">Format</p>
|
||||
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
|
||||
{{user}}: Hi, Marc!
|
||||
{{character}}: Nice to meet you</pre>
|
||||
</div>
|
||||
<textarea id="personaExampleDialogues" rows="5" placeholder="Write example exchanges that define the AI's tone, phrasing, pacing, and personality."></textarea>
|
||||
</div>
|
||||
<textarea id="personaExampleDialogues" rows="5"
|
||||
placeholder="Write example exchanges that define the AI's tone, phrasing, pacing, and personality."></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="personaSave" type="submit" class="primary-action">Save persona</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden">
|
||||
<div class="modal-card roleplay-starter-card side-panel-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Start roleplay</p>
|
||||
<h3 id="roleplayStarterTitle">Choose your roleplay</h3>
|
||||
</div>
|
||||
<button id="roleplayStarterClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="roleplay-starter-intro">
|
||||
<p id="roleplayStarterIntroCopy" class="persona-helper">Pick a character and choose who you are before
|
||||
moving on to the opening scene.</p>
|
||||
</div>
|
||||
<p id="roleplayStarterNotice" class="status hidden"></p>
|
||||
<div class="roleplay-starter-actions">
|
||||
<button id="roleplayNewPersona" class="secondary-action" type="button">Create AI Character</button>
|
||||
</div>
|
||||
<div id="roleplayStarterStepOne" class="persona-form-grid">
|
||||
<label class="field field-wide">
|
||||
<span>1. Character</span>
|
||||
<select id="roleplayCharacterSelect"></select>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>2. You in the scene</span>
|
||||
<select id="roleplayUserPersonaSelect"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="personaSave" type="submit" class="primary-action">Save persona</button>
|
||||
<div id="roleplayStarterStepTwo" class="roleplay-starter-page hidden">
|
||||
<label class="field field-wide">
|
||||
<span>Optional opening direction</span>
|
||||
<textarea id="roleplayScenarioInput" rows="4"
|
||||
placeholder="Describe the kind of opening scene you want, such as a setting, conflict, relationship dynamic, or mood. Leave it empty and the AI will invent its own scenario."></textarea>
|
||||
</label>
|
||||
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own. No
|
||||
character limit.</p>
|
||||
<div id="roleplayStarterSuggestions" class="quick-prompts" aria-label="Scenario suggestions"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden">
|
||||
<div class="modal-card roleplay-starter-card side-panel-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Start roleplay</p>
|
||||
<h3 id="roleplayStarterTitle">Choose your roleplay</h3>
|
||||
<div class="popup-actions">
|
||||
<button id="roleplayStarterCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="roleplayStarterBack" type="button" class="secondary-action hidden">Back</button>
|
||||
<button id="roleplayStarterConfirm" type="button">Continue</button>
|
||||
</div>
|
||||
<button id="roleplayStarterClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="roleplay-starter-intro">
|
||||
<p id="roleplayStarterIntroCopy" class="persona-helper">Pick a character and choose who you are before moving on to the opening scene.</p>
|
||||
</div>
|
||||
<p id="roleplayStarterNotice" class="status hidden"></p>
|
||||
<div class="roleplay-starter-actions">
|
||||
<button id="roleplayNewPersona" class="secondary-action" type="button">Create AI Character</button>
|
||||
</div>
|
||||
<div id="roleplayStarterStepOne" class="persona-form-grid">
|
||||
<label class="field field-wide">
|
||||
<span>1. Character</span>
|
||||
<select id="roleplayCharacterSelect"></select>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>2. You in the scene</span>
|
||||
<select id="roleplayUserPersonaSelect"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="roleplayStarterStepTwo" class="roleplay-starter-page hidden">
|
||||
<label class="field field-wide">
|
||||
<span>Optional opening direction</span>
|
||||
<textarea id="roleplayScenarioInput" rows="4" placeholder="Describe the kind of opening scene you want, such as a setting, conflict, relationship dynamic, or mood. Leave it empty and the AI will invent its own scenario."></textarea>
|
||||
</label>
|
||||
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own. No character limit.</p>
|
||||
<div id="roleplayStarterSuggestions" class="quick-prompts" aria-label="Scenario suggestions"></div>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button id="roleplayStarterCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="roleplayStarterBack" type="button" class="secondary-action hidden">Back</button>
|
||||
<button id="roleplayStarterConfirm" type="button">Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="popupModal" class="modal-overlay hidden">
|
||||
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="popupTitle" aria-describedby="popupDescription">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p id="popupEyebrow" class="eyebrow">Action</p>
|
||||
<h3 id="popupTitle">Confirm action</h3>
|
||||
<div id="popupModal" class="modal-overlay hidden">
|
||||
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="popupTitle"
|
||||
aria-describedby="popupDescription">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p id="popupEyebrow" class="eyebrow">Action</p>
|
||||
<h3 id="popupTitle">Confirm action</h3>
|
||||
</div>
|
||||
<button id="popupClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<p id="popupDescription" class="subtitle">Please confirm this action.</p>
|
||||
<label id="popupField" class="field hidden">
|
||||
<span id="popupInputLabel">Value</span>
|
||||
<input id="popupInput" type="text" autocomplete="off" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button id="popupCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="popupConfirm" type="button">Confirm</button>
|
||||
</div>
|
||||
<button id="popupClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<p id="popupDescription" class="subtitle">Please confirm this action.</p>
|
||||
<label id="popupField" class="field hidden">
|
||||
<span id="popupInputLabel">Value</span>
|
||||
<input id="popupInput" type="text" autocomplete="off"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button id="popupCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="popupConfirm" type="button">Confirm</button>
|
||||
</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">Where shall we start?</h3>
|
||||
<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">Where shall we start?</h3>
|
||||
</div>
|
||||
<button id="onboardingSkip" type="button" class="icon-button">Skip</button>
|
||||
</div>
|
||||
<p id="onboardingSubtitle" class="subtitle">Ask a question, explore an idea, or step into a story.</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>
|
||||
<button id="onboardingSkip" type="button" class="icon-button">Skip</button>
|
||||
</div>
|
||||
<p id="onboardingSubtitle" class="subtitle">Ask a question, explore an idea, or step into a story.</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/dompurify@3.0.6/dist/purify.min.js" defer></script>
|
||||
<script type="module" src="/script.js?v=20260921-reasoning-capability"></script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></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 type="module" src="/js/script.js?v=20260921-reasoning-capability"></script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
+36
-30
@@ -1,37 +1,43 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Discover personas · Elsewhere</title>
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Discover personas · Elsewhere</title>
|
||||
<link rel="icon" href="/img/favicon.svg">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="site-page discover-page">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="site-section page-content">
|
||||
<p class="eyebrow">From the community</p>
|
||||
<h1>Find your next conversation.</h1>
|
||||
<p class="hero-copy">Discover characters and personas made by the community. Sign in when you're ready to bring one into your workspace.</p>
|
||||
<label class="field">
|
||||
<span>Search personas</span>
|
||||
<input id="publicSearch" type="search" placeholder="Search by name or description">
|
||||
</label>
|
||||
<div id="featuredPersonas" class="feature-grid" aria-live="polite">Loading personas…</div>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<a class="site-brand" href="/">elsewhere</a>
|
||||
<span>A space for every conversation.</span>
|
||||
<a href="/privacy.html">Privacy</a>
|
||||
<a href="/tos.html">Terms</a>
|
||||
</footer>
|
||||
<script type="module" src="/pages.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
<!-- site-header -->
|
||||
<main id="main" class="site-section page-content">
|
||||
<p class="eyebrow">From the community</p>
|
||||
<h1>Find your next conversation.</h1>
|
||||
<p class="hero-copy">Discover characters and personas made by the community. Sign in when you're ready to bring
|
||||
one into your workspace.</p>
|
||||
<label class="field">
|
||||
<span>Search personas</span>
|
||||
<input id="publicSearch" type="search" placeholder="Search by name or description">
|
||||
</label>
|
||||
<div id="featuredPersonas" class="feature-grid" aria-live="polite">Loading personas…</div>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<a class="site-brand" href="/">elsewhere</a>
|
||||
<span>A space for every conversation.</span>
|
||||
<a href="/privacy.html">Privacy</a>
|
||||
<a href="/tos.html">Terms</a>
|
||||
</footer>
|
||||
<script type="module" src="/js/pages.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+53
-48
@@ -1,57 +1,62 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Home · Elsewhere</title>
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Home · Elsewhere</title>
|
||||
<link rel="icon" href="/img/favicon.svg">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="site-page home-page">
|
||||
<!-- site-header -->
|
||||
<!-- site-header -->
|
||||
|
||||
<main id="main" class="home-main page-content">
|
||||
<p id="welcomeName" class="eyebrow">Welcome home</p>
|
||||
<h1>What would you like to do?</h1>
|
||||
<p class="hero-copy">Start somewhere new, or pick up a conversation.</p>
|
||||
<div class="feature-grid">
|
||||
<a class="feature-card action-card" href="/app/chats/new">
|
||||
<span class="feature-number">01 / ASK</span>
|
||||
<h2>New conversation</h2>
|
||||
<p>Make space for a question or an idea.</p>
|
||||
<span>Start chatting →</span>
|
||||
</a>
|
||||
<a class="feature-card action-card" href="/market">
|
||||
<span class="feature-number">02 / EXPLORE</span>
|
||||
<h2>Chat with a character</h2>
|
||||
<p>Find someone with a different perspective.</p>
|
||||
<span>Discover personas →</span>
|
||||
</a>
|
||||
<a class="feature-card action-card" href="/app/personas?create=assistant">
|
||||
<span class="feature-number">03 / CREATE</span>
|
||||
<h2>Create a persona</h2>
|
||||
<p>Give a new character a voice of their own.</p>
|
||||
<span>Create persona →</span>
|
||||
</a>
|
||||
</div>
|
||||
<section class="site-section">
|
||||
<div class="section-heading">
|
||||
<h2>Recent conversations</h2>
|
||||
<a href="/app/chats">View all →</a>
|
||||
</div>
|
||||
<div id="recentChats" aria-live="polite">Loading conversations…</div>
|
||||
</section>
|
||||
<p id="homeStatus" role="status">
|
||||
</p>
|
||||
</main>
|
||||
<main id="main" class="home-main page-content">
|
||||
<p id="welcomeName" class="eyebrow">Welcome home</p>
|
||||
<h1>What would you like to do?</h1>
|
||||
<p class="hero-copy">Start somewhere new, or pick up a conversation.</p>
|
||||
<div class="feature-grid">
|
||||
<a class="feature-card action-card" href="/app/chats/new">
|
||||
<span class="feature-number">01 / ASK</span>
|
||||
<h2>New conversation</h2>
|
||||
<p>Make space for a question or an idea.</p>
|
||||
<span>Start chatting →</span>
|
||||
</a>
|
||||
<a class="feature-card action-card" href="/market">
|
||||
<span class="feature-number">02 / EXPLORE</span>
|
||||
<h2>Chat with a character</h2>
|
||||
<p>Find someone with a different perspective.</p>
|
||||
<span>Discover personas →</span>
|
||||
</a>
|
||||
<a class="feature-card action-card" href="/app/personas?create=assistant">
|
||||
<span class="feature-number">03 / CREATE</span>
|
||||
<h2>Create a persona</h2>
|
||||
<p>Give a new character a voice of their own.</p>
|
||||
<span>Create persona →</span>
|
||||
</a>
|
||||
</div>
|
||||
<section class="site-section">
|
||||
<div class="section-heading">
|
||||
<h2>Recent conversations</h2>
|
||||
<a href="/app/chats">View all →</a>
|
||||
</div>
|
||||
<div id="recentChats" aria-live="polite">Loading conversations…</div>
|
||||
</section>
|
||||
<p id="homeStatus" role="status">
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/pages.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/js/pages.js?v=20260909-account-controls">
|
||||
</script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 250 B |
@@ -1,7 +1,7 @@
|
||||
import {get, post, put} from "./app/api.js?v=20260909-account-controls";
|
||||
import {initializeAppearance} from "./app/themeController.js?v=20260909-account-controls";
|
||||
import {clearStatus, setStatus} from "./app/statusNotice.js?v=20260909-account-controls";
|
||||
import {populatePersonaForm, readPersonaForm} from "./app/personaForm.js?v=20260909-account-controls";
|
||||
import { get, post, put } from "./js/api.js?v=20260909-account-controls";
|
||||
import { initializeAppearance } from "./js/themeController.js?v=20260909-account-controls";
|
||||
import { clearStatus, setStatus } from "./js/statusNotice.js?v=20260909-account-controls";
|
||||
import { populatePersonaForm, readPersonaForm } from "./js/personaForm.js?v=20260909-account-controls";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const marketPersonaList = $("marketPersonaList");
|
||||
@@ -70,14 +70,14 @@ const marketPersonaFields = {
|
||||
};
|
||||
|
||||
const validMarketSorts = new Set(["best", "newest", "most_favorited", "most_popular", "top_rated", "alphabetical"]);
|
||||
let marketPersonasByType = {assistant: [], user: []};
|
||||
let marketPersonasByType = { assistant: [], user: [] };
|
||||
let assistantPersonas = [];
|
||||
let userPersonas = [];
|
||||
let pendingMarketPersona = null;
|
||||
let editingPersonaId = null;
|
||||
let activeUserPersonaId = null;
|
||||
let activeMarketView = "ai-characters";
|
||||
let marketSorts = {assistant: "best", user: "best"};
|
||||
let marketSorts = { assistant: "best", user: "best" };
|
||||
let marketActivityDepth = 0;
|
||||
let marketPreviewStep = 1;
|
||||
let marketPopupResolver = null;
|
||||
@@ -181,7 +181,7 @@ function syncMarketQuery() {
|
||||
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
||||
}
|
||||
|
||||
function setMarketSort(personaType, sort, {reload = true} = {}) {
|
||||
function setMarketSort(personaType, sort, { reload = true } = {}) {
|
||||
const normalizedType = personaType === "user" ? "user" : "assistant";
|
||||
const normalizedSort = normalizeMarketSort(sort);
|
||||
marketSorts[normalizedType] = normalizedSort;
|
||||
@@ -474,7 +474,7 @@ async function ratePersona(marketId) {
|
||||
if (raw === null) return;
|
||||
const rating = Number(raw);
|
||||
if (!rating) return;
|
||||
const res = await post(`/personas/market/${marketId}/rate`, {rating});
|
||||
const res = await post(`/personas/market/${marketId}/rate`, { rating });
|
||||
if (res.error) return setMarketStatus(res.error, "error");
|
||||
await refreshMarket();
|
||||
setMarketStatus("Rating saved.", "success");
|
||||
@@ -499,7 +499,7 @@ async function reportPersona(marketId) {
|
||||
confirmLabel: "Submit report"
|
||||
});
|
||||
if (details === null) return;
|
||||
const res = await post(`/personas/market/${marketId}/report`, {reason, details});
|
||||
const res = await post(`/personas/market/${marketId}/report`, { reason, details });
|
||||
if (res.error) return setMarketStatus(res.error, "error");
|
||||
setMarketStatus("Report submitted.", "success");
|
||||
}
|
||||
@@ -525,7 +525,7 @@ async function loadMarketPersonas() {
|
||||
|
||||
async function collectMarketPersona(marketId) {
|
||||
setMarketStatus("Collecting User Persona...");
|
||||
const res = await post(`/personas/market/${marketId}/collect`, {equip: true});
|
||||
const res = await post(`/personas/market/${marketId}/collect`, { equip: true });
|
||||
if (res.error) {
|
||||
setMarketStatus(res.error, "error");
|
||||
return;
|
||||
@@ -540,7 +540,7 @@ async function startMarketPersonaChat(marketId, userPersonaSelection, scenarioPr
|
||||
return;
|
||||
}
|
||||
if (userPersonaSelection === "create_new") {
|
||||
window.location.href = "/app/personas?view=my-roleplay-companions&create=user";
|
||||
window.location.href = "/js/personas?view=my-roleplay-companions&create=user";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -623,7 +623,7 @@ if (marketCreateAiCharacterBtn) {
|
||||
}
|
||||
if (marketCreateUserPersonaBtn) {
|
||||
marketCreateUserPersonaBtn.addEventListener("click", () => {
|
||||
window.location.href = "/app/personas?view=my-roleplay-companions&create=user";
|
||||
window.location.href = "/js/personas?view=my-roleplay-companions&create=user";
|
||||
});
|
||||
}
|
||||
marketPreviewClose.addEventListener("click", closeMarketPreview);
|
||||
@@ -633,7 +633,7 @@ marketPreviewModal.addEventListener("click", (event) => {
|
||||
marketPreviewUserPersonaSelect.addEventListener("change", () => {
|
||||
if (!pendingMarketPersona || pendingMarketPersona.persona_type !== "assistant") return;
|
||||
if (marketPreviewUserPersonaSelect.value === "create_new") {
|
||||
window.location.href = "/app/personas?view=my-roleplay-companions&create=user";
|
||||
window.location.href = "/js/personas?view=my-roleplay-companions&create=user";
|
||||
return;
|
||||
}
|
||||
marketPreviewConfirm.classList.toggle("hidden", !marketPreviewUserPersonaSelect.value);
|
||||
@@ -673,10 +673,10 @@ marketPersonaForm.addEventListener("submit", (event) => {
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
initializeAppearance({title: "Elsewhere Market", nameTargets: brandNameTargets});
|
||||
initializeAppearance({ title: "Elsewhere Market", nameTargets: brandNameTargets });
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
setMarketSort("assistant", params.get("assistantSort") || "best", {reload: false});
|
||||
setMarketSort("user", params.get("userSort") || "best", {reload: false});
|
||||
setMarketSort("assistant", params.get("assistantSort") || "best", { reload: false });
|
||||
setMarketSort("user", params.get("userSort") || "best", { reload: false });
|
||||
setMarketView(params.get("view") || "ai-characters");
|
||||
await loadMarketPersonas();
|
||||
const selectedPersona = [...marketPersonasByType.assistant, ...marketPersonasByType.user].find(persona => persona.id === Number(params.get("persona")));
|
||||
@@ -1,4 +1,4 @@
|
||||
import {get, post} from "/app/api.js?v=20260909-account-controls";
|
||||
import { get, post } from "/js/api.js?v=20260909-account-controls";
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
function returnPath() {
|
||||
@@ -65,7 +65,7 @@ if (form) {
|
||||
showAccountStatus(register ? "Creating your account..." : "Signing in...");
|
||||
const result = await post(register ? "/register" : "/login", {
|
||||
username: form.elements.username.value.trim(), password: form.elements.password.value,
|
||||
...(register ? {inviteCode: form.elements.inviteCode.value.trim()} : {})
|
||||
...(register ? { inviteCode: form.elements.inviteCode.value.trim() } : {})
|
||||
});
|
||||
if (result.error) {
|
||||
const duplicate = register && result.error === "User already exists";
|
||||
@@ -1,12 +1,12 @@
|
||||
import {del, get, post, put, stream} from "./app/api.js?v=20260909-account-controls";
|
||||
import {regenerateInline} from "./app/regeneration.js";
|
||||
import {appendSources, createWebSearchToggle} from "./app/webSearch.js";
|
||||
import {appendAttachments, createAttachmentComposer} from "./app/attachments.js";
|
||||
import {createReasoningToggle, finishReasoning, updateReasoning} from "./app/reasoning.js?v=20260921-reasoning-capability";
|
||||
import {closeChatDrawer, openChatDrawer} from "./app/chatDrawer.js?v=20260909-account-controls";
|
||||
import {onboardingPrompts, requestedChatId} from "./app/constants.js?v=20260909-account-controls";
|
||||
import {getModelProfile} from "./app/modelProfiles.js?v=20260909-account-controls";
|
||||
import {applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance} from "./app/themeController.js?v=20260909-account-controls";
|
||||
import { del, get, post, put, stream } from "./js/api.js?v=20260909-account-controls";
|
||||
import { regenerateInline } from "./js/regeneration.js";
|
||||
import { appendSources, createWebSearchToggle } from "./js/webSearch.js";
|
||||
import { appendAttachments, createAttachmentComposer } from "./js/attachments.js";
|
||||
import { createReasoningToggle, finishReasoning, updateReasoning } from "./js/reasoning.js?v=20260921-reasoning-capability";
|
||||
import { closeChatDrawer, openChatDrawer } from "./js/chatDrawer.js?v=20260909-account-controls";
|
||||
import { onboardingPrompts, requestedChatId } from "./js/constants.js?v=20260909-account-controls";
|
||||
import { getModelProfile } from "./js/modelProfiles.js?v=20260909-account-controls";
|
||||
import { applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance } from "./js/themeController.js?v=20260909-account-controls";
|
||||
import {
|
||||
activeChatTitle,
|
||||
activeUserPersonaStatus,
|
||||
@@ -103,7 +103,7 @@ import {
|
||||
brandLogoTargets,
|
||||
brandNameTargets,
|
||||
userPersonaList
|
||||
} from "./app/dom.js?v=20260909-account-controls";
|
||||
} from "./js/dom.js?v=20260909-account-controls";
|
||||
|
||||
const headerModelButton = document.getElementById("headerModelButton");
|
||||
const webSearchButton = document.getElementById("webSearch");
|
||||
@@ -112,7 +112,7 @@ const reasoningToggle = createReasoningToggle(document.getElementById("reasoning
|
||||
|
||||
let isProcessing = false, activeChatId = null, editingPersonaId = null, editingPersonaType = "assistant", currentUsername = "";
|
||||
let regenerationChatId = null;
|
||||
const attachmentComposer = createAttachmentComposer({onChange: updateSendState, onError: message => setNotice(message, "error")});
|
||||
const attachmentComposer = createAttachmentComposer({ onChange: updateSendState, onError: message => setNotice(message, "error") });
|
||||
let chatSessions = [], currentMessages = [], assistantPersonas = [], userPersonas = [];
|
||||
let activeUserPersonaId = null, currentSummary = null, publishedPersonaIds = new Set();
|
||||
let popupResolver = null, popupMode = null, popupLastFocus = null;
|
||||
@@ -129,7 +129,7 @@ let availableModelsById = new Map();
|
||||
function setNotice(message = "", state = "") {
|
||||
const notice = document.getElementById("workspaceNotice");
|
||||
if (notice) { notice.textContent = state === "error" ? message : ""; notice.classList.toggle("hidden", state !== "error"); }
|
||||
return {message, state};
|
||||
return { message, state };
|
||||
}
|
||||
function setPersonaFormNotice(message = "", state = "") {
|
||||
if (!personaFormNotice) return;
|
||||
@@ -157,7 +157,7 @@ function setChatLoading(active, message = "Loading chats...") {
|
||||
}
|
||||
}
|
||||
|
||||
function setChatActivity(active, {eyebrow = "Please wait", title = "Preparing chat", detail = "The assistant is still working."} = {}) {
|
||||
function setChatActivity(active, { eyebrow = "Please wait", title = "Preparing chat", detail = "The assistant is still working." } = {}) {
|
||||
if (!chatActivityOverlay || !chatActivityEyebrow || !chatActivityTitle || !chatActivityDetail) return;
|
||||
if (active) {
|
||||
chatActivityDepth += 1;
|
||||
@@ -274,7 +274,7 @@ function setMessageContent(el, content) {
|
||||
const markdownParser = window.marked;
|
||||
const purifier = window.DOMPurify;
|
||||
if (markdownParser?.parse && purifier?.sanitize) {
|
||||
el.innerHTML = purifier.sanitize(markdownParser.parse(content, {breaks: true}));
|
||||
el.innerHTML = purifier.sanitize(markdownParser.parse(content, { breaks: true }));
|
||||
} else {
|
||||
el.textContent = content;
|
||||
}
|
||||
@@ -370,7 +370,7 @@ async function saveSelectedModel() {
|
||||
if (!activeChatId || !modelSelect.value) return;
|
||||
const chatId = activeChatId;
|
||||
const selectedModel = modelSelect.value;
|
||||
const res = await put(`/chats/${chatId}/model`, {model: selectedModel});
|
||||
const res = await put(`/chats/${chatId}/model`, { model: selectedModel });
|
||||
if (res.error || !res.chat) return setNotice(res.error || "Unable to switch models.", "error");
|
||||
const chat = getChatById(chatId);
|
||||
if (chat) Object.assign(chat, res.chat);
|
||||
@@ -404,7 +404,7 @@ async function resolveMessageTarget(messageId, fallbackIndex = -1) {
|
||||
if (messageId) {
|
||||
const index = getMessageIndexById(messageId);
|
||||
if (index >= 0) {
|
||||
return {index, message: currentMessages[index]};
|
||||
return { index, message: currentMessages[index] };
|
||||
}
|
||||
}
|
||||
if (activeChatId) {
|
||||
@@ -413,11 +413,11 @@ async function resolveMessageTarget(messageId, fallbackIndex = -1) {
|
||||
if (messageId) {
|
||||
const index = getMessageIndexById(messageId);
|
||||
if (index >= 0) {
|
||||
return {index, message: currentMessages[index]};
|
||||
return { index, message: currentMessages[index] };
|
||||
}
|
||||
}
|
||||
if (fallbackIndex >= 0 && fallbackIndex < currentMessages.length) {
|
||||
return {index: fallbackIndex, message: currentMessages[fallbackIndex]};
|
||||
return { index: fallbackIndex, message: currentMessages[fallbackIndex] };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -488,7 +488,7 @@ async function openCreatedChat(chat) {
|
||||
async function editChatMessage(messageId, fallbackIndex = -1) {
|
||||
const target = await resolveMessageTarget(messageId, fallbackIndex);
|
||||
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
|
||||
const {index, message} = target;
|
||||
const { index, message } = target;
|
||||
const nextContent = await promptPopup({
|
||||
eyebrow: message.role === "user" ? "Your message" : "Assistant message",
|
||||
title: "Edit message",
|
||||
@@ -529,11 +529,11 @@ async function editChatMessage(messageId, fallbackIndex = -1) {
|
||||
}
|
||||
const endpoint = getMessageEndpoint(target);
|
||||
if (!endpoint) return setNotice("Message is not ready yet. Try again.", "error");
|
||||
const res = await put(endpoint, {content});
|
||||
const res = await put(endpoint, { content });
|
||||
if (res.error || !res.message) {
|
||||
return setNotice(res.error || "Unable to edit message.", "error");
|
||||
}
|
||||
currentMessages[index] = {...currentMessages[index], ...res.message};
|
||||
currentMessages[index] = { ...currentMessages[index], ...res.message };
|
||||
syncRetryStateFromMessage(currentMessages[index]);
|
||||
renderMessages();
|
||||
setNotice("Message updated.", "success");
|
||||
@@ -542,7 +542,7 @@ async function editChatMessage(messageId, fallbackIndex = -1) {
|
||||
async function deleteChatMessage(messageId, fallbackIndex = -1) {
|
||||
const target = await resolveMessageTarget(messageId, fallbackIndex);
|
||||
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
|
||||
const {index, message} = target;
|
||||
const { index, message } = target;
|
||||
const confirmed = await confirmPopup({
|
||||
eyebrow: message.role === "user" ? "Your message" : "Assistant message",
|
||||
title: "Delete message",
|
||||
@@ -582,7 +582,7 @@ async function retryLatestAssistantMessage(messageId, fallbackIndex = -1) {
|
||||
const chatId = activeChatId;
|
||||
const card = messagesDiv.querySelector(`[data-message-id="${target.message.id}"]`);
|
||||
if (!card) return;
|
||||
const payload = {model: modelSelect.value, webSearch: webSearchToggle.enabled, reasoning: reasoningToggle.enabled};
|
||||
const payload = { model: modelSelect.value, webSearch: webSearchToggle.enabled, reasoning: reasoningToggle.enabled };
|
||||
regenerationChatId = chatId;
|
||||
setLoadingState(true, false);
|
||||
try {
|
||||
@@ -593,9 +593,9 @@ async function retryLatestAssistantMessage(messageId, fallbackIndex = -1) {
|
||||
if (activeChatId !== chatId) return;
|
||||
const index = getMessageIndexById(message.id);
|
||||
if (index < 0) return;
|
||||
currentMessages[index] = {...currentMessages[index], ...message, thinking: message.thinking, sources: message.sources};
|
||||
currentMessages[index] = { ...currentMessages[index], ...message, thinking: message.thinking, sources: message.sources };
|
||||
syncRetryStateFromMessage(currentMessages[index]);
|
||||
const rendered = addMessage(currentMessages[index], false, false, {messageIndex: index, detached: true});
|
||||
const rendered = addMessage(currentMessages[index], false, false, { messageIndex: index, detached: true });
|
||||
card.replaceChildren(...rendered.childNodes);
|
||||
}
|
||||
});
|
||||
@@ -626,9 +626,9 @@ async function switchResponseVersion(messageId, direction, fallbackIndex = -1) {
|
||||
if (activeChatId !== chatId) return;
|
||||
const index = getMessageIndexById(messageId);
|
||||
if (index < 0) return;
|
||||
currentMessages[index] = {...currentMessages[index], ...res.message, thinking: res.message.thinking, sources: res.message.sources};
|
||||
currentMessages[index] = { ...currentMessages[index], ...res.message, thinking: res.message.thinking, sources: res.message.sources };
|
||||
syncRetryStateFromMessage(currentMessages[index]);
|
||||
const rendered = addMessage(currentMessages[index], false, false, {messageIndex: index, detached: true});
|
||||
const rendered = addMessage(currentMessages[index], false, false, { messageIndex: index, detached: true });
|
||||
card.replaceChildren(...rendered.childNodes);
|
||||
} finally {
|
||||
card.removeAttribute("aria-busy");
|
||||
@@ -642,7 +642,7 @@ async function switchRetryVariant(messageId, direction, fallbackIndex = -1) {
|
||||
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
|
||||
const siblingId = direction < 0 ? target.message.previousSiblingId : target.message.nextSiblingId;
|
||||
if (!siblingId) return;
|
||||
const res = await post(`/chats/${activeChatId}/branches/${siblingId}/activate`, {includeDescendants: true});
|
||||
const res = await post(`/chats/${activeChatId}/branches/${siblingId}/activate`, { includeDescendants: true });
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
currentMessages = res.messages || [];
|
||||
renderMessages();
|
||||
@@ -658,7 +658,7 @@ async function branchFromMessage(messageId) {
|
||||
});
|
||||
if (!confirmed) return;
|
||||
const sourceChatId = activeChatId;
|
||||
const res = await post(`/chats/${sourceChatId}/messages/${messageId}/branch`, {confirmed: true});
|
||||
const res = await post(`/chats/${sourceChatId}/messages/${messageId}/branch`, { confirmed: true });
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
await openCreatedChat(res.chat);
|
||||
msgInput.focus();
|
||||
@@ -689,7 +689,7 @@ function addMessage(contentOrMessage, isUser = false, isLoading = false, options
|
||||
} else {
|
||||
const message = typeof contentOrMessage === "object" && contentOrMessage
|
||||
? contentOrMessage
|
||||
: {id: null, role: isUser ? "user" : "bot", content: String(contentOrMessage || "")};
|
||||
: { id: null, role: isUser ? "user" : "bot", content: String(contentOrMessage || "") };
|
||||
const isMessageUser = message.role === "user";
|
||||
msgDiv.dataset.messageId = message.id;
|
||||
msgDiv.className = `msg ${isMessageUser ? "user" : "bot"}`;
|
||||
@@ -746,32 +746,32 @@ function addMessage(contentOrMessage, isUser = false, isLoading = false, options
|
||||
}
|
||||
|
||||
for (const versioned of [true, false]) {
|
||||
if (Number((versioned ? message.versionCount : message.siblingCount) || 1) <= 1) continue;
|
||||
const navigate = versioned ? switchResponseVersion : switchRetryVariant;
|
||||
const prevBtn = document.createElement("button");
|
||||
prevBtn.type = "button";
|
||||
prevBtn.className = "msg-action-btn";
|
||||
prevBtn.textContent = "Prev";
|
||||
prevBtn.setAttribute("aria-label", versioned ? "Show previous response version" : "Show previous branch");
|
||||
prevBtn.disabled = versioned ? message.versionIndex <= 0 : !message.previousSiblingId;
|
||||
prevBtn.addEventListener("click", () => {
|
||||
void navigate(message.id, -1, options.messageIndex ?? -1);
|
||||
});
|
||||
if (Number((versioned ? message.versionCount : message.siblingCount) || 1) <= 1) continue;
|
||||
const navigate = versioned ? switchResponseVersion : switchRetryVariant;
|
||||
const prevBtn = document.createElement("button");
|
||||
prevBtn.type = "button";
|
||||
prevBtn.className = "msg-action-btn";
|
||||
prevBtn.textContent = "Prev";
|
||||
prevBtn.setAttribute("aria-label", versioned ? "Show previous response version" : "Show previous branch");
|
||||
prevBtn.disabled = versioned ? message.versionIndex <= 0 : !message.previousSiblingId;
|
||||
prevBtn.addEventListener("click", () => {
|
||||
void navigate(message.id, -1, options.messageIndex ?? -1);
|
||||
});
|
||||
|
||||
const nextBtn = document.createElement("button");
|
||||
nextBtn.type = "button";
|
||||
nextBtn.className = "msg-action-btn";
|
||||
nextBtn.textContent = "Next";
|
||||
nextBtn.setAttribute("aria-label", versioned ? "Show next response version" : "Show next branch");
|
||||
nextBtn.disabled = versioned ? message.versionIndex >= message.versionCount - 1 : !message.nextSiblingId;
|
||||
nextBtn.addEventListener("click", () => {
|
||||
void navigate(message.id, 1, options.messageIndex ?? -1);
|
||||
});
|
||||
const nextBtn = document.createElement("button");
|
||||
nextBtn.type = "button";
|
||||
nextBtn.className = "msg-action-btn";
|
||||
nextBtn.textContent = "Next";
|
||||
nextBtn.setAttribute("aria-label", versioned ? "Show next response version" : "Show next branch");
|
||||
nextBtn.disabled = versioned ? message.versionIndex >= message.versionCount - 1 : !message.nextSiblingId;
|
||||
nextBtn.addEventListener("click", () => {
|
||||
void navigate(message.id, 1, options.messageIndex ?? -1);
|
||||
});
|
||||
|
||||
const indexBadge = document.createElement("span");
|
||||
indexBadge.className = "msg-variant-index";
|
||||
indexBadge.textContent = versioned ? `${message.versionIndex + 1} / ${message.versionCount}` : `${Number(message.siblingIndex || 0) + 1}/${message.siblingCount}`;
|
||||
actions.append(prevBtn, indexBadge, nextBtn);
|
||||
const indexBadge = document.createElement("span");
|
||||
indexBadge.className = "msg-variant-index";
|
||||
indexBadge.textContent = versioned ? `${message.versionIndex + 1} / ${message.versionCount}` : `${Number(message.siblingIndex || 0) + 1}/${message.siblingCount}`;
|
||||
actions.append(prevBtn, indexBadge, nextBtn);
|
||||
}
|
||||
|
||||
msgDiv.append(header);
|
||||
@@ -972,7 +972,7 @@ function updateModelHelp() {
|
||||
}
|
||||
const modelId = selectedOption.value;
|
||||
const modelName = selectedOption.textContent || modelId;
|
||||
const profile = getModelProfile(availableModelsById.get(modelId) || {model: modelId, name: modelName});
|
||||
const profile = getModelProfile(availableModelsById.get(modelId) || { model: modelId, name: modelName });
|
||||
if (modelBadgeName) modelBadgeName.textContent = modelName;
|
||||
if (headerModelName) headerModelName.textContent = modelName;
|
||||
modelHelpTitle.textContent = modelName;
|
||||
@@ -993,7 +993,7 @@ function applyTheme(themeKey, persist = true) {
|
||||
}
|
||||
|
||||
function applyThemeMode(modeKey, persist = true) {
|
||||
applySharedThemeMode(modeKey, {persist});
|
||||
applySharedThemeMode(modeKey, { persist });
|
||||
}
|
||||
|
||||
function updateChatActionState() {
|
||||
@@ -1067,10 +1067,10 @@ function renderChatList() {
|
||||
return map;
|
||||
}, new Map());
|
||||
const groups = [
|
||||
{label: "Pinned", items: filtered.filter((chat) => chat.is_pinned && !chat.archived_at)},
|
||||
...Array.from(folderGroups.entries()).map(([label, items]) => ({label, items})),
|
||||
{label: "Recent", items: filtered.filter((chat) => !chat.is_pinned && !chat.folder_name && !chat.archived_at)},
|
||||
{label: "Archived", items: filtered.filter((chat) => chat.archived_at)}
|
||||
{ label: "Pinned", items: filtered.filter((chat) => chat.is_pinned && !chat.archived_at) },
|
||||
...Array.from(folderGroups.entries()).map(([label, items]) => ({ label, items })),
|
||||
{ label: "Recent", items: filtered.filter((chat) => !chat.is_pinned && !chat.folder_name && !chat.archived_at) },
|
||||
{ label: "Archived", items: filtered.filter((chat) => chat.archived_at) }
|
||||
].filter((group) => group.items.length);
|
||||
groups.forEach((group) => {
|
||||
const section = document.createElement("section");
|
||||
@@ -1276,10 +1276,10 @@ function openPopup(options) {
|
||||
});
|
||||
}
|
||||
function confirmPopup(options) {
|
||||
return openPopup({...options, mode: "confirm"}).then((result) => Boolean(result));
|
||||
return openPopup({ ...options, mode: "confirm" }).then((result) => Boolean(result));
|
||||
}
|
||||
function promptPopup(options) {
|
||||
return openPopup({...options, mode: "prompt"}).then((result) => typeof result === "string" ? result : null);
|
||||
return openPopup({ ...options, mode: "prompt" }).then((result) => typeof result === "string" ? result : null);
|
||||
}
|
||||
|
||||
function renderPersonaList(items, activeId, listElement, personaType) {
|
||||
@@ -1303,9 +1303,9 @@ function renderPersonaList(items, activeId, listElement, personaType) {
|
||||
tag.className = `persona-role-tag ${personaType === "user" ? "user" : "ai"}`; tag.textContent = personaType === "user" ? "You" : "AI";
|
||||
title.textContent = persona.name; meta.textContent = persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: n/a";
|
||||
[[personaType === "assistant" ? "Start roleplay" : "Set default", () => equipPersona(persona.id, personaType), personaType === "user" && persona.id === activeId],
|
||||
["Edit", () => openPersonaForm(persona), false],
|
||||
[publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
|
||||
["Delete", () => deletePersona(persona.id), false]]
|
||||
["Edit", () => openPersonaForm(persona), false],
|
||||
[publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
|
||||
["Delete", () => deletePersona(persona.id), false]]
|
||||
.forEach(([label, handler, disabled]) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button"; btn.textContent = label; btn.disabled = disabled; btn.addEventListener("click", handler); actions.appendChild(btn);
|
||||
@@ -1486,7 +1486,7 @@ async function createNewChat() {
|
||||
title: "Opening a new conversation",
|
||||
detail: "Setting up a fresh chat so you can start sending messages."
|
||||
});
|
||||
const res = await post("/chats", {title: "New chat"});
|
||||
const res = await post("/chats", { title: "New chat" });
|
||||
if (res.error || !res.chat) {
|
||||
setChatLoading(false);
|
||||
setChatActivity(false);
|
||||
@@ -1557,7 +1557,7 @@ async function renameChat(id) {
|
||||
}) : "";
|
||||
const title = (nextTitle || "").trim();
|
||||
if (!chat || !title) return;
|
||||
const res = await put(`/chats/${id}`, {title});
|
||||
const res = await put(`/chats/${id}`, { title });
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
chat.title = title; updateWorkspaceCopy(); renderChatList(); setNotice("Chat renamed.", "success");
|
||||
}
|
||||
@@ -1629,7 +1629,7 @@ function exportCurrentChat() {
|
||||
async function backupWorkspace() {
|
||||
const response = await fetch("/exports/workspace");
|
||||
const data = await response.json();
|
||||
const blob = new Blob([JSON.stringify(data.workspace, null, 2)], {type: "application/json;charset=utf-8"});
|
||||
const blob = new Blob([JSON.stringify(data.workspace, null, 2)], { type: "application/json;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
@@ -1663,7 +1663,7 @@ async function importWorkspaceFromFile(file) {
|
||||
return setNotice("Import failed: choose a valid JSON workspace backup.", "error");
|
||||
}
|
||||
|
||||
const preview = await post("/imports/workspace/preview", {workspace});
|
||||
const preview = await post("/imports/workspace/preview", { workspace });
|
||||
if (preview.error || preview.ok === false) {
|
||||
setChatLoading(false);
|
||||
const detail = (preview.errors || []).slice(0, 4).join("; ");
|
||||
@@ -1722,7 +1722,7 @@ async function manageChatMemory() {
|
||||
if (removed.error) return setNotice(removed.error, "error");
|
||||
return setNotice("Memory fact deleted.", "success");
|
||||
}
|
||||
const saved = await post(`/chats/${activeChatId}/memories`, {fact: value});
|
||||
const saved = await post(`/chats/${activeChatId}/memories`, { fact: value });
|
||||
if (saved.error) return setNotice(saved.error, "error");
|
||||
return setNotice("Memory fact saved.", "success");
|
||||
}
|
||||
@@ -1769,7 +1769,7 @@ async function managePersonaState() {
|
||||
placeholder: label
|
||||
});
|
||||
if (value === null) return;
|
||||
const nextState = {...state, [key]: String(value || "").trim() || null};
|
||||
const nextState = { ...state, [key]: String(value || "").trim() || null };
|
||||
const saved = await put(`/chats/${activeChatId}/persona-state`, nextState);
|
||||
if (saved.error) return setNotice(saved.error, "error");
|
||||
return setNotice("Roleplay state saved.", "success");
|
||||
@@ -1859,22 +1859,22 @@ async function sendMessage() {
|
||||
const currentChat = getChatById(activeChatId);
|
||||
if (currentChat && !currentChat.assistant_persona_id && currentChat.title === "New chat") {
|
||||
const autoTitle = message.length > 40 ? `${message.slice(0, 40)}...` : message;
|
||||
currentChat.title = autoTitle; updateWorkspaceCopy(); renderChatList(); await put(`/chats/${activeChatId}`, {title: autoTitle});
|
||||
currentChat.title = autoTitle; updateWorkspaceCopy(); renderChatList(); await put(`/chats/${activeChatId}`, { title: autoTitle });
|
||||
}
|
||||
const userMessage = {role: "user", content: message, attachments};
|
||||
const userMessage = { role: "user", content: message, attachments };
|
||||
currentMessages.push(userMessage); addMessage(userMessage, true); updateChatActionState();
|
||||
msgInput.value = "";
|
||||
resizeComposerInput();
|
||||
setLoadingState(true, false); setNotice("Generating reply...");
|
||||
const loadingMsg = addMessage({role: "bot", content: ""}, false, true, {isNewest: true});
|
||||
const loadingMsg = addMessage({ role: "bot", content: "" }, false, true, { isNewest: true });
|
||||
const loadingBody = loadingMsg.querySelector(".msg-content");
|
||||
const loadingShell = loadingMsg.querySelector(".loading-shell");
|
||||
const loadingLabel = loadingMsg.querySelector(".loading-copy strong");
|
||||
loadingLabel.textContent = "Preparing response...";
|
||||
try {
|
||||
let streamError = null;
|
||||
await stream("/chat/stream", {message, attachments: attachments.map(item => item.id), model: modelSelect.value, chatId: activeChatId, webSearch, reasoning}, {
|
||||
onStatus: ({status, sourceCount, searchState}) => {
|
||||
await stream("/chat/stream", { message, attachments: attachments.map(item => item.id), model: modelSelect.value, chatId: activeChatId, webSearch, reasoning }, {
|
||||
onStatus: ({ status, sourceCount, searchState }) => {
|
||||
if (status === "accepted") {
|
||||
attachmentComposer.clear();
|
||||
webSearchToggle.reset();
|
||||
@@ -1883,16 +1883,16 @@ async function sendMessage() {
|
||||
}
|
||||
loadingLabel.textContent = status === "analyzing" ? "Analyzing images..." : status === "searching" ? "Searching the web..."
|
||||
: searchState === "no_relevant_results" ? "No sufficiently relevant results. Generating response..."
|
||||
: sourceCount === 0 ? "No web results found. Generating response..." : "Generating response...";
|
||||
: sourceCount === 0 ? "No web results found. Generating response..." : "Generating response...";
|
||||
},
|
||||
onThinking: ({fullThinking}) => {
|
||||
onThinking: ({ fullThinking }) => {
|
||||
if (!reasoning) return;
|
||||
const follow = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
|
||||
updateReasoning(loadingMsg, fullThinking, true);
|
||||
loadingShell?.classList.toggle("hidden", Boolean(fullThinking));
|
||||
if (follow) scrollMessagesToBottom();
|
||||
},
|
||||
onChunk: ({fullReply}) => {
|
||||
onChunk: ({ fullReply }) => {
|
||||
const follow = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
|
||||
if (fullReply) finishReasoning(loadingMsg);
|
||||
if (loadingBody) {
|
||||
@@ -1904,7 +1904,7 @@ async function sendMessage() {
|
||||
if (follow) scrollMessagesToBottom();
|
||||
},
|
||||
onDone: () => { webSearchToggle.reset(); reasoningToggle.reset(); finishReasoning(loadingMsg); },
|
||||
onError: ({error}) => {
|
||||
onError: ({ error }) => {
|
||||
streamError = error || "Failed to generate reply.";
|
||||
}
|
||||
});
|
||||
@@ -1942,7 +1942,7 @@ async function sendMessage() {
|
||||
const chatActions = {
|
||||
archive: async () => {
|
||||
const chat = getChatById(activeChatId);
|
||||
if (chat) await updateChatOrganization(chat.id, {archived: !chat.archived_at});
|
||||
if (chat) await updateChatOrganization(chat.id, { archived: !chat.archived_at });
|
||||
},
|
||||
clear: async () => {
|
||||
if (activeChatId) await clearChat(activeChatId);
|
||||
@@ -1962,7 +1962,7 @@ const chatActions = {
|
||||
placeholder: "Research"
|
||||
});
|
||||
if (folderName === null) return;
|
||||
await updateChatOrganization(chat.id, {folderName: String(folderName || "").trim() || null});
|
||||
await updateChatOrganization(chat.id, { folderName: String(folderName || "").trim() || null });
|
||||
},
|
||||
memory: async () => {
|
||||
await manageChatMemory();
|
||||
@@ -1972,7 +1972,7 @@ const chatActions = {
|
||||
},
|
||||
pin: async () => {
|
||||
const chat = getChatById(activeChatId);
|
||||
if (chat) await updateChatOrganization(chat.id, {isPinned: !chat.is_pinned});
|
||||
if (chat) await updateChatOrganization(chat.id, { isPinned: !chat.is_pinned });
|
||||
},
|
||||
rename: async () => {
|
||||
if (activeChatId) await renameChat(activeChatId);
|
||||
@@ -1,11 +1,11 @@
|
||||
import {del, get, post, put} from "./app/api.js?v=20260909-account-controls";
|
||||
import { del, get, post, put } from "./js/api.js?v=20260909-account-controls";
|
||||
import {
|
||||
applyTheme as applySharedTheme,
|
||||
applyThemeMode as applySharedThemeMode,
|
||||
initializeAppearance
|
||||
} from "./app/themeController.js?v=20260909-account-controls";
|
||||
import {clearStatus, setStatus} from "./app/statusNotice.js?v=20260909-account-controls";
|
||||
import {populatePersonaForm, readPersonaForm} from "./app/personaForm.js?v=20260909-account-controls";
|
||||
} from "./js/themeController.js?v=20260909-account-controls";
|
||||
import { clearStatus, setStatus } from "./js/statusNotice.js?v=20260909-account-controls";
|
||||
import { populatePersonaForm, readPersonaForm } from "./js/personaForm.js?v=20260909-account-controls";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const settingsNotice = $("settingsNotice");
|
||||
@@ -127,11 +127,11 @@ function promptSettingsPopup({
|
||||
}
|
||||
|
||||
function applyTheme(themeKey, persist = true) {
|
||||
return applySharedTheme(themeKey, {persist, title: "Elsewhere Settings", themeSelect: settingsThemeSelect});
|
||||
return applySharedTheme(themeKey, { persist, title: "Elsewhere Settings", themeSelect: settingsThemeSelect });
|
||||
}
|
||||
|
||||
function applyThemeMode(modeKey, persist = true) {
|
||||
return applySharedThemeMode(modeKey, {persist, modeSelect: themeModeSelect});
|
||||
return applySharedThemeMode(modeKey, { persist, modeSelect: themeModeSelect });
|
||||
}
|
||||
|
||||
function setSettingsView(view) {
|
||||
@@ -382,7 +382,7 @@ async function publishPersona(id) {
|
||||
});
|
||||
if (tagsInput === null) return;
|
||||
const tags = String(tagsInput || "").split(",").map((tag) => tag.trim()).filter(Boolean);
|
||||
const res = await post(`/personas/${id}/publish`, {tags});
|
||||
const res = await post(`/personas/${id}/publish`, { tags });
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
@@ -435,7 +435,7 @@ async function unpublishPersona(id) {
|
||||
}
|
||||
|
||||
function downloadJson(filename, payload) {
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], {type: "application/json;charset=utf-8"});
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
@@ -479,7 +479,7 @@ async function deleteAccount() {
|
||||
confirmLabel: "Delete account"
|
||||
});
|
||||
if (currentPassword === null) return;
|
||||
const res = await request("/settings/account", {currentPassword}, "DELETE");
|
||||
const res = await request("/settings/account", { currentPassword }, "DELETE");
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
window.location.href = "/";
|
||||
}
|
||||
@@ -492,7 +492,7 @@ usernameForm.addEventListener("submit", async (event) => {
|
||||
setNotice("New username and current password are required.", "error");
|
||||
return;
|
||||
}
|
||||
const res = await request("/settings/username", {username, password}, "PUT");
|
||||
const res = await request("/settings/username", { username, password }, "PUT");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
@@ -516,7 +516,7 @@ passwordForm.addEventListener("submit", async (event) => {
|
||||
setNotice("New passwords do not match.", "error");
|
||||
return;
|
||||
}
|
||||
const res = await request("/settings/password", {currentPassword, newPassword: nextPassword}, "PUT");
|
||||
const res = await request("/settings/password", { currentPassword, newPassword: nextPassword }, "PUT");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
@@ -575,7 +575,7 @@ settingsPopupInput.addEventListener("keydown", (event) => {
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
initializeAppearance({title: "Elsewhere Settings", themeSelect: settingsThemeSelect, modeSelect: themeModeSelect});
|
||||
initializeAppearance({ title: "Elsewhere Settings", themeSelect: settingsThemeSelect, modeSelect: themeModeSelect });
|
||||
workspaceModeSelect.value = localStorage.getItem("krishd-workspace-mode") || "basic";
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const personaPage = location.pathname === "/app/personas";
|
||||
+29
-22
@@ -1,32 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Elsewhere</title>
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="icon" href="/img/favicon.svg">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="site-page landing-page">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="landing-entry">
|
||||
<div class="entry-door" aria-hidden="true"><span></span></div>
|
||||
<div class="entry-copy">
|
||||
<p class="eyebrow">A space for every conversation.</p>
|
||||
<h1>Take a thought<br>somewhere.</h1>
|
||||
<p>Talk it through. Make something up.<br>Meet a character and see where it goes.</p>
|
||||
<div class="site-actions">
|
||||
<a data-start-link class="primary-action" href="/register">Step inside <span aria-hidden="true">→</span></a>
|
||||
<a href="/market">Meet the personas</a>
|
||||
<!-- site-header -->
|
||||
<main id="main" class="landing-entry">
|
||||
<div class="entry-door" aria-hidden="true"><span></span></div>
|
||||
<div class="entry-copy">
|
||||
<p class="eyebrow">A space for every conversation.</p>
|
||||
<h1>Take a thought<br>somewhere.</h1>
|
||||
<p>Talk it through. Make something up.<br>Meet a character and see where it goes.</p>
|
||||
<div class="site-actions">
|
||||
<a data-start-link class="primary-action" href="/register">Step inside <span
|
||||
aria-hidden="true">→</span></a>
|
||||
<a href="/market">Meet the personas</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="site-footer"><span>elsewhere / a little room for imagination</span><a href="/privacy.html">Privacy</a><a href="/tos.html">Terms</a></footer>
|
||||
<script type="module" src="/pages.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</main>
|
||||
<footer class="site-footer"><span>elsewhere / a little room for imagination</span><a
|
||||
href="/privacy.html">Privacy</a><a href="/tos.html">Terms</a></footer>
|
||||
<script type="module" src="/js/pages.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
+234
-214
@@ -1,242 +1,262 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
|
||||
<title>Elsewhere Discover</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls"/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
</head>
|
||||
|
||||
<body class="site-page market-page" data-theme="fakegpt" data-theme-mode="light">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="market-shell page-content">
|
||||
<header class="market-header market-header-simple">
|
||||
<div>
|
||||
<p class="eyebrow">Shared personas</p>
|
||||
<h1>Discover</h1>
|
||||
</div>
|
||||
<div class="market-header-actions"><button id="refreshMarket" class="secondary-action" type="button">Refresh</button></div>
|
||||
</header>
|
||||
|
||||
<div class="market-search">
|
||||
<label for="marketSearch">Search</label>
|
||||
<input id="marketSearch" type="search" placeholder="Search by name, creator, or description"/>
|
||||
</div>
|
||||
|
||||
<p id="marketStatus" class="status persona-status">Loading market personas...</p>
|
||||
|
||||
<section class="settings-layout market-settings-layout">
|
||||
<aside class="settings-nav" aria-label="Market sections" role="tablist">
|
||||
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true" data-market-view="ai-characters">Characters</button>
|
||||
<button class="settings-nav-item" type="button" role="tab" aria-selected="false" data-market-view="user-personas">User personas</button>
|
||||
</aside>
|
||||
|
||||
<div class="settings-content">
|
||||
<section class="settings-view active" role="tabpanel" data-market-panel="ai-characters">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Discover</p>
|
||||
<h2>AI Characters</h2>
|
||||
</div>
|
||||
<div class="market-section-actions">
|
||||
<label class="market-sort-control" for="marketAssistantSort">
|
||||
<span>Sort</span>
|
||||
<select id="marketAssistantSort">
|
||||
<option value="best">Best</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="most_favorited">Most Favorited</option>
|
||||
<option value="most_popular">Most Popular</option>
|
||||
<option value="top_rated">Top Rated</option>
|
||||
<option value="alphabetical">A-Z</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="marketCreateAiCharacter" type="button" class="secondary-action">Create AI Character</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="marketPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-market-panel="user-personas">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Discover</p>
|
||||
<h2>User Personas</h2>
|
||||
</div>
|
||||
<div class="market-section-actions">
|
||||
<label class="market-sort-control" for="marketUserSort">
|
||||
<span>Sort</span>
|
||||
<select id="marketUserSort">
|
||||
<option value="best">Best</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="most_favorited">Most Favorited</option>
|
||||
<option value="most_popular">Most Popular</option>
|
||||
<option value="top_rated">Top Rated</option>
|
||||
<option value="alphabetical">A-Z</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="marketCreateUserPersona" type="button" class="secondary-action">Create User Persona</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="marketUserPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="marketPreviewModal" class="modal-overlay hidden">
|
||||
<div class="modal-card market-preview-card">
|
||||
<div class="modal-header">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="market-shell page-content">
|
||||
<header class="market-header market-header-simple">
|
||||
<div>
|
||||
<p class="eyebrow">Persona preview</p>
|
||||
<h3 id="marketPreviewTitle">Persona details</h3>
|
||||
<h4 id="marketPreviewName" class="market-preview-name">Persona details</h4>
|
||||
<p id="marketPreviewIntro" class="subtitle">Review this persona and choose how you want to use it.</p>
|
||||
<p class="eyebrow">Shared personas</p>
|
||||
<h1>Discover</h1>
|
||||
</div>
|
||||
<button id="marketPreviewClose" type="button" class="icon-button">Close</button>
|
||||
<div class="market-header-actions"><button id="refreshMarket" class="secondary-action"
|
||||
type="button">Refresh</button></div>
|
||||
</header>
|
||||
|
||||
<div class="market-search">
|
||||
<label for="marketSearch">Search</label>
|
||||
<input id="marketSearch" type="search" placeholder="Search by name, creator, or description" />
|
||||
</div>
|
||||
<div id="marketPreviewBody" class="market-preview-body">
|
||||
<p id="marketPreviewMeta" class="persona-guidance"></p>
|
||||
<div class="market-preview-details">
|
||||
|
||||
<p id="marketStatus" class="status persona-status">Loading market personas...</p>
|
||||
|
||||
<section class="settings-layout market-settings-layout">
|
||||
<aside class="settings-nav" aria-label="Market sections" role="tablist">
|
||||
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true"
|
||||
data-market-view="ai-characters">Characters</button>
|
||||
<button class="settings-nav-item" type="button" role="tab" aria-selected="false"
|
||||
data-market-view="user-personas">User personas</button>
|
||||
</aside>
|
||||
|
||||
<div class="settings-content">
|
||||
<section class="settings-view active" role="tabpanel" data-market-panel="ai-characters">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Discover</p>
|
||||
<h2>AI Characters</h2>
|
||||
</div>
|
||||
<div class="market-section-actions">
|
||||
<label class="market-sort-control" for="marketAssistantSort">
|
||||
<span>Sort</span>
|
||||
<select id="marketAssistantSort">
|
||||
<option value="best">Best</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="most_favorited">Most Favorited</option>
|
||||
<option value="most_popular">Most Popular</option>
|
||||
<option value="top_rated">Top Rated</option>
|
||||
<option value="alphabetical">A-Z</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="marketCreateAiCharacter" type="button" class="secondary-action">Create AI
|
||||
Character</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="marketPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-market-panel="user-personas">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Discover</p>
|
||||
<h2>User Personas</h2>
|
||||
</div>
|
||||
<div class="market-section-actions">
|
||||
<label class="market-sort-control" for="marketUserSort">
|
||||
<span>Sort</span>
|
||||
<select id="marketUserSort">
|
||||
<option value="best">Best</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="most_favorited">Most Favorited</option>
|
||||
<option value="most_popular">Most Popular</option>
|
||||
<option value="top_rated">Top Rated</option>
|
||||
<option value="alphabetical">A-Z</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="marketCreateUserPersona" type="button" class="secondary-action">Create User
|
||||
Persona</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="marketUserPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="marketPreviewModal" class="modal-overlay hidden">
|
||||
<div class="modal-card market-preview-card">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h4>Pronouns</h4>
|
||||
<p id="marketPreviewPronouns"></p>
|
||||
<p class="eyebrow">Persona preview</p>
|
||||
<h3 id="marketPreviewTitle">Persona details</h3>
|
||||
<h4 id="marketPreviewName" class="market-preview-name">Persona details</h4>
|
||||
<p id="marketPreviewIntro" class="subtitle">Review this persona and choose how you want to use it.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Appearance</h4>
|
||||
<p id="marketPreviewAppearance"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Background</h4>
|
||||
<p id="marketPreviewBackground"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Details</h4>
|
||||
<p id="marketPreviewDetails"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Example dialogues</h4>
|
||||
<p id="marketPreviewExampleDialogues"></p>
|
||||
<button id="marketPreviewClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div id="marketPreviewBody" class="market-preview-body">
|
||||
<p id="marketPreviewMeta" class="persona-guidance"></p>
|
||||
<div class="market-preview-details">
|
||||
<div>
|
||||
<h4>Pronouns</h4>
|
||||
<p id="marketPreviewPronouns"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Appearance</h4>
|
||||
<p id="marketPreviewAppearance"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Background</h4>
|
||||
<p id="marketPreviewBackground"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Details</h4>
|
||||
<p id="marketPreviewDetails"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Example dialogues</h4>
|
||||
<p id="marketPreviewExampleDialogues"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="market-preview-actions">
|
||||
<div id="marketPreviewStepOne" class="roleplay-starter-page hidden">
|
||||
<label id="marketPreviewUserPersonaField" class="field market-preview-user-field hidden">
|
||||
<span>You in this roleplay</span>
|
||||
<select id="marketPreviewUserPersonaSelect"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="marketPreviewStepTwo" class="roleplay-starter-page hidden">
|
||||
<label id="marketPreviewScenarioField" class="field market-preview-scenario-field">
|
||||
<span>Scenario (optional)</span>
|
||||
<textarea id="marketPreviewScenario" rows="4" placeholder="Describe the opening scene you want. Leave it empty and the AI Character will invent its own scenario."></textarea>
|
||||
</label>
|
||||
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own. No character limit.</p>
|
||||
</div>
|
||||
<div class="market-preview-footer">
|
||||
<button id="marketPreviewConfirm" type="button" class="primary-action">Take persona</button>
|
||||
<div class="market-preview-actions">
|
||||
<div id="marketPreviewStepOne" class="roleplay-starter-page hidden">
|
||||
<label id="marketPreviewUserPersonaField" class="field market-preview-user-field hidden">
|
||||
<span>You in this roleplay</span>
|
||||
<select id="marketPreviewUserPersonaSelect"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="marketPreviewStepTwo" class="roleplay-starter-page hidden">
|
||||
<label id="marketPreviewScenarioField" class="field market-preview-scenario-field">
|
||||
<span>Scenario (optional)</span>
|
||||
<textarea id="marketPreviewScenario" rows="4"
|
||||
placeholder="Describe the opening scene you want. Leave it empty and the AI Character will invent its own scenario."></textarea>
|
||||
</label>
|
||||
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own.
|
||||
No character limit.</p>
|
||||
</div>
|
||||
<div class="market-preview-footer">
|
||||
<button id="marketPreviewConfirm" type="button" class="primary-action">Take persona</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="marketPersonaModal" class="modal-overlay hidden">
|
||||
<div class="modal-card persona-modal-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">AI Character editor</p>
|
||||
<h3 id="marketPersonaFormTitle">Create AI Character</h3>
|
||||
<div id="marketPersonaModal" class="modal-overlay hidden">
|
||||
<div class="modal-card persona-modal-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">AI Character editor</p>
|
||||
<h3 id="marketPersonaFormTitle">Create AI Character</h3>
|
||||
</div>
|
||||
<button id="marketPersonaClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<button id="marketPersonaClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<form id="marketPersonaForm" class="persona-form">
|
||||
<div class="persona-form-intro">
|
||||
<p class="persona-helper">Create or update your AI Characters for roleplay chats and marketplace publishing.</p>
|
||||
</div>
|
||||
<p id="marketPersonaFormNotice" class="status hidden"></p>
|
||||
<div class="persona-form-grid">
|
||||
<label class="field">
|
||||
<span>Character name</span>
|
||||
<input id="marketPersonaName" placeholder="Character name" required/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Pronouns</span>
|
||||
<input id="marketPersonaPronouns" placeholder="e.g. she/her, they/them"/>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Appearance</span>
|
||||
<textarea id="marketPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Background</span>
|
||||
<textarea id="marketPersonaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Details</span>
|
||||
<textarea id="marketPersonaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Example dialogues</span>
|
||||
<div class="dialogue-format-card" aria-hidden="true">
|
||||
<p class="dialogue-format-label">Format</p>
|
||||
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
|
||||
<form id="marketPersonaForm" class="persona-form">
|
||||
<div class="persona-form-intro">
|
||||
<p class="persona-helper">Create or update your AI Characters for roleplay chats and marketplace
|
||||
publishing.</p>
|
||||
</div>
|
||||
<p id="marketPersonaFormNotice" class="status hidden"></p>
|
||||
<div class="persona-form-grid">
|
||||
<label class="field">
|
||||
<span>Character name</span>
|
||||
<input id="marketPersonaName" placeholder="Character name" required />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Pronouns</span>
|
||||
<input id="marketPersonaPronouns" placeholder="e.g. she/her, they/them" />
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Appearance</span>
|
||||
<textarea id="marketPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Background</span>
|
||||
<textarea id="marketPersonaBackground" rows="3"
|
||||
placeholder="Backstory, history, role, or origins"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Details</span>
|
||||
<textarea id="marketPersonaDetails" rows="4"
|
||||
placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Example dialogues</span>
|
||||
<div class="dialogue-format-card" aria-hidden="true">
|
||||
<p class="dialogue-format-label">Format</p>
|
||||
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
|
||||
{{user}}: Hi, Marc!
|
||||
{{character}}: Nice to meet you</pre>
|
||||
</div>
|
||||
<textarea id="marketPersonaExampleDialogues" rows="5" placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
|
||||
</div>
|
||||
<textarea id="marketPersonaExampleDialogues" rows="5"
|
||||
placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="marketPersonaSave" type="submit" class="primary-action">Save AI Character</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="marketActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite"
|
||||
aria-atomic="true">
|
||||
<div class="chat-activity-card">
|
||||
<div class="chat-activity-mark" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<p id="marketActivityEyebrow" class="eyebrow">Please wait</p>
|
||||
<h3 id="marketActivityTitle">Opening roleplay</h3>
|
||||
<p id="marketActivityDetail" class="subtitle">The AI character is preparing the first message.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="marketPopupModal" class="modal-overlay hidden">
|
||||
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="marketPopupTitle"
|
||||
aria-describedby="marketPopupDescription">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p id="marketPopupEyebrow" class="eyebrow">Action</p>
|
||||
<h3 id="marketPopupTitle">Confirm action</h3>
|
||||
</div>
|
||||
<button id="marketPopupClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<p id="marketPopupDescription" class="subtitle">Please confirm this action.</p>
|
||||
<label id="marketPopupField" class="field">
|
||||
<span id="marketPopupInputLabel">Value</span>
|
||||
<input id="marketPopupInput" type="text" autocomplete="off" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="marketPersonaSave" type="submit" class="primary-action">Save AI Character</button>
|
||||
<div class="popup-actions">
|
||||
<button id="marketPopupCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="marketPopupConfirm" type="button">Confirm</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/js/market.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
|
||||
<div id="marketActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite" aria-atomic="true">
|
||||
<div class="chat-activity-card">
|
||||
<div class="chat-activity-mark" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<p id="marketActivityEyebrow" class="eyebrow">Please wait</p>
|
||||
<h3 id="marketActivityTitle">Opening roleplay</h3>
|
||||
<p id="marketActivityDetail" class="subtitle">The AI character is preparing the first message.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="marketPopupModal" class="modal-overlay hidden">
|
||||
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="marketPopupTitle" aria-describedby="marketPopupDescription">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p id="marketPopupEyebrow" class="eyebrow">Action</p>
|
||||
<h3 id="marketPopupTitle">Confirm action</h3>
|
||||
</div>
|
||||
<button id="marketPopupClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<p id="marketPopupDescription" class="subtitle">Please confirm this action.</p>
|
||||
<label id="marketPopupField" class="field">
|
||||
<span id="marketPopupInputLabel">Value</span>
|
||||
<input id="marketPopupInput" type="text" autocomplete="off"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button id="marketPopupCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="marketPopupConfirm" type="button">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/market.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script></body>
|
||||
</html>
|
||||
</html>
|
||||
+84
-76
@@ -1,94 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Privacy Policy – DeepFake</title>
|
||||
<link rel="stylesheet" href="style.css?v=20260909-account-controls" />
|
||||
<link rel="stylesheet" href="legal.css?v=20260909-account-controls" />
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
|
||||
<link rel="stylesheet" href="/css/legal.css?v=20260909-account-controls" />
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="site-page legal-page">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="legal-content">
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>Last updated: <strong>06.01.2026</strong></p>
|
||||
<!-- site-header -->
|
||||
<main id="main" class="legal-content">
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>Last updated: <strong>06.01.2026</strong></p>
|
||||
|
||||
<section>
|
||||
<h2>1. Controller / Contact</h2>
|
||||
<p><strong>Application:</strong> DeepFake</p>
|
||||
<p><strong>Owner/Controller:</strong> Kristian Cerni</p>
|
||||
<p><strong>Contact:</strong> <a href="https://github.com/KrisHD1337">https://github.com/KrisHD1337</a></p>
|
||||
<p>This policy applies to all users within Switzerland, the EU, and other jurisdictions.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>1. Controller / Contact</h2>
|
||||
<p><strong>Application:</strong> DeepFake</p>
|
||||
<p><strong>Owner/Controller:</strong> Kristian Cerni</p>
|
||||
<p><strong>Contact:</strong> <a href="https://github.com/KrisHD1337">https://github.com/KrisHD1337</a></p>
|
||||
<p>This policy applies to all users within Switzerland, the EU, and other jurisdictions.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>2. What We Collect</h2>
|
||||
<p>We collect only what’s needed to provide our service:</p>
|
||||
<ul>
|
||||
<li><strong>Account Data:</strong> Username, email (optional), hashed password</li>
|
||||
<li><strong>Chat Data:</strong> Messages you send and the AI responses</li>
|
||||
</ul>
|
||||
<p>All data is processed to provide your chat history and account functionality.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>2. What We Collect</h2>
|
||||
<p>We collect only what’s needed to provide our service:</p>
|
||||
<ul>
|
||||
<li><strong>Account Data:</strong> Username, email (optional), hashed password</li>
|
||||
<li><strong>Chat Data:</strong> Messages you send and the AI responses</li>
|
||||
</ul>
|
||||
<p>All data is processed to provide your chat history and account functionality.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. Legal Basis (GDPR)</h2>
|
||||
<p>For EU/EEA users, we process data based on:</p>
|
||||
<ul>
|
||||
<li>Contract performance (to provide your account and chat history)</li>
|
||||
<li>Legitimate interests (system security, fraud prevention)</li>
|
||||
</ul>
|
||||
<p>Your data will not be processed for marketing without explicit consent.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>3. Legal Basis (GDPR)</h2>
|
||||
<p>For EU/EEA users, we process data based on:</p>
|
||||
<ul>
|
||||
<li>Contract performance (to provide your account and chat history)</li>
|
||||
<li>Legitimate interests (system security, fraud prevention)</li>
|
||||
</ul>
|
||||
<p>Your data will not be processed for marketing without explicit consent.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4. How We Use Data</h2>
|
||||
<p>Your data is used to:</p>
|
||||
<ul>
|
||||
<li>Authenticate and manage your account</li>
|
||||
<li>Store and retrieve chat history</li>
|
||||
<li>Improve and maintain the app</li>
|
||||
</ul>
|
||||
<p>We do not sell your data.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>4. How We Use Data</h2>
|
||||
<p>Your data is used to:</p>
|
||||
<ul>
|
||||
<li>Authenticate and manage your account</li>
|
||||
<li>Store and retrieve chat history</li>
|
||||
<li>Improve and maintain the app</li>
|
||||
</ul>
|
||||
<p>We do not sell your data.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Data Retention</h2>
|
||||
<p>We retain data as long as your account is active. If you request deletion, your data will be deleted unless we must retain it for legal reasons.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>5. Data Retention</h2>
|
||||
<p>We retain data as long as your account is active. If you request deletion, your data will be deleted
|
||||
unless we must retain it for legal reasons.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Data Transfers</h2>
|
||||
<p>Your data may be stored or processed where our servers reside. Switzerland has an adequacy decision with the EU, meaning transfers can occur without additional safeguards.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>6. Data Transfers</h2>
|
||||
<p>Your data may be stored or processed where our servers reside. Switzerland has an adequacy decision with
|
||||
the EU, meaning transfers can occur without additional safeguards.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>7. Your Rights (GDPR + Swiss rights)</h2>
|
||||
<p>Depending on jurisdiction, you may have the right to:</p>
|
||||
<ul>
|
||||
<li>Access your data</li>
|
||||
<li>Correct inaccuracies</li>
|
||||
<li>Request deletion</li>
|
||||
<li>Restrict or object to processing</li>
|
||||
<li>Request data portability (GDPR)</li>
|
||||
</ul>
|
||||
<p>To exercise these rights, contact us at: <a href="mailto:kriscerni@gmail.com">kriscerni@gmail.com</a></p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>7. Your Rights (GDPR + Swiss rights)</h2>
|
||||
<p>Depending on jurisdiction, you may have the right to:</p>
|
||||
<ul>
|
||||
<li>Access your data</li>
|
||||
<li>Correct inaccuracies</li>
|
||||
<li>Request deletion</li>
|
||||
<li>Restrict or object to processing</li>
|
||||
<li>Request data portability (GDPR)</li>
|
||||
</ul>
|
||||
<p>To exercise these rights, contact us at: <a href="mailto:kriscerni@gmail.com">kriscerni@gmail.com</a></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>8. Data Security</h2>
|
||||
<p>We implement reasonable technical and organizational measures to protect your data — encryption for passwords and secure database access.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>8. Data Security</h2>
|
||||
<p>We implement reasonable technical and organizational measures to protect your data — encryption for
|
||||
passwords and secure database access.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>9. Changes to this Policy</h2>
|
||||
<p>We may update this document. Changes will be posted with a “Last updated” date.</p>
|
||||
</section>
|
||||
</main>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
<section>
|
||||
<h2>9. Changes to this Policy</h2>
|
||||
<p>We may update this document. Changes will be posted with a “Last updated” date.</p>
|
||||
</section>
|
||||
</main>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
+306
-285
@@ -1,303 +1,324 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
|
||||
<meta charset="UTF-8" />
|
||||
<title>Elsewhere Settings</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/style.css?v=20260909-account-controls"/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
</head>
|
||||
|
||||
<body class="site-page settings-page" data-theme="fakegpt" data-theme-mode="light">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="settings-shell page-content">
|
||||
<header class="market-header market-header-simple">
|
||||
<div>
|
||||
<p class="eyebrow">Elsewhere settings</p>
|
||||
<h1>Preferences</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p id="settingsNotice" class="status">Loading settings...</p>
|
||||
|
||||
<section class="settings-layout">
|
||||
<aside class="settings-nav" aria-label="Settings sections" role="tablist">
|
||||
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true" data-settings-view="personal">Account</button>
|
||||
|
||||
<button class="settings-nav-item" type="button" role="tab" aria-selected="false" data-settings-view="design">Appearance</button>
|
||||
<button id="diagnosticsNavItem" class="settings-nav-item hidden" type="button" role="tab" aria-selected="false" data-settings-view="diagnostics">Diagnostics</button>
|
||||
</aside>
|
||||
|
||||
<div class="settings-content">
|
||||
<section class="settings-view active" role="tabpanel" data-settings-panel="personal">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Profile</p>
|
||||
<h2>Username</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="usernameForm" class="settings-form-stack">
|
||||
<label class="field">
|
||||
<span>Current username</span>
|
||||
<input id="currentUsername" readonly/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>New username</span>
|
||||
<input id="newUsername" autocomplete="username"/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Current password</span>
|
||||
<input id="usernamePassword" type="password" autocomplete="current-password"/>
|
||||
</label>
|
||||
<button id="saveUsername" type="submit">Save username</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Security</p>
|
||||
<h2>Password</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="passwordForm" class="settings-form-stack">
|
||||
<label class="field">
|
||||
<span>Current password</span>
|
||||
<input id="currentPassword" type="password" autocomplete="current-password"/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>New password</span>
|
||||
<input id="newPassword" type="password" autocomplete="new-password"/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Repeat new password</span>
|
||||
<input id="repeatPassword" type="password" autocomplete="new-password"/>
|
||||
</label>
|
||||
<button id="savePassword" type="submit">Save password</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Account data</p>
|
||||
<h2>Export and sessions</h2>
|
||||
<p class="subtitle">Download your account data or sign out every active browser session.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="exportAccount" type="button" class="secondary-action">Export account</button>
|
||||
<button id="logoutAllSessions" type="button" class="secondary-action">Logout all sessions</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Danger zone</p>
|
||||
<h2>Delete account</h2>
|
||||
<p class="subtitle">This permanently removes your account, chats, personas, templates, and market activity.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="deleteAccount" type="button" class="secondary-action danger">Delete account</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="my-roleplay-companions">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Create & manage</p>
|
||||
<h2>My personas</h2>
|
||||
</div>
|
||||
<div class="market-header-actions action-pair">
|
||||
<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>
|
||||
<h3>AI Characters</h3>
|
||||
<div id="settingsAiCharacterList" class="persona-list"></div>
|
||||
<h3>User Personas</h3>
|
||||
<div id="settingsUserPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="design">
|
||||
<div class="settings-panel settings-panel-compact">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Appearance</p>
|
||||
<h2>Make yourself at home</h2>
|
||||
<p class="subtitle">Choose a style for your conversation space.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-theme-row">
|
||||
<label class="field">
|
||||
<span>Style</span>
|
||||
<select id="settingsThemeSelect">
|
||||
<option value="fakegpt">Grove</option>
|
||||
<option value="fraud">Parchment</option>
|
||||
<option value="germini">Aurora</option>
|
||||
<option value="slopilot">Current</option>
|
||||
<option value="beta-ai">Iris</option>
|
||||
<option value="confusity">Graphite</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Light or dark</span>
|
||||
<select id="themeModeSelect">
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</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>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="diagnostics">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Admin</p>
|
||||
<h2>Model diagnostics</h2>
|
||||
<p class="subtitle">Backend status, model inventory, latency, and configuration warnings.</p>
|
||||
</div>
|
||||
<button id="refreshDiagnostics" type="button" class="secondary-action">Refresh</button>
|
||||
</div>
|
||||
<div class="diagnostics-grid">
|
||||
<div class="diagnostics-card">
|
||||
<span>Status</span>
|
||||
<strong id="diagnosticsStatus">Unknown</strong>
|
||||
<p id="diagnosticsBackend" class="subtitle">Backend unavailable</p>
|
||||
</div>
|
||||
<div class="diagnostics-card">
|
||||
<span>Latency</span>
|
||||
<strong id="diagnosticsLatency">n/a</strong>
|
||||
<p id="diagnosticsCheckedAt" class="subtitle">Not checked yet</p>
|
||||
</div>
|
||||
<div class="diagnostics-card">
|
||||
<span>Models</span>
|
||||
<strong id="diagnosticsModelCount">0</strong>
|
||||
<p id="diagnosticsCacheAge" class="subtitle">Cache age n/a</p>
|
||||
</div>
|
||||
<div class="diagnostics-card">
|
||||
<span>Active requests</span>
|
||||
<strong id="diagnosticsActiveRequests">0</strong>
|
||||
<p id="diagnosticsLastFailure" class="subtitle">No recorded failures</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="diagnostics-columns">
|
||||
<div>
|
||||
<h3>Available models</h3>
|
||||
<div id="diagnosticsModels" class="diagnostics-list"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Config warnings</h3>
|
||||
<div id="diagnosticsWarnings" class="diagnostics-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="settingsPersonaModal" class="modal-overlay hidden">
|
||||
<div class="modal-card persona-modal-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Persona editor</p>
|
||||
<h3 id="settingsPersonaFormTitle">Create persona</h3>
|
||||
<!-- site-header -->
|
||||
<main id="main" class="settings-shell page-content">
|
||||
<header class="market-header market-header-simple">
|
||||
<div>
|
||||
<p class="eyebrow">Elsewhere settings</p>
|
||||
<h1>Preferences</h1>
|
||||
</div>
|
||||
<button id="settingsPersonaClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<form id="settingsPersonaForm" class="persona-form">
|
||||
<div class="persona-form-intro">
|
||||
<p id="settingsPersonaHelper" class="persona-helper">Create a persona for roleplay chats.</p>
|
||||
</header>
|
||||
|
||||
<p id="settingsNotice" class="status">Loading settings...</p>
|
||||
|
||||
<section class="settings-layout">
|
||||
<aside class="settings-nav" aria-label="Settings sections" role="tablist">
|
||||
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true"
|
||||
data-settings-view="personal">Account</button>
|
||||
|
||||
<button class="settings-nav-item" type="button" role="tab" aria-selected="false"
|
||||
data-settings-view="design">Appearance</button>
|
||||
<button id="diagnosticsNavItem" class="settings-nav-item hidden" type="button" role="tab"
|
||||
aria-selected="false" data-settings-view="diagnostics">Diagnostics</button>
|
||||
</aside>
|
||||
|
||||
<div class="settings-content">
|
||||
<section class="settings-view active" role="tabpanel" data-settings-panel="personal">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Profile</p>
|
||||
<h2>Username</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="usernameForm" class="settings-form-stack">
|
||||
<label class="field">
|
||||
<span>Current username</span>
|
||||
<input id="currentUsername" readonly />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>New username</span>
|
||||
<input id="newUsername" autocomplete="username" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Current password</span>
|
||||
<input id="usernamePassword" type="password" autocomplete="current-password" />
|
||||
</label>
|
||||
<button id="saveUsername" type="submit">Save username</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Security</p>
|
||||
<h2>Password</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="passwordForm" class="settings-form-stack">
|
||||
<label class="field">
|
||||
<span>Current password</span>
|
||||
<input id="currentPassword" type="password" autocomplete="current-password" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>New password</span>
|
||||
<input id="newPassword" type="password" autocomplete="new-password" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Repeat new password</span>
|
||||
<input id="repeatPassword" type="password" autocomplete="new-password" />
|
||||
</label>
|
||||
<button id="savePassword" type="submit">Save password</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Account data</p>
|
||||
<h2>Export and sessions</h2>
|
||||
<p class="subtitle">Download your account data or sign out every active browser session.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="exportAccount" type="button" class="secondary-action">Export account</button>
|
||||
<button id="logoutAllSessions" type="button" class="secondary-action">Logout all
|
||||
sessions</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Danger zone</p>
|
||||
<h2>Delete account</h2>
|
||||
<p class="subtitle">This permanently removes your account, chats, personas, templates,
|
||||
and market activity.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="deleteAccount" type="button" class="secondary-action danger">Delete
|
||||
account</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true"
|
||||
data-settings-panel="my-roleplay-companions">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Create & manage</p>
|
||||
<h2>My personas</h2>
|
||||
</div>
|
||||
<div class="market-header-actions action-pair">
|
||||
<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>
|
||||
<h3>AI Characters</h3>
|
||||
<div id="settingsAiCharacterList" class="persona-list"></div>
|
||||
<h3>User Personas</h3>
|
||||
<div id="settingsUserPersonaList" class="persona-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="design">
|
||||
<div class="settings-panel settings-panel-compact">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Appearance</p>
|
||||
<h2>Make yourself at home</h2>
|
||||
<p class="subtitle">Choose a style for your conversation space.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-theme-row">
|
||||
<label class="field">
|
||||
<span>Style</span>
|
||||
<select id="settingsThemeSelect">
|
||||
<option value="fakegpt">Grove</option>
|
||||
<option value="fraud">Parchment</option>
|
||||
<option value="germini">Aurora</option>
|
||||
<option value="slopilot">Current</option>
|
||||
<option value="beta-ai">Iris</option>
|
||||
<option value="confusity">Graphite</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Light or dark</span>
|
||||
<select id="themeModeSelect">
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</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>
|
||||
</section>
|
||||
|
||||
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="diagnostics">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Admin</p>
|
||||
<h2>Model diagnostics</h2>
|
||||
<p class="subtitle">Backend status, model inventory, latency, and configuration
|
||||
warnings.</p>
|
||||
</div>
|
||||
<button id="refreshDiagnostics" type="button" class="secondary-action">Refresh</button>
|
||||
</div>
|
||||
<div class="diagnostics-grid">
|
||||
<div class="diagnostics-card">
|
||||
<span>Status</span>
|
||||
<strong id="diagnosticsStatus">Unknown</strong>
|
||||
<p id="diagnosticsBackend" class="subtitle">Backend unavailable</p>
|
||||
</div>
|
||||
<div class="diagnostics-card">
|
||||
<span>Latency</span>
|
||||
<strong id="diagnosticsLatency">n/a</strong>
|
||||
<p id="diagnosticsCheckedAt" class="subtitle">Not checked yet</p>
|
||||
</div>
|
||||
<div class="diagnostics-card">
|
||||
<span>Models</span>
|
||||
<strong id="diagnosticsModelCount">0</strong>
|
||||
<p id="diagnosticsCacheAge" class="subtitle">Cache age n/a</p>
|
||||
</div>
|
||||
<div class="diagnostics-card">
|
||||
<span>Active requests</span>
|
||||
<strong id="diagnosticsActiveRequests">0</strong>
|
||||
<p id="diagnosticsLastFailure" class="subtitle">No recorded failures</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="diagnostics-columns">
|
||||
<div>
|
||||
<h3>Available models</h3>
|
||||
<div id="diagnosticsModels" class="diagnostics-list"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Config warnings</h3>
|
||||
<div id="diagnosticsWarnings" class="diagnostics-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<p id="settingsPersonaFormNotice" class="status hidden"></p>
|
||||
<div class="persona-form-grid">
|
||||
<label class="field field-wide">
|
||||
<span>Persona for</span>
|
||||
<select id="settingsPersonaType">
|
||||
<option value="assistant">AI Character</option>
|
||||
<option value="user">Your persona</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Name</span>
|
||||
<input id="settingsPersonaName" placeholder="Persona name" required/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Pronouns</span>
|
||||
<input id="settingsPersonaPronouns" placeholder="e.g. she/her, they/them"/>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Appearance</span>
|
||||
<textarea id="settingsPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Background</span>
|
||||
<textarea id="settingsPersonaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Details</span>
|
||||
<textarea id="settingsPersonaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
|
||||
</label>
|
||||
<label id="settingsPersonaExamplesField" class="field field-wide hidden">
|
||||
<span>Example dialogues</span>
|
||||
<div class="dialogue-format-card" aria-hidden="true">
|
||||
<p class="dialogue-format-label">Format</p>
|
||||
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="settingsPersonaModal" class="modal-overlay hidden">
|
||||
<div class="modal-card persona-modal-card">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Persona editor</p>
|
||||
<h3 id="settingsPersonaFormTitle">Create persona</h3>
|
||||
</div>
|
||||
<button id="settingsPersonaClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<form id="settingsPersonaForm" class="persona-form">
|
||||
<div class="persona-form-intro">
|
||||
<p id="settingsPersonaHelper" class="persona-helper">Create a persona for roleplay chats.</p>
|
||||
</div>
|
||||
<p id="settingsPersonaFormNotice" class="status hidden"></p>
|
||||
<div class="persona-form-grid">
|
||||
<label class="field field-wide">
|
||||
<span>Persona for</span>
|
||||
<select id="settingsPersonaType">
|
||||
<option value="assistant">AI Character</option>
|
||||
<option value="user">Your persona</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Name</span>
|
||||
<input id="settingsPersonaName" placeholder="Persona name" required />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Pronouns</span>
|
||||
<input id="settingsPersonaPronouns" placeholder="e.g. she/her, they/them" />
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Appearance</span>
|
||||
<textarea id="settingsPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Background</span>
|
||||
<textarea id="settingsPersonaBackground" rows="3"
|
||||
placeholder="Backstory, history, role, or origins"></textarea>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Details</span>
|
||||
<textarea id="settingsPersonaDetails" rows="4"
|
||||
placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
|
||||
</label>
|
||||
<label id="settingsPersonaExamplesField" class="field field-wide hidden">
|
||||
<span>Example dialogues</span>
|
||||
<div class="dialogue-format-card" aria-hidden="true">
|
||||
<p class="dialogue-format-label">Format</p>
|
||||
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
|
||||
{{user}}: Hi, Marc!
|
||||
{{character}}: Nice to meet you</pre>
|
||||
</div>
|
||||
<textarea id="settingsPersonaExampleDialogues" rows="5" placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
|
||||
</div>
|
||||
<textarea id="settingsPersonaExampleDialogues" rows="5"
|
||||
placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="settingsPersonaSave" type="submit" class="primary-action">Save persona</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settingsPopupModal" class="modal-overlay hidden">
|
||||
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="settingsPopupTitle"
|
||||
aria-describedby="settingsPopupDescription">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p id="settingsPopupEyebrow" class="eyebrow">Action</p>
|
||||
<h3 id="settingsPopupTitle">Confirm action</h3>
|
||||
</div>
|
||||
<button id="settingsPopupClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p>
|
||||
<label id="settingsPopupField" class="field">
|
||||
<span id="settingsPopupInputLabel">Value</span>
|
||||
<input id="settingsPopupInput" type="text" autocomplete="off" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="persona-actions">
|
||||
<button id="settingsPersonaSave" type="submit" class="primary-action">Save persona</button>
|
||||
<div class="popup-actions">
|
||||
<button id="settingsPopupCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="settingsPopupConfirm" type="button">Confirm</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/js/settings.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
|
||||
<div id="settingsPopupModal" class="modal-overlay hidden">
|
||||
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="settingsPopupTitle" aria-describedby="settingsPopupDescription">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p id="settingsPopupEyebrow" class="eyebrow">Action</p>
|
||||
<h3 id="settingsPopupTitle">Confirm action</h3>
|
||||
</div>
|
||||
<button id="settingsPopupClose" type="button" class="icon-button">Close</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p>
|
||||
<label id="settingsPopupField" class="field">
|
||||
<span id="settingsPopupInputLabel">Value</span>
|
||||
<input id="settingsPopupInput" type="text" autocomplete="off"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button id="settingsPopupCancel" type="button" class="secondary-action">Cancel</button>
|
||||
<button id="settingsPopupConfirm" type="button">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/settings.js?v=20260909-account-controls"></script>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script></body>
|
||||
</html>
|
||||
</html>
|
||||
+47
-38
@@ -1,52 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Terms of Service – DeepFake</title>
|
||||
<link rel="stylesheet" href="style.css?v=20260909-account-controls" />
|
||||
<link rel="stylesheet" href="legal.css?v=20260909-account-controls" />
|
||||
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
|
||||
<link rel="stylesheet" href="/css/legal.css?v=20260909-account-controls" />
|
||||
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
|
||||
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="site-page legal-page">
|
||||
<!-- site-header -->
|
||||
<main id="main" class="legal-content">
|
||||
<h1>Terms of Service</h1>
|
||||
<p>Last updated: <strong>06.01.2026</strong></p>
|
||||
<!-- site-header -->
|
||||
<main id="main" class="legal-content">
|
||||
<h1>Terms of Service</h1>
|
||||
<p>Last updated: <strong>06.01.2026</strong></p>
|
||||
|
||||
<section>
|
||||
<h2>Service</h2>
|
||||
<p>You agree that DeepFake AI is provided “as is,” and functionality may change without notice.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Service</h2>
|
||||
<p>You agree that DeepFake AI is provided “as is,” and functionality may change without notice.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>User Conduct</h2>
|
||||
<p>You are responsible for content you submit and for maintaining secure account credentials. Do not use DeepFake for unlawful purposes.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>User Conduct</h2>
|
||||
<p>You are responsible for content you submit and for maintaining secure account credentials. Do not use
|
||||
DeepFake for unlawful purposes.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Data Processing</h2>
|
||||
<p>By using the service, you consent to storing and processing your data as described in the <a href="/privacy.html">Privacy Policy</a>.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Data Processing</h2>
|
||||
<p>By using the service, you consent to storing and processing your data as described in the <a
|
||||
href="/privacy.html">Privacy Policy</a>.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Limitation of Liability</h2>
|
||||
<p>We are not liable for service interruptions, data loss, or any indirect damages arising from your use of DeepFake AI.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Limitation of Liability</h2>
|
||||
<p>We are not liable for service interruptions, data loss, or any indirect damages arising from your use of
|
||||
DeepFake AI.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Governing Law</h2>
|
||||
<p>These Terms are governed by applicable laws in your jurisdiction (e.g., Switzerland or EU Member States).</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Governing Law</h2>
|
||||
<p>These Terms are governed by applicable laws in your jurisdiction (e.g., Switzerland or EU Member States).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Changes to Terms</h2>
|
||||
<p>We may modify these Terms at any time. Updates will be reflected with a new “Last updated” date.</p>
|
||||
</section>
|
||||
</main>
|
||||
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
<section>
|
||||
<h2>Changes to Terms</h2>
|
||||
<p>We may modify these Terms at any time. Updates will be reflected with a new “Last updated” date.</p>
|
||||
</section>
|
||||
</main>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user