mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-13 08:05:23 +00:00
* feat(ai-chat): make reusable skills ai_skill resources you select per workspace * chore: pin the ee ref to the skill telemetry counters * fix: address review findings on skill authoring, import and migration * fix: enforce skill selection in read_skill and stop imports clobbering resources * feat: carry format_extension from the hub into synced resource types * fix: let an edit set or clear a resource type's format_extension * fix: regenerate the sqlx cache and close the review round findings * fix: close the round-2 findings on folder ACLs, cached sync and truncation * refactor: make the skills migration non-destructive and use design-system inputs * feat(ai-sessions): add a context panel listing what the chat can use Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmdCVM1ZvTcpatv78jN8Ed * fix: track the prompt rebuild signal and trim the review round's nits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmdCVM1ZvTcpatv78jN8Ed * fix: keep the panel from perturbing an in-flight turn Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmdCVM1ZvTcpatv78jN8Ed * fix: count a folder by its readable children Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmdCVM1ZvTcpatv78jN8Ed * feat(ai-sessions): replace the context panel with an assistant settings modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * feat(ai-sessions): page-based MCP editing and fuzzy tool search Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * feat(ai-sessions): tool detail page and a shared list row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * feat(ai-sessions): add a files & folders section to the assistant settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * chore: point the ee ref at the merged ee branch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: keep hidden sections from answering keys and swallowing a failed save Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: restore the staged-fork write guard and narrow the round-2 findings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: restore the workspace-race guards and extend them to MCP Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: keep an in-flight settings read from overwriting typed instructions Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * docs: describe the tool row as the one line it renders Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: keep the prompt entries on the home composer, which has no settings modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: create the editor with the gutter its caller asked for Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * test: restore the attachment status label guard dropped in the merge Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: surface a refused mcp selection write instead of painting the switch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: refuse instruction writes to a staged fork's parent, and read the target's role Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: pin the instructions role and field to the target workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld1m9NAGLdPPrNBiu5PSQK * fix: retry a deferred instructions reload, and use Button for the row label Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGnKFvaX61wiMU1CzQp6XG * fix: leave the arrows to a control that answered them, and say when a role read failed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGnKFvaX61wiMU1CzQp6XG * chore: update ee-repo-ref to a2776856c50e80c9dbcf6e689a66ce86567c03fa This commit updates the EE repository reference after PR #765 was merged in windmill-ee-private. Previous ee-repo-ref: dd7466e749753568a23c91ba5e165020769206b8 New ee-repo-ref: a2776856c50e80c9dbcf6e689a66ce86567c03fa Automated by sync-ee-ref workflow. * fix: withhold the page navigator from a parked section Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGnKFvaX61wiMU1CzQp6XG --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
159 lines
5.3 KiB
Svelte
159 lines
5.3 KiB
Svelte
<!--
|
|
@component
|
|
The Files & folders section of the assistant settings modal: everything linked to this
|
|
chat, whether it is readable, and the way to unlink it.
|
|
|
|
Attaching happens in the composer — this is where what is already attached is accounted
|
|
for, which is the one place the difference between "attached" and "readable" is visible.
|
|
-->
|
|
<script lang="ts">
|
|
import { Button, ListRow, Section } from '$lib/components/common'
|
|
import EmptyState from '$lib/components/common/emptyState/EmptyState.svelte'
|
|
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
|
import { FileText, Folder, Paperclip, Unlink } from 'lucide-svelte'
|
|
import { getAiChatManager } from './aiChatManagerContext'
|
|
import { attachmentStatusLabel, countReadyAttachments } from './agentContext'
|
|
|
|
let {
|
|
count = $bindable(),
|
|
blocksClose = $bindable()
|
|
}: {
|
|
/** Attachments the assistant can actually read, for the sidebar badge. */
|
|
count: number
|
|
/** True while the confirmation is up, so the modal leaves the keys to it. */
|
|
blocksClose: boolean
|
|
} = $props()
|
|
|
|
const aiChatManager = getAiChatManager()
|
|
|
|
let folders = $derived(aiChatManager.attachedFiles.folders)
|
|
// Files dropped on a past message are listed alongside the session's own: the
|
|
// composer's bar hides them because their chip lives on that message, but the
|
|
// assistant reads them like any other row.
|
|
let files = $derived([
|
|
...aiChatManager.attachedFiles.standalone,
|
|
...aiChatManager.attachedFiles.messageAttached
|
|
])
|
|
let messageScoped = $derived(
|
|
new Set(aiChatManager.attachedFiles.messageAttached.map((f) => f.id))
|
|
)
|
|
let total = $derived(folders.length + files.length)
|
|
let ready = $derived(countReadyAttachments(folders, files))
|
|
|
|
let pendingDetach = $state<{ kind: 'file' | 'folder'; name: string } | undefined>(undefined)
|
|
|
|
$effect(() => {
|
|
count = ready
|
|
})
|
|
$effect(() => {
|
|
blocksClose = pendingDetach !== undefined
|
|
})
|
|
|
|
function detach(target: { kind: 'file' | 'folder'; name: string }) {
|
|
if (target.kind === 'folder') aiChatManager.attachedFiles.removeFolder(target.name)
|
|
else aiChatManager.attachedFiles.removeFile(target.name)
|
|
pendingDetach = undefined
|
|
}
|
|
</script>
|
|
|
|
<Section
|
|
label="Files & folders"
|
|
description="Files and folders linked to this chat, which the assistant can open and search."
|
|
class="flex flex-col gap-4"
|
|
>
|
|
{#snippet action()}
|
|
{#if total > 0}
|
|
<!-- Both numbers when they differ: one count under a heading about what the
|
|
assistant can use would hide the rows it cannot open. -->
|
|
<span class="shrink-0 text-2xs text-secondary tabular-nums">
|
|
{ready === total ? `${total} usable` : `${ready} of ${total} usable`}
|
|
</span>
|
|
{/if}
|
|
{/snippet}
|
|
|
|
{#if total === 0}
|
|
<EmptyState
|
|
icon={Paperclip}
|
|
title="Nothing attached"
|
|
description="Drop a file or a folder on the chat, or use the paperclip in the composer, and the assistant can open and search it."
|
|
/>
|
|
{:else}
|
|
<div class="flex flex-col gap-0.5">
|
|
{#each folders as folder (folder.name)}
|
|
{#snippet icon()}
|
|
<Folder size={16} class="text-tertiary" />
|
|
{/snippet}
|
|
{#snippet title()}
|
|
<span class="truncate leading-5">{folder.name}</span>
|
|
{@const label = attachmentStatusLabel(folder.status)}
|
|
{#if label}
|
|
<span class="shrink-0 text-2xs font-normal text-hint">{label}</span>
|
|
{/if}
|
|
{/snippet}
|
|
{#snippet subtitle()}
|
|
{folder.files.length}
|
|
{folder.files.length === 1 ? 'file' : 'files'}
|
|
{/snippet}
|
|
{#snippet trailing()}
|
|
<Button
|
|
unifiedSize="sm"
|
|
variant="subtle"
|
|
startIcon={{ icon: Unlink }}
|
|
iconOnly
|
|
title="Unlink folder"
|
|
onClick={() => (pendingDetach = { kind: 'folder', name: folder.name })}
|
|
/>
|
|
{/snippet}
|
|
<ListRow {icon} {title} {subtitle} {trailing} />
|
|
{/each}
|
|
{#each files as file (file.id ?? file.name)}
|
|
{#snippet icon()}
|
|
<FileText size={16} class="text-tertiary" />
|
|
{/snippet}
|
|
{#snippet title()}
|
|
<span class="truncate leading-5">{file.name}</span>
|
|
{@const label = attachmentStatusLabel(file.status)}
|
|
{#if label}
|
|
<span class="shrink-0 text-2xs font-normal text-hint">{label}</span>
|
|
{/if}
|
|
{/snippet}
|
|
{#snippet subtitle()}
|
|
Attached to a message
|
|
{/snippet}
|
|
{#snippet trailing()}
|
|
<Button
|
|
unifiedSize="sm"
|
|
variant="subtle"
|
|
startIcon={{ icon: Unlink }}
|
|
iconOnly
|
|
title="Unlink file"
|
|
onClick={() => (pendingDetach = { kind: 'file', name: file.name })}
|
|
/>
|
|
{/snippet}
|
|
<!-- A message-scoped row has no unlink here: those are rebuilt from the
|
|
transcript on every load, so `removeFile` refuses them and the chip on the
|
|
message is what actually drops one. -->
|
|
<ListRow
|
|
{icon}
|
|
{title}
|
|
subtitle={messageScoped.has(file.id) ? subtitle : undefined}
|
|
trailing={messageScoped.has(file.id) ? undefined : trailing}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</Section>
|
|
|
|
<ConfirmationModal
|
|
open={pendingDetach !== undefined}
|
|
title={pendingDetach?.kind === 'folder' ? 'Unlink folder' : 'Unlink file'}
|
|
confirmationText="Unlink"
|
|
onConfirmed={() => pendingDetach && detach(pendingDetach)}
|
|
onCanceled={() => (pendingDetach = undefined)}
|
|
>
|
|
<span class="text-xs text-primary">
|
|
The assistant loses access to <span class="font-semibold">{pendingDetach?.name}</span>. Nothing
|
|
is deleted from your disk, and you can attach it again from the composer.
|
|
</span>
|
|
</ConfirmationModal>
|