frontend: sidebar rework, add in chat
This commit is contained in:
1 parent
1232872360
commit
5b75d95a8c
7 files changed
+317
-270
No files matched your search
+167
-3
@@ -1,10 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
import Sidebar from './components/main/Sidebar.vue'
|
import Sidebar from './components/Sidebar.vue'
|
||||||
import Alerts from './components/main/Alerts.vue'
|
import Alerts from './components/main/Alerts.vue'
|
||||||
import ContextMenu from './components/main/ContextMenu.vue'
|
import ContextMenu from './components/main/ContextMenu.vue'
|
||||||
|
|
||||||
|
import * as iconoir from '@iconoir/vue';
|
||||||
|
|
||||||
import * as ui from './ui';
|
import * as ui from './ui';
|
||||||
import * as backend from './backend';
|
import * as backend from './backend';
|
||||||
import * as runs from './runs';
|
import * as runs from './runs';
|
||||||
@@ -12,6 +14,26 @@ import * as runs from './runs';
|
|||||||
// possible states: loading, login, signup, logged
|
// possible states: loading, login, signup, logged
|
||||||
const loginState = ref('loading');
|
const loginState = ref('loading');
|
||||||
|
|
||||||
|
// sidebar state
|
||||||
|
const sidebarCollapsed = ref(false);
|
||||||
|
const mode = ref('chats');
|
||||||
|
const chats = ref([]);
|
||||||
|
const agents = ref([]);
|
||||||
|
|
||||||
|
// View titles use this class for their sidebar interactions
|
||||||
|
watch(sidebarCollapsed, newValue => {
|
||||||
|
if (newValue) {
|
||||||
|
document.body.classList.remove('with-sidebar-expanded');
|
||||||
|
} else {
|
||||||
|
document.body.classList.add('with-sidebar-expanded');
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
// They communicate upwards too
|
||||||
|
ui.listen('sidebar-expand', ev => {
|
||||||
|
sidebarCollapsed.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
function handleAuthResult (success) {
|
function handleAuthResult (success) {
|
||||||
loginState.value = success ? 'logged' : 'login';
|
loginState.value = success ? 'logged' : 'login';
|
||||||
}
|
}
|
||||||
@@ -39,6 +61,18 @@ function signupSubmit (formElem) {
|
|||||||
backend.signup(username, email, password).then(handleAuthResult);
|
backend.signup(username, email, password).then(handleAuthResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate the sidebar nav once the user is logged in
|
||||||
|
watch(loginState, async state => {
|
||||||
|
if (state != 'logged') return;
|
||||||
|
|
||||||
|
chats.value = (await backend.getChats()).map(it => {
|
||||||
|
return Object.assign({}, it);
|
||||||
|
});
|
||||||
|
agents.value = (await backend.getAgents()).map(it => {
|
||||||
|
return Object.assign({}, it);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const runsListener = runs.onCreated(run => {
|
const runsListener = runs.onCreated(run => {
|
||||||
console.log("new run", run);
|
console.log("new run", run);
|
||||||
});
|
});
|
||||||
@@ -93,7 +127,63 @@ backend.authResult().then(handleAuthResult);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="loginState=='logged'">
|
<template v-if="loginState=='logged'">
|
||||||
<Sidebar />
|
<Sidebar align="left" minWidth=150 maxWidth=400 v-model:collapsed="sidebarCollapsed">
|
||||||
|
<div class="tab-bar">
|
||||||
|
<div class="tab-group">
|
||||||
|
<div @click="mode='chats'" class="tab" :class="{active: mode=='chats'}">
|
||||||
|
<iconoir.Message />
|
||||||
|
</div>
|
||||||
|
<div @click="mode='agents'" class="tab" :class="{active: mode=='agents'}">
|
||||||
|
<iconoir.Group />
|
||||||
|
</div>
|
||||||
|
<div @click="mode='settings'" class="tab" :class="{active: mode=='settings'}">
|
||||||
|
<iconoir.Settings />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-group">
|
||||||
|
<button class="hollow icon" style="margin: auto;" @click="sidebarCollapsed=true;">
|
||||||
|
<<
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav v-if="mode=='runs'">
|
||||||
|
</nav>
|
||||||
|
<nav v-if="mode=='chats'">
|
||||||
|
<RouterLink v-for="it in chats"
|
||||||
|
:to="'/chat/' + it.id"
|
||||||
|
activeClass="active"
|
||||||
|
class="nav-item">
|
||||||
|
<iconoir.Message class="icon" />{{it.name}}
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
to="/chat/create"
|
||||||
|
activeClass="active"
|
||||||
|
class="nav-item">
|
||||||
|
<iconoir.PlusCircle class="icon" />Create Chat
|
||||||
|
</RouterLink>
|
||||||
|
</nav>
|
||||||
|
<nav v-if="mode=='agents'">
|
||||||
|
<RouterLink v-for="it in agents"
|
||||||
|
:to="'/agent/' + it.agentname"
|
||||||
|
activeClass="active"
|
||||||
|
class="nav-item">
|
||||||
|
<iconoir.User class="icon" />{{it.name}}
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
to="/agent/create"
|
||||||
|
activeClass="active"
|
||||||
|
class="nav-item">
|
||||||
|
<iconoir.PlusCircle class="icon" />Create Agent
|
||||||
|
</RouterLink>
|
||||||
|
</nav>
|
||||||
|
<nav v-if="mode=='settings'">
|
||||||
|
<RouterLink to="/user" activeClass="active" class="nav-item no-icon">User</RouterLink>
|
||||||
|
<RouterLink to="/models" activeClass="active" class="nav-item no-icon">Manage Models</RouterLink>
|
||||||
|
<RouterLink to="/remotes" activeClass="active" class="nav-item no-icon">Remotes</RouterLink>
|
||||||
|
<RouterLink to="/drives" activeClass="active" class="nav-item no-icon">Drives</RouterLink>
|
||||||
|
<RouterLink to="/runs" activeClass="active" class="nav-item no-icon">Agent Runs</RouterLink>
|
||||||
|
</nav>
|
||||||
|
</Sidebar>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -118,4 +208,78 @@ form {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sidebar tabs (slot content, compiled in this component's scope) */
|
||||||
|
.tab {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
list-style-type: none;
|
||||||
|
height: var(--header-height);
|
||||||
|
width: var(--header-height);
|
||||||
|
|
||||||
|
padding-top: 3px;
|
||||||
|
color: var(--color-text-mute);
|
||||||
|
/*flex-grow: 1;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-bar, .tab-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.tab-bar {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active, .tab:hover {
|
||||||
|
background: linear-gradient(to bottom, var(--color-surface-strong) 20%, var(--color-surface) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
color: var(--color-heading);
|
||||||
|
padding-top: 0;
|
||||||
|
border-top: 3px solid var(--color-heading);
|
||||||
|
}
|
||||||
|
.tab.active {
|
||||||
|
color: var(--color-highlight);
|
||||||
|
padding-top: 0;
|
||||||
|
border-top: 3px solid var(--color-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar nav items */
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
|
||||||
|
padding: 0 0.6em;
|
||||||
|
width: 100%;
|
||||||
|
height: 3em;
|
||||||
|
|
||||||
|
border-left: 3px solid rgba(1,1,1,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item .icon {
|
||||||
|
margin-right: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
color: var(--color-heading);
|
||||||
|
background: linear-gradient(to left, var(--color-surface) 0%, var(--color-surface-strong) 80%);
|
||||||
|
border-color: var(--color-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
color: var(--color-highlight);
|
||||||
|
background: linear-gradient(to left, var(--color-surface) 0%, var(--color-surface-strong) 80%);
|
||||||
|
border-color: var(--color-highlight);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
--color-surface: var(--color-background-soft);
|
--color-surface: var(--color-background-soft);
|
||||||
--color-surface-strong: var(--color-background-mute);
|
--color-surface-strong: var(--color-background-mute);
|
||||||
--color-surface-input: var(--color-background);
|
--color-surface-input: #1e1e1e;
|
||||||
|
|
||||||
--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);
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ main {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
main section {
|
.row { display: flex; flex-direction: row; flex: 1; }
|
||||||
|
.column { display: flex; flex-direction: column; flex: 1; }
|
||||||
|
|
||||||
|
main section, .sidebar section {
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
@@ -21,15 +24,6 @@ main section {
|
|||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-sidebar {
|
|
||||||
height: 100dvh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
z-index: var(--z-index-sidebar);
|
|
||||||
background-color: var(--color-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-header {
|
.main-header {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
@@ -62,6 +56,12 @@ button.hollow:hover {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.icon {
|
||||||
|
width: 3em;
|
||||||
|
height: 3em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
color: var(--color-heading);
|
color: var(--color-heading);
|
||||||
border-color: var(--color-border-hover);
|
border-color: var(--color-border-hover);
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
// The container owns the collapsed state via v-model:collapsed
|
||||||
|
const collapsed = defineModel('collapsed');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// Which side of the layout the sidebar sits on. Required.
|
||||||
|
align: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
validator: (value) => ['left', 'right'].includes(value)
|
||||||
|
},
|
||||||
|
minWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
validator: (value) => isFinite(parseNumber(value)),
|
||||||
|
},
|
||||||
|
maxWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: Infinity,
|
||||||
|
validator: (value) => isFinite(parseNumber(value)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sidebarWidth = ref(200);
|
||||||
|
let dragState = null;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.body.addEventListener('mousemove', documentMouseMove);
|
||||||
|
document.body.addEventListener('mouseup', documentMouseUp);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.body.removeEventListener('mousemove', documentMouseMove);
|
||||||
|
document.body.removeEventListener('mouseup', documentMouseUp);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch([props.minWidth, sidebarWidth.value], ([min, width]) => {
|
||||||
|
if (width < min) sidebarWidth.value = min;
|
||||||
|
}, {immediate: true});
|
||||||
|
|
||||||
|
function dragBarMouseDown (event) {
|
||||||
|
dragState = {x: event.clientX, y: event.clientY, width: sidebarWidth.value};
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentMouseMove (event) {
|
||||||
|
if (dragState) {
|
||||||
|
let widthDiff = event.clientX - dragState.x;
|
||||||
|
// A right-aligned sidebar grows when dragging towards the left.
|
||||||
|
if (props.align === 'right') widthDiff = -widthDiff;
|
||||||
|
let newWidth = dragState.width + widthDiff;
|
||||||
|
newWidth = Math.min(props.maxWidth, Math.max(props.minWidth, newWidth));
|
||||||
|
sidebarWidth.value = newWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentMouseUp (event) {
|
||||||
|
if (dragState) {
|
||||||
|
dragState = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sidebarStyle () {
|
||||||
|
return {
|
||||||
|
flex: `0 0 ${sidebarWidth.value}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="sidebar" :style="sidebarStyle()" v-if="!collapsed">
|
||||||
|
<div :class="['drag-bar', align]" @mousedown="dragBarMouseDown($event)"></div>
|
||||||
|
<slot></slot>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-bar {
|
||||||
|
height: 100%;
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
width: 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
.drag-bar.left { right: 0; }
|
||||||
|
.drag-bar.right { left: 0; }
|
||||||
|
|
||||||
|
/* Adapt sidebar to small screens */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.sidebar {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.drag-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="main-header">
|
<header class="main-header">
|
||||||
<button class="header-button when-sidebar-collapsed" @click="sidebarExpand()">
|
<button class="hollow icon when-sidebar-collapsed" @click="sidebarExpand()">
|
||||||
>>
|
>>
|
||||||
<!--i class="fa-solid fa-angles-right"></i-->
|
<!--i class="fa-solid fa-angles-right"></i-->
|
||||||
</button>
|
</button>
|
||||||
@@ -30,11 +30,4 @@
|
|||||||
.main-header {
|
.main-header {
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-button {
|
|
||||||
height: var(--header-height);
|
|
||||||
width: var(--header-height);
|
|
||||||
background-color: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import {
|
|
||||||
ref, computed, reactive, watch,
|
|
||||||
onMounted, onUnmounted,
|
|
||||||
} from "vue";
|
|
||||||
|
|
||||||
import * as backend from '../../backend';
|
|
||||||
import * as ui from '../../ui';
|
|
||||||
|
|
||||||
import * as iconoir from '@iconoir/vue';
|
|
||||||
|
|
||||||
const chats = ref([]);
|
|
||||||
const agents = ref([]);
|
|
||||||
|
|
||||||
const mode = ref('chats');
|
|
||||||
const sidebarExpanded = ref(true);
|
|
||||||
|
|
||||||
const MIN_WIDTH = 150;
|
|
||||||
const MAX_WIDTH = 400;
|
|
||||||
const sidebarWidth = ref(200);
|
|
||||||
let dragState = null;
|
|
||||||
|
|
||||||
watch(sidebarExpanded, newValue => {
|
|
||||||
if (newValue) {
|
|
||||||
document.body.classList.add('with-sidebar-expanded');
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('with-sidebar-expanded');
|
|
||||||
}
|
|
||||||
}, {immediate: true});
|
|
||||||
|
|
||||||
ui.listen('sidebar-expand', ev => {
|
|
||||||
sidebarExpanded.value = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
document.body.addEventListener('mousemove', documentMouseMove);
|
|
||||||
document.body.addEventListener('mouseup', documentMouseUp);
|
|
||||||
|
|
||||||
chats.value = (await backend.getChats()).map(it => {
|
|
||||||
return Object.assign({}, it);
|
|
||||||
});
|
|
||||||
agents.value = (await backend.getAgents()).map(it => {
|
|
||||||
return Object.assign({}, it);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.body.removeEventListener('mousemove', documentMouseMove);
|
|
||||||
document.body.removeEventListener('mouseup', documentMouseUp);
|
|
||||||
});
|
|
||||||
|
|
||||||
function dragBarMouseDown (event) {
|
|
||||||
dragState = {x: event.clientX, y: event.clientY, width: sidebarWidth.value};
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
console.log("Set Drag State", dragState);
|
|
||||||
}
|
|
||||||
|
|
||||||
function documentMouseMove (event) {
|
|
||||||
if (dragState) {
|
|
||||||
let widthDiff = event.clientX - dragState.x;
|
|
||||||
let newWidth = dragState.width + widthDiff;
|
|
||||||
newWidth = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, newWidth));
|
|
||||||
sidebarWidth.value = newWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function documentMouseUp (event) {
|
|
||||||
if (dragState) {
|
|
||||||
dragState = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sidebarStyle () {
|
|
||||||
return {
|
|
||||||
flex: `0 0 ${sidebarWidth.value}px`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<aside class="main-sidebar" :style="sidebarStyle()" v-if="sidebarExpanded">
|
|
||||||
<div class="drag-bar" @mousedown="dragBarMouseDown($event)"></div>
|
|
||||||
<div class="tab-bar">
|
|
||||||
<div class="tab-group">
|
|
||||||
<div @click="mode='chats'" class="tab" :class="{active: mode=='chats'}">
|
|
||||||
<iconoir.Message />
|
|
||||||
</div>
|
|
||||||
<div @click="mode='agents'" class="tab" :class="{active: mode=='agents'}">
|
|
||||||
<iconoir.Group />
|
|
||||||
</div>
|
|
||||||
<div @click="mode='settings'" class="tab" :class="{active: mode=='settings'}">
|
|
||||||
<iconoir.Settings />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-group">
|
|
||||||
<button class="header-button" @click="sidebarExpanded=false;">
|
|
||||||
<!--i class="fa-solid fa-angles-left"></i-->
|
|
||||||
<<
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav v-if="mode=='runs'">
|
|
||||||
</nav>
|
|
||||||
<nav v-if="mode=='chats'">
|
|
||||||
<RouterLink v-for="it in chats" :to="'/chat/' + it.id" class="nav-item">{{it.name}}</RouterLink>
|
|
||||||
<RouterLink to="/chat/create" class="nav-item">Create Chat</RouterLink>
|
|
||||||
</nav>
|
|
||||||
<nav v-if="mode=='agents'">
|
|
||||||
<RouterLink v-for="it in agents" :to="'/agent/' + it.agentname" class="nav-item">{{it.name}}</RouterLink>
|
|
||||||
<RouterLink to="/agent/create" class="nav-item">Create Agent</RouterLink>
|
|
||||||
</nav>
|
|
||||||
<nav v-if="mode=='settings'">
|
|
||||||
<RouterLink to="/user" class="nav-item">User</RouterLink>
|
|
||||||
<RouterLink to="/models" class="nav-item">Manage Models</RouterLink>
|
|
||||||
<RouterLink to="/remotes" class="nav-item">Remotes</RouterLink>
|
|
||||||
<RouterLink to="/drives" class="nav-item">Drives</RouterLink>
|
|
||||||
<RouterLink to="/runs" class="nav-item">Agent Runs</RouterLink>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.main-sidebar {
|
|
||||||
background-color: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-bar, .tab-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.tab-bar {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
list-style-type: none;
|
|
||||||
height: var(--header-height);
|
|
||||||
width: var(--header-height);
|
|
||||||
|
|
||||||
padding-top: 3px;
|
|
||||||
color: var(--color-text-mute);
|
|
||||||
/*flex-grow: 1;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active, .tab:hover {
|
|
||||||
background: linear-gradient(to bottom, var(--color-surface-strong) 20%, var(--color-surface) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab:hover {
|
|
||||||
color: var(--color-heading);
|
|
||||||
padding-top: 0;
|
|
||||||
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 {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
color: inherit;
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item:hover {
|
|
||||||
color: var(--color-heading);
|
|
||||||
background-color: var(--color-surface-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item i {
|
|
||||||
font-size: 80%;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-bar {
|
|
||||||
height: 100%;
|
|
||||||
border-right: 1px solid var(--color-border);
|
|
||||||
width: 0.5em;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
cursor: ew-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adapt sidebar to small screens */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.main-sidebar {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.drag-bar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-button {
|
|
||||||
height: var(--header-height);
|
|
||||||
width: var(--header-height);
|
|
||||||
/*border-radius: 1.5em;*/
|
|
||||||
background-color: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.header-button:hover {
|
|
||||||
background-color: var(--color-background-hover);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
+30
-20
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import TitleBar from '../components/TitleBar.vue';
|
import TitleBar from '../components/TitleBar.vue';
|
||||||
import Message from '../components/Message.vue';
|
import Message from '../components/Message.vue';
|
||||||
|
import Sidebar from '../components/Sidebar.vue'
|
||||||
|
|
||||||
import * as backend from '../backend';
|
import * as backend from '../backend';
|
||||||
import * as ui from '../ui';
|
import * as ui from '../ui';
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
const expandedLogs = ui.useToggleSet();
|
const expandedLogs = ui.useToggleSet();
|
||||||
const inputFieldElem = ref(null);
|
const inputFieldElem = ref(null);
|
||||||
const chatContainerElem = ref(null);
|
const chatContainerElem = ref(null);
|
||||||
|
const sidebarCollapsed = ref(false);
|
||||||
|
|
||||||
let _isMounted = false;
|
let _isMounted = false;
|
||||||
|
|
||||||
@@ -194,12 +196,13 @@
|
|||||||
<main>
|
<main>
|
||||||
<TitleBar>
|
<TitleBar>
|
||||||
<h1>{{ chat.name || 'Chat #' + chat.id }}</h1>
|
<h1>{{ chat.name || 'Chat #' + chat.id }}</h1>
|
||||||
<div>
|
<button class="hollow icon" style="margin-left: auto;" @click="sidebarCollapsed=!sidebarCollapsed">
|
||||||
<button @click="renameChat(chat)">Rename</button>
|
<iconoir.Settings />
|
||||||
<button @click="deleteChat(chat)">Delete</button>
|
</button>
|
||||||
</div>
|
|
||||||
</TitleBar>
|
</TitleBar>
|
||||||
|
|
||||||
|
<div class="row" style="overflow: auto;">
|
||||||
|
<div class="column">
|
||||||
<div class="messages-container" ref="chatContainerElem">
|
<div class="messages-container" ref="chatContainerElem">
|
||||||
<div class="message-container" v-for="msg in chatEntries">
|
<div class="message-container" v-for="msg in chatEntries">
|
||||||
<Message
|
<Message
|
||||||
@@ -229,33 +232,46 @@
|
|||||||
|
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<div>
|
<div>
|
||||||
<button class="hollow" :class="{primary: creatingThread}" @click="newThreadClick()">
|
<button class="hollow icon" :class="{primary: creatingThread}" @click="newThreadClick()">
|
||||||
<iconoir.ChatPlusInSolid v-if='creatingThread' />
|
<iconoir.ChatPlusInSolid v-if='creatingThread' />
|
||||||
<iconoir.ChatPlusIn v-else />
|
<iconoir.ChatPlusIn v-else />
|
||||||
</button>
|
</button>
|
||||||
<button class="hollow" :class="{primary: noteMode}" @click="noteMode=!noteMode">
|
<button class="hollow icon" :class="{primary: noteMode}" @click="noteMode=!noteMode">
|
||||||
<iconoir.PostSolid v-if='noteMode' />
|
<iconoir.PostSolid v-if='noteMode' />
|
||||||
<iconoir.Post v-else />
|
<iconoir.Post v-else />
|
||||||
</button>
|
</button>
|
||||||
<button class="hollow" @click="sendPicture()">
|
<button class="hollow icon" @click="sendPicture()">
|
||||||
<iconoir.Camera />
|
<iconoir.Camera />
|
||||||
</button>
|
</button>
|
||||||
<button class="hollow" @click="uploadFile()">
|
<button class="hollow icon" @click="uploadFile()">
|
||||||
<iconoir.Attachment />
|
<iconoir.Attachment />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="hollow" @click="addRemoteResource()">
|
<button class="hollow icon primary" id="send-button" @click="sendMessage()">
|
||||||
Add Remote
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button class="hollow primary" id="send-button" @click="sendMessage()">
|
|
||||||
<iconoir.SendDiagonalSolid width="3em" height="3em" />
|
<iconoir.SendDiagonalSolid width="3em" height="3em" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Sidebar align="right" minWidth=300 maxWidth=800 v-model:collapsed="sidebarCollapsed">
|
||||||
|
<section>
|
||||||
|
<h3>Settings</h3>
|
||||||
|
<div>
|
||||||
|
<button @click="renameChat(chat)">Rename</button>
|
||||||
|
<button @click="deleteChat(chat)">Delete</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Remotes</h3>
|
||||||
|
<button @click="addRemoteResource()">
|
||||||
|
Add Remote to Current Thread
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</Sidebar>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -320,12 +336,6 @@
|
|||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-row button {
|
|
||||||
width: 4em;
|
|
||||||
height: 4em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#send-button {
|
#send-button {
|
||||||
width: 5em;
|
width: 5em;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
|
|||||||
Reference in new issue
Block a user