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