refac
This commit is contained in:
1 parent
9c7ce154e7
commit
29eeda9f9a
6 files changed
+205
-13
No files matched your search
@@ -2586,14 +2586,44 @@
|
|||||||
const message = error?.detail ?? error?.message ?? $i18n.t('Context compaction failed');
|
const message = error?.detail ?? error?.message ?? $i18n.t('Context compaction failed');
|
||||||
toast.error(message, { id: toastId });
|
toast.error(message, { id: toastId });
|
||||||
} finally {
|
} finally {
|
||||||
messageInput?.setText('');
|
|
||||||
prompt = '';
|
|
||||||
document.getElementById('chat-input')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStatusCommand = () => {
|
const handleStatusCommand = () => {
|
||||||
messageInput?.showStatus();
|
messageInput?.showStatus();
|
||||||
|
document.getElementById('chat-input')?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModelCommand = (modelId = '') => {
|
||||||
|
if (!modelId) {
|
||||||
|
const currentModels = (atSelectedModel?.id ? [atSelectedModel.id] : selectedModels).filter(
|
||||||
|
Boolean
|
||||||
|
);
|
||||||
|
toast.message(
|
||||||
|
currentModels.length
|
||||||
|
? `Current model: ${currentModels.join(', ')}`
|
||||||
|
: $i18n.t('Model not selected')
|
||||||
|
);
|
||||||
|
messageInput?.setText('');
|
||||||
|
prompt = '';
|
||||||
|
document.getElementById('chat-input')?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = $models.find((model) => model.id === modelId);
|
||||||
|
if (!model) {
|
||||||
|
toast.error(`Model not found: ${modelId}`);
|
||||||
|
messageInput?.setText('');
|
||||||
|
prompt = '';
|
||||||
|
document.getElementById('chat-input')?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
atSelectedModel = undefined;
|
||||||
|
selectedModels = [model.id];
|
||||||
|
saveSessionSelectedModels();
|
||||||
|
toast.success(`Model switched to: ${model.id}`);
|
||||||
messageInput?.setText('');
|
messageInput?.setText('');
|
||||||
prompt = '';
|
prompt = '';
|
||||||
document.getElementById('chat-input')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
@@ -2640,12 +2670,15 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(`${error}`, { id: toastId });
|
toast.error(`${error}`, { id: toastId });
|
||||||
} finally {
|
} finally {
|
||||||
messageInput?.setText('');
|
|
||||||
prompt = '';
|
|
||||||
document.getElementById('chat-input')?.focus();
|
document.getElementById('chat-input')?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearCommandInput = () => {
|
||||||
|
messageInput?.setText('');
|
||||||
|
prompt = '';
|
||||||
|
};
|
||||||
|
|
||||||
const submitHandler = async (userPrompt, { _raw = false } = {}) => {
|
const submitHandler = async (userPrompt, { _raw = false } = {}) => {
|
||||||
console.log('submitHandler', userPrompt, $chatId);
|
console.log('submitHandler', userPrompt, $chatId);
|
||||||
|
|
||||||
@@ -2658,17 +2691,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (String(userPrompt).trim() === '/compact') {
|
if (String(userPrompt).trim() === '/compact') {
|
||||||
|
clearCommandInput();
|
||||||
await handleManualCompact();
|
await handleManualCompact();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (String(userPrompt).trim() === '/status') {
|
if (String(userPrompt).trim() === '/status') {
|
||||||
|
clearCommandInput();
|
||||||
handleStatusCommand();
|
handleStatusCommand();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (String(userPrompt).trim() === '/fork') {
|
if (String(userPrompt).trim() === '/fork') {
|
||||||
|
clearCommandInput();
|
||||||
await handleForkChat();
|
await handleForkChat();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const modelCommandMatch = String(userPrompt)
|
||||||
|
.trim()
|
||||||
|
.match(/^\/model(?:\s+([\s\S]+))?$/);
|
||||||
|
if (modelCommandMatch) {
|
||||||
|
handleModelCommand(modelCommandMatch[1]?.trim() ?? '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingOAuthTools.length > 0) {
|
if (pendingOAuthTools.length > 0) {
|
||||||
toast.warning($i18n.t('Please connect all required integrations before sending a message'));
|
toast.warning($i18n.t('Please connect all required integrations before sending a message'));
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
import { DOMParser } from 'prosemirror-model';
|
||||||
|
import { Selection, TextSelection } from 'prosemirror-state';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import dayjs from '$lib/dayjs';
|
import dayjs from '$lib/dayjs';
|
||||||
import duration from 'dayjs/plugin/duration';
|
import duration from 'dayjs/plugin/duration';
|
||||||
@@ -547,6 +549,63 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const replaceSlashRangeWithPrompt = async (editor, range, text: string) => {
|
||||||
|
text = await textVariableHandler(text);
|
||||||
|
|
||||||
|
const { state, view } = editor;
|
||||||
|
let tr = state.tr;
|
||||||
|
|
||||||
|
if ($settings?.insertPromptAsRichText ?? false) {
|
||||||
|
const htmlContent = DOMPurify.sanitize(
|
||||||
|
marked
|
||||||
|
.parse(text, {
|
||||||
|
breaks: true,
|
||||||
|
gfm: true
|
||||||
|
})
|
||||||
|
.trim()
|
||||||
|
);
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = htmlContent;
|
||||||
|
const fragment = DOMParser.fromSchema(state.schema).parse(tempDiv);
|
||||||
|
const nodesToInsert = [];
|
||||||
|
|
||||||
|
fragment.content.forEach((node) => {
|
||||||
|
if (node.type.name === 'paragraph') {
|
||||||
|
nodesToInsert.push(...node.content.content);
|
||||||
|
} else {
|
||||||
|
nodesToInsert.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tr = tr.replaceWith(range.from, range.to, nodesToInsert);
|
||||||
|
const newPos = range.from + nodesToInsert.reduce((sum, node) => sum + node.nodeSize, 0);
|
||||||
|
tr = tr.setSelection(Selection.near(tr.doc.resolve(newPos)));
|
||||||
|
} else if (text.includes('\n')) {
|
||||||
|
const nodes = text
|
||||||
|
.split('\n')
|
||||||
|
.map((line, index) =>
|
||||||
|
index === 0
|
||||||
|
? state.schema.text(line ? line : [])
|
||||||
|
: state.schema.nodes.paragraph.create({}, line ? state.schema.text(line) : undefined)
|
||||||
|
);
|
||||||
|
tr = tr.replaceWith(range.from, range.to, nodes);
|
||||||
|
const newPos = nodes.reduce((pos, node) => pos + node.nodeSize, range.from);
|
||||||
|
tr = tr.setSelection(TextSelection.near(tr.doc.resolve(newPos)));
|
||||||
|
} else {
|
||||||
|
tr = tr.replaceWith(range.from, range.to, text !== '' ? state.schema.text(text) : []);
|
||||||
|
tr = tr.setSelection(
|
||||||
|
state.selection.constructor.near(tr.doc.resolve(range.from + text.length + 1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.dispatch(tr);
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
await inputVariableHandler(text);
|
||||||
|
await tick();
|
||||||
|
document.getElementById('chat-input')?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
let command = '';
|
let command = '';
|
||||||
export let showCommands = false;
|
export let showCommands = false;
|
||||||
$: showCommands =
|
$: showCommands =
|
||||||
@@ -587,6 +646,7 @@
|
|||||||
|
|
||||||
let chatInputContainerElement;
|
let chatInputContainerElement;
|
||||||
let chatInputElement;
|
let chatInputElement;
|
||||||
|
let modelSelector;
|
||||||
|
|
||||||
let filesInputElement;
|
let filesInputElement;
|
||||||
let commandsElement;
|
let commandsElement;
|
||||||
@@ -1178,6 +1238,29 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
char: '/',
|
char: '/',
|
||||||
|
command: ({ editor, range, props }) => {
|
||||||
|
if (props?.type === 'prompt') {
|
||||||
|
void replaceSlashRangeWithPrompt(editor, range, props.content ?? '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['compact', 'fork', 'status', 'model'].includes(props?.id)) {
|
||||||
|
editor.chain().focus().deleteRange(range).run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.insertContentAt(range, [
|
||||||
|
{
|
||||||
|
type: 'mention',
|
||||||
|
attrs: props
|
||||||
|
},
|
||||||
|
{ type: 'text', text: ' ' }
|
||||||
|
])
|
||||||
|
.run();
|
||||||
|
},
|
||||||
render: getSuggestionRenderer(CommandSuggestionList, {
|
render: getSuggestionRenderer(CommandSuggestionList, {
|
||||||
i18n,
|
i18n,
|
||||||
canCompact: () => !!history?.currentId && contextCompactionEnabled,
|
canCompact: () => !!history?.currentId && contextCompactionEnabled,
|
||||||
@@ -1189,6 +1272,7 @@
|
|||||||
onCompact: compactHandler,
|
onCompact: compactHandler,
|
||||||
onStatus: statusHandler,
|
onStatus: statusHandler,
|
||||||
onFork: forkHandler,
|
onFork: forkHandler,
|
||||||
|
onModel: () => modelSelector?.open(),
|
||||||
onSelect: (e) => {
|
onSelect: (e) => {
|
||||||
const { type, data } = e;
|
const { type, data } = e;
|
||||||
|
|
||||||
@@ -2238,6 +2322,7 @@
|
|||||||
<div class="self-end flex space-x-1 mr-1 shrink-0 gap-[0.5px]">
|
<div class="self-end flex space-x-1 mr-1 shrink-0 gap-[0.5px]">
|
||||||
<div class="flex min-w-0 max-w-[10rem] items-center sm:max-w-[13rem]">
|
<div class="flex min-w-0 max-w-[10rem] items-center sm:max-w-[13rem]">
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
|
bind:this={modelSelector}
|
||||||
bind:selectedModels
|
bind:selectedModels
|
||||||
showSetDefault={!history?.currentId}
|
showSetDefault={!history?.currentId}
|
||||||
placement="auto"
|
placement="auto"
|
||||||
|
|||||||
@@ -8,13 +8,19 @@
|
|||||||
|
|
||||||
export let char = '';
|
export let char = '';
|
||||||
export let query = '';
|
export let query = '';
|
||||||
export let command: (payload: { id: string; label: string }) => void;
|
export let command: (payload: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
content?: string;
|
||||||
|
type?: string;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
export let onSelect: (e: any) => void = () => {};
|
export let onSelect: (e: any) => void = () => {};
|
||||||
export let onUpload: (e: any) => void = () => {};
|
export let onUpload: (e: any) => void = () => {};
|
||||||
export let onCompact: () => void = () => {};
|
export let onCompact: () => void = () => {};
|
||||||
export let onStatus: () => void = () => {};
|
export let onStatus: () => void = () => {};
|
||||||
export let onFork: () => void = () => {};
|
export let onFork: () => void = () => {};
|
||||||
|
export let onModel: () => void = () => {};
|
||||||
export let insertTextHandler: (text: string) => void = () => {};
|
export let insertTextHandler: (text: string) => void = () => {};
|
||||||
export let canCompact: boolean | (() => boolean) = false;
|
export let canCompact: boolean | (() => boolean) = false;
|
||||||
export let compactDisabled: boolean | (() => boolean) = false;
|
export let compactDisabled: boolean | (() => boolean) = false;
|
||||||
@@ -93,16 +99,24 @@
|
|||||||
const { type, data } = e;
|
const { type, data } = e;
|
||||||
|
|
||||||
if (type === 'prompt') {
|
if (type === 'prompt') {
|
||||||
insertTextHandler(data.content);
|
command({
|
||||||
|
id: data.command,
|
||||||
|
label: data.command,
|
||||||
|
content: data.content,
|
||||||
|
type: 'prompt'
|
||||||
|
});
|
||||||
} else if (type === 'command' && data.id === 'compact') {
|
} else if (type === 'command' && data.id === 'compact') {
|
||||||
insertTextHandler('');
|
command({ id: data.id, label: data.id });
|
||||||
onCompact();
|
onCompact();
|
||||||
} else if (type === 'command' && data.id === 'status') {
|
} else if (type === 'command' && data.id === 'status') {
|
||||||
insertTextHandler('');
|
command({ id: data.id, label: data.id });
|
||||||
onStatus();
|
onStatus();
|
||||||
} else if (type === 'command' && data.id === 'fork') {
|
} else if (type === 'command' && data.id === 'fork') {
|
||||||
insertTextHandler('');
|
command({ id: data.id, label: data.id });
|
||||||
onFork();
|
onFork();
|
||||||
|
} else if (type === 'command' && data.id === 'model') {
|
||||||
|
command({ id: data.id, label: data.id });
|
||||||
|
onModel();
|
||||||
} else if (type === 'skill') {
|
} else if (type === 'skill') {
|
||||||
command({
|
command({
|
||||||
id: `${data.id}|${data.name}`,
|
id: `${data.id}|${data.name}`,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { getSkillItems } from '$lib/apis/skills';
|
import { getSkillItems } from '$lib/apis/skills';
|
||||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
import Cube from '$lib/components/icons/Cube.svelte';
|
import Cube from '$lib/components/icons/Cube.svelte';
|
||||||
|
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@@ -38,7 +39,8 @@
|
|||||||
: []),
|
: []),
|
||||||
...(canStatus && 'status'.startsWith(query.toLowerCase())
|
...(canStatus && 'status'.startsWith(query.toLowerCase())
|
||||||
? [{ type: 'command', data: { id: 'status' } }]
|
? [{ type: 'command', data: { id: 'status' } }]
|
||||||
: [])
|
: []),
|
||||||
|
...('model'.startsWith(query.toLowerCase()) ? [{ type: 'command', data: { id: 'model' } }] : [])
|
||||||
];
|
];
|
||||||
|
|
||||||
$: filteredPrompts = prompts
|
$: filteredPrompts = prompts
|
||||||
@@ -266,6 +268,32 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{:else if item.data.id === 'model'}
|
||||||
|
<Tooltip content="Show or switch the current model." placement="top">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Model: show or switch the current model."
|
||||||
|
class="slash-command-row flex items-center gap-2 w-full h-6 px-2 rounded-xl text-xs text-left transition-colors duration-75
|
||||||
|
{commandIdx === selectedIdx ? 'app-interactive-active' : ''}"
|
||||||
|
on:mousedown={(e) => e.preventDefault()}
|
||||||
|
on:click={() => {
|
||||||
|
onSelect(item);
|
||||||
|
}}
|
||||||
|
on:mouseenter={() => {
|
||||||
|
selectedIdx = commandIdx;
|
||||||
|
}}
|
||||||
|
on:focus={() => {}}
|
||||||
|
data-selected={commandIdx === selectedIdx}
|
||||||
|
>
|
||||||
|
<span class="app-icon-muted flex items-center justify-center w-4 shrink-0">
|
||||||
|
<Sparkles className="size-3.5" />
|
||||||
|
</span>
|
||||||
|
<span class="flex-1 min-w-0 flex items-baseline gap-1.5 overflow-hidden">
|
||||||
|
<span class="truncate">Model</span>
|
||||||
|
<span class="app-muted text-[0.625rem] truncate shrink-0">/model</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import {
|
import {
|
||||||
user,
|
user,
|
||||||
MODEL_DOWNLOAD_POOL,
|
MODEL_DOWNLOAD_POOL,
|
||||||
|
mobile,
|
||||||
models,
|
models,
|
||||||
temporaryChatEnabled,
|
temporaryChatEnabled,
|
||||||
settings,
|
settings,
|
||||||
@@ -173,6 +174,17 @@
|
|||||||
schedulePositionUpdate();
|
schedulePositionUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const focusSearchInput = () => {
|
||||||
|
if (!$mobile) {
|
||||||
|
document.getElementById('model-search-input')?.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const focusChatInput = () => {
|
||||||
|
if (!$mobile) {
|
||||||
|
document.getElementById('chat-input')?.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggleOpen = async () => {
|
const toggleOpen = async () => {
|
||||||
show = !show;
|
show = !show;
|
||||||
if (show) {
|
if (show) {
|
||||||
@@ -182,12 +194,20 @@
|
|||||||
updatePosition();
|
updatePosition();
|
||||||
await tick();
|
await tick();
|
||||||
updatePosition();
|
updatePosition();
|
||||||
window.setTimeout(() => document.getElementById('model-search-input')?.focus(), 0);
|
for (const delay of [0, 50, 150]) {
|
||||||
|
window.setTimeout(focusSearchInput, delay);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById(`model-selector-${id}-button`)?.blur();
|
document.getElementById(`model-selector-${id}-button`)?.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const open = async () => {
|
||||||
|
if (!show) {
|
||||||
|
await toggleOpen();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handlePointerDown = (e: PointerEvent) => {
|
const handlePointerDown = (e: PointerEvent) => {
|
||||||
if (!show) return;
|
if (!show) return;
|
||||||
const target = e.target as Node;
|
const target = e.target as Node;
|
||||||
@@ -416,11 +436,13 @@
|
|||||||
values = [item.value];
|
values = [item.value];
|
||||||
value = item.value;
|
value = item.value;
|
||||||
show = false;
|
show = false;
|
||||||
|
window.setTimeout(focusChatInput, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = item.value;
|
value = item.value;
|
||||||
show = false;
|
show = false;
|
||||||
|
window.setTimeout(focusChatInput, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setDefaultHandler = async () => {
|
const setDefaultHandler = async () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function getSuggestionRenderer(Component: any, ComponentProps = {}) {
|
|||||||
char: props?.text?.charAt(0),
|
char: props?.text?.charAt(0),
|
||||||
query: props?.query,
|
query: props?.query,
|
||||||
command: (item) => {
|
command: (item) => {
|
||||||
props.command({ id: item.id, label: item.label });
|
props.command(item);
|
||||||
},
|
},
|
||||||
...ComponentProps
|
...ComponentProps
|
||||||
},
|
},
|
||||||
@@ -88,7 +88,7 @@ export function getSuggestionRenderer(Component: any, ComponentProps = {}) {
|
|||||||
component.$set({
|
component.$set({
|
||||||
query: props.query,
|
query: props.query,
|
||||||
command: (item) => {
|
command: (item) => {
|
||||||
props.command({ id: item.id, label: item.label });
|
props.command(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user