mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor(ChatArea): extract message rendering into separate components
Extract user and assistant message rendering logic from ChatArea into dedicated UserMessage and AssistantMessage components, removing unused dependencies and styles.
This commit is contained in:
parent
f4c3b5b826
commit
0b54cdcc10
5 changed files with 433 additions and 405 deletions
292
Components/AssistantMessage.svelte
Normal file
292
Components/AssistantMessage.svelte
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
<script lang="ts">
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type { StreamingMarkdownService } from "Services/StreamingMarkdownService";
|
||||
import type { ConversationContent } from "Conversations/ConversationContent";
|
||||
import { setElementIcon } from "Helpers/ElementHelper";
|
||||
import { fade } from "svelte/transition";
|
||||
import { ArtifactAction, artifactActionToCopy } from "Enums/ArtifactAction";
|
||||
import { basename } from "path-browserify";
|
||||
|
||||
export let message: ConversationContent;
|
||||
export let isSubmitting: boolean = false;
|
||||
|
||||
let streamingMarkdownService: StreamingMarkdownService = Resolve<StreamingMarkdownService>(Services.StreamingMarkdownService);
|
||||
|
||||
function messageRenderAction(element: HTMLElement, message: ConversationContent) {
|
||||
streamingMarkdownService.render(message.getDisplayContent(), element);
|
||||
return {
|
||||
update(newMessage: ConversationContent) {
|
||||
streamingMarkdownService.render(newMessage.getDisplayContent(), element, !isSubmitting);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function tallyArtifactsByAction(artifacts: { action: ArtifactAction }[]): Partial<Record<ArtifactAction, number>> {
|
||||
const tally: Partial<Record<ArtifactAction, number>> = {};
|
||||
for (const artifact of artifacts) {
|
||||
tally[artifact.action] = (tally[artifact.action] ?? 0) + 1;
|
||||
}
|
||||
return tally;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="message-container assistant">
|
||||
<div class="message-bubble assistant">
|
||||
<div class="markdown-content">
|
||||
<div use:messageRenderAction={message} class="streaming-content"></div>
|
||||
</div>
|
||||
{#if message.artifacts.length > 0}
|
||||
{@const artifactTally = tallyArtifactsByAction(message.artifacts)}
|
||||
<div class="artifacts-container" in:fade={{ duration: 300 }}>
|
||||
<span class="artifacts-container-title">{message.artifacts.length} FILES CHANGED</span>
|
||||
<div class="artifacts-tally">
|
||||
{#each Object.values(ArtifactAction) as action}
|
||||
{#if artifactTally[action]}
|
||||
<div class="artifact-tally-container">
|
||||
<span class="artifact-tally-ellipse artifact-{action}"></span>
|
||||
<span class="artifact-tally-count">{artifactTally[action]}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="artifacts-list-container">
|
||||
{#each message.artifacts as artifact}
|
||||
<div class="artifact-card" aria-label="{artifact.filePath}">
|
||||
<span class="artifact-ellipse artifact-{artifact.action}"></span>
|
||||
<div
|
||||
class="artifact-icon"
|
||||
use:setElementIcon={artifact.getIconName()}
|
||||
></div>
|
||||
<span class="artifact-name">{basename(artifact.filePath)}</span>
|
||||
<span class="artifact-action artifact-action-{artifact.action}">{artifactActionToCopy(artifact.action)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.message-container {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
justify-content: flex-start;
|
||||
animation: fadeIn 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.streaming-content {
|
||||
justify-content: left;
|
||||
min-height: 1em; /* Ensure the element exists for binding */
|
||||
}
|
||||
|
||||
/* Streaming message styles */
|
||||
.content-fade-in {
|
||||
animation: reveal-fade 0.5s ease-in-out forwards;
|
||||
}
|
||||
|
||||
@keyframes reveal-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.artifacts-container {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: auto auto;
|
||||
width: 100%;
|
||||
margin-top: var(--size-4-3);
|
||||
margin-bottom: var(--size-4-6);
|
||||
gap: var(--size-4-4);
|
||||
}
|
||||
|
||||
.artifacts-container-title {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
font-size: var(--font-smallest);
|
||||
}
|
||||
|
||||
.artifacts-tally {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.artifact-tally-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.artifact-tally-ellipse {
|
||||
flex-shrink: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-left: var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifact-create {
|
||||
background: var(--color-green);
|
||||
}
|
||||
|
||||
.artifact-modify {
|
||||
background: var(--color-blue);
|
||||
}
|
||||
|
||||
.artifact-delete {
|
||||
background: var(--color-red);
|
||||
}
|
||||
|
||||
.artifact-tally-count {
|
||||
font-size: var(--font-smallest);
|
||||
margin-left: var(--size-4-1);
|
||||
}
|
||||
|
||||
.artifacts-list-container {
|
||||
grid-row: 2;
|
||||
grid-column: 1 / 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
gap: var(--size-4-1);
|
||||
}
|
||||
|
||||
.artifacts-list-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.artifact-card {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: auto auto 1fr auto;
|
||||
gap: var(--size-4-3);
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--background-secondary-alt);
|
||||
border-radius: var(--size-4-2);
|
||||
padding: 0 var(--size-4-1) 0 var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifact-ellipse {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
flex-shrink: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.artifact-ellipse-create {
|
||||
background: var(--color-green);
|
||||
}
|
||||
|
||||
.artifact-ellipse-modify {
|
||||
background: var(--color-blue);
|
||||
}
|
||||
|
||||
.artifact-ellipse-delete {
|
||||
background: var(--color-red);
|
||||
}
|
||||
|
||||
.artifact-icon {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artifact-name {
|
||||
grid-row: 1;
|
||||
grid-column: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
font-size: var(--font-smaller);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.artifact-action {
|
||||
grid-row: 1;
|
||||
grid-column: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-smallest);
|
||||
font-weight: var(--font-semibold);
|
||||
border-radius: var(--size-4-2);
|
||||
padding: var(--size-2-1) var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifact-action-create {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-green) 25%,
|
||||
black 20%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-green) 100%,
|
||||
white 10%
|
||||
);
|
||||
}
|
||||
|
||||
.artifact-action-modify {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-blue) 25%,
|
||||
black 20%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-blue) 100%,
|
||||
white 10%
|
||||
);
|
||||
}
|
||||
|
||||
.artifact-action-delete {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-red) 25%,
|
||||
black 20%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-red) 100%,
|
||||
white 10%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,19 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type { StreamingMarkdownService } from "Services/StreamingMarkdownService";
|
||||
import ThoughtIndicator from "./ThoughtIndicator.svelte";
|
||||
import StreamingIndicator from "./StreamingIndicator.svelte";
|
||||
import UserMessage from "./UserMessage.svelte";
|
||||
import AssistantMessage from "./AssistantMessage.svelte";
|
||||
import { Greeting } from "Enums/Greeting";
|
||||
import { Role } from "Enums/Role";
|
||||
import type { ConversationContent } from "Conversations/ConversationContent";
|
||||
import { tick } from "svelte";
|
||||
import { setElementIcon } from "Helpers/ElementHelper";
|
||||
import { setIcon } from "obsidian";
|
||||
import { fade } from "svelte/transition";
|
||||
import GraphAnimation from "./GraphAnimation.svelte";
|
||||
import { ArtifactAction, artifactActionToCopy } from "Enums/ArtifactAction";
|
||||
import { basename } from "path-browserify";
|
||||
|
||||
export let messages: ConversationContent[] = [];
|
||||
export let currentThought: string | null = null;
|
||||
|
|
@ -61,8 +57,6 @@
|
|||
|
||||
let scrollToBottomButton: HTMLButtonElement;
|
||||
|
||||
let streamingMarkdownService: StreamingMarkdownService = Resolve<StreamingMarkdownService>(Services.StreamingMarkdownService);
|
||||
|
||||
type Turn = { id: number, messages: ConversationContent[] };
|
||||
|
||||
$: turns = groupTurns(messages);
|
||||
|
|
@ -102,23 +96,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
function messageRenderAction(element: HTMLElement, message: ConversationContent) {
|
||||
streamingMarkdownService.render(message.getDisplayContent(), element);
|
||||
return {
|
||||
update(newMessage: ConversationContent) {
|
||||
streamingMarkdownService.render(newMessage.getDisplayContent(), element, !isSubmitting);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function tallyArtifactsByAction(artifacts: { action: ArtifactAction }[]): Partial<Record<ArtifactAction, number>> {
|
||||
const tally: Partial<Record<ArtifactAction, number>> = {};
|
||||
for (const artifact of artifacts) {
|
||||
tally[artifact.action] = (tally[artifact.action] ?? 0) + 1;
|
||||
}
|
||||
return tally;
|
||||
}
|
||||
|
||||
$: if (scrollToBottomButton) {
|
||||
setIcon(scrollToBottomButton, "arrow-down");
|
||||
}
|
||||
|
|
@ -136,69 +113,9 @@
|
|||
{@const content = message.getDisplayContent()}
|
||||
{#if message.shouldDisplayContent && content.trim() !== ""}
|
||||
{#if message.role === Role.User}
|
||||
<div class="message-container {Role.User}">
|
||||
<div class="message-bubble {Role.User}">
|
||||
<div class="message-text-user-container" contenteditable="false">
|
||||
<div class="message-text-user">
|
||||
{@html content}
|
||||
</div>
|
||||
</div>
|
||||
{#if message.references.length > 0}
|
||||
<hr class="message-attachment-break"/>
|
||||
<div class="message-attachments-container">
|
||||
{#each message.references as reference}
|
||||
<div class="message-attachmanet" aria-label="{reference.fileName}">
|
||||
<div
|
||||
class="message-attachment-icon"
|
||||
use:setElementIcon={reference.getIconName()}
|
||||
></div>
|
||||
<div class="message-attachment-info">
|
||||
<div class="message-attachment-name">{reference.fileName}</div>
|
||||
<div class="message-attachment-size">{reference.size}MB</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<UserMessage {message} />
|
||||
{:else}
|
||||
<div class="message-container {Role.Assistant}">
|
||||
<div class="message-bubble {Role.Assistant}">
|
||||
<div class="markdown-content">
|
||||
<div use:messageRenderAction={message} class="streaming-content"></div>
|
||||
</div>
|
||||
{#if message.artifacts.length > 0}
|
||||
{@const artifactTally = tallyArtifactsByAction(message.artifacts)}
|
||||
<div class="artifacts-container" in:fade={{ duration: 300 }}>
|
||||
<span class="artifacts-container-title">{message.artifacts.length} FILES CHANGED</span>
|
||||
<div class="artifacts-tally">
|
||||
{#each Object.values(ArtifactAction) as action}
|
||||
{#if artifactTally[action]}
|
||||
<div class="artifact-tally-container">
|
||||
<span class="artifact-tally-ellipse artifact-{action}"></span>
|
||||
<span class="artifact-tally-count">{artifactTally[action]}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="artifacts-list-container">
|
||||
{#each message.artifacts as artifact}
|
||||
<div class="artifact-card" aria-label="{artifact.filePath}">
|
||||
<span class="artifact-ellipse artifact-{artifact.action}"></span>
|
||||
<div
|
||||
class="artifact-icon"
|
||||
use:setElementIcon={artifact.getIconName()}
|
||||
></div>
|
||||
<span class="artifact-name">{basename(artifact.filePath)}</span>
|
||||
<span class="artifact-action artifact-action-{artifact.action}">{artifactActionToCopy(artifact.action)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<AssistantMessage {message} {isSubmitting} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
@ -313,242 +230,6 @@
|
|||
min-height: 100%;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.message-container.user {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.message-container.assistant {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
animation: fadeIn 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.message-bubble.user {
|
||||
word-wrap: break-word;
|
||||
max-width: 70%;
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
padding: 0px var(--size-4-2);
|
||||
}
|
||||
|
||||
.message-bubble.assistant {
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-text-user-container {
|
||||
max-height: 15vh;
|
||||
overflow: scroll;
|
||||
padding-top: var(--size-4-2);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-text-user-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-text-user-container {
|
||||
padding-bottom: var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifacts-container {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: auto auto;
|
||||
width: 100%;
|
||||
margin-top: var(--size-4-3);
|
||||
gap: var(--size-4-4);
|
||||
}
|
||||
|
||||
.artifacts-container-title {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
font-size: var(--font-smallest);
|
||||
}
|
||||
|
||||
.artifacts-tally {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.artifact-tally-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.artifact-tally-ellipse {
|
||||
flex-shrink: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-left: var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifact-create {
|
||||
background: var(--color-green);
|
||||
}
|
||||
|
||||
.artifact-modify {
|
||||
background: var(--color-blue);
|
||||
}
|
||||
|
||||
.artifact-delete {
|
||||
background: var(--color-red);
|
||||
}
|
||||
|
||||
.artifact-tally-count {
|
||||
font-size: var(--font-smallest);
|
||||
margin-left: var(--size-4-1);
|
||||
}
|
||||
|
||||
.artifacts-list-container {
|
||||
grid-row: 2;
|
||||
grid-column: 1 / 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
gap: var(--size-4-1);
|
||||
}
|
||||
|
||||
.artifacts-list-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.artifact-card {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: auto auto 1fr auto;
|
||||
gap: var(--size-4-3);
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--background-secondary-alt);
|
||||
border-radius: var(--size-4-2);
|
||||
padding: 0 var(--size-4-1) 0 var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifact-ellipse {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
flex-shrink: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.artifact-ellipse-create {
|
||||
background: var(--color-green);
|
||||
}
|
||||
|
||||
.artifact-ellipse-modify {
|
||||
background: var(--color-blue);
|
||||
}
|
||||
|
||||
.artifact-ellipse-delete {
|
||||
background: var(--color-red);
|
||||
}
|
||||
|
||||
.artifact-icon {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artifact-name {
|
||||
grid-row: 1;
|
||||
grid-column: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
font-size: var(--font-smaller);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.artifact-action {
|
||||
grid-row: 1;
|
||||
grid-column: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-smallest);
|
||||
font-weight: var(--font-semibold);
|
||||
border-radius: var(--size-4-2);
|
||||
padding: var(--size-2-1) var(--size-4-2);
|
||||
}
|
||||
|
||||
.artifact-action-create {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-green) 25%,
|
||||
black 20%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-green) 100%,
|
||||
white 10%
|
||||
);
|
||||
}
|
||||
|
||||
.artifact-action-modify {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-blue) 25%,
|
||||
black 20%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-blue) 100%,
|
||||
white 10%
|
||||
);
|
||||
}
|
||||
|
||||
.artifact-action-delete {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-red) 25%,
|
||||
black 20%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-red) 100%,
|
||||
white 10%
|
||||
);
|
||||
}
|
||||
|
||||
.conversation-empty-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
|
|
@ -590,84 +271,4 @@
|
|||
white-space: nowrap;
|
||||
padding: var(--size-2-2);
|
||||
}
|
||||
|
||||
.streaming-content {
|
||||
justify-content: left;
|
||||
min-height: 1em; /* Ensure the element exists for binding */
|
||||
}
|
||||
|
||||
/* Streaming message styles */
|
||||
.content-fade-in {
|
||||
animation: reveal-fade 0.5s ease-in-out forwards;
|
||||
}
|
||||
|
||||
@keyframes reveal-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message attachments styles */
|
||||
.message-attachment-break {
|
||||
color: var(--background-secondary-alt);
|
||||
margin: 0 0 var(--size-4-2) 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.message-attachments-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
gap: var(--size-4-2);
|
||||
margin-bottom: var(--size-4-2);
|
||||
}
|
||||
|
||||
.message-attachments-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-attachmanet {
|
||||
display: grid;
|
||||
grid-template-rows: var(--size-4-2) auto var(--size-4-2);
|
||||
grid-template-columns: var(--size-4-2) auto var(--size-4-2) auto var(--size-4-2);
|
||||
background-color: var(--background-secondary-alt);
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message-attachment-icon {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.message-attachment-info {
|
||||
grid-row: 2;
|
||||
grid-column: 4;
|
||||
min-width: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-attachment-name {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: var(--font-smaller);
|
||||
}
|
||||
|
||||
.message-attachment-size {
|
||||
padding: 0;
|
||||
font-size: var(--font-smallest);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
134
Components/UserMessage.svelte
Normal file
134
Components/UserMessage.svelte
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<script lang="ts">
|
||||
import { setElementIcon } from "Helpers/ElementHelper";
|
||||
import type { ConversationContent } from "Conversations/ConversationContent";
|
||||
|
||||
export let message: ConversationContent;
|
||||
|
||||
$: content = message.getDisplayContent();
|
||||
</script>
|
||||
|
||||
<div class="message-container user">
|
||||
<div class="message-bubble user">
|
||||
<div class="message-text-user-container" contenteditable="false">
|
||||
<div class="message-text-user">
|
||||
{@html content}
|
||||
</div>
|
||||
</div>
|
||||
{#if message.references.length > 0}
|
||||
<hr class="message-attachment-break"/>
|
||||
<div class="message-attachments-container">
|
||||
{#each message.references as reference}
|
||||
<div class="message-attachmanet" aria-label="{reference.fileName}">
|
||||
<div
|
||||
class="message-attachment-icon"
|
||||
use:setElementIcon={reference.getIconName()}
|
||||
></div>
|
||||
<div class="message-attachment-info">
|
||||
<div class="message-attachment-name">{reference.fileName}</div>
|
||||
<div class="message-attachment-size">{reference.size}MB</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.message-container {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
justify-content: flex-end;
|
||||
animation: fadeIn 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
word-wrap: break-word;
|
||||
max-width: 70%;
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
padding: 0px var(--size-4-2);
|
||||
}
|
||||
|
||||
.message-text-user-container {
|
||||
max-height: 15vh;
|
||||
overflow: scroll;
|
||||
padding-top: var(--size-4-2);
|
||||
padding-bottom: var(--size-4-2);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-text-user-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-attachment-break {
|
||||
color: var(--background-secondary-alt);
|
||||
margin: 0 0 var(--size-4-2) 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.message-attachments-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
gap: var(--size-4-2);
|
||||
margin-bottom: var(--size-4-2);
|
||||
}
|
||||
|
||||
.message-attachments-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-attachmanet {
|
||||
display: grid;
|
||||
grid-template-rows: var(--size-4-2) auto var(--size-4-2);
|
||||
grid-template-columns: var(--size-4-2) auto var(--size-4-2) auto var(--size-4-2);
|
||||
background-color: var(--background-secondary-alt);
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message-attachment-icon {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.message-attachment-info {
|
||||
grid-row: 2;
|
||||
grid-column: 4;
|
||||
min-width: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-attachment-name {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: var(--font-smaller);
|
||||
}
|
||||
|
||||
.message-attachment-size {
|
||||
padding: 0;
|
||||
font-size: var(--font-smallest);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -19,7 +19,7 @@ describe('OpenAIConversationNamingAgent', () => {
|
|||
// Mock SettingsService
|
||||
mockSettingsService = {
|
||||
settings: {
|
||||
model: AIProviderModel.GPT_5_4_Nano,
|
||||
model: AIProviderModel.GPT_5_6_Luna,
|
||||
apiKeys: {
|
||||
claude: 'test-claude-key',
|
||||
openai: 'test-openai-key',
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { AIToolResponse } from '../../AIClasses/ToolDefinitions/AIToolResponse';
|
|||
import { AIToolResponsePayload } from '../../AIClasses/ToolDefinitions/AIToolResponsePayload';
|
||||
import type { ExecutionStep } from '../../Types/ExecutionStep';
|
||||
import { Artifact } from '../../Conversations/Artifact';
|
||||
import { ArtifactAction } from 'Enums/ArtifactAction';
|
||||
|
||||
/**
|
||||
* UNIT TESTS - ExecutionAgent
|
||||
|
|
@ -406,7 +407,7 @@ describe('ExecutionAgent - Unit Tests', () => {
|
|||
instruction: 'Create new file with content'
|
||||
};
|
||||
|
||||
const artifact = new Artifact('new-note.md', 'text/markdown', '', '# New Note\n\nContent here');
|
||||
const artifact = new Artifact('new-note.md', 'text/markdown', ArtifactAction.Create, '', '# New Note\n\nContent here');
|
||||
mockAIToolService.performAITool.mockResolvedValueOnce(
|
||||
new AIToolResponse(
|
||||
AITool.WriteVaultFile,
|
||||
|
|
|
|||
Loading…
Reference in a new issue