This commit is contained in:
Pavel committed 2026-09-25 12:26:39 +04:00
1 parent 2c57472d0a
commit 0dd379c271
223 files changed
+5297 -4598

No files matched your search

+192 -67
View File
@@ -23,23 +23,23 @@ through `@theme inline`, so `bg-`, `text-`, `border-`, `ring-`, `fill-` and
`stroke-` all accept them, including opacity modifiers such as
`bg-success/10`. One token class replaces a light + dark pair.
| Token | Use for | Replaces |
| --------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
| `background`, `foreground` | page surface and primary text | `bg-white`, `text-gray-700/800/900`, `dark:text-gray-200/300` |
| `card`, `card-foreground` | raised surfaces (cards, dialogs, dropdowns) | `bg-white dark:bg-[#2b2c31]` |
| `popover`, `popover-foreground` | floating menus and popovers | same as card |
| `muted`, `muted-foreground` | quiet fills and secondary text, icons, placeholders, timestamps | `bg-gray-100/200`, `text-gray-400/500/600`, `dark:text-gray-400` |
| `accent`, `accent-foreground` | hover and selected fills | `hover:bg-gray-100`, `dark:hover:bg-gray-700` |
| `border`, `input`, `ring` | dividers, field borders, focus rings | `border-gray-200/300`, `dark:border-gray-600/700` |
| `primary`, `primary-foreground` | brand purple: primary actions, links, selected states | `bg-purple-*`, `text-purple-*`, `#7D54D1`, `#A076F6` |
| Token | Use for | Replaces |
| --------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `background`, `foreground` | page surface and primary text | `bg-white`, `text-gray-700/800/900`, `dark:text-gray-200/300` |
| `card`, `card-foreground` | raised surfaces (cards, dialogs, dropdowns) | `bg-white dark:bg-[#2b2c31]` |
| `popover`, `popover-foreground` | floating menus and popovers | same as card |
| `muted`, `muted-foreground` | quiet fills and secondary text, icons, placeholders, timestamps | `bg-gray-100/200`, `text-gray-400/500/600`, `dark:text-gray-400` |
| `accent`, `accent-foreground` | hover and selected fills | `hover:bg-gray-100`, `dark:hover:bg-gray-700` |
| `border`, `input`, `ring` | dividers, field borders, focus rings | `border-gray-200/300`, `dark:border-gray-600/700` |
| `primary`, `primary-foreground` | brand purple: primary actions, links, selected states | `bg-purple-*`, `text-purple-*`, `#7D54D1`, `#A076F6` |
| `secondary`, `secondary-foreground` | pressed and active toggles (a brand tint), secondary buttons, the question bubble | `bg-accent` on an active icon button |
| `destructive`, `destructive-foreground` | errors, failed states, dangerous actions | `text-red-*`, `bg-red-50/100`, `#B42318`, `#E60000` |
| `success`, `success-foreground` | completed, active, healthy | `text-green-*`, `text-emerald-*`, `bg-green-50/100` |
| `warning`, `warning-foreground` | paused, pending review, degraded | `text-amber-*`, `text-yellow-*`, `text-orange-*`, `bg-amber-50/100` |
| `info`, `info-foreground` | running, informational | `text-blue-*`, `bg-blue-50/100` |
| `sidebar-*` | the navigation rail | |
| `chart-1` to `chart-5` | data series only, never UI chrome (see below) | |
| `answer-bubble` | assistant message background | |
| `destructive`, `destructive-foreground` | errors, failed states, dangerous actions | `text-red-*`, `bg-red-50/100`, `#B42318`, `#E60000` |
| `success`, `success-foreground` | completed, active, healthy | `text-green-*`, `text-emerald-*`, `bg-green-50/100` |
| `warning`, `warning-foreground` | paused, pending review, degraded | `text-amber-*`, `text-yellow-*`, `text-orange-*`, `bg-amber-50/100` |
| `info`, `info-foreground` | running, informational | `text-blue-*`, `bg-blue-50/100` |
| `sidebar-*` | the navigation rail | |
| `chart-1` to `chart-5` | data series only, never UI chrome (see below) | |
| `answer-bubble` | assistant message background | |
Status colours are tuned to stay vivid rather than turning brown or olive,
so their contrast is low. Against white in light mode, `destructive` and
@@ -89,11 +89,11 @@ Patterns:
### Button (`ui/button.tsx`)
| Prop | Values |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `variant` | `default`, `secondary`, `outline`, `outline-primary`, `ghost`, `ghost-muted`, `ghost-destructive`, `link`, `destructive`, `destructive-outline`, `combobox`, `sidebar-item`, `tab`, `section-toggle` |
| `size` | `xs`, `sm`, `default`, `lg`, `field`, `icon-xs`, `icon-sm`, `icon`, `icon-lg`, `inline` |
| `shape` | `default` (rounded-md), `pill` (rounded-full, wider padding) |
| Prop | Values |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `variant` | `default`, `secondary`, `outline`, `outline-primary`, `ghost`, `ghost-muted`, `ghost-destructive`, `ghost-on-accent`, `ghost-destructive-on-accent`, `link`, `destructive`, `destructive-outline`, `combobox`, `sidebar-item`, `tab`, `section-toggle` |
| `size` | `xs`, `sm`, `default`, `lg`, `field`, `icon-xs`, `icon-sm`, `icon`, `icon-lg`, `inline` |
| `shape` | `default` (rounded-md), `pill` (rounded-full, wider padding) |
- Round brand buttons (`rounded-3xl px-5`, `rounded-full px-6`): `shape="pill"`,
plus `size="lg"` if they were `px-6`.
@@ -102,6 +102,14 @@ Patterns:
- Icon actions that remove something (a trash can on a member or shared
resource row) are `variant="ghost-destructive"`: muted at rest, red on
hover, so the warning shows even where the click deletes without asking.
- Icon buttons on a row that is already `bg-accent` or `bg-sidebar-accent`
while you point at it (a highlighted Command item, a card or tile with
`hover:bg-accent`, a hovered sidebar row) are `variant="ghost-on-accent"`
(delete: `ghost-destructive-on-accent`). They hover with a
`foreground/15` (`destructive/15`) tint, which shows on accent where
ghost's accent square would not.
- Icons are `lucide-react` at the default stroke (2). Don't pass
`strokeWidth`, and don't use an `<img>` for a glyph lucide has.
- Bordered purple buttons (`border-primary text-primary hover:bg-primary`):
`variant="outline-primary"`.
- Tiny inline actions (`h-auto px-2 py-1 text-xs`): `size="xs"`.
@@ -117,9 +125,10 @@ Patterns:
while `data-placeholder` is set (`data-placeholder={value ? undefined : ''}`).
Pass only layout (`w-full justify-between`) and keep the chevron as the
last child.
- A button or picker that sits in a row of fields is `size="field"`: 42px,
the height of `Input` (default) and `SelectTrigger size="lg"`, so a form
column has one height. With `shape="pill"` its text starts 21px in, like
- A button or picker that sits in a row of fields is `size="field"`: 42px.
`Input` and `SelectTrigger` take `size="field"` too (the same 42px as
Input `default` and SelectTrigger `lg`), so a form column has one name for
one height; prefer `field` in new form rows. With `shape="pill"` its text starts 21px in, like
the Input and Select pills beside it. The agent form's pickers are
`combobox field pill`, its Add button `outline-primary field pill`.
- Rows in the navigation sidebar (`hover:bg-sidebar-accent … pl-3 gap-2.5
@@ -136,20 +145,37 @@ rounded-3xl`) are `variant="sidebar-item"`: left-aligned, full-radius, normal
gives it `foreground` text and a `primary` underline. Padding-free tabs (a
sub-nav with `gap-6`) add `size="inline"`, which keeps 4px above the line;
the row draws the 1px baseline, and `-mb-px` lays the underline over it.
Tabs that switch a panel in place use `ui/tabs` instead.
Use them for a row of route links (the agent sub-nav: a `<nav>` whose
current link carries `aria-current="page"`). Tabs that switch a panel in
place are `ui/tabs` with `variant="underline"` on `TabsList` and each
`TabsTrigger`: the same pixels, plus `role="tablist"`/`"tab"`,
`aria-selected` and arrow-key focus; wrap the panel in `TabsContent`
(FilePicker's My Files / Shared with Me). The `default` variant is a pill
tab, unused in the app.
- A section panel's disclosure header (NewAgent's Advanced and Guardrails
panels) is `variant="section-toggle" size="sm"` with `-ml-3 w-fit
justify-start` and `aria-expanded`: a lucide `ChevronRight` first
justify-start` and `aria-expanded`: a lucide `ChevronRight` first
(`rotate-90` while open) in primary, then the foreground title, with a
primary underline on hover. The button draws no focus ring; the panel does,
so keyboard focus outlines the whole white block:
`has-[[data-variant=section-toggle]:focus-visible]:ring-3 …:ring-ring/50
…:ring-inset` on the panel `<div>` (inset, because a scrolling column clips
…:ring-inset` on the panel `<div>` (inset, because a scrolling column clips
an outset ring). Status badges go beside the button, not inside it, or the
hover underline runs under them.
- Drop `text-white` on default and destructive buttons; the foreground token
already provides it. Drop `disabled:cursor-not-allowed`; the base disables
pointer events.
already provides it. Drop `disabled:cursor-not-allowed` on buttons; the
base disables pointer events. Fields are the other way round (see Focus,
elevation and stacking: "Disabled").
- A button that is busy (saving, creating, testing) takes `loading`: it
disables itself, sets `aria-busy`, and draws a 16px spinner over the label,
which stays in the layout (invisible) so the width doesn't jump. Keep the
idle label; never hand-place a `Spinner` in a button, swap the label for
"Saving…", or pin a fixed width to stop the jump. The one exception is a
button whose busy state says something the user needs: a progress figure
(a connector's Sync shows "42%") or a mode (the composer's Voice button shows
"Transcribing"). It keeps its busy label with a spinning mark where its
icon was: Sync uses `Spinner size="sm"` at icon size (`size-4`); Voice still
spins lucide's `LoaderCircle`.
### Card (`ui/card.tsx`)
@@ -186,15 +212,18 @@ modal) leave it out and stay plain buttons. Only layout classes on it.
pill, including schedule and run status pills. HTTP method pills take their variant from
`getMethodBadgeVariant` (`utils/httpMethodColors.ts`): GET `success`, POST
`info`, PUT `warning`, DELETE `destructive`, PATCH `default`, anything else
`neutral`.
`neutral`. `MultiSelect` (`ui/multi-select.tsx`) shows its first two picks as
`default` Badges with a remove X, then "+N more", on a `Button
variant="combobox" size="field"` trigger that grows past 42px when the chips
wrap.
### Input (`ui/input.tsx`)
| Prop | Values |
| ------- | ---------------------------------------------------------------- |
| `size` | `sm` (h-8), `default` (h-10.5, the historical 42px), `lg` (h-12) |
| `shape` | `default`, `pill` |
| `variant` | `default`, `bare` (no border, padding, radius, shadow or ring), `filled` (card fill) |
| Prop | Values |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `size` | `sm` (h-8), `default` (h-10.5, the historical 42px), `lg` (h-12), `field` (h-10.5, the form-row name) |
| `shape` | `default`, `pill` |
| `variant` | `default`, `bare` (no border, padding, radius, shadow or ring), `filled` (card fill) |
Text alignment classes (`text-right`) and `font-mono` (code fields) are
allowed on Input and Textarea. A floating
@@ -216,9 +245,11 @@ instead of showing the panel through; never pass `bg-card` for it.
### SelectTrigger (`ui/select.tsx`)
`size`: `sm`, `default`, `lg`; `variant`: `default`, `ghost`; `shape`:
`default`, `pill`. The large rounded selects (`rounded-3xl px-5 py-3`) are
`size="lg" shape="pill"`.
`size`: `sm` (32px), `default` (36px), `lg` (42px), `field` (42px, the
form-row name); `variant`: `default`, `ghost`; `shape`: `default`, `pill`.
Pills pad `px-5` at every size but `sm` (`px-3`), so their text starts 21px
in like the Input and Button field pills. The large rounded selects
(`rounded-3xl px-5 py-3`) are `size="lg" shape="pill"`.
### Textarea (`ui/textarea.tsx`)
@@ -226,7 +257,51 @@ instead of showing the panel through; never pass `bg-card` for it.
`none`, `vertical` (default), `both`. Same border, ring and invalid styling
as Input. `variant`: `default` (transparent), `filled` (card fill), with
the same rule as Input: a textarea on a muted panel is `variant="filled"`,
never `bg-card`. Replaces the 21 raw `<textarea>` elements.
never `bg-card`. Three raw `<textarea>` elements stay, because each sits
under an overlay it must line up with: the chat composer, PromptTextArea's
variable highlighter and the chunk editor behind its line-number gutter.
### FormField (`ui/form-field.tsx`)
A labelled field is `<FormField label required hint error disabled>` with
the field as its only child. It stacks `Label`, the field, a muted `text-xs`
hint and a red `text-xs` error (`role="alert"`) 6px apart (`gap-1.5`), puts
the required star 4px after the label (`aria-hidden`; the field gets
`aria-required`, not native `required`), and dims the label while
`disabled`. `Input`, `Textarea`, `SelectTrigger` (even nested in `Select`),
`MultiSelect` and `Checkbox` read their `id`, `aria-invalid`, `aria-describedby`,
`aria-required` and `disabled` from it, so don't set those by hand; an id
the field already has wins. Any other control: pass `id` to FormField and
the same id to the control. One FormField holds one control: a second
field inside it (an "Add ref" box under a picker) needs its own `id`, or it
takes the field's. Popovers reset the wiring (`FormFieldBoundary`), so a
picker's search box is safe. There is one size: labels are `text-sm`
everywhere, also in dense panels (Guardrails). `className` is layout only.
Floating-label Inputs (`<Input label>`) label themselves and don't go in a
FormField. Never hand-build a `<label className="mb-2 block …">` or a
`div.flex-col` + `Label` + `<p>` stack.
### SettingRow (`ui/setting-row.tsx`)
A setting with a control on the right (a Switch, a short Input) is
`<SettingRow label description htmlFor alignStart as after>{control}</SettingRow>`,
grouped in `<SettingRows>`, which splits rows with `divide-border/50` and pads
each 12px (none at the group's ends). The title is a `Label` for the control
(`htmlFor` = the control's id), so every switch has a name and clicking the
title toggles it; `as="h2" | "h3"` keeps a heading tag, and the control then
needs its own `aria-label`. `alignStart` top-aligns the control for wrapping
descriptions. `after` holds a field that belongs to the row, 8px under it
(the agent form's limit Inputs). Inline "switch + label" pairs (a filter
toggle) are not SettingRows.
### Checkbox (`ui/checkbox.tsx`)
`<Checkbox checked onCheckedChange>` (Radix), never `<input type="checkbox">`:
native boxes take the OS accent and ignore dark mode. `size`: `default`
(16px, option rows), `sm` (14px, table cells). Unchecked it is an
`border-input` box; checked, `primary` with a `primary-foreground` check.
Name it with a `Label htmlFor` or `aria-label`. For an on/off setting with a
description use a Switch in a SettingRow instead.
### Tooltip (`ui/tooltip.tsx`)
@@ -238,10 +313,9 @@ button as well.
### Spinner and Skeleton (`ui/spinner.tsx`, `ui/skeleton.tsx`)
`Spinner size="sm | default | lg"` draws in `currentColor`, so colour it
with a `text-*` token on it or its parent. `components/Spinner.tsx` is a
compatibility wrapper (`small | medium | large`) for existing imports.
`Skeleton` is a pulsing muted block sized with layout classes and rounded
as needed; `components/SkeletonLoader.tsx` should compose it.
with a `text-*` token on it or its parent. `Skeleton` is a pulsing muted
block sized with layout classes and rounded as needed. Never put one inside
an element that pulses itself; the two animations multiply.
### Progress (`ui/progress.tsx`)
@@ -333,12 +407,40 @@ rounded box) on a wrapper around `Accordion`.
rejects imports of it elsewhere. App code uses `Modal` (sizes `sm` to
`full`, `mobileVariant="sheet"`) or `CommandDialog`.
A modal's heading is its `title` (20px, `text-xl leading-tight
font-semibold`) and `description` (muted `text-sm`, 8px under the title).
Don't pass `hideTitle` to draw your own `<h2>`; `hideTitle` is only for
dialogs whose top line is not a title (MoveToFolderModal's breadcrumb,
Upload's step headings, ScheduleFormModal's editable name, the search
palette).
Buttons go in `footer`, never in `children`. The footer stacks full width on
phones (primary on top) and sits in a right-aligned row from `sm` up. The
standard pair is `footer={<ModalActions cancelLabel onCancel submitLabel
onSubmit pending disabled destructive />}`: a ghost Cancel and a primary
(or `destructive`) submit, both `size="lg" shape="pill"`; `pending` shows
the submit's `loading` spinner. A left-hand extra (Test connection) is
`footerStart`, and `submitProps` / `cancelProps` carry `type="submit"`,
`form` or a test id. A lone button is a `size="lg" shape="pill"` Button.
A search palette is `CommandDialog` on desktop. Pass cmdk options to its
inner `Command` through `commandProps` (`shouldFilter={false}` when results
come from a server search, a controlled `value`). On phones the same palette
goes in `Modal mobileVariant="sheet"` as `<Command variant="palette">`, which
gives it the dialog's 48px input row and row spacing, so both widths look
the same (`modals/SearchConversationsModal.tsx`).
the same (`modals/SearchConversationsModal.tsx`). `CommandDialog` sits on
`DialogContent`, which has Modal's surface (`bg-card rounded-2xl
shadow-modal`, no border) and the same blurred overlay.
Every phone bottom sheet has one shape: `bg-card`, 16px top corners, no top
border, at most 90% of the viewport, and bottom padding that clears the iPhone
home indicator. `SheetContent side="bottom"` gives it with `pb-safe-0` (the
bare inset); pass `handle` for the grab bar, and `showCloseButton={false}`
when the handle is the only dismiss affordance besides the overlay.
`Modal mobileVariant="sheet"` shares the shape and the `SheetHandle`, with
`pb-safe` (the inset, at least 1rem) under its footer. `pb-safe` and
`pb-safe-0` are the `index.css` utilities for `env(safe-area-inset-bottom)`;
never spell `env()` in a class.
In a picker list, mark the item that is currently chosen with
`CommandItem checked` (a `secondary` brand tint through `data-checked`), not
@@ -347,6 +449,17 @@ follows the pointer and arrow keys, so an accent fill would look like hover.
While a checked item is also highlighted it keeps its tint and text and gains
a 1px inset `primary` ring, so the chosen row never turns plain grey.
### ActionMenu (`ui/dropdown-menu.tsx`)
The three-dots menu on a card, tile or row. Pass `options: MenuOption[]`
(`label`, `onClick`, optional lucide `icon`, `variant: 'destructive'`,
`disabled`) and a `triggerLabel`. It renders a `ghost-on-accent` `icon-xs`
trigger with `EllipsisVertical`, and stops clicks and keys on the trigger and
the menu from reaching the host, so it can sit inside a clickable card.
`className` takes layout only (position, margin) and lands on the trigger;
the menu is at least 144px wide and grows with its labels. `open`/`onOpenChange` make it controlled. Don't hand-build
this menu from `DropdownMenu`, and don't declare a local option type.
### Table, Label, dialog text
`TableCell` and `TableHeader` accept typography and alignment classes and
@@ -356,6 +469,10 @@ a 1px inset `primary` ring, so the chosen row never turns plain grey.
`PopoverContent`, `SheetContent`, `DropdownMenuContent` and `Modal` accept
`p-0` for edge-to-edge content (MoveToFolderModal's banded folder picker);
Modal merges it after its own `p-8`, so no `!` is needed. Everything else on these components is layout only.
`MessageScrollerViewport` and `MessageScrollerContent` accept layout and
spacing: the primitive measures Content's padding-block for its scroll and
spacer math, and the Viewport's top gap must scroll with the messages, so
padding there cannot move to a margin or a wrapper.
## Spacing, type and radii
@@ -372,10 +489,21 @@ or add a token.
`focus-visible:border-ring` on fields. Never `focus:` (mouse users see it)
and never `ring-2` or `ring-[3px]`; the `ui/` components already carry it,
so plain elements should become components rather than copy the classes.
Inside `ui/`, spell it with the `focusRing` constant from `lib/utils.ts`
(with `invalidState` and `fieldFrame` for fields) rather than retyping it.
- **Close buttons** on Modal, Sheet and DialogContent are a `ghost-muted`
`size="icon-sm"` Button (32px, accent square on hover) at `top-2 right-2`.
- **Disabled**: buttons (Button, Accordion, Tabs) use
`disabled:pointer-events-none disabled:opacity-50`, so the pointer passes
through. Fields (Input, SelectTrigger, Switch, Textarea, CommandInput,
Label) use `disabled:cursor-not-allowed disabled:opacity-50` and never
`pointer-events-none`, which would hide the cursor.
- **Elevation**: cards have no shadow; fields and outline buttons
`shadow-xs`; popovers, menus and select lists `shadow-md`; sheets
`shadow-lg`; modals `shadow-modal`; toasts `shadow-toast`. Nothing else
uses a shadow class.
`shadow-lg`; modals (Modal and DialogContent) `shadow-modal`; toasts
`shadow-toast`. The one exception is the Switch thumb, `shadow-lg`: a white
knob on a white card needs the lift to read as raised. Nothing else uses
a shadow class; accordions are panels and have none.
- **Stacking**: `z-10` sticky headers and table heads inside a page;
`z-20` in-page floating chrome (banners, scroll-to-bottom, drag
handles); `z-50` everything portalled (overlay, modal, sheet, popover,
@@ -397,22 +525,18 @@ Places where a rule is knowingly disabled. Each one carries the same reason
as a comment at the call site; add a row here when you add a disable so the
list stays reviewable.
| Where | Rule | Why |
| ----------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `components/Notification.tsx` | `shadcn/no-restyle` | The promo banner's close control is a bare 16px image on the purple gradient: no `size` fits it, and `ghost`'s accent hover fill would paint a grey square on the gradient. |
| `components/ui/calendar.tsx` | `shadcn/require-static-classes` | The day button merges `defaultClassNames.day`, which react-day-picker returns from `getDefaultClassNames()` at runtime; no static form exists. |
| `navigation/SidebarLevel.tsx` | `shadcn/no-arbitrary-values` | The incoming sidebar panel casts a strong shadow off its left edge while it slides (`shadow-[-12px_0_24px_-6px_rgba(0,0,0,0.45)]`); no scale shadow is horizontal, and the container clips it once the panel comes to rest. |
| `components/ui/modal.tsx` | `shadcn/no-arbitrary-values` | The mobile sheet's `pb-[max(env(safe-area-inset-bottom),1rem)]` keeps its footer clear of the iPhone home indicator (about 34px, since `index.html` sets `viewport-fit=cover`) and equals `pb-4` everywhere else; no scale step can express `env()`. |
| `components/MessageInput.tsx` | `shadcn/no-restyle` | The empty composer's send button is a grey circle (`bg-muted`, `dark:bg-accent`), not a faded brand one; no variant is neutral while disabled, and `secondary` is the brand-tinted pressed state. |
| `Hero.tsx` | `shadcn/no-restyle` | The landing page's model picker keeps its hero look: a borderless muted pill at 16px (`rounded-4xl px-6 py-4 text-base`) whose menu hangs from it as one shape. Three disables: `SelectTrigger`, `SelectContent`, `SelectItem`. |
| `agents/AgentsList.tsx` | `shadcn/no-restyle` | Inside a folder, the breadcrumb trail replaces the section `<h2>`, so its `BreadcrumbList` keeps heading typography (`text-foreground text-lg font-semibold gap-2`). It's the only breadcrumb that does. |
| `settings/Prompts.tsx` | `shadcn/no-restyle` | The row icon buttons (edit, duplicate, share, delete) sit in a cmdk row that is already `bg-accent` while highlighted, so ghost's accent hover would be invisible; they keep a `foreground/15` (delete: `destructive/15`) hover tint. Four disables. |
| `Navigation.tsx` | `shadcn/no-restyle` | The agent pin is a bare 16px image on a sidebar row that already fills on hover; an accent fill would be invisible on it, so hover dims the icon (`hover:opacity-75`) instead. |
| `conversation/MarkdownAnswer.tsx`, `components/ArtifactSidebar.tsx` | `shadcn/no-inline-styles` | SyntaxHighlighter's `style` prop is its Prism theme object (`oneLight` / `vscDarkPlus`), picked by theme at runtime. It is not CSS, so no class or custom property can replace it. One disable per file. |
| `components/MultiSelectPopover.tsx` | `shadcn/no-restyle`, `shadcn/no-arbitrary-values` | The phone bottom sheet (`SheetContent side="bottom"`) keeps its sheet shape: a rounded top, no gap, a card fill in dark so it matches the `Command` inside it, and `pb-[env(safe-area-inset-bottom)]` to clear the iPhone home indicator (the same inset as `ui/modal.tsx`). One disable. |
| `agents/workflow/WorkflowPreview.tsx` | `shadcn/no-restyle` | The Preview minimap's node rows are status tiles: the fill, border and ring follow the step (success, primary running + pulse, destructive, muted pending; a ring on the active row) and stay pinned on hover, pending and running rows stay unfaded while disabled, and clickable rows dim to 80% on hover. No Button variant is status-tinted. The rule reports each string inside `cn(...)`, so it's a `/* eslint-disable */` … `/* eslint-enable */` pair around the `className` attribute. |
| `components/MermaidRenderer.tsx` | `shadcn/no-restyle` | The zoom − / + buttons sit on the diagram's `bg-black/70` overlay, where ghost's accent hover paints a light square with dark text; they hover to `white/20` with white text instead (`dark:hover:bg-white/20` too, to beat ghost's dark hover). Two disables. |
| `conversation/ConversationMessages.tsx`, `agents/workflow/WorkflowPreview.tsx` | `shadcn/no-restyle` | MessageScroller padding. The primitive measures `MessageScrollerContent`'s padding-block for its scroll and spacer math, and the Viewport's top gap must scroll with the messages, so neither can move to a margin or a wrapper. The two callers use different values. Five disables. |
| Where | Rule | Why |
| ------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `components/Notification.tsx` | `shadcn/no-restyle` | The promo banner's close X (`ghost icon-xs`) sits on the purple gradient. Any fill would be a grey square on it, so hover dims the icon instead (`hover:bg-transparent hover:opacity-70`, plus `dark:hover:bg-transparent` to beat ghost's dark hover), and `text-primary-foreground` keeps it white, because the button inherits the page colour. One user, so it isn't a variant. |
| `components/ui/calendar.tsx` | `shadcn/require-static-classes` | The day button merges `defaultClassNames.day`, which react-day-picker returns from `getDefaultClassNames()` at runtime; no static form exists. |
| `navigation/SidebarLevel.tsx` | `shadcn/no-arbitrary-values` | The incoming sidebar panel casts a strong shadow off its left edge while it slides (`shadow-[-12px_0_24px_-6px_rgba(0,0,0,0.45)]`); no scale shadow is horizontal, and the container clips it once the panel comes to rest. |
| `components/MessageInput.tsx` | `shadcn/no-restyle` | The empty composer's send button is a grey circle (`bg-muted`, `dark:bg-accent`), not a faded brand one; no variant is neutral while disabled, and `secondary` is the brand-tinted pressed state. |
| `Hero.tsx` | `shadcn/no-restyle` | The landing page's model picker keeps its hero look: a borderless muted pill at 16px (`rounded-4xl px-6 py-4 text-base`) whose menu hangs from it as one shape. Three disables: `SelectTrigger`, `SelectContent`, `SelectItem`. |
| `agents/AgentsList.tsx` | `shadcn/no-restyle` | Inside a folder, the breadcrumb trail replaces the section `<h2>`, so its `BreadcrumbList` keeps heading typography (`text-foreground text-lg font-semibold gap-2`). It's the only breadcrumb that does. |
| `conversation/MarkdownAnswer.tsx`, `components/ArtifactSidebar.tsx` | `shadcn/no-inline-styles` | SyntaxHighlighter's `style` prop is its Prism theme object (`oneLight` / `vscDarkPlus`), picked by theme at runtime. It is not CSS, so no class or custom property can replace it. One disable per file. |
| `agents/workflow/WorkflowPreview.tsx` | `shadcn/no-restyle` | The Preview minimap's node rows are status tiles: the fill, border and ring follow the step (success, primary running + pulse, destructive, muted pending; a ring on the active row) and stay pinned on hover, pending and running rows stay unfaded while disabled, and clickable rows dim to 80% on hover. No Button variant is status-tinted. The rule reports each string inside `cn(...)`, so it's a `/* eslint-disable */` … `/* eslint-enable */` pair around the `className` attribute. |
| `agents/schedules/ScheduleFormModal.tsx` | `shadcn/no-restyle` | The schedule's name is the dialog's editable title: a `bare` Input with title type (`text-xl font-semibold`), so the dialog passes `hideTitle`. One disable. |
| `components/MermaidRenderer.tsx` | `shadcn/no-restyle` | The zoom − / + buttons sit on the diagram's `bg-black/70` overlay, where ghost's accent hover paints a light square with dark text; they hover to `white/20` with white text instead (`dark:hover:bg-white/20` too, to beat ghost's dark hover). Two disables. |
Note that a multi-line reason has to be a `/* ... */` block comment;
consecutive `//` lines only disable the next comment line, not the code.
@@ -481,11 +605,12 @@ Known traps:
files unrelated to design; `npm run lint-fix` clears them.
- Port 5173 may be held by another checkout; check the Vite banner shows
this repo's path before trusting what you see.
- `components/Spinner.tsx` and `components/StatusBadge.tsx` are transitional
wrappers; migrate their callers to `ui/spinner` and `ui/badge`, then
delete them. `components/SkeletonLoader.tsx` and
`components/FileUpload.tsx` keep their logic but should render `Skeleton`
and `Dropzone`.
- `components/SkeletonLoader.tsx` renders `Skeleton` in its table, logs,
default and files layouts. Its analysis, dropdown, chunk-card and
connected-state layouts, and the four tile mirrors, pulse their own
surface, so they wait for the Card work. `components/FileUpload.tsx`
keeps its logic but should render `Dropzone`. (`agents/schedules/StatusBadge.tsx`
is a status→variant map over `Badge`, not a wrapper; keep it.)
## Working with the lint
+7
View File
@@ -183,6 +183,13 @@ export default [
pattern: '^(ToastContent|ToastItem|ToastFooter)$',
allow: ['layout', 'spacing'],
},
{
// The primitive measures Content's padding-block for its scroll
// math, and the Viewport's top gap must scroll with the
// messages, so padding here is part of the layout.
pattern: '^MessageScroller(Viewport|Content)$',
allow: ['layout', 'spacing'],
},
{
pattern: '^OptionCard$',
allow: ['layout'],
+1 -1
View File
@@ -17,7 +17,7 @@ import DocsGPTMarkWhite from './assets/logo-w.svg';
import ActionButtons from './components/ActionButtons';
import AdminRoute from './components/AdminRoute';
import ErrorBoundary from './components/ErrorBoundary';
import Spinner from './components/Spinner';
import { Spinner } from '@/components/ui/spinner';
import { Button } from './components/ui/button';
import { ToastViewport } from './components/ui/toast';
import UploadToast from './components/UploadToast';
+17 -36
View File
@@ -7,6 +7,8 @@ import {
Menu,
PanelLeftClose,
PanelLeftOpen,
Pin,
PinOff,
Plus,
Search as SearchIcon,
Settings as SettingsIcon,
@@ -22,12 +24,10 @@ import conversationService from './api/services/conversationService';
import userService from './api/services/userService';
import Discord from './assets/discord.svg';
import Github from './assets/git_nav.svg';
import Pin from './assets/pin.svg';
import { Avatar } from './components/ui/avatar';
import { Button } from './components/ui/button';
import Spinner from './components/Spinner';
import { Spinner } from '@/components/ui/spinner';
import Twitter from './assets/TwitterX.svg';
import UnPin from './assets/unpin.svg';
import Help from './components/Help';
import {
handleAbort,
@@ -405,7 +405,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
onClick={() => setNavOpen(true)}
aria-label="Open navigation menu"
>
<PanelLeftOpen className="size-5" strokeWidth={1.75} />
<PanelLeftOpen className="size-5" />
</Button>
{activeSection ? (
<SectionRail
@@ -425,7 +425,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
onClick={() => newChat()}
aria-label="Start new chat"
>
<Plus className="size-5" strokeWidth={1.75} />
<Plus className="size-5" />
</Button>
)}
<Button
@@ -438,7 +438,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
}}
aria-label={t('manageAgents')}
>
<LayoutGrid className="size-5" strokeWidth={1.75} />
<LayoutGrid className="size-5" />
</Button>
{conversations?.data && conversations.data.length > 0 && (
<Button
@@ -449,7 +449,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
aria-label={t('modals.searchConversations.searchPlaceholder')}
title={t('modals.searchConversations.searchPlaceholder')}
>
<SearchIcon className="size-5" strokeWidth={1.75} />
<SearchIcon className="size-5" />
</Button>
)}
<div className="mt-auto flex flex-col items-center gap-2">
@@ -460,7 +460,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
onClick={() => goToLevel('/settings')}
aria-label={t('settings.label')}
>
<SettingsIcon className="size-5" strokeWidth={1.75} />
<SettingsIcon className="size-5" />
</Button>
</div>
</>
@@ -498,15 +498,9 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
aria-label={navOpen ? 'Collapse sidebar' : 'Expand sidebar'}
>
{navOpen ? (
<PanelLeftClose
className="size-5 transition-all duration-300 ease-in-out hover:scale-110"
strokeWidth={1.75}
/>
<PanelLeftClose className="size-5 transition-all duration-300 ease-in-out hover:scale-110" />
) : (
<PanelLeftOpen
className="size-5 transition-all duration-300 ease-in-out hover:scale-110"
strokeWidth={1.75}
/>
<PanelLeftOpen className="size-5 transition-all duration-300 ease-in-out hover:scale-110" />
)}
</Button>
</div>
@@ -531,7 +525,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
>
<Plus
className="text-muted-foreground group-hover:text-foreground size-5 shrink-0"
strokeWidth={1.75}
aria-label="Create new chat"
/>
<p className="text-muted-foreground group-hover:text-foreground text-sm">
@@ -548,7 +541,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
role="status"
aria-label="Loading conversations"
>
<Spinner size="small" />
<Spinner size="sm" />
</div>
)}
{recentAgents?.length > 0 ? (
@@ -585,14 +578,12 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
</p>
</div>
<div
className={`${isMobile || isTablet ? 'flex' : 'invisible flex group-hover:visible'} items-center px-3`}
className={`${isMobile || isTablet ? 'flex' : 'invisible flex group-hover:visible'} items-center px-1.5`}
>
<Button
type="button"
variant="ghost"
size="icon-sm"
/* eslint-disable-next-line shadcn/no-restyle -- the pin is a bare 16px image on a sidebar row that already fills on hover; an accent fill would be invisible on it, so hover dims the icon instead */
className="h-auto w-auto rounded-full p-0 hover:bg-transparent hover:opacity-75"
variant="ghost-on-accent"
size="icon-xs"
onClick={(e) => {
e.stopPropagation();
handleTogglePin(agent);
@@ -601,10 +592,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
agent.pinned ? 'Unpin agent' : 'Pin agent'
}
>
<img
src={agent.pinned ? UnPin : Pin}
className="h-4 w-4"
></img>
{agent.pinned ? <PinOff /> : <Pin />}
</Button>
</div>
</div>
@@ -630,7 +618,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
<div className="flex w-6 justify-center">
<LayoutGrid
className="text-muted-foreground size-5"
strokeWidth={1.75}
aria-label="manage-agents"
/>
</div>
@@ -661,7 +648,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
>
<LayoutGrid
className="text-muted-foreground size-5 shrink-0"
strokeWidth={1.75}
aria-label="manage-agents"
/>
<p className="text-foreground overflow-hidden text-sm leading-6 text-ellipsis whitespace-nowrap">
@@ -688,11 +674,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
)}
title={t('modals.searchConversations.searchPlaceholder')}
>
<SearchIcon
className="size-5"
strokeWidth={1.75}
aria-label="search"
/>
<SearchIcon className="size-5" aria-label="search" />
</Button>
</div>
<div>
@@ -781,7 +763,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
>
<SettingsIcon
className="text-muted-foreground size-5 shrink-0"
strokeWidth={1.75}
aria-label="Settings"
/>
<p className="text-foreground text-sm dark:text-white">
@@ -849,7 +830,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
onClick={() => setNavOpen(true)}
aria-label="Toggle mobile menu"
>
<Menu className="size-5" strokeWidth={1.75} />
<Menu className="size-5" />
</Button>
</div>
</div>
+3 -3
View File
@@ -1,6 +1,6 @@
import type { VariantProps } from 'class-variance-authority';
import Spinner from '../components/Spinner';
import { Spinner } from '@/components/ui/spinner';
import type { badgeVariants } from '../components/ui/badge';
import { formatDateOnly, formatDateTime } from '../utils/dateTimeUtils';
@@ -30,9 +30,9 @@ export function StatCard({
sub?: string;
}) {
return (
<div className="border-border dark:border-border rounded-2xl border px-6 py-5">
<div className="border-border rounded-2xl border px-6 py-5">
<p className="text-muted-foreground text-sm">{label}</p>
<p className="text-foreground dark:text-foreground mt-1 text-2xl font-bold tabular-nums">
<p className="text-foreground mt-1 text-2xl font-bold tabular-nums">
{value}
</p>
{sub ? <p className="text-muted-foreground mt-1 text-xs">{sub}</p> : null}
+4 -6
View File
@@ -176,9 +176,9 @@ export default function Usage() {
/>
</div>
<div className="border-border dark:border-border mt-4 h-[345px] w-full overflow-hidden rounded-2xl border px-6 py-5">
<div className="border-border mt-4 h-[345px] w-full overflow-hidden rounded-2xl border px-6 py-5">
<div className="flex flex-row items-center justify-between gap-3">
<p className="text-foreground dark:text-foreground font-bold">
<p className="text-foreground font-bold">
{metric === 'cost' ? 'Spend' : 'Token usage'}
</p>
<div
@@ -203,10 +203,8 @@ export default function Usage() {
</div>
</div>
<div className="border-border dark:border-border mt-4 w-full overflow-hidden rounded-2xl border px-6 py-5">
<p className="text-foreground dark:text-foreground mb-3 font-bold">
Top users
</p>
<div className="border-border mt-4 w-full overflow-hidden rounded-2xl border px-6 py-5">
<p className="text-foreground mb-3 font-bold">Top users</p>
{topUsers.length === 0 ? (
<p className="text-muted-foreground text-sm">No usage.</p>
) : (
+52 -69
View File
@@ -1,4 +1,5 @@
import {
ChevronDown,
Eye,
Gauge,
LogOut,
@@ -11,14 +12,15 @@ import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import adminService from '../api/services/adminService';
import ThreeDots from '../assets/three-dots.svg';
import { Badge } from '../components/ui/badge';
import { Button } from '../components/ui/button';
import {
ActionMenu,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
type MenuOption,
} from '../components/ui/dropdown-menu';
import { Input } from '../components/ui/input';
import { Modal } from '../components/ui/modal';
@@ -337,56 +339,31 @@ export default function Users() {
className="flex items-center justify-end"
onClick={(e) => e.stopPropagation()}
>
<DropdownMenu
<ActionMenu
open={menuUserId === u.user_id}
onOpenChange={(open) =>
setMenuUserId(open ? u.user_id : null)
}
>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="ghost-muted"
size="icon"
disabled={disabled}
className="h-[35px] w-7"
aria-label="User actions"
>
<img
src={ThreeDots}
alt="User actions"
className="filter dark:invert"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="min-w-[176px]"
>
<DropdownMenuItem
onSelect={() => openDetail(u.user_id)}
>
<Eye size={16} />
<span>View details</span>
</DropdownMenuItem>
{buildActions(u.user_id, isAdmin, u.active).map(
(act) => (
<DropdownMenuItem
key={act.key}
variant={
act.destructive
? 'destructive'
: 'default'
}
onSelect={act.perform}
>
<act.icon size={16} />
<span>{act.label}</span>
</DropdownMenuItem>
),
)}
</DropdownMenuContent>
</DropdownMenu>
disabled={disabled}
triggerLabel="User actions"
options={[
{
label: 'View details',
icon: Eye,
onClick: () => openDetail(u.user_id),
},
...buildActions(u.user_id, isAdmin, u.active).map(
(act): MenuOption => ({
label: act.label,
icon: act.icon,
variant: act.destructive
? 'destructive'
: 'default',
onClick: act.perform,
}),
),
]}
/>
</div>
</TableCell>
</TableRow>
@@ -450,29 +427,35 @@ export default function Users() {
size="lg"
footer={
detail ? (
<div className="flex flex-wrap justify-end gap-2">
{buildActions(
detail.user.user_id,
(detail.roles ?? []).includes('admin'),
detail.user?.active ?? true,
).map((act) => (
<Button
key={act.key}
type="button"
variant={act.destructive ? 'destructive-outline' : 'outline'}
size="sm"
onClick={() => {
// Close the detail dialog before any confirm dialog opens
// (avoids stacked modals); the list + toast reflect the result.
setDetail(null);
act.perform();
}}
>
<act.icon size={16} />
{act.label}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="button" variant="outline" size="lg" shape="pill">
Actions
<ChevronDown />
</Button>
))}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{buildActions(
detail.user.user_id,
(detail.roles ?? []).includes('admin'),
detail.user?.active ?? true,
).map((act) => (
<DropdownMenuItem
key={act.key}
variant={act.destructive ? 'destructive' : 'default'}
onSelect={() => {
// Close the detail dialog before any confirm dialog opens
// (avoids stacked modals); the list + toast reflect the result.
setDetail(null);
act.perform();
}}
>
<act.icon aria-hidden="true" />
<span>{act.label}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : undefined
}
>
+59 -207
View File
@@ -1,45 +1,31 @@
import { SyntheticEvent, useState } from 'react';
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Users } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import {
Activity,
Copy,
Download,
ExternalLink,
Folder,
Pencil,
Pin,
PinOff,
Trash2,
Users,
} from 'lucide-react';
import userService from '../api/services/userService';
import Download from '../assets/download.svg';
import Duplicate from '../assets/duplicate.svg';
import Edit from '../assets/edit.svg';
import FolderIcon from '../assets/folder.svg';
import Link from '../assets/link-gray.svg';
import Monitoring from '../assets/monitoring.svg';
import Pin from '../assets/pin.svg';
import Trash from '../assets/red-trash.svg';
import ThreeDots from '../assets/three-dots.svg';
import UnPin from '../assets/unpin.svg';
import { Avatar } from '../components/ui/avatar';
import { Badge } from '../components/ui/badge';
import { Button } from '../components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../components/ui/dropdown-menu';
import { ActionMenu, type MenuOption } from '../components/ui/dropdown-menu';
import { Modal } from '../components/ui/modal';
import ConfirmationModal from '../modals/ConfirmationModal';
import MoveToFolderModal from '../modals/MoveToFolderModal';
import { ActiveState } from '../models/misc';
import { useSidebarLevel } from '../navigation/SidebarLevelProvider';
import ShareToTeamModal from '../teams/ShareToTeamModal';
type AgentMenuOption = {
icon: string | LucideIcon;
label: string;
onClick: (event: SyntheticEvent) => void;
variant: 'default' | 'destructive';
iconWidth?: number;
iconHeight?: number;
};
import {
selectAgents,
selectToken,
@@ -83,57 +69,43 @@ export default function AgentCard({
const [shareModalOpen, setShareModalOpen] = useState(false);
const [exportError, setExportError] = useState<string | null>(null);
const menuOptionsConfig: Record<string, AgentMenuOption[]> = {
const openEditor = () => {
if (agent.agent_type === 'workflow') {
goToLevel(agentEditPath(agent.id, true));
} else {
goToLevel(agentEditPath(agent.id));
}
};
const pinOption: MenuOption = {
icon: agent.pinned ? PinOff : Pin,
label: agent.pinned ? t('agents.card.unpin') : t('agents.card.pin'),
onClick: () => togglePin(),
};
const menuOptionsConfig: Record<string, MenuOption[]> = {
template: [
{
icon: Duplicate,
icon: Copy,
label: t('modals.prompts.duplicate'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
handleDuplicate();
},
variant: 'default',
iconWidth: 18,
iconHeight: 18,
onClick: () => handleDuplicate(),
},
],
user: [
{
icon: Monitoring,
icon: Activity,
label: t('agents.form.buttons.logs'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
goToLevel(agentLogsPath(agent.id));
},
variant: 'default',
iconWidth: 14,
iconHeight: 14,
onClick: () => goToLevel(agentLogsPath(agent.id)),
},
{
icon: Edit,
icon: Pencil,
label: t('agents.edit'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
if (agent.agent_type === 'workflow') {
goToLevel(agentEditPath(agent.id, true));
} else {
goToLevel(agentEditPath(agent.id));
}
},
variant: 'default',
iconWidth: 14,
iconHeight: 14,
onClick: openEditor,
},
{
icon: Download,
label: t('agents.exportAgent'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
handleExport();
},
variant: 'default',
iconWidth: 14,
iconHeight: 14,
onClick: () => handleExport(),
},
// Sharing is an owner-only action: only show it for agents the user
// owns ('user'), not agents shared into their workspace by a team.
@@ -142,54 +114,21 @@ export default function AgentCard({
{
icon: Users,
label: t('agents.shareWithTeam'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setShareModalOpen(true);
},
variant: 'default' as const,
iconWidth: 14,
iconHeight: 14,
},
]
: []),
...(agent.status === 'published'
? [
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned
? t('agents.card.unpin')
: t('agents.card.pin'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
},
variant: 'default' as const,
iconWidth: 18,
iconHeight: 18,
onClick: () => setShareModalOpen(true),
},
]
: []),
...(agent.status === 'published' ? [pinOption] : []),
{
icon: FolderIcon,
icon: Folder,
label: t('agents.folders.moveToFolder'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setMoveModalState('ACTIVE');
},
variant: 'default',
iconWidth: 16,
iconHeight: 15,
onClick: () => setMoveModalState('ACTIVE'),
},
{
icon: Trash,
icon: Trash2,
label: t('agents.form.buttons.delete'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setDeleteConfirmation('ACTIVE');
},
onClick: () => setDeleteConfirmation('ACTIVE'),
variant: 'destructive',
iconWidth: 13,
iconHeight: 13,
},
],
// Agents shared with the user via a team. They don't own it, so only
@@ -198,71 +137,24 @@ export default function AgentCard({
// Logs / Export / Share / Move-to-folder / Delete stay owner-only.
team: [
{
icon: Edit,
icon: Pencil,
label: t('agents.edit'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
if (agent.agent_type === 'workflow') {
goToLevel(agentEditPath(agent.id, true));
} else {
goToLevel(agentEditPath(agent.id));
}
},
variant: 'default',
iconWidth: 14,
iconHeight: 14,
onClick: openEditor,
},
...(agent.status === 'published'
? [
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned
? t('agents.card.unpin')
: t('agents.card.pin'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
},
variant: 'default' as const,
iconWidth: 18,
iconHeight: 18,
},
]
: []),
...(agent.status === 'published' ? [pinOption] : []),
],
shared: [
{
icon: Link,
icon: ExternalLink,
label: t('agents.card.open'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
navigate(sharedAgentPath(agent.shared_token));
},
variant: 'default',
iconWidth: 12,
iconHeight: 12,
onClick: () => navigate(sharedAgentPath(agent.shared_token)),
},
pinOption,
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned ? t('agents.card.unpin') : t('agents.card.pin'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
},
variant: 'default',
iconWidth: 18,
iconHeight: 18,
},
{
icon: Trash,
icon: Trash2,
label: t('agents.card.remove'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
handleHideSharedAgent();
},
onClick: () => handleHideSharedAgent(),
variant: 'destructive',
iconWidth: 13,
iconHeight: 13,
},
],
};
@@ -404,58 +296,17 @@ export default function AgentCard({
}
}}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
onClick={(e) => e.stopPropagation()}
className="absolute top-4 right-4 z-10 cursor-pointer"
aria-label="agent-actions"
>
<img
src={ThreeDots}
alt={'use-agent'}
className="h-[19px] w-[19px]"
/>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[144px]">
{menuOptions.map((option, index) => (
<DropdownMenuItem
key={index}
variant={option.variant}
onClick={(e) => e.stopPropagation()}
onSelect={(event) => {
option.onClick(event as unknown as SyntheticEvent);
}}
>
{typeof option.icon === 'string' ? (
<img
src={option.icon}
alt=""
width={option.iconWidth ?? 16}
height={option.iconHeight ?? 16}
/>
) : (
<option.icon
size={Math.max(
option.iconWidth ?? 16,
option.iconHeight ?? 16,
)}
strokeWidth={1.75}
aria-hidden="true"
/>
)}
<span>{option.label}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<ActionMenu
options={menuOptions}
triggerLabel={t('agents.card.actions')}
align="end"
className="absolute top-3 right-3 z-10"
/>
{/* Team access badge — pinned to the top row, left of the ⋯ menu
(right-11 clears the 19px trigger at right-4) so the two align. */}
(right-11 clears the 28px trigger at right-3) so the two align. */}
{agent.ownership === 'team' && (
<Badge variant="neutral" className="absolute top-4 right-11 z-10">
<Users size={11} strokeWidth={2} aria-hidden="true" />
<Users size={11} aria-hidden="true" />
{agent.team_access === 'editor'
? t('agents.teamBadge.editor')
: t('agents.teamBadge.viewer')}
@@ -511,6 +362,7 @@ export default function AgentCard({
<Button
type="button"
onClick={() => setExportError(null)}
size="lg"
shape="pill"
>
{t('agents.close')}
+1 -6
View File
@@ -191,12 +191,7 @@ export default function AgentsList() {
label={t('agents.searchPlaceholder')}
labelSurface="background"
shape="pill"
leftIcon={
<Search
className="text-muted-foreground size-4"
strokeWidth={1.75}
/>
}
leftIcon={<Search className="text-muted-foreground size-4" />}
/>
</div>
</div>
+17 -71
View File
@@ -1,29 +1,13 @@
import { SyntheticEvent, useState } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Pencil, Trash2 } from 'lucide-react';
import Edit from '../assets/edit.svg';
import Trash from '../assets/red-trash.svg';
import ThreeDots from '../assets/three-dots.svg';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../components/ui/dropdown-menu';
import { ActionMenu, type MenuOption } from '../components/ui/dropdown-menu';
import ConfirmationModal from '../modals/ConfirmationModal';
import FolderNameModal from '../modals/FolderManagementModal';
import { ActiveState } from '../models/misc';
import { AgentFolder } from './types';
type FolderMenuOption = {
icon: string;
label: string;
onClick: (event: SyntheticEvent) => void;
variant: 'default' | 'destructive';
iconWidth?: number;
iconHeight?: number;
};
type FolderCardProps = {
folder: AgentFolder;
agentCount: number;
@@ -47,28 +31,17 @@ export default function FolderCard({
const [renameModalState, setRenameModalState] =
useState<ActiveState>('INACTIVE');
const menuOptions: FolderMenuOption[] = [
const menuOptions: MenuOption[] = [
{
icon: Edit,
icon: Pencil,
label: t('agents.folders.rename'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setRenameModalState('ACTIVE');
},
variant: 'default',
iconWidth: 14,
iconHeight: 14,
onClick: () => setRenameModalState('ACTIVE'),
},
{
icon: Trash,
icon: Trash2,
label: t('agents.folders.delete'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setDeleteConfirmation('ACTIVE');
},
onClick: () => setDeleteConfirmation('ACTIVE'),
variant: 'destructive',
iconWidth: 13,
iconHeight: 13,
},
];
@@ -101,42 +74,15 @@ export default function FolderCard({
({agentCount})
</span>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
className="ml-2 shrink-0 cursor-pointer"
aria-label={t('agents.folders.menuAriaLabel', {
folderName: folder.name,
defaultValue: 'Folder actions',
})}
>
<img src={ThreeDots} alt="" className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[144px]">
{menuOptions.map((option, index) => (
<DropdownMenuItem
key={index}
variant={option.variant}
onClick={(e) => e.stopPropagation()}
onSelect={(event) => {
option.onClick(event as unknown as SyntheticEvent);
}}
>
<img
src={option.icon}
alt=""
width={option.iconWidth ?? 16}
height={option.iconHeight ?? 16}
/>
<span>{option.label}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<ActionMenu
options={menuOptions}
triggerLabel={t('agents.folders.menuAriaLabel', {
folderName: folder.name,
defaultValue: 'Folder actions',
})}
align="end"
className="ml-2 shrink-0"
/>
</div>
<ConfirmationModal
message={t('agents.folders.deleteConfirm')}
+82 -100
View File
@@ -3,12 +3,14 @@ import {
ChevronRight,
CircleCheck,
CircleX,
Database,
MoreHorizontal,
} from 'lucide-react';
import React, {
useCallback,
useEffect,
useMemo,
useId,
useRef,
useState,
} from 'react';
@@ -25,6 +27,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { FormField } from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
import {
@@ -34,6 +37,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { SettingRow, SettingRows } from '@/components/ui/setting-row';
import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
@@ -43,14 +47,12 @@ import modelService from '../api/services/modelService';
import userService from '../api/services/userService';
import ScienceSparkDarkIcon from '../assets/science-spark-dark.svg';
import ScienceSparkIcon from '../assets/science-spark.svg';
import SourceIcon from '../assets/source.svg';
import { FileUpload } from '../components/FileUpload';
import {
MultiSelectPopover,
type MultiSelectPopoverItem,
} from '../components/MultiSelectPopover';
import SourcesPopoverFooter from '../components/SourcesPopoverFooter';
import Spinner from '../components/Spinner';
import ToolIcon from '../components/ToolIcon';
import AgentDetailsModal from '../modals/AgentDetailsModal';
import ShareToTeamModal from '../teams/ShareToTeamModal';
@@ -184,6 +186,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
const [submitError, setSubmitError] = useState<string | null>(null);
const [jsonSchemaText, setJsonSchemaText] = useState('');
const [jsonSchemaValid, setJsonSchemaValid] = useState(true);
const tokenLimitSwitchId = useId();
const requestLimitSwitchId = useId();
const promptOverrideSwitchId = useId();
const [isAdvancedSectionExpanded, setIsAdvancedSectionExpanded] =
useState(false);
@@ -268,7 +273,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
own: t('agents.form.sourcePopup.groupOwn'),
team: t('agents.form.sourcePopup.groupTeam'),
},
SourceIcon,
<Database />,
);
// An attached source the caller can't list — an owner's private source on
// a team-shared agent — still needs a row of its own, or it reads as
@@ -276,7 +281,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
const listed = new Set(items.map((item) => item.id));
const unlisted = Array.from(selectedSourceIds)
.filter((id) => !listed.has(id))
.map((id) => ({ id, label: resolveSourceLabel(id), icon: SourceIcon }));
.map((id) => ({ id, label: resolveSourceLabel(id), icon: <Database /> }));
return [...items, ...unlisted];
}, [resolveSourceLabel, selectedSourceIds, sourceDocs, t]);
@@ -827,32 +832,20 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
variant="outline-primary"
shape="pill"
disabled={isDraftBlocked()}
loading={draftLoading}
onClick={handleSaveDraft}
className="min-w-28"
>
<span className="flex items-center justify-center transition-all duration-200">
{draftLoading ? (
<Spinner size="small" />
) : (
t('agents.form.buttons.saveDraft')
)}
</span>
{t('agents.form.buttons.saveDraft')}
</Button>
)}
<Button
type="button"
disabled={!isPublishable() || !hasChanges}
loading={publishLoading}
onClick={handlePublish}
shape="pill"
className="min-w-28"
>
<span className="flex items-center justify-center transition-all duration-200">
{publishLoading ? (
<Spinner size="small" />
) : (
modeConfig[effectiveMode].buttonText
)}
</span>
{modeConfig[effectiveMode].buttonText}
</Button>
{modeConfig[effectiveMode].showAccessDetails && (
<DropdownMenu>
@@ -1174,10 +1167,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
}
/>
{selectedModelIds.size > 0 && (
<div>
<label className="mb-2 block text-sm font-medium">
{t('agents.form.labels.defaultModel')}
</label>
<FormField label={t('agents.form.labels.defaultModel')}>
<Select
value={agent.default_model_id || undefined}
onValueChange={(value) =>
@@ -1201,7 +1191,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
))}
</SelectContent>
</Select>
</div>
</FormField>
)}
</div>
</div>
@@ -1274,17 +1264,36 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
</div>
)}
<div className="mt-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-sm font-medium">
{t('agents.form.advanced.tokenLimiting')}
</h2>
<p className="text-muted-foreground mt-1 text-xs">
{t('agents.form.advanced.tokenLimitingDescription')}
</p>
</div>
<SettingRows className="mt-6">
<SettingRow
label={t('agents.form.advanced.tokenLimiting')}
description={t(
'agents.form.advanced.tokenLimitingDescription',
)}
htmlFor={tokenLimitSwitchId}
after={
<Input
type="number"
min="0"
value={agent.token_limit || ''}
onChange={(e) =>
setAgent({
...agent,
token_limit: e.target.value
? parseInt(e.target.value)
: undefined,
})
}
disabled={!agent.limited_token_mode}
placeholder={t(
'agents.form.placeholders.enterTokenLimit',
)}
shape="pill"
/>
}
>
<Switch
id={tokenLimitSwitchId}
checked={agent.limited_token_mode}
onCheckedChange={(checked) => {
setAgent({
@@ -1296,37 +1305,36 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
});
}}
/>
</div>
<Input
type="number"
min="0"
value={agent.token_limit || ''}
onChange={(e) =>
setAgent({
...agent,
token_limit: e.target.value
? parseInt(e.target.value)
: undefined,
})
</SettingRow>
<SettingRow
label={t('agents.form.advanced.requestLimiting')}
description={t(
'agents.form.advanced.requestLimitingDescription',
)}
htmlFor={requestLimitSwitchId}
after={
<Input
type="number"
min="0"
value={agent.request_limit || ''}
onChange={(e) =>
setAgent({
...agent,
request_limit: e.target.value
? parseInt(e.target.value)
: undefined,
})
}
disabled={!agent.limited_request_mode}
placeholder={t(
'agents.form.placeholders.enterRequestLimit',
)}
shape="pill"
/>
}
disabled={!agent.limited_token_mode}
placeholder={t('agents.form.placeholders.enterTokenLimit')}
shape="pill"
className="mt-2"
/>
</div>
<div className="mt-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-sm font-medium">
{t('agents.form.advanced.requestLimiting')}
</h2>
<p className="text-muted-foreground mt-1 text-xs">
{t('agents.form.advanced.requestLimitingDescription')}
</p>
</div>
>
<Switch
id={requestLimitSwitchId}
checked={agent.limited_request_mode}
onCheckedChange={(checked) => {
setAgent({
@@ -1338,42 +1346,16 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
});
}}
/>
</div>
<Input
type="number"
min="0"
value={agent.request_limit || ''}
onChange={(e) =>
setAgent({
...agent,
request_limit: e.target.value
? parseInt(e.target.value)
: undefined,
})
}
disabled={!agent.limited_request_mode}
placeholder={t(
'agents.form.placeholders.enterRequestLimit',
</SettingRow>
<SettingRow
label={t('agents.form.advanced.systemPromptOverride')}
description={t(
'agents.form.advanced.systemPromptOverrideDescription',
)}
shape="pill"
className="mt-2"
/>
</div>
<div className="mt-6">
<div className="flex items-center justify-between gap-4">
<div className="min-w-0 flex-1">
<h2 className="text-sm font-medium">
{t('agents.form.advanced.systemPromptOverride')}
</h2>
<p className="text-muted-foreground mt-1 text-xs">
{t(
'agents.form.advanced.systemPromptOverrideDescription',
)}
</p>
</div>
htmlFor={promptOverrideSwitchId}
>
<Switch
className="shrink-0"
id={promptOverrideSwitchId}
checked={agent.allow_system_prompt_override}
onCheckedChange={(checked) =>
setAgent({
@@ -1382,8 +1364,8 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
})
}
/>
</div>
</div>
</SettingRow>
</SettingRows>
</div>
)}
</div>
+1 -1
View File
@@ -8,7 +8,7 @@ import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import NoFilesIcon from '../assets/no-files.svg';
import MessageInput from '../components/MessageInput';
import { Avatar } from '../components/ui/avatar';
import Spinner from '../components/Spinner';
import { Spinner } from '@/components/ui/spinner';
import ConversationMessages from '../conversation/ConversationMessages';
import { Query } from '../conversation/conversationModels';
import {
+3 -3
View File
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next';
import { Pencil } from 'lucide-react';
import EditIcon from '../assets/edit.svg';
import ToolIcon from '../components/ToolIcon';
import { Avatar } from '../components/ui/avatar';
import { Button } from '../components/ui/button';
@@ -23,7 +23,7 @@ export default function SharedAgentCard({
agent.shared_metadata !== null &&
Object.keys(agent.shared_metadata).length > 0;
return (
<div className="border-border dark:border-border flex w-full max-w-[720px] flex-col rounded-3xl border p-6 shadow-xs sm:w-fit sm:min-w-[480px]">
<div className="border-border flex w-full max-w-[720px] flex-col rounded-3xl border p-6 shadow-xs sm:w-fit sm:min-w-[480px]">
<div className="flex items-start gap-3">
<div className="flex h-12 w-12 items-center justify-center overflow-hidden rounded-full p-1">
<Avatar
@@ -52,7 +52,7 @@ export default function SharedAgentCard({
className="shrink-0"
aria-label={t('agents.edit')}
>
<img src={EditIcon} alt="" className="h-3.5 w-3.5" />
<Pencil />
{t('agents.edit')}
</Button>
)}
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { FormField } from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
import {
Select,
@@ -13,6 +14,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { SettingRow } from '@/components/ui/setting-row';
import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
@@ -113,6 +115,8 @@ export default function GuardrailsSection({
const [catalog, setCatalog] = React.useState<GuardrailCatalog | null>(null);
const [loadError, setLoadError] = React.useState<string | null>(null);
const [openSettings, setOpenSettings] = React.useState<string | null>(null);
const enabledId = React.useId();
const failOpenId = React.useId();
const config = value ?? DEFAULT_GUARDRAILS;
@@ -292,53 +296,51 @@ export default function GuardrailsSection({
</Alert>
)}
<div className="mt-4 flex items-center justify-between gap-4">
<div className="min-w-0 flex-1">
<h3 className="text-sm font-medium">
{t('agents.form.guardrails.enable')}
</h3>
<p className="text-muted-foreground mt-1 text-xs">
{t('agents.form.guardrails.enableDescription')}
</p>
</div>
<SettingRow
className="mt-4"
label={t('agents.form.guardrails.enable')}
description={t('agents.form.guardrails.enableDescription')}
htmlFor={enabledId}
>
<Switch
className="shrink-0"
id={enabledId}
checked={config.enabled}
disabled={disabled}
data-testid="guardrails-enabled"
onCheckedChange={(checked) => patch({ enabled: checked })}
/>
</div>
</SettingRow>
{config.enabled && (
<>
<div className="mt-5">
<label className="mb-2 block text-sm font-medium">
{t('agents.form.guardrails.mode')}
</label>
<Select
value={config.mode}
onValueChange={(mode) =>
patch({ mode: mode as GuardrailsConfig['mode'] })
}
disabled={disabled}
>
<SelectTrigger
className="w-full"
size="lg"
shape="pill"
data-testid="guardrails-mode"
<FormField label={t('agents.form.guardrails.mode')}>
<Select
value={config.mode}
onValueChange={(mode) =>
patch({ mode: mode as GuardrailsConfig['mode'] })
}
disabled={disabled}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{(catalog?.modes ?? Object.keys(MODE_KEYS)).map((mode) => (
<SelectItem key={mode} value={mode}>
{t(MODE_KEYS[mode] ?? mode)}
</SelectItem>
))}
</SelectContent>
</Select>
<SelectTrigger
className="w-full"
size="lg"
shape="pill"
data-testid="guardrails-mode"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{(catalog?.modes ?? Object.keys(MODE_KEYS)).map(
(mode) => (
<SelectItem key={mode} value={mode}>
{t(MODE_KEYS[mode] ?? mode)}
</SelectItem>
),
)}
</SelectContent>
</Select>
</FormField>
{config.mode === 'monitor_only' && (
<p className="text-warning mt-2 text-xs">
{t('agents.form.guardrails.monitorHint')}
@@ -394,10 +396,11 @@ export default function GuardrailsSection({
</div>
</div>
<div className="mt-6">
<label className="mb-2 block text-sm font-medium">
{t('agents.form.guardrails.blockMessage')}
</label>
<FormField
className="mt-6"
label={t('agents.form.guardrails.blockMessage')}
hint={t('agents.form.guardrails.blockMessageDescription')}
>
<Input
type="text"
value={config.block_message}
@@ -407,33 +410,27 @@ export default function GuardrailsSection({
onChange={(e) => patch({ block_message: e.target.value })}
shape="pill"
/>
<p className="text-muted-foreground mt-1 text-xs">
{t('agents.form.guardrails.blockMessageDescription')}
</p>
</div>
</FormField>
<div className="mt-6 flex items-center justify-between gap-4">
<div className="min-w-0 flex-1">
<h3 className="text-sm font-medium">
{t('agents.form.guardrails.failOpen')}
</h3>
<p className="text-muted-foreground mt-1 text-xs">
{t('agents.form.guardrails.failOpenDescription')}
</p>
</div>
<SettingRow
className="mt-6"
label={t('agents.form.guardrails.failOpen')}
description={t('agents.form.guardrails.failOpenDescription')}
htmlFor={failOpenId}
>
<Switch
className="shrink-0"
id={failOpenId}
checked={config.fail_open}
disabled={disabled}
data-testid="guardrails-fail-open"
onCheckedChange={(checked) => patch({ fail_open: checked })}
/>
</div>
</SettingRow>
<div className="mt-4">
<label className="mb-2 block text-sm font-medium">
{t('agents.form.guardrails.timeout')}
</label>
<FormField
className="mt-4"
label={t('agents.form.guardrails.timeout')}
>
<NumberField
value={config.timeout_ms}
min={100}
@@ -444,7 +441,7 @@ export default function GuardrailsSection({
testId="guardrails-timeout"
onCommit={(timeout_ms) => patch({ timeout_ms })}
/>
</div>
</FormField>
</>
)}
</div>
@@ -771,8 +768,7 @@ function CheckSettings({
const set = (next: Record<string, any>) => onChange({ ...s, ...next });
const listField = (fieldKey: string, label: string, placeholder: string) => (
<div className="mt-2">
<label className="mb-1 block text-xs font-medium">{label}</label>
<FormField className="mt-2" label={label}>
<Textarea
rows={2}
size="sm"
@@ -790,7 +786,7 @@ function CheckSettings({
}
variant="filled"
/>
</div>
</FormField>
);
return (
@@ -851,10 +847,10 @@ function CheckSettings({
)}
{control.check === 'policy' && (
<div className="mt-2">
<label className="mb-1 block text-xs font-medium">
{t('agents.form.guardrails.policyText')}
</label>
<FormField
className="mt-2"
label={t('agents.form.guardrails.policyText')}
>
<Textarea
rows={4}
size="sm"
@@ -864,15 +860,12 @@ function CheckSettings({
onChange={(e) => set({ policy: e.target.value })}
variant="filled"
/>
</div>
</FormField>
)}
{control.check === 'groundedness' && (
<div className="mt-2 grid grid-cols-2 gap-2">
<div>
<label className="mb-1 block text-xs font-medium">
{t('agents.form.guardrails.minOverlap')}
</label>
<FormField label={t('agents.form.guardrails.minOverlap')}>
<NumberField
value={s.min_overlap ?? 0.3}
min={0}
@@ -882,11 +875,8 @@ function CheckSettings({
disabled={disabled}
onCommit={(min_overlap) => set({ min_overlap })}
/>
</div>
<div>
<label className="mb-1 block text-xs font-medium">
{t('agents.form.guardrails.minWords')}
</label>
</FormField>
<FormField label={t('agents.form.guardrails.minWords')}>
<NumberField
value={s.min_words ?? 25}
min={1}
@@ -896,15 +886,16 @@ function CheckSettings({
disabled={disabled}
onCommit={(min_words) => set({ min_words })}
/>
</div>
</FormField>
</div>
)}
{control.check === 'policy' && (
<div className="mt-2">
<label className="mb-1 block text-xs font-medium">
{t('agents.form.guardrails.confidence')}
</label>
<FormField
className="mt-2"
label={t('agents.form.guardrails.confidence')}
hint={t('agents.form.guardrails.confidenceHint')}
>
<NumberField
value={s.confidence_threshold ?? 0.7}
min={0}
@@ -914,10 +905,7 @@ function CheckSettings({
disabled={disabled}
onCommit={(confidence_threshold) => set({ confidence_threshold })}
/>
<p className="text-muted-foreground mt-1 text-xs">
{t('agents.form.guardrails.confidenceHint')}
</p>
</div>
</FormField>
)}
</div>
);
@@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { FormField } from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
import {
Popover,
PopoverContent,
@@ -16,6 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { TimePicker } from '@/components/ui/time-picker';
import { Modal } from '../../components/ui/modal';
@@ -196,15 +199,32 @@ export default function ScheduleFormModal({
mobileVariant="sheet"
className="w-[min(560px,92vw)]"
contentClassName="max-h-[80vh]"
footer={
<Button
type="button"
size="lg"
shape="pill"
loading={submitting}
onClick={submit}
>
{isEdit
? t('agents.schedules.modal.save')
: t('agents.schedules.modal.create')}
</Button>
}
>
<div className="flex flex-col gap-5">
<div className="flex items-start gap-3 pr-6">
<input
<Input
type="text"
variant="bare"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t('agents.schedules.modal.namePlaceholder')}
className="text-foreground placeholder:text-muted-foreground w-full bg-transparent text-xl font-semibold outline-none"
/* eslint-disable-next-line shadcn/no-restyle -- the schedule's
name is the dialog's editable title, so the bare field keeps
title type (20px semibold); DESIGN.md Approved exceptions. */
className="w-full text-xl font-semibold"
aria-label={t('agents.schedules.modal.namePlaceholder')}
/>
</div>
@@ -252,35 +272,16 @@ export default function ScheduleFormModal({
</div>
</div>
<label className="flex flex-col gap-2">
<span className="text-foreground text-sm font-medium">
{t('agents.schedules.modal.instructionsLabel')}
</span>
<textarea
<FormField label={t('agents.schedules.modal.instructionsLabel')}>
<Textarea
value={instruction}
onChange={(e) => setInstruction(e.target.value)}
placeholder={t('agents.schedules.modal.instructionsPlaceholder')}
rows={5}
className="border-border bg-background text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none focus-visible:ring-2"
/>
</label>
</FormField>
{error && <p className="text-destructive text-sm">{error}</p>}
<div className="flex justify-end">
<Button
type="button"
disabled={submitting}
onClick={submit}
shape="pill"
>
{submitting
? '…'
: isEdit
? t('agents.schedules.modal.save')
: t('agents.schedules.modal.create')}
</Button>
</div>
</div>
</Modal>
);
@@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import userService from '../../api/services/userService';
import Spinner from '../../components/Spinner';
import { Spinner } from '@/components/ui/spinner';
import { Button } from '../../components/ui/button';
import ConfirmationModal from '../../modals/ConfirmationModal';
import { ActiveState } from '../../models/misc';
+89 -155
View File
@@ -8,7 +8,6 @@ import {
Flag,
GitBranch,
Link,
Loader2,
Pencil,
Play,
Plus,
@@ -41,6 +40,8 @@ import ReactFlow, {
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { FormField } from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
import { MultiSelect } from '@/components/ui/multi-select';
import {
@@ -52,6 +53,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { SettingRow } from '@/components/ui/setting-row';
import { Sheet, SheetContent } from '@/components/ui/sheet';
import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea';
@@ -1603,34 +1605,29 @@ function WorkflowBuilderInner() {
ref={workflowSettingsRef}
className="border-border bg-card absolute top-full left-0 z-50 mt-2 w-80 rounded-xl border p-4 shadow-lg"
>
<div className="mb-3">
<label className="text-foreground mb-1 block text-sm font-medium">
Workflow Name
</label>
<FormField label="Workflow Name" className="mb-3">
<Input
type="text"
value={workflowName}
onChange={(e) => setWorkflowName(e.target.value)}
placeholder="Enter workflow name"
/>
</div>
<div className="mb-3">
<label className="text-foreground mb-1 block text-sm font-medium">
Description
</label>
</FormField>
<FormField label="Description" className="mb-3">
<Textarea
value={workflowDescription}
onChange={(e) => setWorkflowDescription(e.target.value)}
rows={3}
placeholder="Describe what this workflow does"
/>
</div>
<div className="mb-3">
<label className="text-foreground mb-1 block text-sm font-medium">
Agent Image
</label>
</FormField>
<FormField
label="Agent Image"
hint="Image updates are included the next time you save."
className="mb-3"
>
{currentAgentImage && !imageFile && (
<div className="mb-2 flex items-center gap-2">
<div className="flex items-center gap-2">
<img
src={currentAgentImage}
alt="Agent image"
@@ -1658,25 +1655,15 @@ function WorkflowBuilderInner() {
},
]}
/>
<p className="text-muted-foreground mt-1 text-xs">
Image updates are included the next time you save.
</p>
</div>
</FormField>
<div className="mb-3">
<div className="flex items-center justify-between">
<div>
<label
htmlFor="workflow-system-prompt-override"
className="text-foreground block text-sm font-medium"
>
{t('agents.form.advanced.systemPromptOverride')}
</label>
<p className="text-muted-foreground mt-0.5 text-xs">
{t(
'agents.form.advanced.systemPromptOverrideDescription',
)}
</p>
</div>
<SettingRow
label={t('agents.form.advanced.systemPromptOverride')}
description={t(
'agents.form.advanced.systemPromptOverrideDescription',
)}
htmlFor="workflow-system-prompt-override"
>
<Switch
id="workflow-system-prompt-override"
checked={Boolean(
@@ -1690,7 +1677,7 @@ function WorkflowBuilderInner() {
}))
}
/>
</div>
</SettingRow>
</div>
<Button
type="button"
@@ -1722,10 +1709,10 @@ function WorkflowBuilderInner() {
variant="destructive-outline"
shape="pill"
onClick={() => setDeleteConfirmation('ACTIVE')}
disabled={isDeletingAgent}
loading={isDeletingAgent}
>
<Trash2 size={16} />
{isDeletingAgent ? 'Deleting...' : 'Delete'}
Delete
</Button>
)}
<Button
@@ -1749,20 +1736,11 @@ function WorkflowBuilderInner() {
type="button"
onClick={handlePrimaryAction}
disabled={isPrimaryActionDisabled}
loading={showPrimaryActionSpinner}
size="lg"
shape="pill"
className="relative"
>
<span
className={
showPrimaryActionSpinner ? 'opacity-0' : 'opacity-100'
}
>
{primaryActionLabel}
</span>
{showPrimaryActionSpinner ? (
<Loader2 size={16} className="absolute animate-spin" />
) : null}
{primaryActionLabel}
</Button>
</div>
</div>
@@ -1995,10 +1973,7 @@ function WorkflowBuilderInner() {
{selectedNode.type !== 'start' &&
selectedNode.type !== 'end' && (
<>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Title
</label>
<FormField label="Title">
<Input
type="text"
value={
@@ -2014,14 +1989,11 @@ function WorkflowBuilderInner() {
}
placeholder="Enter node title"
/>
</div>
</FormField>
{selectedNode.type === 'agent' && (
<>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Agent Type
</label>
<FormField label="Agent Type">
<Select
value={
selectedNode.data.config?.agent_type ||
@@ -2048,11 +2020,8 @@ function WorkflowBuilderInner() {
</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Model
</label>
</FormField>
<FormField label="Model">
<Select
value={
selectedNode.data.config?.model_id || ''
@@ -2126,11 +2095,8 @@ function WorkflowBuilderInner() {
})()}
</SelectContent>
</Select>
</div>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
System Prompt
</label>
</FormField>
<FormField label="System Prompt">
<Textarea
value={
selectedNode.data.config?.system_prompt ??
@@ -2147,7 +2113,7 @@ function WorkflowBuilderInner() {
rows={3}
placeholder="System prompt for the agent"
/>
</div>
</FormField>
<PromptTextArea
label="Prompt Template"
value={
@@ -2167,10 +2133,7 @@ function WorkflowBuilderInner() {
selectedNodeId={selectedNode.id}
placeholder="Use {{ agent.variable }} for dynamic content"
/>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Output Variable
</label>
<FormField label="Output Variable">
<Input
type="text"
value={
@@ -2188,24 +2151,22 @@ function WorkflowBuilderInner() {
}}
placeholder="Variable name for output"
/>
</div>
</FormField>
<div className="flex items-center gap-2">
<input
type="checkbox"
<Checkbox
id="stream_to_user"
checked={
selectedNode.data.config
?.stream_to_user ?? true
}
onChange={(e) =>
onCheckedChange={(checked) =>
handleUpdateNodeData({
config: {
...(selectedNode.data.config || {}),
stream_to_user: e.target.checked,
stream_to_user: checked === true,
},
})
}
className="h-4 w-4"
/>
<label
htmlFor="stream_to_user"
@@ -2214,10 +2175,7 @@ function WorkflowBuilderInner() {
Stream output to user
</label>
</div>{' '}
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Tools
</label>
<FormField label="Tools">
<MultiSelect
options={availableTools.map((tool) => ({
value: tool.id,
@@ -2238,11 +2196,8 @@ function WorkflowBuilderInner() {
searchPlaceholder="Search tools..."
emptyText="No tools available"
/>
</div>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Sources
</label>
</FormField>
<FormField label="Sources">
<MultiSelect
options={sourceOptions}
selected={
@@ -2260,7 +2215,7 @@ function WorkflowBuilderInner() {
searchPlaceholder="Search sources..."
emptyText="No sources available"
/>
</div>
</FormField>
<NodeDocumentsControl
key={selectedNode.id}
value={
@@ -2279,10 +2234,10 @@ function WorkflowBuilderInner() {
label="Documents"
helpText="Documents passed to this agent from uploads or upstream nodes."
/>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
File passing
</label>
<FormField
label="File passing"
hint="Auto: send native when the model supports it, otherwise extract text."
>
<Select
value={normalizeFilePassing(
selectedNode.data.config?.file_passing,
@@ -2310,17 +2265,24 @@ function WorkflowBuilderInner() {
))}
</SelectContent>
</Select>
<p className="text-muted-foreground mt-1 text-xs">
Auto: send native when the model supports
it, otherwise extract text.
</p>
</div>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Structured Output (JSON Schema)
</label>
</FormField>
<FormField
label="Structured Output (JSON Schema)"
hint={
selectedAgentJsonSchemaText.trim() !== '' &&
!selectedAgentJsonSchemaError
? 'Valid JSON schema'
: undefined
}
error={
selectedAgentJsonSchemaText.trim() !== '' &&
selectedAgentJsonSchemaError
? `Invalid JSON schema: ${selectedAgentJsonSchemaError}`
: undefined
}
>
{!selectedAgentModelSupportsStructuredOutput && (
<p className="text-destructive mb-2 text-xs">
<p className="text-destructive text-xs">
Selected model does not support structured
output.
</p>
@@ -2342,29 +2304,12 @@ function WorkflowBuilderInner() {
"required": ["summary"]
}`}
/>
{selectedAgentJsonSchemaText.trim() !==
'' && (
<p
className={`mt-2 text-xs ${
selectedAgentJsonSchemaError
? 'text-destructive'
: 'text-success'
}`}
>
{selectedAgentJsonSchemaError
? `Invalid JSON schema: ${selectedAgentJsonSchemaError}`
: 'Valid JSON schema'}
</p>
)}
</div>
</FormField>
</>
)}
{selectedNode.type === 'note' && (
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Note Content
</label>
<FormField label="Note Content">
<Textarea
value={selectedNode.data.content || ''}
onChange={(e) =>
@@ -2375,7 +2320,7 @@ function WorkflowBuilderInner() {
rows={4}
placeholder="Enter note content"
/>
</div>
</FormField>
)}
{selectedNode.type === 'state' && (
@@ -2857,10 +2802,7 @@ function WorkflowBuilderInner() {
Run code in the workflow sandbox. Produced
files are saved as artifacts.
</p>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Code
</label>
<FormField label="Code">
<Textarea
value={selectedNode.data.config?.code ?? ''}
onChange={(e) =>
@@ -2876,7 +2818,7 @@ function WorkflowBuilderInner() {
spellCheck={false}
placeholder={'print("hello world")'}
/>
</div>
</FormField>
<NodeDocumentsControl
key={selectedNode.id}
value={selectedNode.data.config?.inputs ?? []}
@@ -2892,10 +2834,7 @@ function WorkflowBuilderInner() {
label="Input files"
helpText="Artifacts/upstream refs staged as files in the sandbox (one becomes inputs/<name>)."
/>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Output Variable
</label>
<FormField label="Output Variable">
<Input
type="text"
value={
@@ -2912,11 +2851,8 @@ function WorkflowBuilderInner() {
}
placeholder="Variable name for output"
/>
</div>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Timeout (seconds)
</label>
</FormField>
<FormField label="Timeout (seconds)">
<Input
type="number"
min={1}
@@ -2942,11 +2878,22 @@ function WorkflowBuilderInner() {
}}
placeholder="Optional"
/>
</div>
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
Structured Output (JSON Schema)
</label>
</FormField>
<FormField
label="Structured Output (JSON Schema)"
hint={
selectedCodeJsonSchemaText.trim() !== '' &&
!selectedCodeJsonSchemaError
? 'Valid JSON schema'
: undefined
}
error={
selectedCodeJsonSchemaText.trim() !== '' &&
selectedCodeJsonSchemaError
? `Invalid JSON schema: ${selectedCodeJsonSchemaError}`
: undefined
}
>
<Textarea
value={selectedCodeJsonSchemaText}
onChange={(e) =>
@@ -2962,20 +2909,7 @@ function WorkflowBuilderInner() {
"required": ["result"]
}`}
/>
{selectedCodeJsonSchemaText.trim() !== '' && (
<p
className={`mt-2 text-xs ${
selectedCodeJsonSchemaError
? 'text-destructive'
: 'text-success'
}`}
>
{selectedCodeJsonSchemaError
? `Invalid JSON schema: ${selectedCodeJsonSchemaError}`
: 'Valid JSON schema'}
</p>
)}
</div>
</FormField>
</>
)}
</>
@@ -1,6 +1,7 @@
import {
Bot,
CheckCircle2,
ChevronDown,
Circle,
CircleAlert,
Code2,
@@ -30,7 +31,6 @@ import {
} from '@/components/ui/message-scroller';
import { cn } from '@/lib/utils';
import ChevronDownIcon from '../../assets/chevron-down.svg';
import MessageInput from '../../components/MessageInput';
import ConversationBubble from '../../conversation/ConversationBubble';
import { Query } from '../../conversation/conversationModels';
@@ -134,12 +134,10 @@ function ExecutionDetails({
{completedSteps.length === 1 ? 'step' : 'steps'})
</span>
</p>
<img
src={ChevronDownIcon}
alt="ChevronDown"
<ChevronDown
className={cn(
'h-4 w-4 transform transition-transform duration-200 dark:invert',
isOpen ? 'rotate-180' : '',
'transition-transform duration-200',
isOpen && 'rotate-180',
)}
/>
</Button>
@@ -286,12 +284,10 @@ function RunArtifactsSection({
className="-ml-2.5"
>
<p className="text-base font-semibold">Artifacts</p>
<img
src={ChevronDownIcon}
alt="ChevronDown"
<ChevronDown
className={cn(
'h-4 w-4 transform transition-transform duration-200 dark:invert',
isOpen ? 'rotate-180' : '',
'transition-transform duration-200',
isOpen && 'rotate-180',
)}
/>
</Button>
@@ -660,10 +656,7 @@ export default function WorkflowPreview({
) : (
<MessageScrollerProvider autoScroll>
<MessageScroller>
<MessageScrollerViewport
/* eslint-disable-next-line shadcn/no-restyle -- MessageScroller padding: the primitive measures Content's padding-block for its scroll math, and the Viewport's top gap must scroll with the messages. See DESIGN.md, Approved exceptions. */
className="scrollbar-thin px-4 pt-4"
>
<MessageScrollerViewport className="scrollbar-thin px-4 pt-4">
<MessageScrollerContent className="w-full">
{queries.map((query, index) => {
const querySteps = query.executionSteps || [];
@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import userService from '../../api/services/userService';
import DocumentArtifactView from '../../components/DocumentArtifactView';
import Spinner from '../../components/Spinner';
import { Spinner } from '@/components/ui/spinner';
import {
isDocumentArtifact,
type DocumentArtifact,
@@ -132,7 +132,7 @@ export default function WorkflowRunArtifacts({
if (loading) {
return (
<div className="text-muted-foreground flex items-center gap-2 px-3 py-4 text-sm">
<Spinner size="small" /> Loading artifacts...
<Spinner size="sm" /> Loading artifacts...
</div>
);
}
@@ -1,7 +1,8 @@
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { useId, useState } from 'react';
import { Button } from '@/components/ui/button';
import { FormField } from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
import { MultiSelect } from '@/components/ui/multi-select';
@@ -41,6 +42,7 @@ export default function NodeDocumentsControl({
getDocumentsMode(value),
);
const [refDraft, setRefDraft] = useState('');
const fieldId = useId();
const chosen = documentsModeToInputDocuments('choose', value);
const showChoose = mode === 'choose';
@@ -59,11 +61,13 @@ export default function NodeDocumentsControl({
};
return (
<div>
<label className="text-foreground mb-2 block text-sm font-medium">
{label}
</label>
<div className="border-border bg-card flex gap-1 rounded-xl border p-1">
<FormField label={label} hint={helpText} id={fieldId}>
<div
id={fieldId}
role="group"
aria-label={label}
className="border-border bg-card flex gap-1 rounded-xl border p-1"
>
{MODE_OPTIONS.map(({ mode: optionMode, label: modeLabel }) => (
<Button
key={optionMode}
@@ -78,7 +82,7 @@ export default function NodeDocumentsControl({
))}
</div>
{showChoose && (
<div className="mt-2 flex flex-col gap-2">
<div className="flex flex-col gap-2">
<MultiSelect
options={withChosenDocumentOptions(options, chosen)}
selected={chosen}
@@ -91,6 +95,7 @@ export default function NodeDocumentsControl({
/>
<div className="flex gap-2">
<Input
id={`${fieldId}-ref`}
type="text"
value={refDraft}
onChange={(e) => setRefDraft(e.target.value)}
@@ -114,9 +119,6 @@ export default function NodeDocumentsControl({
</div>
</div>
)}
{helpText && (
<p className="text-muted-foreground mt-1 text-xs">{helpText}</p>
)}
</div>
</FormField>
);
}
@@ -3,6 +3,7 @@ import {
type CSSProperties,
useCallback,
useEffect,
useId,
useMemo,
useRef,
useState,
@@ -10,6 +11,7 @@ import {
import { Edge, Node } from 'reactflow';
import { Button } from '@/components/ui/button';
import { FormField } from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
import {
Popover,
@@ -245,6 +247,10 @@ function VariableListWithSearch({
onSelect: (templatePath: string) => void;
}) {
const [search, setSearch] = useState('');
// Own id: this list renders inside PromptTextArea's FormField (also through
// the popover portal), whose context would otherwise hand the textarea's id
// to this search box too.
const searchId = useId();
const filtered = useMemo(
() =>
@@ -263,6 +269,7 @@ function VariableListWithSearch({
<div className="border-border flex items-center gap-2 border-b px-3 py-2">
<Search className="text-muted-foreground h-3.5 w-3.5 shrink-0" />
<Input
id={searchId}
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
@@ -330,6 +337,7 @@ export default function PromptTextArea({
rows = 4,
label,
}: PromptTextAreaProps) {
const textareaId = useId();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
@@ -444,95 +452,96 @@ export default function PromptTextArea({
}
}, [showDropdown]);
return (
<div>
{label && (
<label className="text-foreground mb-2 block text-sm font-medium">
{label}
</label>
)}
const field = (
<div
ref={wrapperRef}
className="border-border focus-within:ring-ring bg-card relative rounded-xl border transition-all focus-within:ring-2"
>
<div
ref={wrapperRef}
className="border-border focus-within:ring-ring bg-card relative rounded-xl border transition-all focus-within:ring-2"
ref={overlayRef}
aria-hidden
className="pointer-events-none absolute inset-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm wrap-break-word whitespace-pre-wrap"
>
<div
ref={overlayRef}
aria-hidden
className="pointer-events-none absolute inset-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm wrap-break-word whitespace-pre-wrap"
>
{value ? (
<HighlightedOverlay text={value} />
) : (
<span className="text-muted-foreground">{placeholder}</span>
)}
</div>
<textarea
ref={textareaRef}
value={value}
onChange={(e) => {
onChange(e.target.value);
setTimeout(checkForTrigger, 0);
}}
onKeyUp={checkForTrigger}
onKeyDown={(e) => {
if (showDropdown && e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
setShowDropdown(false);
}
}}
onScroll={() => {
if (overlayRef.current && textareaRef.current) {
overlayRef.current.scrollTop = textareaRef.current.scrollTop;
}
}}
className="focus-visible:ring-ring/50 focus-visible:border-ring relative w-full rounded-xl bg-transparent px-3 pt-2 pb-8 text-sm text-transparent caret-black outline-none focus-visible:ring-3 dark:caret-white"
rows={rows}
placeholder={placeholder}
spellCheck={false}
/>
<div className="absolute right-4 bottom-1.5 z-10">
<Popover open={contextOpen} onOpenChange={setContextOpen}>
<PopoverTrigger asChild>
<Button type="button" variant="link" size="xs">
<Plus className="h-3 w-3" />
Add context
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
side="top"
className="w-60 p-0"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<VariableListWithSearch
variables={variables}
onSelect={insertVariableFromButton}
/>
</PopoverContent>
</Popover>
</div>
{showDropdown && filtered.length > 0 && (
<div
ref={dropdownRef}
className="border-border bg-card absolute top-(--dropdown-top) left-(--dropdown-left) z-50 w-64 rounded-xl border shadow-lg"
style={
{
'--dropdown-top': `${dropdownPos.top}px`,
'--dropdown-left': `${dropdownPos.left}px`,
} as CSSProperties
}
>
<VariableListWithSearch
variables={filtered}
onSelect={insertVariable}
/>
</div>
{value ? (
<HighlightedOverlay text={value} />
) : (
<span className="text-muted-foreground">{placeholder}</span>
)}
</div>
<textarea
id={textareaId}
ref={textareaRef}
value={value}
onChange={(e) => {
onChange(e.target.value);
setTimeout(checkForTrigger, 0);
}}
onKeyUp={checkForTrigger}
onKeyDown={(e) => {
if (showDropdown && e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
setShowDropdown(false);
}
}}
onScroll={() => {
if (overlayRef.current && textareaRef.current) {
overlayRef.current.scrollTop = textareaRef.current.scrollTop;
}
}}
className="focus-visible:ring-ring/50 focus-visible:border-ring relative w-full rounded-xl bg-transparent px-3 pt-2 pb-8 text-sm text-transparent caret-black outline-none focus-visible:ring-3 dark:caret-white"
rows={rows}
placeholder={placeholder}
spellCheck={false}
/>
<div className="absolute right-4 bottom-1.5 z-10">
<Popover open={contextOpen} onOpenChange={setContextOpen}>
<PopoverTrigger asChild>
<Button type="button" variant="link" size="xs">
<Plus className="h-3 w-3" />
Add context
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
side="top"
className="w-60 p-0"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<VariableListWithSearch
variables={variables}
onSelect={insertVariableFromButton}
/>
</PopoverContent>
</Popover>
</div>
{showDropdown && filtered.length > 0 && (
<div
ref={dropdownRef}
className="border-border bg-card absolute top-(--dropdown-top) left-(--dropdown-left) z-50 w-64 rounded-xl border shadow-lg"
style={
{
'--dropdown-top': `${dropdownPos.top}px`,
'--dropdown-left': `${dropdownPos.left}px`,
} as CSSProperties
}
>
<VariableListWithSearch
variables={filtered}
onSelect={insertVariable}
/>
</div>
)}
</div>
);
if (!label) return <div>{field}</div>;
return (
<FormField label={label} id={textareaId}>
{field}
</FormField>
);
}
+1 -1
View File
@@ -82,7 +82,7 @@ export const AgentNode = memo(function AgentNode({
)}
{config.model_id && (
<div
className="text-primary dark:text-primary truncate text-xs"
className="text-primary truncate text-xs"
title={config.model_id}
>
{config.model_id}
-4
View File
@@ -1,4 +0,0 @@
<svg width="72" height="72" viewBox="0 0 72 72" fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M33.8788 9.87836C34.4413 9.31595 35.2043 9 35.9998 9C36.7952 9 37.5582 9.31595 38.1208 9.87836L50.1208 21.8784C50.6672 22.4442 50.9696 23.202 50.9628 23.9886C50.9559 24.7752 50.6404 25.5276 50.0842 26.0838C49.528 26.64 48.7755 26.9555 47.989 26.9624C47.2024 26.9692 46.4446 26.6668 45.8788 26.1204L38.9998 19.2414V47.9994C38.9998 48.795 38.6837 49.5581 38.1211 50.1207C37.5585 50.6833 36.7954 50.9994 35.9998 50.9994C35.2041 50.9994 34.441 50.6833 33.8784 50.1207C33.3158 49.5581 32.9998 48.795 32.9998 47.9994V19.2414L26.1208 26.1204C25.555 26.6668 24.7971 26.9692 24.0106 26.9624C23.224 26.9555 22.4715 26.64 21.9153 26.0838C21.3591 25.5276 21.0436 24.7752 21.0367 23.9886C21.0299 23.202 21.3323 22.4442 21.8788 21.8784L33.8788 9.87836Z" fill="#313131"/>
<path d="M18 51C18 50.2044 17.6839 49.4413 17.1213 48.8787C16.5587 48.3161 15.7956 48 15 48C14.2044 48 13.4413 48.3161 12.8787 48.8787C12.3161 49.4413 12 50.2044 12 51V52.8C12 58.446 16.554 63 22.2 63H49.8C55.446 63 60 58.446 60 52.8V51C60 50.2044 59.6839 49.4413 59.1213 48.8787C58.5587 48.3161 57.7957 48 57 48C56.2043 48 55.4413 48.3161 54.8787 48.8787C54.3161 49.4413 54 50.2044 54 51V52.8C54 55.134 52.134 57 49.8 57H22.2C19.866 57 18 55.134 18 52.8V51Z" fill="#313131"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.16669 11.5H9.83335V13.1666H8.16669V11.5ZM8.16669 4.83329H9.83335V9.83329H8.16669V4.83329ZM8.99169 0.666626C4.39169 0.666626 0.666687 4.39996 0.666687 8.99996C0.666687 13.6 4.39169 17.3333 8.99169 17.3333C13.6 17.3333 17.3334 13.6 17.3334 8.99996C17.3334 4.39996 13.6 0.666626 8.99169 0.666626ZM9.00002 15.6666C5.31669 15.6666 2.33335 12.6833 2.33335 8.99996C2.33335 5.31663 5.31669 2.33329 9.00002 2.33329C12.6834 2.33329 15.6667 5.31663 15.6667 8.99996C15.6667 12.6833 12.6834 15.6666 9.00002 15.6666Z" fill="#ECECF1"/>
</svg>

Before

Width:  |  Height:  |  Size: 636 B

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M440-800v487L216-537l-56 57 320 320 320-320-56-57-224 224v-487h-80Z" />
</svg>

Before

Width:  |  Height:  |  Size: 178 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.0303 7.03033C13.3232 6.73744 13.3232 6.26256 13.0303 5.96967L8.25736 1.1967C7.96447 0.903806 7.48959 0.903806 7.1967 1.1967C6.90381 1.48959 6.90381 1.96447 7.1967 2.25736L11.4393 6.5L7.1967 10.7426C6.90381 11.0355 6.90381 11.5104 7.1967 11.8033C7.48959 12.0962 7.96447 12.0962 8.25736 11.8033L13.0303 7.03033ZM0.5 6.5V7.25H12.5V6.5V5.75H0.5V6.5Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 478 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="13" height="23" viewBox="0 0 13 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.3183 20.8523C12.4198 20.9597 12.4991 21.0861 12.5518 21.2242C12.6044 21.3622 12.6294 21.5093 12.6252 21.657C12.621 21.8047 12.5878 21.9502 12.5274 22.0851C12.467 22.2199 12.3807 22.3416 12.2733 22.4431C12.1659 22.5446 12.0395 22.6239 11.9014 22.6766C11.7634 22.7293 11.6163 22.7542 11.4686 22.75C11.3208 22.7459 11.1754 22.7126 11.0405 22.6522C10.9057 22.5919 10.784 22.5055 10.6825 22.3981L1.12001 12.2731C0.922533 12.0642 0.812501 11.7877 0.812501 11.5002C0.812501 11.2128 0.922533 10.9362 1.12001 10.7273L10.6825 0.601207C10.7833 0.49145 10.905 0.40282 11.0403 0.340468C11.1757 0.278115 11.3221 0.243281 11.4711 0.23799C11.62 0.232697 11.7685 0.257054 11.908 0.309644C12.0474 0.362231 12.175 0.442004 12.2834 0.544328C12.3917 0.646653 12.4787 0.769488 12.5392 0.9057C12.5997 1.04191 12.6325 1.18878 12.6357 1.33779C12.639 1.48679 12.6126 1.63495 12.5581 1.77367C12.5036 1.91238 12.4221 2.03889 12.3183 2.14583L3.48476 11.5002L12.3183 20.8523Z" fill="#7D54D1"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="13" height="23" viewBox="0 0 13 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.681741 20.8523C0.580245 20.9597 0.500899 21.0861 0.448232 21.2242C0.395564 21.3622 0.370607 21.5093 0.374785 21.657C0.378964 21.8047 0.412197 21.9502 0.472586 22.0851C0.532974 22.2199 0.619336 22.3416 0.726741 22.4431C0.834146 22.5446 0.96049 22.6239 1.09856 22.6766C1.23663 22.7293 1.38372 22.7542 1.53144 22.75C1.67915 22.7459 1.8246 22.7126 1.95947 22.6522C2.09434 22.5919 2.216 22.5055 2.31749 22.3981L11.88 12.2731C12.0775 12.0642 12.1875 11.7877 12.1875 11.5002C12.1875 11.2128 12.0775 10.9362 11.88 10.7273L2.31749 0.601207C2.21666 0.49145 2.09503 0.40282 1.95967 0.340468C1.8243 0.278115 1.67789 0.243281 1.52895 0.23799C1.38 0.232697 1.23149 0.257054 1.09204 0.309644C0.95259 0.362231 0.824978 0.442004 0.716618 0.544328C0.608256 0.646653 0.521307 0.769488 0.46082 0.9057C0.400333 1.04191 0.367515 1.18878 0.364269 1.33779C0.361025 1.48679 0.387418 1.63495 0.441917 1.77367C0.496417 1.91238 0.577936 2.03889 0.681739 2.14583L9.51524 11.5002L0.681741 20.8523Z" fill="#7D54D1"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.2857 14H2.57143C1.15179 14 0 12.8242 0 11.375V2.625C0 1.17578 1.15179 0 2.57143 0H10.7143C11.4241 0 12 0.587891 12 1.3125V9.1875C12 9.75898 11.6411 10.2457 11.1429 10.4262V12.25C11.617 12.25 12 12.641 12 13.125C12 13.609 11.617 14 11.1429 14H10.2857ZM2.57143 10.5C2.09732 10.5 1.71429 10.891 1.71429 11.375C1.71429 11.859 2.09732 12.25 2.57143 12.25H9.42857V10.5H2.57143ZM3.42857 4.15625C3.42857 4.51992 3.71518 4.8125 4.07143 4.8125H8.78571C9.14196 4.8125 9.42857 4.51992 9.42857 4.15625C9.42857 3.79258 9.14196 3.5 8.78571 3.5H4.07143C3.71518 3.5 3.42857 3.79258 3.42857 4.15625ZM4.07143 6.125C3.71518 6.125 3.42857 6.41758 3.42857 6.78125C3.42857 7.14492 3.71518 7.4375 4.07143 7.4375H8.78571C9.14196 7.4375 9.42857 7.14492 9.42857 6.78125C9.42857 6.41758 9.14196 6.125 8.78571 6.125H4.07143Z" fill="#6A4DF4"/>
</svg>

Before

Width:  |  Height:  |  Size: 930 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7.5C6 7.36739 5.94732 7.24021 5.85355 7.14645C5.75979 7.05268 5.63261 7 5.5 7H4.5C4.36739 7 4.24021 7.05268 4.14645 7.14645C4.05268 7.24021 4 7.36739 4 7.5V8.5C4 8.63261 4.05268 8.75979 4.14645 8.85355C4.24021 8.94732 4.36739 9 4.5 9H5.5C5.63261 9 5.75979 8.94732 5.85355 8.85355C5.94732 8.75979 6 8.63261 6 8.5V7.5ZM6 10.5C6 10.3674 5.94732 10.2402 5.85355 10.1464C5.75979 10.0527 5.63261 10 5.5 10H4.5C4.36739 10 4.24021 10.0527 4.14645 10.1464C4.05268 10.2402 4 10.3674 4 10.5V11.5C4 11.6326 4.05268 11.7598 4.14645 11.8536C4.24021 11.9473 4.36739 12 4.5 12H5.5C5.63261 12 5.75979 11.9473 5.85355 11.8536C5.94732 11.7598 6 11.6326 6 11.5V10.5ZM7.5 7H8.5C8.63261 7 8.75979 7.05268 8.85355 7.14645C8.94732 7.24021 9 7.36739 9 7.5V8.5C9 8.63261 8.94732 8.75979 8.85355 8.85355C8.75979 8.94732 8.63261 9 8.5 9H7.5C7.36739 9 7.24021 8.94732 7.14645 8.85355C7.05268 8.75979 7 8.63261 7 8.5V7.5C7 7.36739 7.05268 7.24021 7.14645 7.14645C7.24021 7.05268 7.36739 7 7.5 7ZM8.5 10H7.5C7.36739 10 7.24021 10.0527 7.14645 10.1464C7.05268 10.2402 7 10.3674 7 10.5V11.5C7 11.6326 7.05268 11.7598 7.14645 11.8536C7.24021 11.9473 7.36739 12 7.5 12H8.5C8.63261 12 8.75979 11.9473 8.85355 11.8536C8.94732 11.7598 9 11.6326 9 11.5V10.5C9 10.3674 8.94732 10.2402 8.85355 10.1464C8.75979 10.0527 8.63261 10 8.5 10ZM10 7.5C10 7.36739 10.0527 7.24021 10.1464 7.14645C10.2402 7.05268 10.3674 7 10.5 7H11.5C11.6326 7 11.7598 7.05268 11.8536 7.14645C11.9473 7.24021 12 7.36739 12 7.5V8.5C12 8.63261 11.9473 8.75979 11.8536 8.85355C11.7598 8.94732 11.6326 9 11.5 9H10.5C10.3674 9 10.2402 8.94732 10.1464 8.85355C10.0527 8.75979 10 8.63261 10 8.5V7.5Z" fill="#848484"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 0C4.63261 0 4.75979 0.0526784 4.85355 0.146447C4.94732 0.240215 5 0.367392 5 0.5V1H11V0.5C11 0.367392 11.0527 0.240215 11.1464 0.146447C11.2402 0.0526784 11.3674 0 11.5 0C11.6326 0 11.7598 0.0526784 11.8536 0.146447C11.9473 0.240215 12 0.367392 12 0.5V1C13.66 1 15 2.34 15 4V12C15 13.66 13.66 15 12 15H4C2.34 15 1 13.66 1 12V4C1 2.34 2.34 1 4 1V0.5C4 0.367392 4.05268 0.240215 4.14645 0.146447C4.24021 0.0526784 4.36739 0 4.5 0ZM14 4V5H2V4C2 2.9 2.895 2 4 2V2.5C4 2.63261 4.05268 2.75979 4.14645 2.85355C4.24021 2.94732 4.36739 3 4.5 3C4.63261 3 4.75979 2.94732 4.85355 2.85355C4.94732 2.75979 5 2.63261 5 2.5V2H11V2.5C11 2.63261 11.0527 2.75979 11.1464 2.85355C11.2402 2.94732 11.3674 3 11.5 3C11.6326 3 11.7598 2.94732 11.8536 2.85355C11.9473 2.75979 12 2.63261 12 2.5V2C13.1 2 14 2.895 14 4ZM2 12V6H14V12C14 13.1 13.105 14 12 14H4C2.9 14 2 13.105 2 12Z" fill="#848484"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,3 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.5C17.523 2.5 22 6.977 22 12.5C22 18.023 17.523 22.5 12 22.5C6.477 22.5 2 18.023 2 12.5C2 6.977 6.477 2.5 12 2.5ZM15.22 9.47L10.75 13.94L8.78 11.97C8.63783 11.8375 8.44978 11.7654 8.25548 11.7688C8.06118 11.7723 7.87579 11.851 7.73838 11.9884C7.60097 12.1258 7.52225 12.3112 7.51883 12.5055C7.5154 12.6998 7.58752 12.8878 7.72 13.03L10.22 15.53C10.3606 15.6705 10.5512 15.7493 10.75 15.7493C10.9488 15.7493 11.1394 15.6705 11.28 15.53L16.28 10.53C16.3537 10.4613 16.4128 10.3785 16.4538 10.2865C16.4948 10.1945 16.5168 10.0952 16.5186 9.99452C16.5204 9.89382 16.5018 9.79379 16.4641 9.7004C16.4264 9.60701 16.3703 9.52218 16.299 9.45096C16.2278 9.37974 16.143 9.3236 16.0496 9.28588C15.9562 9.24816 15.8562 9.22963 15.7555 9.23141C15.6548 9.23318 15.5555 9.25523 15.4635 9.29622C15.3715 9.33721 15.2887 9.39631 15.22 9.47Z" fill="#0C9D35"/>
</svg>

Before

Width:  |  Height:  |  Size: 958 B

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="11" viewBox="0 0 14 11" fill="none">
<path d="M4.95919 10.1906C4.84318 10.1902 4.72847 10.166 4.62222 10.1194C4.51596 10.0729 4.42041 10.0049 4.34152 9.91985L0.229353 5.54538C0.0756344 5.38157 -0.00671208 5.1634 0.000428491 4.93886C0.00756906 4.71433 0.103612 4.50183 0.267428 4.34812C0.431245 4.1944 0.649417 4.11205 0.873948 4.11919C1.09848 4.12633 1.31098 4.22238 1.4647 4.38619L4.95073 8.10068L12.0666 0.316329C12.1389 0.226405 12.2287 0.152193 12.3306 0.0982513C12.4326 0.0443098 12.5445 0.0117775 12.6594 0.00265255C12.7744 -0.00647237 12.89 0.00800286 12.9992 0.045189C13.1084 0.082375 13.2088 0.141487 13.2943 0.218894C13.3798 0.296301 13.4485 0.390369 13.4964 0.49532C13.5442 0.600272 13.57 0.713891 13.5723 0.829198C13.5746 0.944506 13.5534 1.05907 13.5098 1.16585C13.4662 1.27263 13.4012 1.36937 13.3189 1.45014L5.58533 9.91139C5.50718 9.998 5.41197 10.0675 5.30567 10.1156C5.19938 10.1636 5.0843 10.1892 4.96766 10.1906H4.95919Z" fill="#747474"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

-1
View File
@@ -1 +0,0 @@
<svg width="16px" height="16px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#11ee1c" stroke="#11ee1c" stroke-width="83.96799999999999"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M866.133333 258.133333L362.666667 761.6l-204.8-204.8L98.133333 618.666667 362.666667 881.066667l563.2-563.2z" fill="#0C9D35"></path></g></svg>

Before

Width:  |  Height:  |  Size: 490 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>

Before

Width:  |  Height:  |  Size: 246 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.29154 4.88202L1.70154 0.29202C1.60896 0.199438 1.49905 0.125998 1.37808 0.0758932C1.25712 0.0257882 1.12747 -2.37536e-07 0.99654 -2.44235e-07C0.86561 -2.50934e-07 0.735961 0.0257882 0.614997 0.0758931C0.494033 0.125998 0.384122 0.199438 0.29154 0.29202C0.198958 0.384602 0.125519 0.494513 0.0754137 0.615477C0.0253086 0.736441 -0.00048069 0.86609 -0.000480695 0.99702C-0.000480701 1.12795 0.0253086 1.2576 0.0754136 1.37856C0.125519 1.49953 0.198958 1.60944 0.29154 1.70202L4.17154 5.59202L0.29154 9.47202C0.198958 9.5646 0.125518 9.67451 0.0754133 9.79548C0.0253082 9.91644 -0.000481091 10.0461 -0.000481097 10.177C-0.000481102 10.3079 0.0253082 10.4376 0.0754132 10.5586C0.125518 10.6795 0.198958 10.7894 0.29154 10.882C0.384121 10.9746 0.494032 11.048 0.614996 11.0981C0.73596 11.1483 0.865609 11.174 0.99654 11.174C1.12747 11.174 1.25712 11.1483 1.37808 11.0981C1.49905 11.048 1.60896 10.9746 1.70154 10.882L6.29154 6.29202C6.38424 6.19951 6.45779 6.08962 6.50797 5.96864C6.55815 5.84767 6.58398 5.71799 6.58398 5.58702C6.58398 5.45605 6.55815 5.32637 6.50797 5.2054C6.45779 5.08442 6.38424 4.97453 6.29154 4.88202Z" fill="#666666"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(34 197 94)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>

Before

Width:  |  Height:  |  Size: 281 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(239 68 68)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>

Before

Width:  |  Height:  |  Size: 293 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.08485 4.07133L3.08485 9.20109C3.08485 9.49263 3.20066 9.77222 3.40681 9.97837C3.61295 10.1845 3.89255 10.3003 4.18408 10.3003C4.47562 10.3003 4.75521 10.1845 4.96136 9.97837C5.1675 9.77222 5.28332 9.49263 5.28332 9.20109L5.28332 3.70492C5.28332 3.12185 5.05169 2.56266 4.6394 2.15037C4.22711 1.73808 3.66792 1.50645 3.08485 1.50645C2.50178 1.50645 1.94259 1.73808 1.5303 2.15037C1.118 2.56266 0.88638 3.12185 0.88638 3.70492L0.886379 9.20109C0.886379 10.0757 1.23381 10.9145 1.85225 11.5329C2.47069 12.1514 3.30948 12.4988 4.18408 12.4988C5.05869 12.4988 5.89747 12.1514 6.51591 11.5329C7.13435 10.9145 7.48178 10.0757 7.48178 9.20109L7.48178 4.07133" stroke="#5D5D5D" stroke-width="1.03637" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 854 B

-11
View File
@@ -1,11 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 16C2.53043 16 3.03914 16.2107 3.41421 16.5858C3.78929 16.9609 4 17.4696 4 18C4 18.5304 3.78929 19.0391 3.41421 19.4142C3.03914 19.7893 2.53043 20 2 20C1.46957 20 0.960859 19.7893 0.585786 19.4142C0.210714 19.0391 0 18.5304 0 18C0 17.4696 0.210714 16.9609 0.585786 16.5858C0.960859 16.2107 1.46957 16 2 16ZM7.5 13C8.16304 13 8.79893 13.2634 9.26777 13.7322C9.73661 14.2011 10 14.837 10 15.5C10 16.163 9.73661 16.7989 9.26777 17.2678C8.79893 17.7366 8.16304 18 7.5 18C6.83696 18 6.20107 17.7366 5.73223 17.2678C5.26339 16.7989 5 16.163 5 15.5C5 14.837 5.26339 14.2011 5.73223 13.7322C6.20107 13.2634 6.83696 13 7.5 13ZM10 0C11.272 0.000250351 12.5033 0.448355 13.4779 1.26572C14.4525 2.08308 15.1082 3.2175 15.33 4.47H15.412C16.4105 4.47 17.3682 4.86667 18.0743 5.57274C18.7803 6.27882 19.177 7.23646 19.177 8.235C19.177 9.23354 18.7803 10.1912 18.0743 10.8973C17.3682 11.6033 16.4105 12 15.412 12H4.588C3.58946 12 2.63182 11.6033 1.92574 10.8973C1.21967 10.1912 0.823 9.23354 0.823 8.235C0.823 7.23646 1.21967 6.27882 1.92574 5.57274C2.63182 4.86667 3.58946 4.47 4.588 4.47H4.67C4.89179 3.2175 5.54749 2.08308 6.52211 1.26572C7.49673 0.448355 8.72801 0.000250351 10 0Z" fill="url(#paint0_linear_6161_11984)"/>
<defs>
<linearGradient id="paint0_linear_6161_11984" x1="0" y1="10" x2="19.177" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#70FDF7"/>
<stop offset="0.325" stop-color="#747696"/>
<stop offset="0.68" stop-color="#BD5372"/>
<stop offset="1" stop-color="#F5A06C"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="13" height="16" stroke-width="1.15" viewBox="0 0 14 17" >
<path d="M13.8013 5.01282L8.80645 0.191795C8.67953 0.0691399 8.50734 0.000152609 8.32774 0H6.09677C5.43801 0 4.80623 0.252586 4.34041 0.702193C3.8746 1.1518 3.6129 1.7616 3.6129 2.39744V3.48718H2.48387C1.82511 3.48718 1.19332 3.73977 0.727509 4.18937C0.261693 4.63898 0 5.24878 0 5.88462V14.6026C0 15.2384 0.261693 15.8482 0.727509 16.2978C1.19332 16.7474 1.82511 17 2.48387 17H8.80645C9.46521 17 10.097 16.7474 10.5628 16.2978C11.0286 15.8482 11.2903 15.2384 11.2903 14.6026V13.5128H11.5161C12.1749 13.5128 12.8067 13.2602 13.2725 12.8106C13.7383 12.361 14 11.7512 14 11.1154V5.44872C13.9929 5.28447 13.9219 5.12884 13.8013 5.01282ZM9.03226 2.23179L11.6877 4.79487H9.03226V2.23179ZM9.93548 14.6026C9.93548 14.8916 9.81653 15.1688 9.6048 15.3731C9.39306 15.5775 9.10589 15.6923 8.80645 15.6923H2.48387C2.18443 15.6923 1.89726 15.5775 1.68552 15.3731C1.47379 15.1688 1.35484 14.8916 1.35484 14.6026V5.88462C1.35484 5.5956 1.47379 5.31842 1.68552 5.11405C1.89726 4.90968 2.18443 4.79487 2.48387 4.79487H3.6129V11.1154C3.6129 11.7512 3.8746 12.361 4.34041 12.8106C4.80623 13.2602 5.43801 13.5128 6.09677 13.5128H9.93548V14.6026ZM11.5161 12.2051H6.09677C5.79734 12.2051 5.51016 12.0903 5.29843 11.886C5.08669 11.6816 4.96774 11.4044 4.96774 11.1154V2.39744C4.96774 2.10842 5.08669 1.83124 5.29843 1.62687C5.51016 1.4225 5.79734 1.30769 6.09677 1.30769H7.67742V5.44872C7.67976 5.62143 7.75188 5.78643 7.87842 5.90856C8.00496 6.03069 8.1759 6.10031 8.35484 6.10256H12.6452V11.1154C12.6452 11.4044 12.5262 11.6816 12.3145 11.886C12.1027 12.0903 11.8156 12.2051 11.5161 12.2051Z" fill="#949494"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>

Before

Width:  |  Height:  |  Size: 262 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 0C8.8144 0 10.4902 0.332143 11.739 0.898571C12.362 1.18143 12.9108 1.53643 13.3119 1.96714C13.7172 2.4 14 2.94429 14 3.57143V6.42857C14 7.05571 13.7172 7.59929 13.3119 8.03286C12.9108 8.46357 12.3627 8.81857 11.739 9.10143C10.4902 9.66786 8.8144 10 7 10C5.1856 10 3.5098 9.66786 2.261 9.10143C1.638 8.81857 1.0892 8.46357 0.6881 8.03286C0.2828 7.6 0 7.05571 0 6.42857V3.57143C0 2.94429 0.2828 2.40071 0.6881 1.96714C1.0892 1.53643 1.6373 1.18143 2.261 0.898571C3.5098 0.332143 5.1856 0 7 0ZM12.6 5.77143C12.3375 5.94714 12.047 6.10429 11.739 6.24429C10.4902 6.81071 8.8144 7.14286 7 7.14286C5.1856 7.14286 3.5098 6.81071 2.261 6.24429C1.96243 6.10966 1.67456 5.95157 1.4 5.77143V6.42857C1.4 6.59071 1.47 6.79857 1.7024 7.04857C1.9383 7.30143 2.3128 7.56214 2.8294 7.79643C3.8612 8.26429 5.3354 8.57143 7 8.57143C8.6646 8.57143 10.1388 8.26429 11.1706 7.79643C11.6872 7.56214 12.0617 7.30143 12.2976 7.04857C12.5307 6.79857 12.6 6.59071 12.6 6.42857V5.77143ZM7 1.42857C5.3347 1.42857 3.8612 1.73571 2.8294 2.20357C2.3128 2.43786 1.9383 2.69857 1.7024 2.95143C1.4693 3.20143 1.4 3.40929 1.4 3.57143C1.4 3.73357 1.47 3.94143 1.7024 4.19143C1.9383 4.44429 2.3128 4.705 2.8294 4.93929C3.8612 5.40714 5.3354 5.71429 7 5.71429C8.6646 5.71429 10.1388 5.40714 11.1706 4.93929C11.6872 4.705 12.0617 4.44429 12.2976 4.19143C12.5307 3.94143 12.6 3.73357 12.6 3.57143C12.6 3.40929 12.53 3.20143 12.2976 2.95143C12.0617 2.69857 11.6872 2.43786 11.1706 2.20357C10.1388 1.73643 8.6646 1.42857 7 1.42857Z" fill="#848484"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 16 16" fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M6.37776 10.1001V12.9C6.37776 13.457 6.599 13.9911 6.99282 14.3849C7.38664 14.7788 7.92077 15 8.47772 15L11.2777 8.70011V1.00025H3.38181C3.04419 0.996436 2.71656 1.11477 2.45929 1.33344C2.20203 1.55212 2.03246 1.8564 1.98184 2.19023L1.01585 8.49012C0.985398 8.69076 0.998931 8.89563 1.05551 9.09053C1.1121 9.28543 1.21038 9.46569 1.34355 9.61884C1.47671 9.77198 1.64159 9.89434 1.82674 9.97744C2.01189 10.0605 2.2129 10.1024 2.41583 10.1001H6.37776ZM11.2777 1.00025H13.1466C13.5428 0.993247 13.9277 1.13195 14.2284 1.39002C14.5291 1.64809 14.7245 2.00758 14.7776 2.40023V7.30014C14.7245 7.69279 14.5291 8.05227 14.2284 8.31035C13.9277 8.56842 13.5428 8.70712 13.1466 8.70011H11.2777" fill="none"/>
<path d="M11.2777 8.70011L8.47772 15C7.92077 15 7.38664 14.7788 6.99282 14.3849C6.599 13.9911 6.37776 13.457 6.37776 12.9V10.1001H2.41583C2.2129 10.1024 2.01189 10.0605 1.82674 9.97744C1.64159 9.89434 1.47671 9.77198 1.34355 9.61884C1.21038 9.46569 1.1121 9.28543 1.05551 9.09053C0.998931 8.89563 0.985398 8.69076 1.01585 8.49012L1.98184 2.19023C2.03246 1.8564 2.20203 1.55212 2.45929 1.33344C2.71656 1.11477 3.04419 0.996436 3.38181 1.00025H11.2777M11.2777 8.70011V1.00025M11.2777 8.70011H13.1466C13.5428 8.70712 13.9277 8.56842 14.2284 8.31035C14.5291 8.05227 14.7245 7.69279 14.7776 7.30014V2.40023C14.7245 2.00758 14.5291 1.64809 14.2284 1.39002C13.9277 1.13195 13.5428 0.993247 13.1466 1.00025H11.2777" stroke="current" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9294 5.375V12.4583C12.9294 12.8341 12.7801 13.1944 12.5145 13.4601C12.2488 13.7257 11.8885 13.875 11.5127 13.875H3.01274C2.63701 13.875 2.27668 13.7257 2.011 13.4601C1.74532 13.1944 1.59607 12.8341 1.59607 12.4583V2.54167C1.59607 2.16594 1.74532 1.80561 2.011 1.53993C2.27668 1.27426 2.63701 1.125 3.01274 1.125H8.6794M12.9294 5.375V5.25317C12.9293 4.87747 12.78 4.5172 12.5143 4.25158L9.80282 1.54008C9.53721 1.27439 9.17693 1.12508 8.80124 1.125H8.6794M12.9294 5.375H10.0961C9.72035 5.375 9.36001 5.22574 9.09434 4.96007C8.82866 4.69439 8.6794 4.33406 8.6794 3.95833V1.125" stroke="#949494" stroke-width="1.41667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 781 B

@@ -1,5 +0,0 @@
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.41 10.59L2.83 6L7.41 1.41L6 0L0 6L6 12L7.41 10.59Z" fill="black" fill-opacity="0.54" />
<path d="M15.41 10.59L10.83 6L15.41 1.41L14 0L8 6L14 12L15.41 10.59Z" fill="black"
fill-opacity="0.54" />
</svg>

Before

Width:  |  Height:  |  Size: 324 B

@@ -1,5 +0,0 @@
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.59 10.59L13.17 6L8.59 1.41L10 0L16 6L10 12L8.59 10.59Z" fill="black"
fill-opacity="0.54" />
<path d="M0.59 10.59L5.17 6L0.59 1.41L2 0L8 6L2 12L0.59 10.59Z" fill="black" fill-opacity="0.54" />
</svg>

Before

Width:  |  Height:  |  Size: 322 B

-5
View File
@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<path d="M7 10l5 5 5-5"/>
<path d="M12 15V3"/>
</svg>

Before

Width:  |  Height:  |  Size: 291 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0L5 5L10 0H0Z" fill="gray" fill-opacity="0.80"/>
</svg>

Before

Width:  |  Height:  |  Size: 162 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.8984 5.5H7.22656C5.99687 5.5 5 6.49687 5 7.72656V16.3984C5 17.6281 5.99687 18.625 7.22656 18.625H15.8984C17.1281 18.625 18.125 17.6281 18.125 16.3984V7.72656C18.125 6.49687 17.1281 5.5 15.8984 5.5Z" stroke="#949494" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M14.9805 5.5L15 4.5625C14.9984 3.98285 14.7674 3.4274 14.3575 3.01753C13.9476 2.60765 13.3922 2.37665 12.8125 2.375H4.375C3.71256 2.37696 3.07781 2.64098 2.6094 3.1094C2.14098 3.57781 1.87696 4.21256 1.875 4.875V13.3125C1.87665 13.8922 2.10765 14.4476 2.51753 14.8575C2.9274 15.2674 3.48285 15.4984 4.0625 15.5H5M11.5625 8.9375V15.1875M14.6875 12.0625H8.4375" stroke="#949494" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 833 B

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="15" viewBox="0 0 16 15" fill="none">
<path d="M10.0588 2.74568L12.5294 5.15732M8.41176 14H15M1.82353 10.7845L1 14L4.29412 13.1961L13.8355 3.88237C14.1443 3.58087 14.3178 3.172 14.3178 2.74568C14.3178 2.31936 14.1443 1.9105 13.8355 1.609L13.6939 1.47073C13.385 1.16932 12.9662 1 12.5294 1C12.0927 1 11.6738 1.16932 11.3649 1.47073L1.82353 10.7845Z" stroke="#747474" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 500 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.04867 15.49H15.1775C16.5339 15.49 17.3172 14.7067 17.3172 13.1548V4.83755C17.3172 3.29309 16.5265 2.50977 14.9515 2.50977H2.82302C1.47463 2.50977 0.683594 3.28569 0.683594 4.83755V13.1545C0.683594 14.7138 1.48138 15.49 3.04867 15.49ZM8.10441 9.3803L2.41609 3.76816C2.58163 3.70034 2.77738 3.66273 3.01106 3.66273H14.9971C15.2305 3.66273 15.434 3.70034 15.6072 3.78327L9.92692 9.38062C9.60291 9.7043 9.31652 9.84766 9.01534 9.84766C8.71384 9.84766 8.42841 9.70398 8.10441 9.3803ZM1.83559 13.1548V4.76234L6.16717 9.01162L1.84331 13.2824C1.83592 13.2448 1.83559 13.1998 1.83559 13.1548ZM16.1646 4.84494V13.2599L11.8629 9.0113L16.1649 4.78516L16.1646 4.84494ZM3.01106 14.3377C2.79249 14.3377 2.61184 14.3075 2.4537 14.2397L6.95852 9.78723L7.44838 10.2694C7.97552 10.7891 8.48017 11.0077 9.01534 11.0077C9.54249 11.0077 10.0548 10.7891 10.5823 10.2694L11.0718 9.78723L15.5696 14.2319C15.4111 14.3075 15.2154 14.3374 14.9968 14.3374L3.01106 14.3377Z" fill="#6F6F6F"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4028 7.35671V12.6427C12.4043 12.844 12.3655 13.0436 12.2889 13.2298C12.2122 13.4159 12.0991 13.5849 11.9564 13.7269C11.8136 13.8688 11.6439 13.9808 11.4573 14.0563C11.2706 14.1318 11.0708 14.1694 10.8695 14.1667H3.36483C3.16278 14.1693 2.96226 14.1314 2.77509 14.0553C2.58792 13.9791 2.41789 13.8663 2.27504 13.7234C2.13219 13.5804 2.01941 13.4104 1.94335 13.2232C1.86728 13.036 1.82948 12.8354 1.83217 12.6334V5.12871C1.82975 4.92668 1.86776 4.7262 1.94396 4.53908C2.02017 4.35196 2.13302 4.18196 2.27589 4.0391C2.41875 3.89623 2.58875 3.78338 2.77587 3.70717C2.963 3.63097 3.16347 3.59296 3.3655 3.59537H8.65083M14.1648 1.83337L7.1175 8.88071M14.1648 1.83337H10.6408M14.1648 1.83337V5.35737" stroke="#7D54D1" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 895 B

-11
View File
@@ -1,11 +0,0 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5819_12451)">
<path d="M8.40039 14.3C5.06706 14.3 2.53372 12.4334 0.533724 8.63338L0.400391 8.30005L0.533724 7.96672C2.53372 4.16672 5.06706 2.30005 8.40039 2.30005C11.7337 2.30005 14.2671 4.16672 16.2671 7.96672L16.4004 8.30005L16.2671 8.63338C14.2671 12.4334 11.7337 14.3 8.40039 14.3ZM1.93372 8.30005C3.60039 11.4334 5.73372 12.9667 8.40039 12.9667C11.0671 12.9667 13.2004 11.4334 14.8671 8.30005C13.2004 5.16672 11.0671 3.63338 8.40039 3.63338C5.73372 3.63338 3.60039 5.16672 1.93372 8.30005Z" fill="#747474"/>
<path d="M8.40072 11.6333C6.53405 11.6333 5.06738 10.1667 5.06738 8.30001C5.06738 6.43334 6.53405 4.96667 8.40072 4.96667C10.2674 4.96667 11.734 6.43334 11.734 8.30001C11.734 10.1667 10.2674 11.6333 8.40072 11.6333ZM8.40072 6.30001C7.26738 6.30001 6.40072 7.16667 6.40072 8.30001C6.40072 9.43334 7.26738 10.3 8.40072 10.3C9.53405 10.3 10.4007 9.43334 10.4007 8.30001C10.4007 7.16667 9.53405 6.30001 8.40072 6.30001Z" fill="#747474"/>
</g>
<defs>
<clipPath id="clip0_5819_12451">
<rect width="16" height="16" fill="white" transform="translate(0.400391 0.300049)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="13" height="17" viewBox="0 0 13 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1.94971C0 0.983707 0.784 0.199707 1.75 0.199707H8.336C8.8 0.199707 9.245 0.383707 9.573 0.712707L12.487 3.62671C12.816 3.95471 13 4.39971 13 4.86371V14.4497C13 14.9138 12.8156 15.359 12.4874 15.6871C12.1592 16.0153 11.7141 16.1997 11.25 16.1997H1.75C1.28587 16.1997 0.840752 16.0153 0.512563 15.6871C0.184375 15.359 0 14.9138 0 14.4497V1.94971ZM1.75 1.69971C1.6837 1.69971 1.62011 1.72605 1.57322 1.77293C1.52634 1.81981 1.5 1.8834 1.5 1.94971V14.4497C1.5 14.5877 1.612 14.6997 1.75 14.6997H11.25C11.3163 14.6997 11.3799 14.6734 11.4268 14.6265C11.4737 14.5796 11.5 14.516 11.5 14.4497V6.19971H8.75C8.28587 6.19971 7.84075 6.01533 7.51256 5.68714C7.18437 5.35896 7 4.91384 7 4.44971V1.69971H1.75ZM8.5 1.76171V4.44971C8.5 4.58771 8.612 4.69971 8.75 4.69971H11.438L11.427 4.68671L8.513 1.77271L8.5 1.76171Z" fill="#59636E"/>
</svg>

Before

Width:  |  Height:  |  Size: 938 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.75 0.599915C1.28587 0.599915 0.840752 0.784289 0.512563 1.11248C0.184374 1.44067 0 1.88579 0 2.34991L0 12.8499C0 13.8159 0.784 14.5999 1.75 14.5999H14.25C14.7141 14.5999 15.1592 14.4155 15.4874 14.0874C15.8156 13.7592 16 13.314 16 12.8499V4.34991C16 3.88579 15.8156 3.44067 15.4874 3.11248C15.1592 2.78429 14.7141 2.59991 14.25 2.59991H7.5C7.46119 2.59991 7.42291 2.59088 7.3882 2.57352C7.35348 2.55616 7.32329 2.53096 7.3 2.49991L6.4 1.29991C6.07 0.859915 5.55 0.599915 5 0.599915H1.75Z" fill="#A382E7"/>
</svg>

Before

Width:  |  Height:  |  Size: 621 B

-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 521.01 85.34">
<defs>
<style>
.cls-1 {
fill: #4a00e0;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<g>
<g>
<path class="cls-1" d="M152.09,85.34c-5.46,0-10.42-1.3-14.87-3.92-4.45-2.61-7.98-6.14-10.59-10.59-2.61-4.45-3.92-9.4-3.92-14.86s1.31-10.38,3.92-14.75c2.61-4.37,6.15-7.86,10.59-10.48,4.45-2.61,9.4-3.92,14.87-3.92s10.53,1.29,14.98,3.86c4.45,2.58,7.98,6.07,10.59,10.48,2.61,4.41,3.92,9.35,3.92,14.81s-1.31,10.42-3.92,14.86c-2.61,4.45-6.14,7.98-10.59,10.59-4.45,2.62-9.44,3.92-14.98,3.92ZM152.09,72.93c3.2,0,6.03-.72,8.49-2.17,2.46-1.44,4.37-3.45,5.74-6.03,1.36-2.58,2.05-5.5,2.05-8.78s-.7-6.16-2.11-8.66c-1.4-2.5-3.32-4.47-5.74-5.91-2.42-1.44-5.23-2.17-8.43-2.17s-5.91.72-8.37,2.17c-2.46,1.44-4.37,3.41-5.74,5.91-1.37,2.5-2.05,5.38-2.05,8.66s.68,6.2,2.05,8.78c1.36,2.58,3.28,4.58,5.74,6.03,2.46,1.44,5.25,2.17,8.37,2.17Z"/>
<path class="cls-1" d="M218.1,85.34c-5.54,0-10.55-1.29-15.04-3.86-4.49-2.58-8.02-6.09-10.59-10.53-2.58-4.45-3.86-9.4-3.86-14.87s1.29-10.51,3.86-14.92c2.58-4.41,6.1-7.9,10.59-10.48,4.49-2.58,9.5-3.86,15.04-3.86,4.37,0,8.44.84,12.23,2.52,3.78,1.68,7,4.08,9.66,7.2l-8.43,8.54c-1.64-1.95-3.61-3.41-5.91-4.39-2.3-.97-4.82-1.46-7.55-1.46-3.2,0-6.03.72-8.49,2.17-2.46,1.44-4.37,3.41-5.74,5.91-1.37,2.5-2.05,5.42-2.05,8.78s.68,6.19,2.05,8.72c1.36,2.54,3.28,4.53,5.74,5.97,2.46,1.44,5.29,2.17,8.49,2.17,2.73,0,5.25-.49,7.55-1.46,2.3-.98,4.27-2.44,5.91-4.39l8.43,8.54c-2.65,3.12-5.87,5.52-9.66,7.2-3.79,1.68-7.86,2.52-12.23,2.52Z"/>
<path class="cls-1" d="M267.61,85.34c-3.12,0-6.13-.41-9.01-1.23-2.89-.82-5.54-1.99-7.96-3.51-2.42-1.52-4.53-3.37-6.32-5.56l8.19-8.19c1.95,2.26,4.19,3.96,6.73,5.09,2.54,1.13,5.4,1.7,8.6,1.7,2.89,0,5.07-.43,6.55-1.29,1.48-.86,2.22-2.11,2.22-3.75s-.7-3.04-2.11-3.98c-1.4-.94-3.22-1.74-5.44-2.4-2.22-.66-4.56-1.35-7.02-2.05-2.46-.7-4.8-1.66-7.02-2.87-2.22-1.21-4.04-2.87-5.44-4.97-1.4-2.11-2.11-4.84-2.11-8.19,0-3.59.84-6.67,2.52-9.25,1.68-2.58,4.06-4.57,7.14-5.97,3.08-1.4,6.77-2.11,11.06-2.11,4.52,0,8.52.8,12,2.4,3.47,1.6,6.38,4,8.72,7.2l-8.19,8.19c-1.64-2.03-3.51-3.55-5.62-4.56-2.11-1.01-4.53-1.52-7.26-1.52-2.58,0-4.56.39-5.97,1.17-1.4.78-2.11,1.91-2.11,3.39,0,1.56.7,2.77,2.11,3.63,1.4.86,3.22,1.6,5.44,2.22,2.22.63,4.56,1.31,7.02,2.05,2.46.74,4.8,1.76,7.02,3.04,2.22,1.29,4.04,3.01,5.44,5.15,1.4,2.15,2.11,4.94,2.11,8.37,0,5.46-1.91,9.79-5.74,12.99-3.82,3.2-9.01,4.8-15.57,4.8Z"/>
<path class="cls-1" d="M338.31,85.34c-5.62,0-10.93-1.09-15.92-3.28-4.99-2.18-9.36-5.21-13.11-9.07-3.75-3.86-6.69-8.35-8.84-13.46-2.15-5.11-3.22-10.59-3.22-16.45s1.07-11.31,3.22-16.39c2.14-5.07,5.11-9.54,8.9-13.4,3.78-3.86,8.23-6.87,13.34-9.01,5.11-2.15,10.63-3.22,16.56-3.22,6.48,0,12.48,1.25,18.03,3.75,5.54,2.5,10.18,6.05,13.93,10.65l-9.13,9.13c-2.42-3.43-5.66-6.07-9.72-7.9-4.06-1.83-8.43-2.75-13.11-2.75-5.62,0-10.55,1.23-14.81,3.69-4.25,2.46-7.55,5.87-9.89,10.24-2.34,4.37-3.51,9.44-3.51,15.22s1.19,10.87,3.57,15.28c2.38,4.41,5.6,7.86,9.66,10.36,4.06,2.5,8.66,3.75,13.81,3.75,5.46,0,10.16-.99,14.1-2.98,3.94-1.99,6.96-4.95,9.07-8.9,2.11-3.94,3.16-8.8,3.16-14.57l8.43,6.09h-35v-12.29h40.5v1.87c0,9.76-1.74,17.87-5.21,24.35-3.47,6.48-8.21,11.32-14.22,14.51-6.01,3.2-12.88,4.8-20.6,4.8Z"/>
<path class="cls-1" d="M391.68,84.17V2.24h13.46v81.93h-13.46ZM400.92,53.27v-11.7h20.48c2.81,0,5.28-.57,7.43-1.7,2.14-1.13,3.84-2.73,5.09-4.8,1.25-2.07,1.87-4.51,1.87-7.32s-.63-5.25-1.87-7.32c-1.25-2.07-2.95-3.67-5.09-4.8-2.15-1.13-4.62-1.7-7.43-1.7h-20.48V2.24h21.18c5.15,0,9.77,1.05,13.87,3.16,4.1,2.11,7.33,5.07,9.72,8.9,2.38,3.82,3.57,8.31,3.57,13.46s-1.19,9.64-3.57,13.46c-2.38,3.82-5.62,6.79-9.72,8.9-4.1,2.11-8.72,3.16-13.87,3.16h-21.18Z"/>
<path class="cls-1" d="M454.18,14.53V2.24h66.83v12.29h-66.83ZM480.87,84.17V4.58h13.46v79.59h-13.46Z"/>
</g>
<path class="cls-1" d="M113.79,25.5c-2.18-5.16-5.24-9.65-9.19-13.48-3.95-3.83-8.62-6.79-14.02-8.88-5.4-2.09-11.24-3.14-17.52-3.14h-33.12v70.4c-3.81.33-7.54.21-11.2-.38-5.68-.91-10.85-2.82-15.52-5.73-4.67-2.9-8.61-6.67-11.84-11.29-.41-.59-.81-1.19-1.19-1.8h-.2c.54,2.88,1.37,5.64,2.47,8.28,2.18,5.2,5.24,9.71,9.19,13.54,3.95,3.83,8.6,6.79,13.96,8.88,4.48,1.75,9.26,2.77,14.33,3.06h0s11.24,0,11.24,0h22.12c6.2,0,11.98-1.05,17.34-3.14,5.36-2.09,10.01-5.06,13.96-8.88,3.95-3.83,7.01-8.34,9.19-13.54,2.18-5.2,3.26-10.86,3.26-16.98s-1.09-11.76-3.26-16.92ZM96.81,57.11c-2.3,4.23-5.52,7.49-9.67,9.79-4.15,2.3-8.92,3.44-14.32,3.44h-16.44V14.62h16.44c5.4,0,10.15,1.17,14.26,3.5,4.11,2.34,7.33,5.56,9.67,9.67,2.34,4.11,3.51,8.94,3.51,14.5s-1.15,10.58-3.44,14.81Z"/>
<path class="cls-1" d="M9.74,58.97c.25.21.64.21.99,0h0c.35-.22.6-.61.61-.99l.18-3.94c.02-.51-.37-.84-.87-.74l-1.69.36-2.11.45c-.37.08-.71.37-.87.75h0c-.16.39-.1.77.14.99l3.62,3.11Z"/>
<path class="cls-1" d="M14.41,61.98l5.72,2.68c.39.18.9,0,1.3-.45.4-.46.6-1.11.51-1.65l-.94-5.56c-.12-.73-.74-1.01-1.38-.63l-2.12,1.27-2.65,1.59c-.46.28-.83.84-.92,1.44v.02c-.1.6.09,1.11.48,1.29Z"/>
<path class="cls-1" d="M25.32,65.34c.14.8.61,1.36,1.18,1.42l8.43.86c.58.06,1.15-.39,1.45-1.15h0c.3-.76.27-1.68-.08-2.32l-3.65-6.61c-.48-.86-1.38-.95-2.02-.18l-2.12,2.54-2.66,3.18c-.46.56-.67,1.43-.53,2.23v.02Z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="40" height="39" viewBox="0 0 40 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9477 3.02295H33.8827C35.898 3.02295 37.5388 4.6819 37.5388 6.71923V22.9819C37.5388 25.0193 35.898 26.6782 33.8827 26.6782H11.9477C9.9328 26.6782 8.29192 25.0193 8.29192 22.9819V6.71923C8.29192 4.6819 9.9328 3.02295 11.9477 3.02295ZM33.8827 5.97992H11.9477C11.5442 5.97992 11.2167 6.31098 11.2167 6.71916V20.6741L15.2527 16.595C16.2515 15.5839 17.8791 15.5839 18.8792 16.595L20.6486 18.3795L26.0799 11.7888C26.5653 11.2003 27.276 10.8603 28.0335 10.856C28.7953 10.8735 29.5046 11.1841 29.9946 11.765L34.614 17.2147V6.71923C34.614 6.31104 34.2866 5.97992 33.8827 5.97992ZM6.40446 25.1242C7.16068 27.3803 9.243 28.8957 11.584 28.8957H32.8128L31.4954 33.1312C31.1223 34.5715 29.7916 35.5487 28.3352 35.5487C28.051 35.5485 27.768 35.5117 27.4929 35.4393L4.88059 29.3169C3.13614 28.8305 2.09642 27.0048 2.55267 25.2438L6.10025 13.2714V23.3516C6.10025 23.8543 6.17497 24.3567 6.35333 24.9542L6.40446 25.1242ZM18.53 10.4151C18.53 12.0459 17.2186 13.3721 15.6055 13.3721C13.9926 13.3721 12.6808 12.0458 12.6808 10.4151C12.6808 8.78445 13.9925 7.45815 15.6055 7.45815C17.2186 7.45815 18.53 8.78438 18.53 10.4151Z" fill="#A3A3A3"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM11 15H9V9H11V15ZM11 7H9V5H11V7Z" fill="black" fill-opacity="0.54"/>
</svg>

Before

Width:  |  Height:  |  Size: 273 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 16 16" fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M9.39995 5.89997V3.09999C9.39995 2.54304 9.1787 2.0089 8.78487 1.61507C8.39105 1.22125 7.85691 1 7.29996 1L4.49998 7.29996V14.9999H12.3959C12.7336 15.0037 13.0612 14.8854 13.3185 14.6667C13.5757 14.448 13.7453 14.1437 13.7959 13.8099L14.7619 7.50996C14.7924 7.30931 14.7788 7.10444 14.7222 6.90954C14.6657 6.71464 14.5674 6.53437 14.4342 6.38123C14.301 6.22808 14.1362 6.10572 13.951 6.02262C13.7659 5.93952 13.5649 5.89767 13.3619 5.89997H9.39995ZM4.49998 14.9999H2.39999C2.02869 14.9999 1.6726 14.8524 1.41005 14.5899C1.1475 14.3273 1 13.9712 1 13.5999V8.69995C1 8.32865 1.1475 7.97256 1.41005 7.71001C1.6726 7.44746 2.02869 7.29996 2.39999 7.29996H4.49998" fill="none"/>
<path d="M4.49998 7.29996L7.29996 1C7.85691 1 8.39105 1.22125 8.78487 1.61507C9.1787 2.0089 9.39995 2.54304 9.39995 3.09999V5.89997H13.3619C13.5649 5.89767 13.7659 5.93952 13.951 6.02262C14.1362 6.10572 14.301 6.22808 14.4342 6.38123C14.5674 6.53437 14.6657 6.71464 14.7223 6.90954C14.7788 7.10444 14.7924 7.30931 14.7619 7.50996L13.7959 13.8099C13.7453 14.1437 13.5757 14.448 13.3185 14.6667C13.0612 14.8854 12.7336 15.0037 12.3959 14.9999H4.49998M4.49998 7.29996V14.9999M4.49998 7.29996H2.39999C2.02869 7.29996 1.6726 7.44746 1.41005 7.71001C1.1475 7.97256 1 8.32865 1 8.69995V13.5999C1 13.9712 1.1475 14.3273 1.41005 14.5899C1.6726 14.8524 2.02869 14.9999 2.39999 14.9999H4.49998" stroke="current" stroke-width="1.39999" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1.5H3C2.46957 1.5 1.96086 1.71071 1.58579 2.08579C1.21071 2.46086 1 2.96957 1 3.5V15.5C1 16.0304 1.21071 16.5391 1.58579 16.9142C1.96086 17.2893 2.46957 17.5 3 17.5H15C15.5304 17.5 16.0391 17.2893 16.4142 16.9142C16.7893 16.5391 17 16.0304 17 15.5V11.5M9 9.5L17 1.5M17 1.5V6.5M17 1.5H12" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 486 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 16H2V2H9V0H2C0.89 0 0 0.9 0 2V16C0 17.1 0.89 18 2 18H16C17.1 18 18 17.1 18 16V9H16V16ZM11 0V2H14.59L4.76 11.83L6.17 13.24L16 3.41V7H18V0H11Z" fill="black" fill-opacity="0.54"/>
</svg>

Before

Width:  |  Height:  |  Size: 293 B

-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 233.35 169.38">
<defs>
<style>
.cls-1 {
fill: #4a00e0;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<g>
<path class="cls-1" d="M226.85,50.84c-4.34-10.28-10.44-19.23-18.31-26.86-7.87-7.63-17.19-13.53-27.95-17.71-10.76-4.18-22.41-6.26-34.94-6.26h-66.02v140.35c-7.6.66-15.04.42-22.33-.75-11.32-1.82-21.64-5.63-30.94-11.42-9.3-5.79-17.17-13.29-23.6-22.5-.82-1.17-1.61-2.37-2.37-3.59h-.4c1.08,5.74,2.73,11.25,4.93,16.51,4.34,10.36,10.44,19.36,18.31,26.99,7.87,7.63,17.15,13.53,27.83,17.71,8.93,3.49,18.46,5.52,28.56,6.09h0s22.41,0,22.41,0h44.09c12.37,0,23.89-2.09,34.57-6.26,10.68-4.17,19.96-10.08,27.83-17.71,7.87-7.63,13.97-16.62,18.31-26.99,4.34-10.36,6.51-21.64,6.51-33.85s-2.17-23.45-6.51-33.73ZM193,113.84c-4.58,8.43-11,14.94-19.28,19.52-8.27,4.58-17.79,6.87-28.55,6.87h-32.77V29.15h32.77c10.76,0,20.24,2.33,28.43,6.99,8.19,4.66,14.61,11.08,19.28,19.28,4.66,8.19,6.99,17.83,6.99,28.91s-2.29,21.08-6.87,29.52Z"/>
<path class="cls-1" d="M19.41,117.55c.49.42,1.27.42,1.97,0h0c.7-.43,1.19-1.21,1.22-1.97l.37-7.84c.05-1.03-.73-1.68-1.74-1.47l-3.36.72-4.21.9c-.73.16-1.42.75-1.74,1.5v.02c-.33.75-.21,1.52.28,1.94l7.21,6.21Z"/>
<path class="cls-1" d="M28.73,123.56l11.41,5.35c.78.37,1.8,0,2.6-.9.8-.92,1.2-2.22,1.02-3.29l-1.88-11.08c-.25-1.45-1.47-2.01-2.74-1.25l-4.23,2.53-5.29,3.17c-.92.55-1.65,1.68-1.84,2.87v.03c-.2,1.19.18,2.21.96,2.57Z"/>
<path class="cls-1" d="M50.47,130.26c.28,1.59,1.21,2.71,2.36,2.83l16.8,1.72c1.15.12,2.29-.79,2.88-2.29h0c.6-1.52.54-3.34-.16-4.62l-7.28-13.18c-.95-1.72-2.75-1.89-4.02-.37l-4.23,5.07-5.29,6.35c-.92,1.11-1.34,2.86-1.06,4.45v.04Z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="19" height="17" viewBox="0 0 19 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.85 16.087C4.84924 16.2663 4.91145 16.4239 5.03693 16.5494C5.16233 16.6747 5.31971 16.7373 5.499 16.7373L5.4997 16.7373C5.67889 16.7364 5.83627 16.6743 5.9617 16.5497C6.08758 16.4247 6.15 16.267 6.15 16.0873V10.7993C6.15 10.619 6.08842 10.4607 5.96269 10.3358C5.83772 10.2117 5.68058 10.1501 5.5017 10.1493C5.32173 10.1484 5.16366 10.2105 5.03793 10.3362C4.9124 10.4618 4.85 10.6195 4.85 10.7993V16.087ZM4.85 16.087C4.85 16.0868 4.85 16.0867 4.85 16.0866L5 16.0873M4.85 16.087V16.0873H5M5 16.0873C4.99933 16.2286 5.047 16.3473 5.143 16.4433C5.239 16.5393 5.35767 16.5873 5.499 16.5873L5 16.0873ZM17.4984 0.850356C17.6844 0.844257 17.8447 0.916616 17.9702 1.05647C18.0903 1.18276 18.1491 1.33782 18.1439 1.51269C18.1389 1.6837 18.0787 1.83386 17.9605 1.95296L17.9601 1.95335L12.7431 7.17035L12.7421 7.17131C12.5779 7.33262 12.3846 7.45674 12.1641 7.54381C11.9451 7.63023 11.7235 7.67428 11.5 7.67428C11.277 7.67428 11.0585 7.63017 10.8453 7.54316M17.4984 0.850356L10.8453 7.54316M17.4984 0.850356C17.3204 0.855502 17.1648 0.921665 17.0399 1.04721L17.4984 0.850356ZM10.8453 7.54316C10.632 7.45637 10.4379 7.33351 10.2633 7.17548L10.2578 7.1705L10.258 7.17037L7.83596 4.74937L7.83593 4.74935C7.75166 4.66508 7.6439 4.62029 7.5 4.62029C7.3561 4.62029 7.24834 4.66508 7.16407 4.74935L1.96007 9.95335C1.83516 10.0783 1.67973 10.1443 1.50197 10.1502L1.50128 10.1502C1.31531 10.1555 1.15551 10.0825 1.03084 9.94215C0.910023 9.81657 0.850921 9.66177 0.856065 9.48688C0.861095 9.31586 0.921352 9.1658 1.03993 9.04722L6.25793 3.82922L6.25859 3.82856C6.43436 3.65497 6.63139 3.52612 6.84951 3.44477C7.06174 3.36537 7.27883 3.32528 7.5 3.32528C7.72114 3.32528 7.94133 3.36536 8.15989 3.44418C8.38508 3.5254 8.58031 3.65486 8.74407 3.83122L11.164 6.2502L11.1641 6.25022C11.2483 6.33449 11.3561 6.37928 11.5 6.37928C11.6439 6.37928 11.7517 6.33449 11.8359 6.25022L11.8359 6.25021L17.0396 1.04758L10.8453 7.54316ZM1.5 16.7373L1.5007 16.7373C1.68022 16.7364 1.83649 16.6741 1.9617 16.5497C2.08758 16.4247 2.15 16.267 2.15 16.0873V15.2993C2.15 15.119 2.08842 14.9607 1.96269 14.8358C1.83772 14.7117 1.68058 14.6501 1.5017 14.6493C1.32173 14.6484 1.16366 14.7105 1.03793 14.8362C0.912396 14.9618 0.85 15.1195 0.85 15.2993V16.0873C0.85 16.2663 0.91197 16.4235 1.03657 16.549C1.16172 16.675 1.31979 16.7373 1.5 16.7373ZM9.499 16.7373L9.4997 16.7373C9.67889 16.7364 9.83627 16.6743 9.9617 16.5497C10.0876 16.4247 10.15 16.267 10.15 16.0873V12.7993C10.15 12.619 10.0884 12.4607 9.96269 12.3358C9.83772 12.2117 9.68058 12.1501 9.5017 12.1493C9.32173 12.1484 9.16366 12.2105 9.03793 12.3362C8.9124 12.4618 8.85 12.6195 8.85 12.7993V16.0873C8.85 16.2663 8.91197 16.4235 9.03656 16.549C9.16158 16.6749 9.31924 16.7373 9.499 16.7373ZM13.499 16.7373L13.4997 16.7373C13.6789 16.7364 13.8363 16.6743 13.9617 16.5497C14.0876 16.4247 14.15 16.267 14.15 16.0873V11.2993C14.15 11.119 14.0884 10.9607 13.9627 10.8358C13.8377 10.7117 13.6806 10.6501 13.5017 10.6493C13.3217 10.6484 13.1637 10.7105 13.0379 10.8362C12.9124 10.9618 12.85 11.1195 12.85 11.2993L12.85 16.0866L13 16.0873H12.85V16.0868C12.8492 16.2662 12.9114 16.4238 13.0369 16.5494C13.1623 16.6747 13.3197 16.7373 13.499 16.7373ZM17.499 16.7373L17.4997 16.7373C17.6789 16.7364 17.8363 16.6743 17.9617 16.5497C18.0876 16.4247 18.15 16.267 18.15 16.0873V7.29928C18.15 7.11895 18.0884 6.9607 17.9627 6.83585C17.8377 6.71175 17.6806 6.65013 17.5017 6.64929C17.3217 6.64844 17.1637 6.7105 17.0379 6.83622C16.9124 6.96176 16.85 7.11954 16.85 7.29928L16.85 16.0866L17 16.0873H16.85V16.0868C16.8492 16.2662 16.9114 16.4238 17.0369 16.5494C17.1623 16.6747 17.3197 16.7373 17.499 16.7373Z" fill="#747474" stroke="#747474" stroke-width="0.3"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin-icon lucide-pin"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>

Before

Width:  |  Height:  |  Size: 458 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="13" height="16" viewBox="0 0 13 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.66574 13.7747C1.66574 14.2168 1.84635 14.6407 2.16784 14.9533C2.48933 15.2658 2.92537 15.4414 3.38002 15.4414H10.2372C10.6918 15.4414 11.1279 15.2658 11.4493 14.9533C11.7708 14.6407 11.9515 14.2168 11.9515 13.7747V3.77474H1.66574V13.7747ZM3.38002 5.44141H10.2372V13.7747H3.38002V5.44141ZM9.80859 1.27474L8.95145 0.441406H4.66574L3.80859 1.27474H0.808594V2.94141H12.8086V1.27474H9.80859Z" fill="#f44336"/>
</svg>

Before

Width:  |  Height:  |  Size: 520 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.22066 0.46934C5.36129 0.328889 5.55191 0.25 5.75066 0.25C5.94941 0.25 6.14003 0.328889 6.28066 0.46934L10.5307 4.71934C10.6711 4.85997 10.75 5.05059 10.75 5.24934C10.75 5.44809 10.6711 5.63871 10.5307 5.77934L6.28066 10.0293C6.13851 10.162 5.95041 10.2342 5.75602 10.2309C5.56163 10.2275 5.37614 10.1488 5.23866 10.0113C5.10119 9.87386 5.02247 9.68837 5.01911 9.49398C5.01576 9.29959 5.08802 9.11149 5.22066 8.96934L8.19066 5.99934L0.75066 5.99934C0.551747 5.99934 0.360983 5.92032 0.22033 5.77967C0.0796773 5.63902 0.000659715 5.44825 0.000659724 5.24934C0.000659733 5.05043 0.0796774 4.85966 0.22033 4.71901C0.360983 4.57836 0.551747 4.49934 0.75066 4.49934L8.19066 4.49934L5.22066 1.52934C5.08021 1.38871 5.00132 1.19809 5.00132 0.99934C5.00132 0.800589 5.08021 0.609965 5.22066 0.46934Z" fill="#7D54D1"/>
</svg>

Before

Width:  |  Height:  |  Size: 924 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 0.75C4.62391 0.75 0.25 5.12391 0.25 10.5C0.25 15.8761 4.62391 20.25 10 20.25C15.3761 20.25 19.75 15.8761 19.75 10.5C19.75 5.12391 15.3761 0.75 10 0.75ZM15.0742 7.23234L8.77422 14.7323C8.70511 14.8147 8.61912 14.8812 8.52207 14.9273C8.42502 14.9735 8.31918 14.9983 8.21172 15H8.19906C8.09394 15 7.99 14.9778 7.89398 14.935C7.79797 14.8922 7.71202 14.8297 7.64172 14.7516L4.94172 11.7516C4.87315 11.6788 4.81981 11.5931 4.78483 11.4995C4.74986 11.4059 4.73395 11.3062 4.73805 11.2063C4.74215 11.1064 4.76617 11.0084 4.8087 10.9179C4.85124 10.8275 4.91142 10.7464 4.98572 10.6796C5.06002 10.6127 5.14694 10.5614 5.24136 10.5286C5.33579 10.4958 5.43581 10.4822 5.53556 10.4886C5.63531 10.495 5.73277 10.5213 5.82222 10.5659C5.91166 10.6106 5.99128 10.6726 6.05641 10.7484L8.17938 13.1072L13.9258 6.26766C14.0547 6.11863 14.237 6.02631 14.4335 6.01066C14.6299 5.99501 14.8246 6.05728 14.9754 6.18402C15.1263 6.31075 15.2212 6.49176 15.2397 6.68793C15.2582 6.8841 15.1988 7.07966 15.0742 7.23234Z" fill="#B5B5B5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,3 +0,0 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.41 10.59L2.83 6L7.41 1.41L6 0L0 6L6 12L7.41 10.59Z" fill="black" fill-opacity="0.54" />
</svg>

Before

Width:  |  Height:  |  Size: 204 B

@@ -1,3 +0,0 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.59 1.41L5.17 6L0.59 10.59L2 12L8 6L2 0L0.59 1.41Z" fill="black" fill-opacity="0.54" />
</svg>

Before

Width:  |  Height:  |  Size: 203 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10.5994V3.39941M11.6 3.39941V10.5994" stroke="#5D5D5D" stroke-width="0.9" stroke-linecap="round"/>
<path d="M11.6 7C11.6 8.326 9.4508 9.4 6.8 9.4C4.1492 9.4 2 8.326 2 7M11.6 10.6C11.6 11.926 9.4508 13 6.8 13C4.1492 13 2 11.926 2 10.6M6.8 5.8C9.4508 5.8 11.6 4.726 11.6 3.4C11.6 2.074 9.4508 1 6.8 1C4.1492 1 2 2.074 2 3.4C2 4.726 4.1492 5.8 6.8 5.8Z" stroke="#5D5D5D" stroke-width="0.9"/>
</svg>

Before

Width:  |  Height:  |  Size: 504 B

-25
View File
@@ -1,25 +0,0 @@
<svg width="24" height="27" viewBox="0 0 24 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.73517 17.2121L0.538918 17.9125C0.3753 18.0084 0.239616 18.1455 0.145332 18.31C0.0510491 18.4746 0.00144577 18.661 0.00144577 18.8506C0.00144577 19.0403 0.0510491 19.2267 0.145332 19.3912C0.239616 19.5558 0.3753 19.6929 0.538918 19.7888L11.0514 25.9513C11.2181 26.0489 11.4078 26.1004 11.601 26.1004C11.7941 26.1004 11.9838 26.0489 12.1505 25.9513L22.663 19.7888C22.8266 19.6929 22.9623 19.5558 23.0566 19.3912C23.1509 19.2267 23.2005 19.0403 23.2005 18.8506C23.2005 18.661 23.1509 18.4746 23.0566 18.31C22.9623 18.1455 22.8266 18.0084 22.663 17.9125L21.4668 17.2107L13.2511 22.0276C12.7506 22.321 12.1811 22.4757 11.601 22.4757C11.0209 22.4757 10.4513 22.321 9.95087 22.0276L1.73517 17.2121Z" fill="url(#paint0_linear_4013_8178)"/>
<path d="M1.73517 11.4121L0.538918 12.1124C0.3753 12.2084 0.239616 12.3454 0.145332 12.51C0.0510491 12.6746 0.00144577 12.8609 0.00144577 13.0506C0.00144577 13.2403 0.0510491 13.4266 0.145332 13.5912C0.239616 13.7558 0.3753 13.8928 0.538918 13.9887L11.0514 20.1512C11.2181 20.2489 11.4078 20.3003 11.601 20.3003C11.7941 20.3003 11.9838 20.2489 12.1505 20.1512L22.663 13.9887C22.8266 13.8928 22.9623 13.7558 23.0566 13.5912C23.1509 13.4266 23.2005 13.2403 23.2005 13.0506C23.2005 12.8609 23.1509 12.6746 23.0566 12.51C22.9623 12.3454 22.8266 12.2084 22.663 12.1124L21.4668 11.4106L13.2511 16.2275C12.7506 16.5209 12.1811 16.6756 11.601 16.6756C11.0209 16.6756 10.4513 16.5209 9.95087 16.2275L1.73517 11.4121Z" fill="url(#paint1_linear_4013_8178)"/>
<path d="M12.152 0.149921C11.9849 0.0517579 11.7947 0 11.601 0C11.4072 0 11.217 0.0517579 11.05 0.149921L0.537472 6.31242C0.373854 6.40835 0.23817 6.5454 0.143887 6.70997C0.0496035 6.87454 0 7.06091 0 7.25057C0 7.44024 0.0496035 7.6266 0.143887 7.79117C0.23817 7.95574 0.373854 8.09279 0.537472 8.18872L11.05 14.3512C11.217 14.4494 11.4072 14.5011 11.601 14.5011C11.7947 14.5011 11.9849 14.4494 12.152 14.3512L22.6645 8.18872C22.8281 8.09279 22.9638 7.95574 23.0581 7.79117C23.1523 7.6266 23.2019 7.44024 23.2019 7.25057C23.2019 7.06091 23.1523 6.87454 23.0581 6.70997C22.9638 6.5454 22.8281 6.40835 22.6645 6.31242L12.152 0.149921Z" fill="url(#paint2_linear_4013_8178)"/>
<defs>
<linearGradient id="paint0_linear_4013_8178" x1="0.00144577" y1="21.6555" x2="23.2005" y2="21.6555" gradientUnits="userSpaceOnUse">
<stop stop-color="#70FDF7"/>
<stop offset="0.325" stop-color="#747696"/>
<stop offset="0.68" stop-color="#BD5372"/>
<stop offset="1" stop-color="#F5A06C"/>
</linearGradient>
<linearGradient id="paint1_linear_4013_8178" x1="0.00144577" y1="15.8555" x2="23.2005" y2="15.8555" gradientUnits="userSpaceOnUse">
<stop stop-color="#70FDF7"/>
<stop offset="0.325" stop-color="#747696"/>
<stop offset="0.68" stop-color="#BD5372"/>
<stop offset="1" stop-color="#F5A06C"/>
</linearGradient>
<linearGradient id="paint2_linear_4013_8178" x1="0" y1="7.25057" x2="23.2019" y2="7.25057" gradientUnits="userSpaceOnUse">
<stop stop-color="#70FDF7"/>
<stop offset="0.325" stop-color="#747696"/>
<stop offset="0.68" stop-color="#BD5372"/>
<stop offset="1" stop-color="#F5A06C"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="18" height="18" viewBox="0 0 21 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 9.59652V6.40372C1 5.94773 1.18114 5.51041 1.50358 5.18797C1.82602 4.86553 2.26334 4.68439 2.71933 4.68439H5.21237C5.38045 4.68435 5.54483 4.63503 5.68518 4.54254L10.8432 1.1417C10.9728 1.05636 11.1231 1.00768 11.2781 1.00084C11.4331 0.993993 11.5871 1.02924 11.7237 1.10283C11.8603 1.17643 11.9745 1.28563 12.054 1.41885C12.1336 1.55207 12.1756 1.70435 12.1757 1.85952V14.1407C12.1756 14.2959 12.1336 14.4482 12.054 14.5814C11.9745 14.7146 11.8603 14.8238 11.7237 14.8974C11.5871 14.971 11.4331 15.0063 11.2781 14.9994C11.1231 14.9926 10.9728 14.9439 10.8432 14.8585L5.68518 11.4577C5.54483 11.3652 5.38045 11.3159 5.21237 11.3159H2.71933C2.26334 11.3159 1.82602 11.1347 1.50358 10.8123C1.18114 10.4898 1 10.0525 1 9.59652Z" stroke="#949494" stroke-width="1.4"/>
<path d="M15.1846 4.13149C15.1846 4.13149 16.4741 5.42099 16.4741 7.57016C16.4741 9.71932 15.1846 11.0088 15.1846 11.0088M17.7636 1.55249C17.7636 1.55249 19.9127 3.70166 19.9127 7.57016C19.9127 11.4387 17.7636 13.5878 17.7636 13.5878" stroke="#949494" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 1.14286C9.35622 1.14286 10.682 1.54502 11.8096 2.29849C12.9373 3.05197 13.8162 4.1229 14.3352 5.37588C14.8542 6.62886 14.99 8.0076 14.7254 9.33776C14.4608 10.6679 13.8077 11.8897 12.8487 12.8487C11.8897 13.8077 10.6679 14.4608 9.33776 14.7254C8.00761 14.99 6.62887 14.8542 5.37589 14.3352C4.12291 13.8162 3.05197 12.9373 2.2985 11.8096C1.54502 10.682 1.14286 9.35621 1.14286 8C1.14286 6.18137 1.86531 4.43723 3.15127 3.15127C4.43723 1.8653 6.18137 1.14286 8 1.14286ZM8 0C6.41775 0 4.87103 0.469192 3.55544 1.34824C2.23985 2.22729 1.21447 3.47672 0.608967 4.93853C0.00346626 6.40034 -0.15496 8.00887 0.153721 9.56072C0.462403 11.1126 1.22433 12.538 2.34315 13.6568C3.46197 14.7757 4.88743 15.5376 6.43928 15.8463C7.99113 16.155 9.59966 15.9965 11.0615 15.391C12.5233 14.7855 13.7727 13.7601 14.6518 12.4446C15.5308 11.129 16 9.58225 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34314C12.1566 0.842854 10.1217 0 8 0Z" fill="#949494"/>
<path d="M10.2857 5.71439V10.2858H5.71427V5.71439H10.2857ZM10.2857 4.57153H5.71427C5.41116 4.57153 5.12047 4.69194 4.90615 4.90627C4.69182 5.1206 4.57141 5.41129 4.57141 5.71439V10.2858C4.57141 10.5889 4.69182 10.8796 4.90615 11.0939C5.12047 11.3083 5.41116 11.4287 5.71427 11.4287H10.2857C10.5888 11.4287 10.8795 11.3083 11.0938 11.0939C11.3081 10.8796 11.4286 10.5889 11.4286 10.2858V5.71439C11.4286 5.41129 11.3081 5.1206 11.0938 4.90627C10.8795 4.69194 10.5888 4.57153 10.2857 4.57153Z" fill="#949494"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#747474"><path d="M.01 0h24v24h-24V0z" fill="none"/><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>

Before

Width:  |  Height:  |  Size: 386 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="10" height="25" viewBox="0 0 10 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 3.5C3.9 3.5 3 4.4 3 5.5C3 6.6 3.9 7.5 5 7.5C6.1 7.5 7 6.6 7 5.5C7 4.4 6.1 3.5 5 3.5ZM5 17.5C3.9 17.5 3 18.4 3 19.5C3 20.6 3.9 21.5 5 21.5C6.1 21.5 7 20.6 7 19.5C7 18.4 6.1 17.5 5 17.5ZM5 10.5C3.9 10.5 3 11.4 3 12.5C3 13.6 3.9 14.5 5 14.5C6.1 14.5 7 13.6 7 12.5C7 11.4 6.1 10.5 5 10.5Z" fill="#747474"/>
</svg>

Before

Width:  |  Height:  |  Size: 418 B

-6
View File
@@ -1,6 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.82047 15.07H3.23047C2.40047 15.07 1.73047 14.4 1.73047 13.57V10.98C1.73047 10.58 1.89047 10.2 2.17047 9.91999L4.38047 7.70999C4.58047 7.50999 4.89047 7.50999 5.09047 7.70999C5.29047 7.90999 5.29047 8.21999 5.09047 8.41999L2.88047 10.63C2.78626 10.7223 2.73235 10.8481 2.73047 10.98V13.57C2.73047 13.85 2.95047 14.07 3.23047 14.07H5.82047C5.95047 14.07 6.08047 14.02 6.17047 13.92L8.38047 11.71C8.58047 11.51 8.89047 11.51 9.09047 11.71C9.29047 11.91 9.29047 12.22 9.09047 12.42L6.88047 14.63C6.60047 14.91 6.22047 15.07 5.82047 15.07ZM12.8505 8.44999C12.7849 8.45079 12.7199 8.43786 12.6596 8.41202C12.5993 8.38619 12.5451 8.34803 12.5005 8.29999C12.3005 8.09999 12.3005 7.78999 12.5005 7.58999L14.6005 5.48999C14.7905 5.29999 14.7905 4.97999 14.6005 4.77999L12.0105 2.18999C11.915 2.09844 11.7878 2.04732 11.6555 2.04732C11.5232 2.04732 11.396 2.09844 11.3005 2.18999L9.14047 4.34999C8.94047 4.54999 8.63047 4.54999 8.43047 4.34999C8.23047 4.14999 8.23047 3.83999 8.43047 3.63999L10.5905 1.47999C11.1605 0.90999 12.1405 0.90999 12.7105 1.47999L15.3005 4.06999C15.8805 4.64999 15.8805 5.60999 15.3005 6.18999L13.2005 8.28999C13.1005 8.38999 12.9705 8.43999 12.8505 8.43999V8.44999Z" fill="#5D5D5D"/>
<path d="M11.6305 15.0605C11.2305 15.0605 10.8505 14.9005 10.5705 14.6205L8.38047 12.4305C8.18047 12.2305 8.18047 11.9205 8.38047 11.7205C8.58047 11.5205 8.89047 11.5205 9.09047 11.7205L11.2805 13.9105C11.4705 14.1005 11.8005 14.1005 11.9905 13.9105L14.5805 11.3205C14.7705 11.1305 14.7705 10.8105 14.5805 10.6105L12.3905 8.42055C12.1905 8.22055 12.1905 7.91055 12.3905 7.71055C12.5905 7.51055 12.9005 7.51055 13.1005 7.71055L15.2905 9.90055C15.8705 10.4805 15.8705 11.4405 15.2905 12.0205L12.7005 14.6105C12.4205 14.8905 12.0405 15.0505 11.6405 15.0505L11.6305 15.0605Z" fill="#5D5D5D"/>
<path d="M8.98125 12.8195C8.91567 12.8203 8.85066 12.8073 8.79038 12.7815C8.7301 12.7557 8.6759 12.7175 8.63125 12.6695L2.09125 6.13945C1.51125 5.55945 1.51125 4.59945 2.09125 4.01945L4.68125 1.42945C5.25125 0.859453 6.23125 0.859453 6.80125 1.42945L13.3412 7.96945C13.5412 8.16945 13.5412 8.47945 13.3412 8.67945C13.1412 8.87945 12.8312 8.87945 12.6312 8.67945L6.09125 2.13945C5.99574 2.0479 5.86855 1.99678 5.73625 1.99678C5.60395 1.99678 5.47676 2.0479 5.38125 2.13945L2.79125 4.72945C2.60125 4.91945 2.60125 5.23945 2.79125 5.43945L9.33125 11.9795C9.53125 12.1795 9.53125 12.4895 9.33125 12.6895C9.23125 12.7895 9.10125 12.8395 8.98125 12.8395V12.8195Z" fill="#5D5D5D"/>
<path d="M6.28906 6.01055C6.22349 6.01135 6.15847 5.99841 6.09819 5.97258C6.03791 5.94675 5.98371 5.90858 5.93906 5.86055C5.73906 5.66055 5.73906 5.35055 5.93906 5.15055L7.87906 3.21055C8.07906 3.01055 8.38906 3.01055 8.58906 3.21055C8.78906 3.41055 8.78906 3.72055 8.58906 3.92055L6.64906 5.86055C6.54906 5.96055 6.41906 6.01055 6.29906 6.01055H6.28906ZM8.31906 8.04055C8.25349 8.04135 8.18847 8.02841 8.12819 8.00258C8.06791 7.97675 8.01371 7.93858 7.96906 7.89055C7.76906 7.69055 7.76906 7.38055 7.96906 7.18055L9.90906 5.24055C10.1091 5.04055 10.4191 5.04055 10.6191 5.24055C10.8191 5.44055 10.8191 5.75055 10.6191 5.95055L8.67906 7.89055C8.57906 7.99055 8.44906 8.04055 8.32906 8.04055H8.31906ZM10.3491 10.0705C10.2835 10.0713 10.2185 10.0584 10.1582 10.0326C10.0979 10.0067 10.0437 9.96858 9.99906 9.92055C9.79906 9.72055 9.79906 9.41055 9.99906 9.21055L11.9391 7.27055C12.1391 7.07055 12.4491 7.07055 12.6491 7.27055C12.8491 7.47055 12.8491 7.78055 12.6491 7.98055L10.7091 9.92055C10.6091 10.0205 10.4791 10.0705 10.3591 10.0705H10.3491Z" fill="#5D5D5D"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="15" viewBox="0 0 12 15" fill="none">
<path d="M0.857143 13.3333C0.857143 13.7754 1.03775 14.1993 1.35925 14.5118C1.68074 14.8244 2.11677 15 2.57143 15H9.42857C9.88323 15 10.3193 14.8244 10.6408 14.5118C10.9622 14.1993 11.1429 13.7754 11.1429 13.3333V3.33333H0.857143V13.3333ZM2.57143 5H9.42857V13.3333H2.57143V5ZM9 0.833333L8.14286 0H3.85714L3 0.833333H0V2.5H12V0.833333H9Z" fill="#747474"/>
</svg>

Before

Width:  |  Height:  |  Size: 459 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin-off-icon lucide-pin-off"><path d="M12 17v5"/><path d="M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89"/><path d="m2 2 20 20"/><path d="M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11"/></svg>

Before

Width:  |  Height:  |  Size: 416 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.35 6.04C18.67 2.59 15.64 0 12 0C9.11 0 6.6 1.64 5.35 4.04C2.34 4.36 0 6.91 0 10C0 13.31 2.69 16 6 16H19C21.76 16 24 13.76 24 11C24 8.36 21.95 6.22 19.35 6.04ZM14 9V13H10V9H7L12 4L17 9H14Z" fill="gray" fill-opacity="0.85"/>
</svg>

Before

Width:  |  Height:  |  Size: 339 B

-27
View File
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 855.6 867.3" style="enable-background:new 0 0 855.6 867.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<g id="Layer_2_00000062898455470481303740000016245162613792141746_">
<g id="avatar">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="281.0639" y1="214.2932" x2="737.1139" y2="670.3433" gradientTransform="matrix(1 0 0 -1 0 874.2527)">
<stop offset="0" style="stop-color:#526676"/>
<stop offset="1" style="stop-color:#6B9DBF"/>
</linearGradient>
<path class="st0" d="M725.1,663.3c-0.8,11.7-8,19.2-17.7,25c-25,14.9-51.5,26.3-79.2,35.1c-70.2,22.4-142.4,30.9-215.8,29.8
c-62.5-0.9-123.7-9.8-183.4-28.1c-32-9.8-62.8-22.3-91.7-39.3c-8.9-5.3-13.8-12.1-15-22.5c-2.5-21.2-2.4-42.6,0.3-63.8
c3.1-24,18-40.7,35.5-55.3c19.7-16.5,43.2-26,67.6-33c28.6-8.3,57.4-15.8,86-23.8c9.8,25.7,22.8,47,39,63.5
c22.4,22.8,50.2,35.3,78.4,35.3c41.9,0,80.6-26.8,103.4-71.6c4.2-8.3,8-16.8,11.3-25.5c7.4,2.5,14.9,4.6,22.5,6.5
c16.2,4.1,32.5,7.3,48.6,11.6c26.3,7.1,51.6,16.5,73.8,32.9c24.1,17.8,37.6,41.5,38.6,71.3C727.8,628.7,726.2,646,725.1,663.3z
M317.7,351c2.7,2.5,4.7,5.7,5.8,9.3c3,10.3,4.8,21.1,8.2,31.3c8.2,24.8,20.8,47.4,41,64.4c18.7,15.8,40.5,23.3,61.9,23.3
c31.7,0,62.5-16.5,81.5-47.2c13.6-22.1,21.8-45.9,26.2-71.3c0.6-3.4,1.7-8,4.1-9.4c21.5-13,25.1-34,25.7-56.2
c0.2-8.3-1.7-9.6-8-10.3c0-17,0.6-33.8-0.2-50.6c-0.4-10.4-2.1-20.6-5-30.6c-8.3-27.4-25.6-46.7-53.6-55.1
c-4.3-1.3-10.5,0.2-12.9-3.9c-11-19.4-31.6-25.6-54.4-25.6c-19.3,0-40.2,4.4-58.6,9.2c-21.1,5.4-40.2,14.6-54.8,31.5
c-20.4,23.8-26.2,52.1-24.7,82.4c0.7,14.6,2.9,29.1,4.4,44.1c-10.9,0.9-11.2,4.8-11,13.6C293.9,321.5,301.4,336.5,317.7,351z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 1.83989C16.5202 2.71758 17.7826 3.97997 18.6603 5.50017C19.538 7.02038 20 8.74483 20 10.5002C20 12.2556 19.5379 13.98 18.6602 15.5002C17.7825 17.0204 16.5201 18.2828 14.9999 19.1605C13.4797 20.0381 11.7552 20.5002 9.99984 20.5001C8.24446 20.5001 6.52002 20.038 4.99984 19.1603C3.47965 18.2826 2.21729 17.0202 1.33963 15.5C0.46198 13.9797 -4.45897e-05 12.2553 3.22765e-09 10.4999L0.00500012 10.1759C0.0610032 8.44888 0.563548 6.76585 1.46364 5.29089C2.36373 3.81592 3.63065 2.59934 5.14089 1.75977C6.65113 0.920205 8.35315 0.486289 10.081 0.50033C11.8089 0.514371 13.5036 0.97589 15 1.83989ZM10 13.4999C9.73478 13.4999 9.48043 13.6052 9.29289 13.7928C9.10536 13.9803 9 14.2347 9 14.4999V14.5099C9 14.7751 9.10536 15.0295 9.29289 15.217C9.48043 15.4045 9.73478 15.5099 10 15.5099C10.2652 15.5099 10.5196 15.4045 10.7071 15.217C10.8946 15.0295 11 14.7751 11 14.5099V14.4999C11 14.2347 10.8946 13.9803 10.7071 13.7928C10.5196 13.6052 10.2652 13.4999 10 13.4999ZM10 6.49989C9.73478 6.49989 9.48043 6.60525 9.29289 6.79279C9.10536 6.98032 9 7.23468 9 7.49989V11.4999C9 11.7651 9.10536 12.0195 9.29289 12.207C9.48043 12.3945 9.73478 12.4999 10 12.4999C10.2652 12.4999 10.5196 12.3945 10.7071 12.207C10.8946 12.0195 11 11.7651 11 11.4999V7.49989C11 7.23468 10.8946 6.98032 10.7071 6.79279C10.5196 6.60525 10.2652 6.49989 10 6.49989Z" fill="#EA4335"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

+2 -2
View File
@@ -60,7 +60,7 @@ export default function ActionButtons({
onClick={newChat}
className="lg:hidden"
>
<Plus className="size-5" strokeWidth={1.75} aria-label="NewChat" />
<Plus className="size-5" aria-label="NewChat" />
</Button>
)}
@@ -74,7 +74,7 @@ export default function ActionButtons({
title={t('actionButtons.share')}
onClick={() => setShareModalState(true)}
>
<Share className="size-5" strokeWidth={1.75} aria-label="share" />
<Share className="size-5" aria-label="share" />
</Button>
{isShareModalOpen && (
<ShareConversationModal
+1 -1
View File
@@ -5,7 +5,7 @@ import {
selectIsAdmin,
selectRolesResolved,
} from '../preferences/preferenceSlice';
import Spinner from './Spinner';
import { Spinner } from '@/components/ui/spinner';
/**
* Cosmetic route guard — NOT a security boundary. The server enforces admin
+1 -1
View File
@@ -17,7 +17,7 @@ import { selectToken } from '../preferences/preferenceSlice';
import { isDocumentArtifact, type DocumentArtifact } from './artifactViewUtils';
import CopyButton from './CopyButton';
import DocumentArtifactView from './DocumentArtifactView';
import Spinner from './Spinner';
import { Spinner } from '@/components/ui/spinner';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { Sheet, SheetContent } from './ui/sheet';
+14 -18
View File
@@ -1,4 +1,4 @@
import { Search as SearchIcon } from 'lucide-react';
import { ArrowLeft, File, Folder, Search as SearchIcon } from 'lucide-react';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
@@ -6,9 +6,6 @@ import { useSelector } from 'react-redux';
import { cn } from '@/lib/utils';
import userService from '../api/services/userService';
import ArrowLeft from '../assets/arrow-left.svg';
import FileIcon from '../assets/file.svg';
import FolderIcon from '../assets/folder.svg';
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import NoFilesIcon from '../assets/no-files.svg';
import {
@@ -326,7 +323,7 @@ const Chunks: React.FC<ChunksProps> = ({
: handleGoBack
}
>
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
<ArrowLeft />
</Button>
<div className="flex flex-wrap items-center">
@@ -510,7 +507,6 @@ const Chunks: React.FC<ChunksProps> = ({
leftIcon={
<SearchIcon
className="text-muted-foreground size-4"
strokeWidth={1.75}
aria-label="Search"
/>
}
@@ -529,17 +525,17 @@ const Chunks: React.FC<ChunksProps> = ({
key={index}
title={result.path}
onClick={() => handleSearchResultClick(result)}
className={`hover:bg-muted dark:hover:bg-muted flex cursor-pointer items-center px-3 py-2 ${
className={`hover:bg-muted flex cursor-pointer items-center px-3 py-2 ${
index !== fileSearchResults.length - 1
? 'border-border dark:border-border border-b'
? 'border-border border-b'
: ''
}`}
>
<img
src={result.isFile ? FileIcon : FolderIcon}
alt={result.isFile ? 'File' : 'Folder'}
className="mr-2 h-4 w-4 shrink-0"
/>
{result.isFile ? (
<File className="text-muted-foreground mr-2 size-4 shrink-0" />
) : (
<Folder className="text-primary mr-2 size-4 shrink-0" />
)}
<span className="truncate text-sm">
{result.name ||
result.path.split('/').pop() ||
@@ -568,7 +564,7 @@ const Chunks: React.FC<ChunksProps> = ({
{!editingChunk && !isAddingChunk ? (
<>
<div className="mb-3 flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div className="border-border dark:border-border flex h-[38px] w-full flex-1 items-center overflow-hidden rounded-md border">
<div className="border-border flex h-[38px] w-full flex-1 items-center overflow-hidden rounded-md border">
<div className="text-foreground flex h-full items-center px-4 font-medium whitespace-nowrap">
{totalChunks > 999999
? `${(totalChunks / 1000000).toFixed(2)}M`
@@ -577,7 +573,7 @@ const Chunks: React.FC<ChunksProps> = ({
: totalChunks}{' '}
{t('settings.sources.chunks')}
</div>
<div className="bg-border dark:bg-border h-full w-px"></div>
<div className="bg-border h-full w-px"></div>
<div className="h-full flex-1 px-3 py-2">
<Input
type="text"
@@ -622,7 +618,7 @@ const Chunks: React.FC<ChunksProps> = ({
filteredChunks.map((chunk, index) => (
<div
key={index}
className="border-border dark:border-border relative flex h-[197px] w-full max-w-[487px] transform cursor-pointer flex-col justify-between overflow-hidden rounded-md border transition-transform duration-200 hover:scale-105"
className="border-border relative flex h-[197px] w-full max-w-[487px] transform cursor-pointer flex-col justify-between overflow-hidden rounded-md border transition-transform duration-200 hover:scale-105"
onClick={() => {
setEditingChunk(chunk);
setEditingTitle(chunk.metadata?.title || '');
@@ -650,7 +646,7 @@ const Chunks: React.FC<ChunksProps> = ({
</>
) : isAddingChunk ? (
<div className="w-full">
<div className="border-border dark:border-border relative overflow-hidden rounded-lg border">
<div className="border-border relative overflow-hidden rounded-lg border">
<LineNumberedTextarea
value={editingText}
onChange={setEditingText}
@@ -662,7 +658,7 @@ const Chunks: React.FC<ChunksProps> = ({
) : (
editingChunk && (
<div className="w-full">
<div className="border-border dark:border-border relative flex w-full flex-col overflow-hidden rounded-md border">
<div className="border-border relative flex w-full flex-col overflow-hidden rounded-md border">
<div className="border-border bg-muted dark:border-border dark:bg-card flex w-full items-center justify-between border-b px-4 py-3">
<div className="text-muted-foreground text-sm">
{formatChunkTokens(editingChunk.metadata)}{' '}
+18 -28
View File
@@ -1,8 +1,8 @@
import { useMemo } from 'react';
import { ConfigRequirements } from '../modals/types';
import { FormField } from './ui/form-field';
import { Input } from './ui/input';
import { Label } from './ui/label';
import {
Select,
SelectContent,
@@ -59,26 +59,22 @@ export default function ConfigFields({
const hasEncrypted =
isEditing && spec.secret && hasEncryptedCredentials;
const placeholder = hasEncrypted ? '••••••••' : '';
const hasError = !!errors[key];
const error = errors[key] || undefined;
if (spec.enum) {
return (
<div key={key} className="flex flex-col gap-1.5">
<Label htmlFor={key}>
{spec.label || key}
{spec.required && <span className="text-destructive">*</span>}
</Label>
<FormField
key={key}
id={key}
label={spec.label || key}
required={!!spec.required}
error={error}
>
<Select
value={value || spec.default || ''}
onValueChange={(v) => onChange(key, v)}
>
<SelectTrigger
id={key}
variant="ghost"
size="lg"
aria-invalid={hasError || undefined}
className="w-full"
>
<SelectTrigger variant="ghost" size="lg" className="w-full">
<SelectValue placeholder={spec.label || key} />
</SelectTrigger>
<SelectContent>
@@ -90,19 +86,17 @@ export default function ConfigFields({
))}
</SelectContent>
</Select>
{hasError && (
<p className="text-destructive text-xs">{errors[key]}</p>
)}
</div>
</FormField>
);
}
return (
<div key={key} className="flex flex-col gap-1.5">
<Label htmlFor={key}>
{spec.label || key}
{spec.required && <span className="text-destructive">*</span>}
</Label>
<FormField
key={key}
label={spec.label || key}
required={!!spec.required}
error={error}
>
<Input
id={key}
type={
@@ -130,12 +124,8 @@ export default function ConfigFields({
max={
spec.type === 'number' && key === 'timeout' ? 300 : undefined
}
aria-invalid={hasError || undefined}
/>
{hasError && (
<p className="text-destructive text-xs">{errors[key]}</p>
)}
</div>
</FormField>
);
})}
</div>
+15 -7
View File
@@ -1,10 +1,10 @@
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Check, RefreshCw } from 'lucide-react';
import userService from '../api/services/userService';
import CheckmarkIcon from '../assets/checkMark2.svg';
import SyncIcon from '../assets/sync.svg';
import { Spinner } from '@/components/ui/spinner';
import ConfirmationModal from '../modals/ConfirmationModal';
import { ActiveState } from '../models/misc';
import { selectToken } from '../preferences/preferenceSlice';
@@ -122,11 +122,19 @@ const ConnectorTree: React.FC<ConnectorTreeProps> = ({
: t('settings.sources.sync')
}
>
<img
src={syncDone ? CheckmarkIcon : SyncIcon}
alt={t('settings.sources.sync')}
className={`mr-2 h-4 w-4 brightness-0 invert filter ${isSyncing ? 'animate-spin' : ''}`}
/>
{syncDone ? (
<Check className="mr-2 size-4" />
) : isSyncing ? (
// The busy state shows its percentage, so it keeps the label and
// draws the app's ring spinner at icon size (DESIGN.md, Button).
<Spinner
size="sm"
className="mr-2 size-4"
label={t('settings.sources.syncing')}
/>
) : (
<RefreshCw className="mr-2 size-4" />
)}
{isSyncing
? `${syncProgress}%`
: syncDone
@@ -7,7 +7,7 @@ import { useDarkTheme } from '../hooks';
import { selectToken } from '../preferences/preferenceSlice';
import MarkdownPreview from './MarkdownPreview';
import { renderMermaidDiagram } from './mermaidSecurity';
import Spinner from './Spinner';
import { Spinner } from '@/components/ui/spinner';
import {
buildPreviewDocument,
bytesPreviewModeForMime,
@@ -141,13 +141,9 @@ function DownloadCard({
variant="outline"
size="sm"
onClick={onDownload}
disabled={downloading}
loading={downloading}
>
{downloading ? (
<Spinner size="small" />
) : (
<Download className="h-4 w-4" />
)}
<Download className="h-4 w-4" />
Download
</Button>
</div>
@@ -396,13 +392,9 @@ export default function DocumentArtifactView({
variant="outline"
size="sm"
onClick={handleDownload}
disabled={downloading}
loading={downloading}
>
{downloading ? (
<Spinner size="small" />
) : (
<Download className="h-4 w-4" />
)}
<Download className="h-4 w-4" />
Download
</Button>
@@ -412,13 +404,9 @@ export default function DocumentArtifactView({
variant="ghost"
size="sm"
onClick={handleRestore}
disabled={restoring}
loading={restoring}
>
{restoring ? (
<Spinner size="small" />
) : (
<RotateCcw className="h-4 w-4" />
)}
<RotateCcw className="h-4 w-4" />
Restore
</Button>
)}
@@ -73,4 +73,20 @@ describe('DocumentPagination rows-per-page', () => {
await act(async () => option!.click());
expect(onChange).toHaveBeenCalledWith(50);
});
it('labels the page buttons by their action', async () => {
await render();
const labels = Array.from(
container.querySelectorAll<HTMLButtonElement>('button[aria-label]'),
).map((b) => b.getAttribute('aria-label'));
expect(labels).toEqual(
expect.arrayContaining([
'pagination.firstPage',
'pagination.previousPage',
'pagination.nextPage',
'pagination.lastPage',
]),
);
expect(container.querySelector('img')).toBeNull();
});
});
+14 -24
View File
@@ -1,9 +1,11 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import SingleArrowLeft from '../assets/single-left-arrow.svg';
import SingleArrowRight from '../assets/single-right-arrow.svg';
import DoubleArrowLeft from '../assets/double-arrow-left.svg';
import DoubleArrowRight from '../assets/double-arrow-right.svg';
import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from 'lucide-react';
import { Button } from './ui/button';
import {
Select,
@@ -88,12 +90,9 @@ const Pagination: React.FC<PaginationProps> = ({
size="icon-xs"
onClick={handleFirstPage}
disabled={currentPage === 1}
aria-label={t('pagination.firstPage')}
>
<img
src={DoubleArrowLeft}
alt={t('pagination.firstPage')}
className="dark:brightness-200 dark:invert dark:sepia"
/>
<ChevronsLeft />
</Button>
<Button
type="button"
@@ -101,12 +100,9 @@ const Pagination: React.FC<PaginationProps> = ({
size="icon-xs"
onClick={handlePreviousPage}
disabled={currentPage === 1}
aria-label={t('pagination.previousPage')}
>
<img
src={SingleArrowLeft}
alt={t('pagination.previousPage')}
className="dark:brightness-200 dark:invert dark:sepia"
/>
<ChevronLeft />
</Button>
<Button
type="button"
@@ -114,12 +110,9 @@ const Pagination: React.FC<PaginationProps> = ({
size="icon-xs"
onClick={handleNextPage}
disabled={currentPage === totalPages}
aria-label={t('pagination.nextPage')}
>
<img
src={SingleArrowRight}
alt={t('pagination.nextPage')}
className="dark:brightness-200 dark:invert dark:sepia"
/>
<ChevronRight />
</Button>
<Button
type="button"
@@ -127,12 +120,9 @@ const Pagination: React.FC<PaginationProps> = ({
size="icon-xs"
onClick={handleLastPage}
disabled={currentPage === totalPages}
aria-label={t('pagination.lastPage')}
>
<img
src={DoubleArrowRight}
alt={t('pagination.lastPage')}
className="dark:brightness-200 dark:invert dark:sepia"
/>
<ChevronsRight />
</Button>
</div>
</div>
+9 -3
View File
@@ -86,15 +86,21 @@ describe('FilePicker', () => {
it('renders the drive switch as underline tabs with the active one marked', async () => {
await renderSharePoint();
const list = container.querySelector('[data-slot="tabs-list"]')!;
expect(list.getAttribute('role')).toBe('tablist');
expect(list.getAttribute('data-variant')).toBe('underline');
const tabs = Array.from(
container.querySelectorAll('[data-slot="button"][data-variant="tab"]'),
list.querySelectorAll('[data-slot="tabs-trigger"][role="tab"]'),
);
expect(tabs.map((tab) => tab.textContent)).toEqual([
'filePicker.myFiles',
'filePicker.sharedWithMe',
]);
expect(tabs[0].getAttribute('data-active')).toBe('true');
expect(tabs[1].getAttribute('data-active')).not.toBe('true');
expect(tabs[0].getAttribute('aria-selected')).toBe('true');
expect(tabs[1].getAttribute('aria-selected')).toBe('false');
// The file list below is the active tab's panel.
const panel = container.querySelector('[role="tabpanel"]')!;
expect(panel.id).toBe(tabs[0].getAttribute('aria-controls'));
});
it('shows the folder trail as a breadcrumb with the current folder as the page', async () => {
+206 -186
View File
@@ -1,4 +1,10 @@
import { Search as SearchIcon, TriangleAlert } from 'lucide-react';
import {
Check,
File,
Folder,
Search as SearchIcon,
TriangleAlert,
} from 'lucide-react';
import React, {
Fragment,
useState,
@@ -16,9 +22,6 @@ import {
removeSessionToken,
} from '../utils/providerUtils';
import ConnectorAuth from '../components/ConnectorAuth';
import FileIcon from '../assets/file.svg';
import FolderIcon from '../assets/folder.svg';
import CheckIcon from '../assets/checkmark.svg';
import { Alert, AlertDescription } from './ui/alert';
import {
Breadcrumb,
@@ -28,7 +31,6 @@ import {
BreadcrumbPage,
BreadcrumbSeparator,
} from './ui/breadcrumb';
import { Button } from './ui/button';
import { Input } from './ui/input';
import {
Table,
@@ -39,6 +41,7 @@ import {
TableHeader,
TableCell,
} from './ui/table';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { useDebouncedCallback } from '../hooks';
interface CloudFile {
@@ -50,6 +53,26 @@ interface CloudFile {
isFolder?: boolean;
}
/**
* The file browser under the drive tabs. With the tabs shown it is the active
* tab's panel; without them (one drive) it renders as is.
*/
function DrivePanel({
tabbed,
value,
children,
}: {
tabbed: boolean;
value: string;
children: React.ReactNode;
}) {
return tabbed ? (
<TabsContent value={value}>{children}</TabsContent>
) : (
<>{children}</>
);
}
interface CloudFilePickerProps {
onSelectionChange: (
selectedFileIds: string[],
@@ -354,6 +377,8 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
}
};
const showDriveTabs = provider === 'share_point' && allowsSharedContent;
const handleTabChange = (tab: 'my_files' | 'shared') => {
if (tab === activeTab) return;
setActiveTab(tab);
@@ -448,115 +473,182 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
{isConnected && (
<div className="border-border mt-3 overflow-hidden rounded-lg border">
<div className="border-border rounded-t-lg">
{provider === 'share_point' && allowsSharedContent && (
<div className="border-border flex border-b">
<Button
type="button"
variant="tab"
data-active={activeTab === 'my_files'}
onClick={() => handleTabChange('my_files')}
>
<Tabs
value={activeTab}
onValueChange={(value) =>
handleTabChange(value as 'my_files' | 'shared')
}
>
{showDriveTabs && (
<TabsList variant="underline">
<TabsTrigger variant="underline" value="my_files">
{t('filePicker.myFiles')}
</Button>
<Button
type="button"
variant="tab"
data-active={activeTab === 'shared'}
onClick={() => handleTabChange('shared')}
>
</TabsTrigger>
<TabsTrigger variant="underline" value="shared">
{t('filePicker.sharedWithMe')}
</Button>
</div>
</TabsTrigger>
</TabsList>
)}
<div className="bg-muted rounded-t-lg px-4 pt-4">
<Breadcrumb className="mb-2 min-w-0">
<BreadcrumbList className="flex-nowrap">
{folderPath.map((path, index) => (
<Fragment key={path.id || 'root'}>
{index > 0 && <BreadcrumbSeparator />}
{index === folderPath.length - 1 ? (
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage
title={path.name}
className="max-w-[32ch]"
>
{path.name}
</BreadcrumbPage>
</BreadcrumbItem>
) : (
<BreadcrumbItem>
<BreadcrumbLink asChild>
<button
type="button"
onClick={() => navigateBack(index)}
<DrivePanel tabbed={showDriveTabs} value={activeTab}>
<div className="bg-muted rounded-t-lg px-4 pt-4">
<Breadcrumb className="mb-2 min-w-0">
<BreadcrumbList className="flex-nowrap">
{folderPath.map((path, index) => (
<Fragment key={path.id || 'root'}>
{index > 0 && <BreadcrumbSeparator />}
{index === folderPath.length - 1 ? (
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage
title={path.name}
className="max-w-[32ch]"
>
{path.name}
</button>
</BreadcrumbLink>
</BreadcrumbItem>
)}
</Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
</BreadcrumbPage>
</BreadcrumbItem>
) : (
<BreadcrumbItem>
<BreadcrumbLink asChild>
<button
type="button"
onClick={() => navigateBack(index)}
>
{path.name}
</button>
</BreadcrumbLink>
</BreadcrumbItem>
)}
</Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
<div className="text-muted-foreground mb-3 text-sm">
Select Files from {getProviderConfig(provider).displayName}
<div className="text-muted-foreground mb-3 text-sm">
Select Files from {getProviderConfig(provider).displayName}
</div>
<div className="mb-3 max-w-md">
<Input
type="text"
label={t('filePicker.searchPlaceholder')}
value={searchQuery}
onChange={(e) => handleSearchChange(e.target.value)}
labelSurface="muted"
leftIcon={
<SearchIcon className="text-muted-foreground size-4" />
}
/>
</div>
{/* Selected Files Message */}
<div className="text-muted-foreground pb-3 text-sm">
{t('filePicker.itemsSelected', {
count: selectedFiles.length + selectedFolders.length,
})}
</div>
</div>
<div className="mb-3 max-w-md">
<Input
type="text"
label={t('filePicker.searchPlaceholder')}
value={searchQuery}
onChange={(e) => handleSearchChange(e.target.value)}
labelSurface="muted"
leftIcon={
<SearchIcon
className="text-muted-foreground size-4"
strokeWidth={1.75}
/>
}
/>
</div>
{/* Selected Files Message */}
<div className="text-muted-foreground pb-3 text-sm">
{t('filePicker.itemsSelected', {
count: selectedFiles.length + selectedFolders.length,
})}
</div>
</div>
<div className="border-border scrollbar-overlay h-72 border-t">
<TableContainer
ref={scrollContainerRef}
height="288px"
className="md:w-4xl lg:w-5xl"
bordered={false}
>
{
<>
<Table minWidth="1200px">
<TableHead>
<TableRow>
<TableHeader width="40px"></TableHeader>
<TableHeader width="60%">
{t('filePicker.name')}
</TableHeader>
<TableHeader width="20%">
{t('filePicker.lastModified')}
</TableHeader>
<TableHeader width="20%">
{t('filePicker.size')}
</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{isLoading && files.length === 0
? Array.from({ length: 5 }).map((_, i) => (
<TableRow key={`skeleton-${i}`}>
<div className="border-border scrollbar-overlay h-72 border-t">
<TableContainer
ref={scrollContainerRef}
height="288px"
className="md:w-4xl lg:w-5xl"
bordered={false}
>
{
<>
<Table minWidth="1200px">
<TableHead>
<TableRow>
<TableHeader width="40px"></TableHeader>
<TableHeader width="60%">
{t('filePicker.name')}
</TableHeader>
<TableHeader width="20%">
{t('filePicker.lastModified')}
</TableHeader>
<TableHeader width="20%">
{t('filePicker.size')}
</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{isLoading && files.length === 0
? Array.from({ length: 5 }).map((_, i) => (
<TableRow key={`skeleton-${i}`}>
<TableCell width="40px" align="center">
<div className="bg-muted mx-auto h-5 w-5 animate-pulse rounded" />
</TableCell>
<TableCell>
<div className="bg-muted h-4 w-48 animate-pulse rounded" />
</TableCell>
<TableCell>
<div className="bg-muted h-4 w-24 animate-pulse rounded" />
</TableCell>
<TableCell>
<div className="bg-muted h-4 w-16 animate-pulse rounded" />
</TableCell>
</TableRow>
))
: files.map((file, index) => (
<TableRow
key={`${file.id}-${index}`}
onClick={() => {
if (isFolder(file)) {
handleFolderClick(file.id, file.name);
} else {
handleFileSelect(file.id, false);
}
}}
>
<TableCell width="40px" align="center">
<div
className="border-border mx-auto flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center border text-sm"
onClick={(e) => {
e.stopPropagation();
handleFileSelect(
file.id,
isFolder(file),
);
}}
>
{(isFolder(file)
? selectedFolders
: selectedFiles
).includes(file.id) && (
<Check
role="img"
aria-label="Selected"
className="text-success size-4"
/>
)}
</div>
</TableCell>
<TableCell>
<div className="flex min-w-0 items-center gap-3">
<div className="shrink-0">
{isFolder(file) ? (
<Folder className="text-primary size-6" />
) : (
<File className="text-muted-foreground size-6" />
)}
</div>
<span className="truncate">
{file.name}
</span>
</div>
</TableCell>
<TableCell className="text-xs">
{formatDateTime(file.modifiedTime)}
</TableCell>
<TableCell className="text-xs">
{file.size ? formatBytes(file.size) : '-'}
</TableCell>
</TableRow>
))}
{isLoading &&
files.length > 0 &&
Array.from({ length: 3 }).map((_, i) => (
<TableRow key={`load-more-skeleton-${i}`}>
<TableCell width="40px" align="center">
<div className="bg-muted mx-auto h-5 w-5 animate-pulse rounded" />
</TableCell>
@@ -570,87 +662,15 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
<div className="bg-muted h-4 w-16 animate-pulse rounded" />
</TableCell>
</TableRow>
))
: files.map((file, index) => (
<TableRow
key={`${file.id}-${index}`}
onClick={() => {
if (isFolder(file)) {
handleFolderClick(file.id, file.name);
} else {
handleFileSelect(file.id, false);
}
}}
>
<TableCell width="40px" align="center">
<div
className="border-border mx-auto flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center border text-sm"
onClick={(e) => {
e.stopPropagation();
handleFileSelect(file.id, isFolder(file));
}}
>
{(isFolder(file)
? selectedFolders
: selectedFiles
).includes(file.id) && (
<img
src={CheckIcon}
alt="Selected"
className="h-4 w-4"
/>
)}
</div>
</TableCell>
<TableCell>
<div className="flex min-w-0 items-center gap-3">
<div className="shrink-0">
<img
src={
isFolder(file) ? FolderIcon : FileIcon
}
alt={isFolder(file) ? 'Folder' : 'File'}
className="h-6 w-6"
/>
</div>
<span className="truncate">
{file.name}
</span>
</div>
</TableCell>
<TableCell className="text-xs">
{formatDateTime(file.modifiedTime)}
</TableCell>
<TableCell className="text-xs">
{file.size ? formatBytes(file.size) : '-'}
</TableCell>
</TableRow>
))}
{isLoading &&
files.length > 0 &&
Array.from({ length: 3 }).map((_, i) => (
<TableRow key={`load-more-skeleton-${i}`}>
<TableCell width="40px" align="center">
<div className="bg-muted mx-auto h-5 w-5 animate-pulse rounded" />
</TableCell>
<TableCell>
<div className="bg-muted h-4 w-48 animate-pulse rounded" />
</TableCell>
<TableCell>
<div className="bg-muted h-4 w-24 animate-pulse rounded" />
</TableCell>
<TableCell>
<div className="bg-muted h-4 w-16 animate-pulse rounded" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
}
</TableContainer>
</div>
</div>
</TableBody>
</Table>
</>
}
</TableContainer>
</div>
</DrivePanel>
</Tabs>
</div>
)}
</div>
+7 -15
View File
@@ -1,17 +1,14 @@
import React, { SyntheticEvent, useRef, useState } from 'react';
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import userService from '../api/services/userService';
import Trash from '../assets/red-trash.svg';
import { Trash2 } from 'lucide-react';
import { SOURCE_FILE_TREE_ACCEPT_ATTR } from '../constants/fileUpload';
import ConfirmationModal from '../modals/ConfirmationModal';
import { selectToken } from '../preferences/preferenceSlice';
import TreeBrowser from './tree/TreeBrowser';
import type {
RowMenuContext,
TreeBrowserController,
TreeMenuOption,
} from './tree/types';
import type { MenuOption } from './ui/dropdown-menu';
import type { RowMenuContext, TreeBrowserController } from './tree/types';
import { useReingestSseWaiter } from './tree/useReingestWait';
type QueuedOperation = {
@@ -211,18 +208,13 @@ const FileTree: React.FC<FileTreeProps> = ({
name,
isFile,
defaultViewOption,
}: RowMenuContext): TreeMenuOption[] => {
}: RowMenuContext): MenuOption[] => {
return [
defaultViewOption,
{
icon: Trash,
icon: Trash2,
label: t('convTile.delete'),
onClick: (event: SyntheticEvent) => {
event.stopPropagation();
confirmDeleteItem(name, isFile);
},
iconWidth: 18,
iconHeight: 18,
onClick: () => confirmDeleteItem(name, isFile),
variant: 'destructive',
},
];
+3 -3
View File
@@ -1,10 +1,9 @@
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { FileRejection } from 'react-dropzone';
import { ImageUp } from 'lucide-react';
import { ImageUp, X } from 'lucide-react';
import { Dropzone } from '@/components/ui/dropzone';
import Cross from '../assets/cross.svg';
type UploadTextSegment = {
text: string;
@@ -157,8 +156,9 @@ export const FileUpload = ({
handleRemove();
}}
className="bg-primary hover:bg-primary/90 absolute -top-2 -right-2 rounded-full p-1 transition-colors"
aria-label="remove"
>
<img src={Cross} alt="remove" className="h-3 w-3" />
<X className="text-primary-foreground size-3" />
</button>
</div>
);
@@ -274,13 +274,9 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
type="button"
size="sm"
onClick={() => handleOpenPicker()}
disabled={isLoading}
loading={isLoading}
>
{isLoading
? t('modals.uploadDoc.connectors.googleDrive.loading')
: t(
'modals.uploadDoc.connectors.googleDrive.selectFiles',
)}
{t('modals.uploadDoc.connectors.googleDrive.selectFiles')}
</Button>
</div>
+4 -10
View File
@@ -1,12 +1,11 @@
import { forceCollide, type SimulationNodeDatum } from 'd3-force';
import { Network, X } from 'lucide-react';
import { ArrowLeft, Network, X } from 'lucide-react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import ForceGraph2D, { type ForceGraphMethods } from 'react-force-graph-2d';
import userService from '../api/services/userService';
import ArrowLeft from '../assets/arrow-left.svg';
import { selectToken } from '../preferences/preferenceSlice';
import { Button } from './ui/button';
import SkeletonLoader from './SkeletonLoader';
@@ -168,7 +167,7 @@ const GraphView: React.FC<GraphViewProps> = ({
onClick={onBackToDocuments}
aria-label={t('settings.sources.backToAll')}
>
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
<ArrowLeft />
</Button>
<span className="text-primary font-semibold wrap-break-word">
{sourceName}
@@ -177,12 +176,7 @@ const GraphView: React.FC<GraphViewProps> = ({
</div>
<div className="bg-muted/60 text-muted-foreground dark:bg-accent/40 mb-4 flex items-start gap-2 rounded-xl px-4 py-3 text-xs">
<Network
size={16}
strokeWidth={1.75}
className="mt-0.5 shrink-0"
aria-hidden="true"
/>
<Network size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
<p>
<span className="text-foreground font-medium">
{t('settings.sources.graphrag.view.title')}
@@ -195,7 +189,7 @@ const GraphView: React.FC<GraphViewProps> = ({
<SkeletonLoader count={4} />
) : isEmpty ? (
<div className="border-border text-muted-foreground flex flex-col items-center gap-2 rounded-xl border border-dashed px-6 py-12 text-center text-sm">
<Network size={28} strokeWidth={1.5} aria-hidden="true" />
<Network size={28} aria-hidden="true" />
<p>{t('settings.sources.graphrag.view.empty')}</p>
</div>
) : (
+3 -15
View File
@@ -1,10 +1,8 @@
import { useState, useRef, useEffect } from 'react';
import { ShieldCheck } from 'lucide-react';
import { Info, Mail, ShieldCheck } from 'lucide-react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import Info from '../assets/info.svg';
import PageIcon from '../assets/documentation.svg';
import EmailIcon from '../assets/envelope.svg';
import { useTranslation } from 'react-i18next';
import { selectIsAdmin } from '../preferences/preferenceSlice';
import { Button } from './ui/button';
@@ -46,11 +44,7 @@ const Help = () => {
onClick={toggleDropdown}
className="mx-4 my-auto w-full"
>
<img
src={Info}
alt="info"
className="w-5 shrink-0 filter dark:invert"
/>
<Info className="text-muted-foreground size-5 shrink-0" />
{t('help')}
</Button>
{isOpen && (
@@ -67,7 +61,6 @@ const Help = () => {
>
<ShieldCheck
size={20}
strokeWidth={1.75}
className="text-muted-foreground shrink-0"
/>
{t('admin.label', 'Admin')}
@@ -93,12 +86,7 @@ const Help = () => {
href="mailto:support@docsgpt.cloud"
className="hover:bg-muted text-foreground flex items-start gap-4 rounded-b-xl px-4 py-2"
>
<img
src={EmailIcon}
alt="Email Us"
className="p-0.5 filter dark:invert"
width={20}
/>
<Mail className="size-5 shrink-0" />
{t('emailUs')}
</a>
</div>
+5 -6
View File
@@ -1,5 +1,6 @@
import { envVar } from '@/env';
import { cn } from '@/lib/utils';
import { CloudUpload, Database } from 'lucide-react';
import {
useCallback,
useEffect,
@@ -15,9 +16,7 @@ import { useDispatch, useSelector, useStore } from 'react-redux';
import endpoints from '../api/endpoints';
import userService from '../api/services/userService';
import DragFileUpload from '../assets/DragFileUpload.svg';
import SendArrow from '../assets/send.svg?react';
import SourceIcon from '../assets/source.svg';
import {
addAttachment,
removeAttachment,
@@ -1495,7 +1494,7 @@ export default function MessageInput({
own: t('conversation.sources.groupOwn'),
team: t('conversation.sources.groupTeam'),
},
SourceIcon,
<Database />,
),
[sourceDocs, t],
);
@@ -1842,11 +1841,11 @@ export default function MessageInput({
{handleDragActive &&
createPortal(
<div className="dark:bg-background/85 pointer-events-none fixed top-0 left-0 z-50 flex size-full flex-col items-center justify-center bg-white/85">
<img className="filter dark:invert" src={DragFileUpload} />
<span className="text-muted-foreground dark:text-muted-foreground px-2 text-2xl font-bold">
<CloudUpload className="size-18" />
<span className="text-muted-foreground px-2 text-2xl font-bold">
{t('modals.uploadDoc.drag.title')}
</span>
<span className="text-muted-foreground dark:text-muted-foreground w-48 p-2 text-center text-sm">
<span className="text-muted-foreground w-48 p-2 text-center text-sm">
{t('modals.uploadDoc.drag.description')}
</span>
</div>,
@@ -66,6 +66,18 @@ describe('MultiSelectPopover', () => {
expect(content?.className).toContain('max-h-40');
});
it('uses the shared bottom-sheet shape and handle on phones', () => {
media.isMobile = true;
render();
const content = document.querySelector('[data-slot="sheet-content"]')!;
expect(content.querySelectorAll('[data-slot="sheet-handle"]')).toHaveLength(
1,
);
expect(content.className).toContain('rounded-t-2xl');
expect(content.className).not.toContain('env(');
expect(content.className).not.toContain('dark:bg-card');
});
it('marks chosen rows with CommandItem checked and a primary tick', () => {
render();
const rows = document.querySelectorAll('[data-slot="command-item"]');
+4 -13
View File
@@ -6,7 +6,7 @@ import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import NoFilesIcon from '../assets/no-files.svg';
import { useDarkTheme, useMediaQuery } from '../hooks';
import { cn } from '@/lib/utils';
import Spinner from './Spinner';
import { Spinner } from '@/components/ui/spinner';
import {
Command,
CommandEmpty,
@@ -192,7 +192,7 @@ export function MultiSelectPopover({
{loading ? (
<div className="text-foreground flex items-center justify-center py-8">
<Spinner size="small" />
<Spinner size="sm" />
</div>
) : (
<div className="border-border mx-4 my-4 flex min-h-0 flex-1 flex-col overflow-hidden rounded-md border">
@@ -232,23 +232,14 @@ export function MultiSelectPopover({
<SheetTrigger asChild>{trigger}</SheetTrigger>
<SheetContent
side="bottom"
handle
showCloseButton={false}
onOpenAutoFocus={(e) => e.preventDefault()}
className={cn(
/* eslint-disable-next-line shadcn/no-restyle, shadcn/no-arbitrary-values --
bottom-sheet shape: rounded top, no gap, card fill in dark to match Command's
bg-popover, and pb-[env(safe-area-inset-bottom)] to clear the iPhone home indicator. */
'dark:bg-card flex max-h-[85vh] flex-col gap-0 overflow-hidden rounded-t-2xl p-0 pb-[env(safe-area-inset-bottom)]',
className,
)}
className={cn('overflow-hidden', className)}
>
<SheetTitle className="sr-only">
{title || effectivePlaceholder}
</SheetTitle>
<div
className="bg-border mx-auto mt-2 mb-1 h-1.5 w-12 shrink-0 rounded-full"
aria-hidden="true"
/>
{commandBody}
</SheetContent>
</Sheet>
Loaded 100 of 223 files, more files were not shown because too many files have changed in this diff. Show more