frontend,chat: chat redesign, all message styles are now beautiful
This commit is contained in:
1 parent
73b789b586
commit
98fbed03cc
3 files changed
+107
-80
No files matched your search
@@ -788,6 +788,7 @@ export class ChatSession {
|
|||||||
type: 'thread-header',
|
type: 'thread-header',
|
||||||
threadId: fork.id,
|
threadId: fork.id,
|
||||||
active: false,
|
active: false,
|
||||||
|
isFork: true,
|
||||||
messageCount: fork.messageCount,
|
messageCount: fork.messageCount,
|
||||||
time: firstMessage?.time,
|
time: firstMessage?.time,
|
||||||
|
|
||||||
@@ -796,7 +797,7 @@ export class ChatSession {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const addThreadHeader = (thread) => {
|
const addThreadHeader = (thread, isRoot) => {
|
||||||
let isActive = this.activeThreadIds.includes(thread.id);
|
let isActive = this.activeThreadIds.includes(thread.id);
|
||||||
|
|
||||||
let referenceMessage;
|
let referenceMessage;
|
||||||
@@ -837,11 +838,11 @@ export class ChatSession {
|
|||||||
if (!referenceMessage) return;
|
if (!referenceMessage) return;
|
||||||
|
|
||||||
headers.push({
|
headers.push({
|
||||||
// claim just before id
|
|
||||||
id: 't.' + thread.id,
|
id: 't.' + thread.id,
|
||||||
type: 'thread-header',
|
type: 'thread-header',
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
active: isActive,
|
active: isActive,
|
||||||
|
isFork: !isRoot,
|
||||||
messageCount: isActive ? 0 : thread.messageCount,
|
messageCount: isActive ? 0 : thread.messageCount,
|
||||||
time: referenceMessage.time,
|
time: referenceMessage.time,
|
||||||
|
|
||||||
@@ -851,7 +852,9 @@ export class ChatSession {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.threads.forEach(addThreadHeader);
|
for (let root of this.threads) {
|
||||||
|
addThreadHeader(root, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Headers must be ordered by their attach id
|
// Headers must be ordered by their attach id
|
||||||
headers.sort((a, b) => parseInt(a.attach.id) - parseInt(b.attach.id));
|
headers.sort((a, b) => parseInt(a.attach.id) - parseInt(b.attach.id));
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ marked.use({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['open-thread', 'open-menu', 'toggle-log', 'cancel'])
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -39,7 +40,12 @@ function chatEntryClass(msg) {
|
|||||||
} else if (msg.type == 'resource') {
|
} else if (msg.type == 'resource') {
|
||||||
return ['resource', 'resource-' + (msg.subkind || 'generic')];
|
return ['resource', 'resource-' + (msg.subkind || 'generic')];
|
||||||
} else if (msg.type == 'log') {
|
} else if (msg.type == 'log') {
|
||||||
return ['message', 'log'];
|
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');
|
||||||
|
return cls;
|
||||||
} else {
|
} else {
|
||||||
return ['info'];
|
return ['info'];
|
||||||
}
|
}
|
||||||
@@ -50,6 +56,14 @@ function resourceLabel(msg) {
|
|||||||
return msg.subkind || 'Resource';
|
return msg.subkind || 'Resource';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleClick(msg) {
|
||||||
|
if (msg.type == 'thread-header' || msg.type == 'fork-header') {
|
||||||
|
if (!msg.active && msg.threadId != null) {
|
||||||
|
emit('open-thread', msg.threadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const format = ref(null);
|
const format = ref(null);
|
||||||
const content = ref("");
|
const content = ref("");
|
||||||
|
|
||||||
@@ -94,7 +108,7 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="chatEntryClass(message)" @contextmenu.prevent="$emit('open-menu', message, logMeta)">
|
<div :class="chatEntryClass(message)" @contextmenu.prevent="$emit('open-menu', message, logMeta)" @click="handleClick(message)">
|
||||||
<template v-if="message.type == 'message' || message.type == null">
|
<template v-if="message.type == 'message' || message.type == null">
|
||||||
<div v-if="message.sender != 'user'" class="sender">{{message.sender}}</div>
|
<div v-if="message.sender != 'user'" class="sender">{{message.sender}}</div>
|
||||||
|
|
||||||
@@ -119,18 +133,13 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="message.type == 'thread-header'">
|
<template v-if="message.type == 'thread-header'">
|
||||||
<div class="thread-header"
|
<div class="thread-title">
|
||||||
:class="{ 'thread-active': message.active }"
|
{{ message.threadId == null ? 'New Thread' : `Thread ${message.threadId}` }}
|
||||||
@click="!message.active && message.threadId != null && $emit('open-thread', message.threadId)"
|
<span v-if="message.messageCount" class="thread-count">({{ message.messageCount }} messages)</span>
|
||||||
>
|
</div>
|
||||||
<div class="thread-title">
|
<div v-if="message.preview" class="content">
|
||||||
{{ message.threadId == null ? 'New Thread' : `Thread ${message.threadId}` }}
|
<span class="sender">{{ message.preview.sender }}:</span>
|
||||||
<span v-if="message.messageCount" class="thread-count">({{ message.messageCount }} messages)</span>
|
{{ message.preview.content }}
|
||||||
</div>
|
|
||||||
<div v-if="message.preview" class="thread-preview">
|
|
||||||
<span class="thread-preview-sender">{{ message.preview.sender }}:</span>
|
|
||||||
{{ message.preview.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -139,21 +148,36 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.info {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
max-width: 50em;
|
max-width: 100%;
|
||||||
padding: 10px;
|
padding: 1em;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.note {
|
/* non-messages all share basics */
|
||||||
|
.note, .log, .info, .thread, .resource {
|
||||||
|
padding: 0.6em 1.4em;
|
||||||
|
align-self: center;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* solid entries have horizontal margin */
|
||||||
|
.note, .thread-fork.thread-open, .resource {
|
||||||
|
margin-left: 2em;
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* content by default fits in the entry */
|
||||||
|
.content {
|
||||||
|
white-space: preserve;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
padding: 10px;
|
word-wrap: anywhere;
|
||||||
|
max-width: 50em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
@@ -168,7 +192,6 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
|
|
||||||
.message .content {
|
.message .content {
|
||||||
white-space: preserve;
|
white-space: preserve;
|
||||||
width: fit-content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message ul, .message ol {
|
.message ul, .message ol {
|
||||||
@@ -180,18 +203,21 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
margin-top: -5px; /* half message padding */
|
margin-top: -5px; /* half message padding */
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.log .content {
|
.log .content {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
|
max-width: 70em;
|
||||||
}
|
}
|
||||||
.message.log .log-sender {
|
.log .log-sender {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
.message.log .log-toggle {
|
.log .log-toggle {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -199,10 +225,7 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
.resource {
|
.resource {
|
||||||
align-self: center;
|
border-radius: var(--border-radius);
|
||||||
width: fit-content;
|
|
||||||
padding: 4px 12px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px dashed var(--color-border);
|
border: 1px dashed var(--color-border);
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
@@ -220,58 +243,54 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header {
|
.thread {
|
||||||
align-self: center;
|
width: 100%;
|
||||||
width: fit-content;
|
|
||||||
max-width: 40em;
|
|
||||||
padding: 5px 16px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px dashed var(--color-border);
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
font-size: 80%;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header:hover {
|
.thread.thread-open {
|
||||||
border-style: solid;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header .thread-title {
|
.thread-open:hover {
|
||||||
|
border-top: 1px solid var(--color-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread.thread-fork {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread.thread-fork.thread-open {
|
||||||
|
width: fit-content;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
opacity: 70%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header .thread-count {
|
.thread-count {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
opacity: 80%;
|
opacity: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header .thread-preview {
|
.thread .content {
|
||||||
|
justify-self: center;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
opacity: 60%;
|
opacity: 80%;
|
||||||
max-width: 100%;
|
white-space: normal;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header .thread-preview-sender {
|
.thread:hover .content {
|
||||||
font-style: normal;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thread-header.thread-active {
|
|
||||||
border-style: solid;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thread-header.thread-active .thread-title {
|
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-header:hover {
|
.thread .sender {
|
||||||
border-style: solid;
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+18
-13
@@ -196,19 +196,20 @@
|
|||||||
</TitleBar>
|
</TitleBar>
|
||||||
|
|
||||||
<div class="messages-container" ref="chatContainerElem">
|
<div class="messages-container" ref="chatContainerElem">
|
||||||
<Message
|
<div class="message-container" v-for="msg in chatEntries">
|
||||||
v-for="msg in chatEntries"
|
<Message
|
||||||
:key="msg.id"
|
:key="msg.id"
|
||||||
:message="msg"
|
:message="msg"
|
||||||
@open-menu="chatEntryContextMenu"
|
@open-menu="chatEntryContextMenu"
|
||||||
@open-thread="session.activateThread($event)"
|
@open-thread="session.activateThread($event)"
|
||||||
|
|
||||||
:log-expanded="expandedLogs.has(msg.id)"
|
:log-expanded="expandedLogs.has(msg.id)"
|
||||||
@toggle-log="expandedLogs.toggle(msg.id)"
|
@toggle-log="expandedLogs.toggle(msg.id)"
|
||||||
|
|
||||||
:cancellable="messageRunMap[msg.id]"
|
:cancellable="messageRunMap[msg.id]"
|
||||||
@cancel="session.cancelMessageRun(msg.id)"
|
@cancel="session.cancelMessageRun(msg.id)"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
@@ -261,8 +262,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-container > div {
|
.message-container {
|
||||||
margin-bottom: 10px;
|
width: 100%;
|
||||||
|
max-width: 80em;
|
||||||
|
margin: 0 auto 0.6em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
|
|||||||
Reference in new issue
Block a user