frontend: standardize base styles and color scheme, rework chat input section

This commit is contained in:
Shivering Ocelot committed 2026-09-26 09:13:14 -05:00
1 parent 98fbed03cc
commit 1232872360
8 files changed
+167 -88

No files matched your search

+14 -1
View File
@@ -6,7 +6,7 @@
--vt-c-black: #181818; --vt-c-black: #181818;
--vt-c-black-soft: #222222; --vt-c-black-soft: #222222;
--vt-c-black-mute: #282828; --vt-c-black-mute: #2c2c2c;
--vt-c-indigo: #2c3e50; --vt-c-indigo: #2c3e50;
@@ -25,17 +25,25 @@
--color-green: #00bd7e; --color-green: #00bd7e;
--color-green-dark: #003825; --color-green-dark: #003825;
--color-blue: #3d8de1; --color-blue: #3d8de1;
--color-violet: #9250ff;
} }
/* semantic color and size variables for this project */ /* semantic color and size variables for this project */
:root { :root {
--color-background: var(--vt-c-black); --color-background: var(--vt-c-black);
/* only one background, the rest should be surface colors */
--color-background-soft: var(--vt-c-black-soft); --color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute); --color-background-mute: var(--vt-c-black-mute);
--color-background-hover: var(--vt-c-black-mute); --color-background-hover: var(--vt-c-black-mute);
--color-background-active: var(--vt-c-black-mute); --color-background-active: var(--vt-c-black-mute);
--color-surface: var(--color-background-soft);
--color-surface-strong: var(--color-background-mute);
--color-surface-input: var(--color-background);
--color-border: var(--vt-c-divider-dark-2); --color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1); --color-border-hover: var(--vt-c-divider-dark-1);
@@ -46,6 +54,11 @@
--color-active: var(--color-heading); --color-active: var(--color-heading);
--color-highlight: var(--color-violet);
--color-primary: var(--color-green);
--color-secondary: var(--color-blue);
--color-error: #d54141;
--border-radius: 6px; --border-radius: 6px;
--section-gap: 160px; --section-gap: 160px;
--header-height: 4em; --header-height: 4em;
+48 -4
View File
@@ -13,6 +13,14 @@ main {
overflow: auto; overflow: auto;
} }
main section {
margin: 1em;
padding: 1em;
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
background-color: var(--color-surface);
}
.main-sidebar { .main-sidebar {
height: 100dvh; height: 100dvh;
display: flex; display: flex;
@@ -37,17 +45,53 @@ a {
padding: 3px; padding: 3px;
} }
button { button, .button {
color: inherit; color: var(--color-text);
background-color: var(--color-background-soft); background-color: var(--color-surface-strong);
padding: 0.6em;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
button { button.hollow, .button.hollow {
background: none;
border: none;
}
button.hollow:hover {
border: none;
}
button:hover {
color: var(--color-heading);
border-color: var(--color-border-hover); border-color: var(--color-border-hover);
} }
button.hollow.primary { color: var(--color-primary); }
button.hollow.error { color: var(--color-error); }
input, textarea, select {
font-size: inherit;
padding: 0.6em;
border: 1px solid var(--color-border);
color: var(--color-text);
background-color: var(--color-surface-input);
border-radius: var(--border-radius);
}
input:hover, textarea:hover, select:hover {
border: 1px solid var(--color-border-hover);
}
input:focus, textarea:focus, select:focus {
outline: none;
border: 1px solid var(--color-primary);
}
input:disabled, textarea:disabled, select:disabled {
background-color: var(--color-surface);
}
.with-sidebar-expanded .when-sidebar-collapsed { .with-sidebar-expanded .when-sidebar-collapsed {
display: none; display: none;
} }
+6 -12
View File
@@ -150,18 +150,12 @@ export class ChatSession {
// fast access map // fast access map
this.threadMap = {}; this.threadMap = {};
this.editInfo = computed(() => { this.statusLine = computed(() => {
const parts = [this.creatingThread.value ? 'New Thread' : `Thread ${this.currentThreadId.value}`]; return this.creatingThread.value ?
if (this.editingMessageId.value != null) { (this.editingMessageId.value != null ? 'Forking Message' : 'New Thread') :
if (this.noteMode.value) { (this.editingMessageId.value != null ? 'Editing Message' :
parts.push(`Editing Note ${this.editingMessageId.value}`); this.currentThreadId == null ? 'Writing in Chat Root' : null
} else { );
parts.push(`Editing ${this.editingMessageId.value}`);
}
} else if (this.noteMode.value) {
parts.push('Editing Note');
}
return parts.join(' - ');
}); });
// Internals // Internals
+8 -13
View File
@@ -43,8 +43,7 @@ function chatEntryClass(msg) {
return ['log']; return ['log'];
} else if (msg.type == 'thread-header') { } else if (msg.type == 'thread-header') {
let cls = ['thread']; let cls = ['thread'];
if (!msg.active) cls.push('thread-open') if (!msg.active) cls.push(msg.isFork ? 'thread-fork' : 'thread-closed')
if (msg.isFork) cls.push('thread-fork');
return cls; return cls;
} else { } else {
return ['info']; return ['info'];
@@ -247,22 +246,18 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
width: 100%; width: 100%;
text-align: center; text-align: center;
user-select: none; user-select: none;
border-top: 1px solid var(--color-border);
} }
.thread.thread-open { .thread:hover {
border-color: 1px solid var(--color-text);
}
.thread-closed {
border-bottom: 1px solid var(--color-border);
cursor: pointer; cursor: pointer;
} }
.thread-open:hover { .thread-fork {
border-top: 1px solid var(--color-heading);
}
.thread.thread-fork {
border-top: none;
}
.thread.thread-fork.thread-open {
width: fit-content; width: fit-content;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
+4
View File
@@ -27,6 +27,10 @@
padding: 0 0.4em; padding: 0 0.4em;
} }
.main-header {
background-color: var(--color-surface);
}
.header-button { .header-button {
height: var(--header-height); height: var(--header-height);
width: var(--header-height); width: var(--header-height);
+17 -9
View File
@@ -124,6 +124,10 @@
</template> </template>
<style scoped> <style scoped>
.main-sidebar {
background-color: var(--color-surface);
}
.tab-bar, .tab-group { .tab-bar, .tab-group {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -147,16 +151,19 @@
/*flex-grow: 1;*/ /*flex-grow: 1;*/
} }
.tab:hover { .tab.active, .tab:hover {
background-color: var(--color-background-mute); background: linear-gradient(to bottom, var(--color-surface-strong) 20%, var(--color-surface) 100%);
} }
.tab.active { .tab:hover {
color: var(--color-text-heading); color: var(--color-heading);
background-color: var(--color-background);
padding-top: 0; padding-top: 0;
border-top: 3px solid var(--color-active); border-top: 3px solid var(--color-heading);
}
.tab.active {
color: var(--color-highlight);
padding-top: 0;
border-top: 3px solid var(--color-highlight);
} }
.nav-item { .nav-item {
@@ -177,7 +184,8 @@
} }
.nav-item:hover { .nav-item:hover {
background-color: rgba(255, 255, 255, 0.1); color: var(--color-heading);
background-color: var(--color-surface-strong);
} }
.nav-item i { .nav-item i {
@@ -197,7 +205,7 @@
/* Adapt sidebar to small screens */ /* Adapt sidebar to small screens */
@media (max-width: 480px) { @media (max-width: 480px) {
.sidebar { .main-sidebar {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
-7
View File
@@ -350,13 +350,6 @@
</template> </template>
<style scoped> <style scoped>
section {
margin: 1em;
padding: 1em;
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
}
.basic-config > div { .basic-config > div {
margin-right: 2em; margin-right: 2em;
display: inline-block; display: inline-block;
+70 -42
View File
@@ -11,6 +11,8 @@
import * as ui from '../ui'; import * as ui from '../ui';
import { ChatSession } from '../chat'; import { ChatSession } from '../chat';
import * as iconoir from '@iconoir/vue';
const props = defineProps({ const props = defineProps({
id: { id: {
type: String, type: String,
@@ -19,10 +21,12 @@
}); });
// Chat state and logic live in the session; the view binds to its refs // Chat state and logic live in the session; the view binds to its refs
// only declared refs work reactively
const session = new ChatSession(); const session = new ChatSession();
const { const {
chat, chatEntries, editInfo, chat, chatEntries, statusLine,
noteMode, editingMessageId, messageRunMap, noteMode, editingMessageId, messageRunMap,
creatingThread,
} = session; } = session;
// UI state coupled to the document itself // UI state coupled to the document itself
@@ -96,6 +100,14 @@
ui.alert("TODO: Rename Chats"); ui.alert("TODO: Rename Chats");
} }
async function newThreadClick () {
if (editingMessageId.value != null) {
creatingThread.value = !creatingThread.value;
} else {
session.startNewThread()
}
}
// === Special Chat Functions === // // === Special Chat Functions === //
@@ -116,7 +128,6 @@
{value: 'pin', title: "Pin message"}, {value: 'pin', title: "Pin message"},
{value: 'edit', title: "Edit in-place"}, {value: 'edit', title: "Edit in-place"},
{value: 'edit-fork', title: "Edit in new thread"}, {value: 'edit-fork', title: "Edit in new thread"},
{value: 'fork', title: "Fork thread from here"},
]; ];
// Long logs can be expanded/collapsed // Long logs can be expanded/collapsed
@@ -141,12 +152,6 @@
editMessage(message, true); editMessage(message, true);
break; break;
} }
case 'fork': {
let newThread = await session.createFork(message.threadId, message.id);
ui.alert(`Forked thread ${newThread.id} from ${message.threadId}`);
session.activateThread(newThread.id);
break;
}
case 'toggle-log': { case 'toggle-log': {
expandedLogs.toggle(message.id); expandedLogs.toggle(message.id);
break; break;
@@ -213,35 +218,42 @@
</div> </div>
<div class="input-container"> <div class="input-container">
<div v-if="editInfo" style="font-size: 75%; margin-bottom: 0.7em;"> <div v-if="statusLine" class="status-line">
{{editInfo}} {{statusLine}}
<button class="hollow status-close" v-if="editingMessageId != null" @click="editMessage(null)">
<iconoir.Xmark />
</button>
</div> </div>
<div class="input-field" ref="inputFieldElem" @keydown.ctrl.enter.prevent="sendMessage" contenteditable></div> <div class="input-field" ref="inputFieldElem" @keydown.ctrl.enter.prevent="sendMessage" contenteditable></div>
<div class="input-row"> <div class="input-row">
<button @click="session.startNewThread()"> <div>
New Thread <button class="hollow" :class="{primary: creatingThread}" @click="newThreadClick()">
</button> <iconoir.ChatPlusInSolid v-if='creatingThread' />
<button @click="addRemoteResource()"> <iconoir.ChatPlusIn v-else />
Add Remote </button>
</button> <button class="hollow" :class="{primary: noteMode}" @click="noteMode=!noteMode">
<button @click="noteMode=!noteMode"> <iconoir.PostSolid v-if='noteMode' />
{{noteMode ? 'Cancel Note' : 'Make Note'}} <iconoir.Post v-else />
</button> </button>
<button v-if="editingMessageId != null" @click="editMessage(null)"> <button class="hollow" @click="sendPicture()">
Cancel Edit <iconoir.Camera />
</button> </button>
<button @click="sendPicture()"> <button class="hollow" @click="uploadFile()">
Send Picture <iconoir.Attachment />
</button> </button>
<button @click="uploadFile()"> </div>
Upload File <div>
</button> <button class="hollow" @click="addRemoteResource()">
<button id="send-button" @click="sendMessage()"> Add Remote
Send </button>
<span style="font-size: 66%;">(Ctrl+Enter)</span> </div>
</button> <div>
<button class="hollow primary" id="send-button" @click="sendMessage()">
<iconoir.SendDiagonalSolid width="3em" height="3em" />
</button>
</div>
</div> </div>
</div> </div>
</main> </main>
@@ -253,6 +265,17 @@
flex-direction: column; flex-direction: column;
} }
.status-line {
font-size: 75%;
margin-bottom: 0.7em;
}
.status-close {
float: right;
padding: 0;
margin-right: 1em;
}
.messages-container { .messages-container {
flex: 1; flex: 1;
overflow-y: scroll; overflow-y: scroll;
@@ -272,13 +295,17 @@
.input-container { .input-container {
padding: 10px; padding: 10px;
padding-bottom: 0;
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
display: flex;
flex-direction: column;
} }
.input-field { .input-field {
white-space: preserve; white-space: preserve;
outline: none; outline: none;
margin-bottom: 0.5em;
} }
.input-field:empty::before { .input-field:empty::before {
@@ -288,19 +315,20 @@
.input-row { .input-row {
width: 100%; width: 100%;
display: flex;
justify-content: space-between;
align-items: end;
}
.input-row button {
width: 4em;
height: 4em;
padding: 0;
} }
#send-button { #send-button {
padding: 8px 16px; width: 5em;
border: none; height: 4em;
border-radius: 4px;
background-color: #4caf50;
color: #fff;
cursor: pointer; cursor: pointer;
height: min-content;
/* align-self: end; /* for flex rows */
float: right; /* for flow rows */
} }
</style> </style>