This commit is contained in:
Timothy Jaeryang Baek 2026-09-14 15:47:50 -04:00
parent 924a4a10fb
commit 58078ab304
3 changed files with 65 additions and 1 deletions

View file

@ -174,6 +174,12 @@
(history.currentId && history.messages[history.currentId]?.done != true) ||
generating);
$: canCompact = !!history?.currentId;
$: hasChatContent = history?.currentId
? createMessagesList(history, history.currentId).some(
(message) =>
!!message?.content || !!message?.tool_calls || !!message?.files || !!message?.models
)
: false;
$: canToggleTemporary =
!embedded &&
!chatId &&
@ -578,6 +584,12 @@
}
};
const createSkillHandler = async () => {
prompt = '/skills:create';
await tick();
dispatch('submit', prompt);
};
const insertTextAtCursor = async (text: string) => {
const chatInput = document.getElementById('chat-input');
if (!chatInput) return;
@ -1418,7 +1430,17 @@
return;
}
if (['compact', 'fork', 'status', 'model', 'settings', 'temporary'].includes(props?.id)) {
if (
[
'compact',
'fork',
'status',
'model',
'settings',
'temporary',
'skills:create'
].includes(props?.id)
) {
editor.chain().focus().deleteRange(range).run();
return;
}
@ -1445,6 +1467,7 @@
($_user?.role === 'admin' || ($_user?.permissions?.chat?.import ?? true)),
forkDisabled: () => isActive,
canTemporary: () => canToggleTemporary,
hasChatContent: hasChatContent,
temporaryEnabled: () => $temporaryChatEnabled === true,
contextUsage: () => statusContextUsage,
onCompact: compactHandler,
@ -1453,6 +1476,7 @@
onModel: () => modelSelector?.open(),
onSettings: () => showSettings.set(true),
onTemporary: temporaryHandler,
onCreateSkill: createSkillHandler,
onSelect: (e) => {
const { type, data } = e;

View file

@ -23,6 +23,7 @@
export let onModel: () => void = () => {};
export let onSettings: () => void = () => {};
export let onTemporary: () => void = () => {};
export let onCreateSkill: () => void = () => {};
export let insertTextHandler: (text: string) => void = () => {};
export let canCompact: boolean | (() => boolean) = false;
export let compactDisabled: boolean | (() => boolean) = false;
@ -31,6 +32,7 @@
export let forkDisabled: boolean | (() => boolean) = false;
export let canTemporary: boolean | (() => boolean) = false;
export let temporaryEnabled: boolean | (() => boolean) = false;
export let hasChatContent = false;
export let contextUsage = null;
$: compactAvailable = typeof canCompact === 'function' ? canCompact() : canCompact;
@ -104,6 +106,7 @@
temporaryEnabled={isTemporaryEnabled}
{contextPercent}
{contextHasThreshold}
{hasChatContent}
onSelect={(e) => {
const { type, data } = e;
@ -132,6 +135,9 @@
} else if (type === 'command' && data.id === 'temporary') {
command({ id: data.id, label: data.id });
onTemporary();
} else if (type === 'command' && data.id === 'skills:create') {
command({ id: data.id, label: data.id });
onCreateSkill();
} else if (type === 'skill') {
command({
id: `${data.id}|${data.name}`,

View file

@ -20,6 +20,7 @@
import ChatBubbleDottedChecked from '$lib/components/icons/ChatBubbleDottedChecked.svelte';
import Cube from '$lib/components/icons/Cube.svelte';
import Knobs from '$lib/components/icons/Knobs.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
import Sparkles from '$lib/components/icons/Sparkles.svelte';
const i18n: any = getContext('i18n');
@ -33,6 +34,7 @@
export let forkDisabled = false;
export let canTemporary = false;
export let temporaryEnabled = false;
export let hasChatContent = false;
export let contextPercent = 0;
export let contextHasThreshold = false;
@ -66,6 +68,9 @@
: []),
...('settings'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'settings' } }]
: []),
...(hasChatContent && $selectedTerminalId && 'skills:create'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'skills:create' } }]
: [])
];
@ -375,6 +380,35 @@
</span>
</button>
</Tooltip>
{:else if item.data.id === 'skills:create'}
<Tooltip
content={$i18n.t('Create a reusable terminal skill from this chat.')}
placement="top"
>
<button
type="button"
aria-label={$i18n.t('Create skill: create a reusable terminal skill from this chat.')}
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">
<Plus className="size-3.5" />
</span>
<span class="flex-1 min-w-0 flex items-baseline gap-1.5 overflow-hidden">
<span class="truncate">{$i18n.t('Create skill')}</span>
<span class="app-muted text-[0.625rem] truncate shrink-0">/skills:create</span>
</span>
</button>
</Tooltip>
{:else if item.data.id === 'settings'}
<Tooltip content={$i18n.t('Open settings.')} placement="top">
<button