Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/lib/common/shared/Label.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
export let text = "";
export let className = "";
export let style = "";
export let color = "primary";
export let ellipsis = false;
/** @type {string | number} */
export let index;
/** @type {(args0: number | string) => void} */
Expand All @@ -20,9 +22,9 @@
</script>

<div class={`label-container ${className}`} style={`${style}`}>
<button class="btn btn-primary btn-sm label-btn">
<button class={`btn btn-${color} btn-sm label-btn`}>
<div style="display: flex;">
<span class="d-none d-sm-inline-block me-2 label-text">
<span class={`me-2 label-text ${ellipsis ? 'label-ellipsis' : 'd-none d-sm-inline-block'}`}>
{text}
</span>
<i
Expand Down Expand Up @@ -55,6 +57,13 @@
text-align: left;
}

.label-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 120px;
}

.label-close {
cursor: pointer;
display: flex;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/helpers/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ export function replaceNewLine(text) {
* @returns {string}
*/
export function replaceMarkdown(text) {
let res = text.replace(/#([\s]+)/g, '\\# ').replace(/[-|=]{3,}/g, '@@@');
let res = text.replace(/#([\s]+)/g, '\\# ').replace(/^-\s*/gm, '• ').replace(/[-|=]{3,}/g, '•••');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Dash line content corrupted 🐞 Bug ✓ Correctness

replaceMarkdown now replaces any start-of-line '-' with a bullet, so content like "-1" or "--flag"
is rewritten (e.g., "-1" → "• 1") before Markdown rendering. This changes the meaning of
user/assistant messages and logs rendered through Markdown.svelte.
Agent Prompt
### Issue description
`replaceMarkdown()` rewrites any line beginning with `-` into a bullet because it uses `/^\-\s*/gm` (`\s*` matches zero chars). This corrupts non-list text like negative numbers (`-1` -> `• 1`) and CLI flags (`--help` -> `• -help`) before `marked()` renders it.

### Issue Context
`Markdown.svelte` applies `replaceMarkdown()` for all non-raw markdown rendering.

### Fix Focus Areas
- src/lib/helpers/http.js[346-362]
- src/lib/common/markdown/Markdown.svelte[51-62]

### Suggested change
Update the regex to only match markdown list items (dash followed by whitespace), e.g.:
- `res = res.replace(/^-\s+/gm, '• ');`
Optionally require content after whitespace: `res.replace(/^-\s+(?=\S)/gm, '• ')`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


let regex1 = new RegExp('\\*(.*)\\*', 'g');
let regex2 = new RegExp('\\*([\\*]+)\\*', 'g');
Expand Down
2 changes: 1 addition & 1 deletion src/lib/scss/custom/components/_markdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
}

ul {
list-style-position: inside !important;
list-style-position: outside !important;
}
}

Expand Down
16 changes: 7 additions & 9 deletions src/lib/scss/custom/pages/_chat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -829,16 +829,14 @@
display: flex;
flex-wrap: wrap;
overflow-y: auto;
scrollbar-width: none;
scrollbar-width: thin;
min-height: 80px;
max-height: 180px;
}

.conv-tag-unit {
flex: 0 0 50%;

label {
font-size: 14px;
font-weight: 400;
}
}
.conv-tag-add {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
102 changes: 63 additions & 39 deletions src/routes/chat/[agentId]/[conversationId]/chat-box.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
Input
DropdownItem
} from '@sveltestrap/sveltestrap';
import {
conversationStore,
Expand Down Expand Up @@ -56,6 +55,7 @@
import LoadingToComplete from '$lib/common/spinners/LoadingToComplete.svelte';
import AudioSpeaker from '$lib/common/audio-player/AudioSpeaker.svelte';
import CodeScript from '$lib/common/shared/CodeScript.svelte';
import Label from '$lib/common/shared/Label.svelte';
import { realtimeChat } from '$lib/services/realtime-chat-service';
import { webSpeech } from '$lib/services/web-speech';
import LocalStorageManager from '$lib/helpers/utils/storage-manager';
Expand All @@ -64,7 +64,7 @@
import { utcToLocal } from '$lib/helpers/datetime';
import { replaceNewLine } from '$lib/helpers/http';
import { isAudio, isExcel, isPdf } from '$lib/helpers/utils/file';
import { ChatAction, ConversationTag, EditorType, FileSourceType, RichType, SenderAction, UserRole } from '$lib/helpers/enums';
import { ChatAction, EditorType, FileSourceType, RichType, SenderAction, UserRole } from '$lib/helpers/enums';
import ChatTextArea from './chat-util/chat-text-area.svelte';
import RichContent from './rich-content/rich-content.svelte';
import RcMessage from "./rich-content/rc-message.svelte";
Expand Down Expand Up @@ -131,12 +131,9 @@
/** @type {string[]} */
let chatUtilOptions = [];
/** @type {string[]} */
let selectedTags = [];
let convTags = [];
let newTagText = '';

/** @type {import('$commonTypes').KeyValuePair[]} */
let tagOptions = Object.entries(ConversationTag).map(([k, v]) => (
{ key: k, value: v }
));

/** @type {any[]} */
let scrollbars = [];
Expand Down Expand Up @@ -239,7 +236,8 @@
conversation = await getConversation(params.conversationId, true);
dialogs = await getDialogs(params.conversationId, dialogCount);
conversationUser = conversation?.user;
selectedTags = conversation?.tags || [];
convTags = conversation?.tags || [];

latestStateLog = conversation?.states;
initUserSentMessages(dialogs);
initChatView();
Expand Down Expand Up @@ -1409,44 +1407,55 @@
function toggleTagModal() {
isOpenTagModal = !isOpenTagModal;
if (!isOpenTagModal) {
selectedTags = conversation?.tags || [];
newTagText = '';
convTags = conversation?.tags || [];
}
}

/**
* @param {any} e
* @param {string} value
*/
function changeTagSelection(e, value) {
const checked = e.target.checked;
if (checked) {
selectedTags = [...new Set([...selectedTags, value])];
} else {
selectedTags = selectedTags.filter(x => x !== value);
/** @param {number | string} idx */
function removeTag(idx) {
const tag = convTags?.[/** @type {number} */ (idx)];
if (!tag) return;
convTags = convTags.filter(t => t !== tag);
}

function addTag() {
const tag = _.trim(newTagText);
if (!tag || convTags.includes(tag)) {
return;
}
convTags = [...convTags, tag];
newTagText = '';
}

function updateChatTags() {
const originalTags = conversation?.tags || [];
const toAddTags = convTags.filter(t => !originalTags.includes(t));
const toDeleteTags = originalTags.filter((/** @type {string} */ t) => !convTags.includes(t));

if (toAddTags.length === 0 && toDeleteTags.length === 0) {
isOpenTagModal = false;
return;
}

isLoading = true;
updateConversationTags(
params.conversationId,
{
toAddTags: selectedTags,
toDeleteTags: tagOptions.filter(x => !selectedTags.includes(x.value)).map(x => x.value)
})
{ toAddTags, toDeleteTags })
.then(res => {
if (res) {
conversation.tags = [...convTags];
isComplete = true;
successText = "Tags has been updated!";
successText = "Tags have been updated!";
setTimeout(() => {
isComplete = false;
successText = "";
}, duration);
} else {
throw "Failed to update chat tags.";
throw "Failed to update tags.";
}
}).catch(() => {
selectedTags = conversation?.tags || [];
convTags = conversation?.tags || [];
isError = true;
errorText = "Failed to update tags!";
setTimeout(() => {
Comment on lines +1436 to 1461

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Tag modal stale state 🐞 Bug ✓ Correctness

updateChatTags() closes the Tags modal by setting isOpenTagModal=false directly, bypassing
toggleTagModal()’s close-time reset of newTagText and convTags. As a result, typed-but-not-added tag
text (and other transient state) can persist into subsequent modal openings and lead to unintended
edits.
Agent Prompt
### Issue description
The Tags modal reset logic lives inside `toggleTagModal()` and only runs when closing via that function. `updateChatTags()` closes the modal by directly setting `isOpenTagModal = false` (including the no-op early return and in `.finally()`), so `newTagText`/`convTags` are not reset for those close paths.

### Issue Context
DialogModal unmounts on close, but component state variables persist; reopening the modal reuses `newTagText` because the reset was skipped.

### Fix Focus Areas
- src/routes/chat/[agentId]/[conversationId]/chat-box.svelte[1407-1468]

### Suggested change
Introduce a dedicated close helper that always resets, and use it everywhere you close the modal:
```js
function closeTagModal() {
  isOpenTagModal = false;
  newTagText = '';
  convTags = conversation?.tags || [];
}
```
Then replace `isOpenTagModal = false` in `updateChatTags()` (early return + finally) with `closeTagModal()`.
Optionally also initialize state on open (inside `toggleTagModal()` when opening) to guarantee a clean modal each time.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Expand Down Expand Up @@ -1567,23 +1576,38 @@
closeable
toggleModal={() => toggleTagModal()}
confirmBtnText={'Confirm'}
cancelBtnText={'Cancel'}
cancelBtnText={''}
confirm={() => updateChatTags()}
cancel={() => toggleTagModal()}
close={() => toggleTagModal()}
>
<div class="conv-tags-container">
{#each tagOptions as op}
<div class="conv-tag-unit">
<Input
type="checkbox"
label={op.value}
checked={selectedTags.includes(op.value)}
on:change={e => changeTagSelection(e, op.value)}
/>
</div>
{#each convTags as tag, idx}
<Label
text={tag}
index={idx}
color="info"
ellipsis
onClose={removeTag}
/>
{/each}
</div>
<div class="conv-tag-add">
<input
class="form-control form-control-sm"
type="text"
placeholder="Enter new tag..."
maxlength={50}
bind:value={newTagText}
on:keydown={e => { if (e.key === 'Enter') addTag(); }}
/>
<button
class="btn btn-primary btn-sm"
disabled={!_.trim(newTagText)}
on:click={() => addTag()}
>
<i class="bx bx-plus" />
</button>
</div>
</DialogModal>

<DialogModal
Expand Down Expand Up @@ -1763,7 +1787,7 @@
disabled={disableAction}
on:click={() => toggleTagModal()}
>
Add Tags
Tags
</DropdownItem>
{/if}
{#if agent?.id === LEARNER_AGENT_ID && mode === TRAINING_MODE}
Expand Down