Skip to content

Commit 7e3cc9a

Browse files
andresdjassoclaude
authored andcommitted
feat(knowledge,tables): redraw knowledge's empty state and add docs/create chips
Knowledge gets the same treatment tables just went through: no brand colour (the `--brand-knowledge` accent is gone), no card chrome, ink mixed from `--text-secondary`, and the landing page's intersected corner fade. The graphic is a document and the chunks it is embedded as. Its fade is held back further than the tables grid on both axes — the document has to stay whole for the graphic to mean anything, so only the chunk grid may trail off. The three chunks the edges actually land on are the only filled ones; filling the whole first column left a chunk with no edge feeding it. Both empty states now carry two chips in the frame's action slot — a docs link and the create action, each running the same handler as the header's primary chip and inheriting its disabled state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 444e26d commit 7e3cc9a

5 files changed

Lines changed: 138 additions & 60 deletions

File tree

apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/gallery-preview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
} from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state'
1010

1111
const ENTRIES = [
12-
{ label: 'Knowledge', node: <KnowledgeEmptyState /> },
13-
{ label: 'Tables', node: <TablesEmptyState /> },
12+
{ label: 'Knowledge', node: <KnowledgeEmptyState onCreate={() => {}} /> },
13+
{ label: 'Tables', node: <TablesEmptyState onCreate={() => {}} /> },
1414
{ label: 'Logs', node: <LogsEmptyState /> },
1515
{ label: 'Files', node: <FilesEmptyState /> },
1616
{ label: 'Skills', node: <SkillsEmptyState /> },
Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,133 @@
1+
import { Chip, ChipLink, Plus } from '@sim/emcn'
2+
import { BookOpen } from '@sim/emcn/icons'
13
import { EmptyState } from '@/components/empty-state/empty-state'
2-
import {
3-
Bar,
4-
Vignette,
5-
} from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/vignette'
64

75
/**
8-
* A document fanning into the chunks it is embedded as — the moment that makes a
9-
* knowledge base a knowledge base, drawn in the editor vignette's connector
10-
* language (6px-radius smooth steps in `--workflow-edge`).
6+
* Neutral ink at graded strengths.
7+
*
8+
* `--surface-4`/`--surface-5` are near-white in light mode (#f5f5f5/#f3f3f3), so
9+
* skeleton geometry built on them dissolves against the page. Mixing
10+
* `--text-secondary` into transparent gives a real mid-grey that inverts with the
11+
* theme — the idiom the workflow editor's vignette uses for the one bar it needs
12+
* you to actually see.
13+
*/
14+
const INK = {
15+
heading: 'color-mix(in srgb, var(--text-secondary) 30%, transparent)',
16+
line: 'color-mix(in srgb, var(--text-secondary) 15%, transparent)',
17+
chunk: 'color-mix(in srgb, var(--text-secondary) 11%, transparent)',
18+
} as const
19+
20+
/**
21+
* Crisp at the top-left, dissolving through the bottom-right — the same
22+
* two-gradient intersect the landing page's workflow vignette uses, so the
23+
* geometry blends into the page rather than sitting on it as a card.
24+
*
25+
* Held back further than the tables grid on both axes: the document has to stay
26+
* whole for the graphic to mean anything, and only the chunks it fans into may
27+
* trail off.
28+
*/
29+
const CORNER_FADE =
30+
'[-webkit-mask-image:linear-gradient(to_right,#000_66%,transparent_100%),linear-gradient(to_bottom,#000_70%,transparent_100%)] [mask-image:linear-gradient(to_right,#000_66%,transparent_100%),linear-gradient(to_bottom,#000_70%,transparent_100%)] [-webkit-mask-composite:source-in] [mask-composite:intersect]'
31+
32+
/** Text-line widths inside the document; the first reads as its heading. */
33+
const DOCUMENT_LINES = [64, 78, 58, 72, 46] as const
34+
35+
/**
36+
* Smooth steps at the editor vignette's 6px corner radius, fanning from the
37+
* document's right edge to the first column of chunks.
1138
*/
1239
const CHUNK_EDGE_PATHS = [
13-
'M190,80 H207 Q213,80 213,74 V52 Q213,46 219,46 H236',
14-
'M190,80 H236',
15-
'M190,80 H207 Q213,80 213,86 V108 Q213,114 219,114 H236',
40+
'M130,71 H141 Q147,71 147,65 V51 Q147,45 153,45 H164',
41+
'M130,71 H164',
42+
'M130,71 H141 Q147,71 147,77 V91 Q147,97 153,97 H164',
1643
] as const
1744

45+
const CHUNK_COLUMNS = [164, 190, 216, 242, 268, 294] as const
46+
const CHUNK_ROWS = [35, 61, 87, 113] as const
47+
48+
/** Rows the edges actually land on — only those chunks read as filled. */
49+
const CONNECTED_ROWS = new Set([35, 61, 87])
50+
51+
/** A document, and the chunks it is embedded as running off the far corner. */
1852
function KnowledgeGraphic() {
1953
return (
20-
<Vignette>
54+
<div aria-hidden='true' className={`relative h-[148px] w-[320px] ${CORNER_FADE}`}>
55+
<div className='absolute top-[34px] left-[26px] h-[74px] w-[104px] rounded-[6px] border border-[var(--border-1)] px-3 pt-[13px]'>
56+
{DOCUMENT_LINES.map((width, index) => (
57+
<span
58+
key={width}
59+
className='mb-[9px] block h-[5px] rounded-full'
60+
style={{ width, background: index === 0 ? INK.heading : INK.line }}
61+
/>
62+
))}
63+
</div>
64+
2165
<svg className='absolute inset-0' fill='none' viewBox='0 0 320 148' width='320' height='148'>
22-
<defs>
23-
<linearGradient
24-
id='knowledge-empty-edge'
25-
x1='190'
26-
y1='0'
27-
x2='236'
28-
y2='0'
29-
gradientUnits='userSpaceOnUse'
30-
>
31-
<stop stopColor='var(--workflow-edge)' />
32-
<stop offset='1' stopColor='var(--workflow-edge)' />
33-
</linearGradient>
34-
</defs>
3566
{CHUNK_EDGE_PATHS.map((d) => (
3667
<path
3768
key={d}
3869
d={d}
39-
stroke='url(#knowledge-empty-edge)'
70+
stroke='var(--workflow-edge)'
4071
strokeWidth='1.5'
4172
strokeLinecap='round'
4273
strokeLinejoin='round'
4374
/>
4475
))}
4576
</svg>
4677

47-
<span className='absolute top-[22px] left-[78px] h-[92px] w-[124px] rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-1)] opacity-40' />
48-
<span className='absolute top-[28px] left-[72px] h-[92px] w-[124px] rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)] opacity-70' />
49-
50-
<div className='absolute top-[34px] left-[66px] h-[92px] w-[124px] rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)]'>
51-
<div className='flex items-center gap-2 px-3 pt-3 pb-2.5'>
52-
<span className='size-[14px] shrink-0 rounded-[4px] bg-[var(--surface-5)]' />
53-
<Bar className='h-2 w-[62px]' />
54-
</div>
55-
<div className='space-y-[7px] px-3'>
56-
<Bar className='h-2 w-[98px]' />
57-
<Bar className='h-2 w-[84px]' />
58-
<span className='block h-2 w-[92px] rounded-[3px] bg-[color-mix(in_srgb,var(--brand-knowledge)_26%,transparent)]' />
59-
<Bar className='h-2 w-[70px]' />
60-
</div>
61-
</div>
62-
63-
{[35, 69, 103].map((top, index) => (
64-
<span
65-
key={top}
66-
className='absolute left-[236px] size-[22px] rounded-[6px] border border-[var(--border-1)] bg-[var(--surface-3)]'
67-
style={{ top }}
68-
>
78+
{CHUNK_ROWS.map((top) =>
79+
CHUNK_COLUMNS.map((left) => (
6980
<span
70-
className='absolute inset-[5px] rounded-[3px]'
81+
key={`${left}-${top}`}
82+
className='absolute size-[20px] rounded-[5px] border border-[var(--border-1)]'
7183
style={{
84+
left,
85+
top,
7286
background:
73-
index === 1
74-
? 'color-mix(in srgb, var(--brand-knowledge) 34%, transparent)'
75-
: 'var(--surface-5)',
87+
left === CHUNK_COLUMNS[0] && CONNECTED_ROWS.has(top) ? INK.chunk : undefined,
7688
}}
7789
/>
78-
</span>
79-
))}
80-
</Vignette>
90+
))
91+
)}
92+
</div>
8193
)
8294
}
8395

96+
const KNOWLEDGE_DOCS_URL = 'https://docs.sim.ai/knowledgebase'
97+
98+
interface KnowledgeEmptyStateProps {
99+
/** Opens the create-base modal — the same action the header's primary chip runs. */
100+
onCreate: () => void
101+
/** Mirrors the header chip's disabled state: no edit rights on the workspace. */
102+
createDisabled?: boolean
103+
}
104+
84105
/** Empty state for the knowledge bases list when the workspace has none. */
85-
export function KnowledgeEmptyState() {
106+
export function KnowledgeEmptyState({
107+
onCreate,
108+
createDisabled = false,
109+
}: KnowledgeEmptyStateProps) {
86110
return (
87111
<EmptyState
88112
graphic={<KnowledgeGraphic />}
89113
title='No knowledge bases yet'
90114
description='Upload documents to give your agents a memory they can search.'
115+
action={
116+
<div className='flex items-center gap-2'>
117+
<ChipLink
118+
href={KNOWLEDGE_DOCS_URL}
119+
target='_blank'
120+
rel='noopener noreferrer'
121+
variant='border'
122+
leftIcon={BookOpen}
123+
>
124+
Docs
125+
</ChipLink>
126+
<Chip variant='primary' onClick={onCreate} disabled={createDisabled} leftIcon={Plus}>
127+
New base
128+
</Chip>
129+
</div>
130+
}
91131
/>
92132
)
93133
}

apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Chip, ChipLink, Plus } from '@sim/emcn'
2+
import { BookOpen } from '@sim/emcn/icons'
13
import { EmptyState } from '@/components/empty-state/empty-state'
24

35
/**
@@ -96,13 +98,38 @@ function TablesGraphic() {
9698
)
9799
}
98100

101+
const TABLES_DOCS_URL = 'https://docs.sim.ai/tables'
102+
103+
interface TablesEmptyStateProps {
104+
/** Creates a table — the same action the header's primary chip runs. */
105+
onCreate: () => void
106+
/** Mirrors the header chip's disabled state: no edit rights, or a create already in flight. */
107+
createDisabled?: boolean
108+
}
109+
99110
/** Empty state for the tables list when the workspace has none. */
100-
export function TablesEmptyState() {
111+
export function TablesEmptyState({ onCreate, createDisabled = false }: TablesEmptyStateProps) {
101112
return (
102113
<EmptyState
103114
graphic={<TablesGraphic />}
104115
title='No tables yet'
105116
description='Create a table to store structured data your agents can read and write.'
117+
action={
118+
<div className='flex items-center gap-2'>
119+
<ChipLink
120+
href={TABLES_DOCS_URL}
121+
target='_blank'
122+
rel='noopener noreferrer'
123+
variant='border'
124+
leftIcon={BookOpen}
125+
>
126+
Docs
127+
</ChipLink>
128+
<Chip variant='primary' onClick={onCreate} disabled={createDisabled} leftIcon={Plus}>
129+
New table
130+
</Chip>
131+
</div>
132+
}
106133
/>
107134
)
108135
}

apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,11 @@ export function Knowledge() {
13671367
filter={filterConfig}
13681368
/>
13691369
<Resource.Table
1370-
emptyState={showEmptyState ? <KnowledgeEmptyState /> : undefined}
1370+
emptyState={
1371+
showEmptyState ? (
1372+
<KnowledgeEmptyState onCreate={handleOpenCreateModal} createDisabled={!canEdit} />
1373+
) : undefined
1374+
}
13711375
columns={COLUMNS}
13721376
rows={rows}
13731377
selectable={canEdit ? selectableConfig : undefined}

apps/sim/app/workspace/[workspaceId]/tables/tables.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,14 @@ export function Tables() {
12791279
filter={filterConfig}
12801280
/>
12811281
<Resource.Table
1282-
emptyState={showEmptyState ? <TablesEmptyState /> : undefined}
1282+
emptyState={
1283+
showEmptyState ? (
1284+
<TablesEmptyState
1285+
onCreate={handleCreateTable}
1286+
createDisabled={uploading || !canEdit || createTable.isPending}
1287+
/>
1288+
) : undefined
1289+
}
12831290
columns={COLUMNS}
12841291
rows={rows}
12851292
selectable={canEdit ? selectableConfig : undefined}

0 commit comments

Comments
 (0)