mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement: ui
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,9 @@ bun-debug.log*
|
||||
**/standalone/
|
||||
sim-standalone.tar.gz
|
||||
|
||||
# redis
|
||||
dump.rdb
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
:root,
|
||||
.light {
|
||||
--bg: #fefefe; /* main canvas - neutral near-white */
|
||||
--surface-1: #fefefe; /* sidebar, panels */
|
||||
--surface-1: #f9f9f9; /* sidebar, panels */
|
||||
--surface-2: #ffffff; /* blocks, cards, modals - pure white */
|
||||
--surface-3: #f7f7f7; /* popovers, headers */
|
||||
--surface-4: #f5f5f5; /* buttons base */
|
||||
@@ -95,14 +95,15 @@
|
||||
--border-1: #e0e0e0; /* stronger border */
|
||||
--surface-6: #e5e5e5; /* popovers, elevated surfaces */
|
||||
--surface-7: #d9d9d9;
|
||||
--surface-active: #eeeeee; /* hover/active state */
|
||||
|
||||
--workflow-edge: #e0e0e0; /* workflow handles/edges - matches border-1 */
|
||||
|
||||
/* Text - neutral */
|
||||
--text-primary: #2d2d2d;
|
||||
--text-secondary: #4e4e4e;
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #525252;
|
||||
--text-tertiary: #5c5c5c;
|
||||
--text-muted: #737373;
|
||||
--text-muted: #707070;
|
||||
--text-subtle: #8c8c8c;
|
||||
--text-inverse: #ffffff;
|
||||
--text-muted-inverse: #a0a0a0;
|
||||
@@ -212,6 +213,7 @@
|
||||
--border-1: #3d3d3d;
|
||||
--surface-6: #454545;
|
||||
--surface-7: #505050;
|
||||
--surface-active: #303030; /* hover/active state */
|
||||
|
||||
--workflow-edge: #454545; /* workflow handles/edges - same as surface-6 in dark */
|
||||
|
||||
@@ -241,7 +243,7 @@
|
||||
--white: #ffffff;
|
||||
|
||||
/* Font weights - standard weights for dark mode */
|
||||
--font-weight-base: 440;
|
||||
--font-weight-base: 450;
|
||||
--font-weight-medium: 480;
|
||||
--font-weight-semibold: 550;
|
||||
|
||||
|
||||
@@ -113,9 +113,19 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (actualChatId) {
|
||||
const userMsg = {
|
||||
id: userMessageId,
|
||||
role: 'user' as const,
|
||||
content: message,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
await db
|
||||
.update(copilotChats)
|
||||
.set({ conversationId: userMessageId })
|
||||
.set({
|
||||
messages: [...conversationHistory, userMsg],
|
||||
conversationId: userMessageId,
|
||||
})
|
||||
.where(eq(copilotChats.id, actualChatId))
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export function Home({ chatId, streamId }: HomeProps = {}) {
|
||||
|
||||
if (!hasMessages) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center bg-[#FCFCFC] px-[24px] dark:bg-[var(--surface-1)]'>
|
||||
<div className='flex h-full flex-col items-center justify-center bg-[#FCFCFC] px-[24px] dark:bg-[var(--surface-2)]'>
|
||||
<h1 className='mb-[24px] font-[450] font-season text-[32px] text-[var(--text-primary)] tracking-[-0.02em]'>
|
||||
What do you want to do?
|
||||
</h1>
|
||||
@@ -73,7 +73,7 @@ export function Home({ chatId, streamId }: HomeProps = {}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex h-full bg-[#FCFCFC] dark:bg-[var(--surface-1)]'>
|
||||
<div className='flex h-full bg-[#FCFCFC] dark:bg-[var(--surface-2)]'>
|
||||
<div className='flex h-full min-w-0 flex-1 flex-col'>
|
||||
<div className='min-h-0 flex-1 overflow-y-auto px-[16px] py-[16px]'>
|
||||
<div className='mx-auto max-w-[768px] space-y-[16px]'>
|
||||
|
||||
@@ -386,7 +386,7 @@ export function SearchModal({
|
||||
backgroundClip: 'padding-box',
|
||||
}}
|
||||
/>
|
||||
<span className='truncate font-medium text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
<span className='truncate font-base text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
{workflow.name}
|
||||
{workflow.isCurrent && ' (current)'}
|
||||
</span>
|
||||
@@ -407,7 +407,7 @@ export function SearchModal({
|
||||
<div className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
|
||||
<Blimp className='h-[14px] w-[14px] text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]' />
|
||||
</div>
|
||||
<span className='truncate font-medium text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
<span className='truncate font-base text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
{task.name}
|
||||
</span>
|
||||
</Command.Item>
|
||||
@@ -441,7 +441,7 @@ export function SearchModal({
|
||||
onSelect={() => handleWorkspaceSelect(workspace)}
|
||||
className='group flex h-[28px] w-full cursor-pointer items-center gap-[8px] rounded-[6px] px-[10px] text-left text-[15px] aria-selected:bg-[var(--border)] aria-selected:shadow-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50'
|
||||
>
|
||||
<span className='truncate font-medium text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
<span className='truncate font-base text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
{workspace.name}
|
||||
{workspace.isCurrent && ' (current)'}
|
||||
</span>
|
||||
@@ -481,11 +481,11 @@ export function SearchModal({
|
||||
<div className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
|
||||
<Icon className='h-[14px] w-[14px] text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]' />
|
||||
</div>
|
||||
<span className='truncate font-medium text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
<span className='truncate font-base text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
{page.name}
|
||||
</span>
|
||||
{page.shortcut && (
|
||||
<span className='ml-auto flex-shrink-0 font-medium text-[13px] text-[var(--text-subtle)]'>
|
||||
<span className='ml-auto flex-shrink-0 font-base text-[13px] text-[var(--text-subtle)]'>
|
||||
{page.shortcut}
|
||||
</span>
|
||||
)}
|
||||
@@ -503,7 +503,7 @@ export function SearchModal({
|
||||
}
|
||||
|
||||
const groupHeadingClassName =
|
||||
'[&_[cmdk-group-heading]]:pt-[2px] [&_[cmdk-group-heading]]:pb-[4px] [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-[13px] [&_[cmdk-group-heading]]:text-[var(--text-subtle)] [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wide'
|
||||
'[&_[cmdk-group-heading]]:pt-[2px] [&_[cmdk-group-heading]]:pb-[4px] [&_[cmdk-group-heading]]:font-base [&_[cmdk-group-heading]]:text-[13px] [&_[cmdk-group-heading]]:text-[var(--text-subtle)] [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wide'
|
||||
|
||||
interface CommandItemProps {
|
||||
value: string
|
||||
@@ -541,7 +541,7 @@ function CommandItem({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span className='truncate font-medium text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
<span className='truncate font-base text-[var(--text-tertiary)] group-aria-selected:text-[var(--text-primary)]'>
|
||||
{children}
|
||||
</span>
|
||||
</Command.Item>
|
||||
|
||||
@@ -468,7 +468,7 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
|
||||
return (
|
||||
<div className='flex flex-shrink-0 flex-col border-t px-[13.5px] pt-[8px] pb-[10px]'>
|
||||
<div className='flex h-[18px] items-center'>
|
||||
<span className='font-medium text-[12px] text-[var(--text-primary)]'>
|
||||
<span className='font-base text-[12px] text-[var(--text-primary)]'>
|
||||
{PLAN_NAMES[planType]}
|
||||
</span>
|
||||
</div>
|
||||
@@ -490,7 +490,7 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
|
||||
<div className='flex min-w-0 flex-1 items-center gap-[6px]'>
|
||||
{showPlanText && (
|
||||
<>
|
||||
<span className='flex-shrink-0 font-medium text-[12px] text-[var(--text-primary)]'>
|
||||
<span className='flex-shrink-0 font-base text-[12px] text-[var(--text-primary)]'>
|
||||
{PLAN_NAMES[planType]}
|
||||
</span>
|
||||
<div className='h-[14px] w-[1.5px] flex-shrink-0 bg-[var(--divider)]' />
|
||||
@@ -498,16 +498,16 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
|
||||
)}
|
||||
<div className='flex min-w-0 flex-1 items-center gap-[4px]'>
|
||||
{statusText.isError ? (
|
||||
<span className='font-medium text-[12px] text-[var(--text-error)]'>
|
||||
<span className='font-base text-[12px] text-[var(--text-error)]'>
|
||||
{statusText.text}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className='font-medium text-[12px] text-[var(--text-secondary)] tabular-nums'>
|
||||
<span className='font-base text-[12px] text-[var(--text-secondary)] tabular-nums'>
|
||||
${usage.current.toFixed(2)}
|
||||
</span>
|
||||
<span className='font-medium text-[12px] text-[var(--text-secondary)]'>/</span>
|
||||
<span className='font-medium text-[12px] text-[var(--text-secondary)] tabular-nums'>
|
||||
<span className='font-base text-[12px] text-[var(--text-secondary)]'>/</span>
|
||||
<span className='font-base text-[12px] text-[var(--text-secondary)] tabular-nums'>
|
||||
${usage.limit.toFixed(2)}
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -445,9 +445,9 @@ export function FolderItem({
|
||||
aria-expanded={isExpanded}
|
||||
aria-label={`${folder.name} folder, ${isExpanded ? 'expanded' : 'collapsed'}`}
|
||||
className={clsx(
|
||||
'group mx-[2px] flex h-[28px] cursor-pointer items-center gap-[8px] rounded-[8px] px-[8px] text-[14px]',
|
||||
!isAnyDragActive && 'hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]',
|
||||
isSelected ? 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]' : '',
|
||||
'group mx-[2px] flex h-[30px] cursor-pointer items-center gap-[8px] rounded-[8px] px-[8px] text-[14px]',
|
||||
!isAnyDragActive && 'hover:bg-[var(--surface-active)]',
|
||||
isSelected ? 'bg-[var(--surface-active)]' : '',
|
||||
(isDragging || (isAnyDragActive && isSelected)) && 'opacity-50'
|
||||
)}
|
||||
onClick={handleClick}
|
||||
@@ -460,19 +460,19 @@ export function FolderItem({
|
||||
>
|
||||
<ChevronRight
|
||||
className={clsx(
|
||||
'h-[14px] w-[14px] flex-shrink-0 text-[var(--text-secondary)] transition-transform duration-100',
|
||||
'h-[16px] w-[16px] flex-shrink-0 text-[var(--text-muted)] transition-transform duration-100',
|
||||
isExpanded && 'rotate-90'
|
||||
)}
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{isExpanded ? (
|
||||
<FolderOpen
|
||||
className='h-[14px] w-[14px] flex-shrink-0 text-[var(--text-secondary)]'
|
||||
className='h-[16px] w-[16px] flex-shrink-0 text-[var(--text-muted)]'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
) : (
|
||||
<Folder
|
||||
className='h-[14px] w-[14px] flex-shrink-0 text-[var(--text-secondary)]'
|
||||
className='h-[16px] w-[16px] flex-shrink-0 text-[var(--text-muted)]'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
)}
|
||||
@@ -483,7 +483,7 @@ export function FolderItem({
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleRenameKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
className='min-w-0 flex-1 border-0 bg-transparent p-0 font-medium text-[14px] text-[var(--text-primary)] outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
className='min-w-0 flex-1 border-0 bg-transparent p-0 font-base text-[14px] text-[var(--text-primary)] outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
maxLength={50}
|
||||
disabled={isRenaming}
|
||||
onClick={(e) => {
|
||||
@@ -498,7 +498,7 @@ export function FolderItem({
|
||||
) : (
|
||||
<div className='flex min-w-0 flex-1 items-center gap-[8px]'>
|
||||
<span
|
||||
className='min-w-0 flex-1 truncate font-medium text-[var(--text-secondary)]'
|
||||
className='min-w-0 flex-1 truncate font-base text-[var(--text-secondary)]'
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
{folder.name}
|
||||
@@ -513,7 +513,7 @@ export function FolderItem({
|
||||
!isAnyDragActive && 'group-hover:opacity-100'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className='h-[14px] w-[14px] text-[var(--text-tertiary)]' />
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-muted)]' />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -360,15 +360,10 @@ export function WorkflowItem({
|
||||
href={`/workspace/${workspaceId}/w/${workflow.id}`}
|
||||
data-item-id={workflow.id}
|
||||
className={clsx(
|
||||
'group mx-[2px] flex h-[28px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px]',
|
||||
active && 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]',
|
||||
!active &&
|
||||
!isAnyDragActive &&
|
||||
'hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]',
|
||||
isSelected &&
|
||||
selectedWorkflows.size > 1 &&
|
||||
!active &&
|
||||
'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]',
|
||||
'group mx-[2px] flex h-[30px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px]',
|
||||
active && 'bg-[var(--surface-active)]',
|
||||
!active && !isAnyDragActive && 'hover:bg-[var(--surface-active)]',
|
||||
isSelected && selectedWorkflows.size > 1 && !active && 'bg-[var(--surface-active)]',
|
||||
(isDragging || (isAnyDragActive && isSelected)) && 'opacity-50'
|
||||
)}
|
||||
draggable={!isEditing && !dragDisabled}
|
||||
@@ -378,7 +373,7 @@ export function WorkflowItem({
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<div
|
||||
className='h-[14px] w-[14px] flex-shrink-0 rounded-[4px] border-[2px]'
|
||||
className='h-[16px] w-[16px] flex-shrink-0 rounded-[4px] border-[2.5px]'
|
||||
style={{
|
||||
backgroundColor: workflow.color,
|
||||
borderColor: `${workflow.color}60`,
|
||||
@@ -394,7 +389,7 @@ export function WorkflowItem({
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
className='w-full min-w-0 border-0 bg-transparent p-0 font-medium text-[14px] text-[var(--text-primary)] outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
className='w-full min-w-0 border-0 bg-transparent p-0 font-base text-[14px] text-[var(--text-primary)] outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
maxLength={100}
|
||||
disabled={isRenaming}
|
||||
onClick={(e) => {
|
||||
@@ -409,7 +404,7 @@ export function WorkflowItem({
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'min-w-0 truncate font-medium',
|
||||
'min-w-0 truncate font-base',
|
||||
active ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'
|
||||
)}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
@@ -432,7 +427,7 @@ export function WorkflowItem({
|
||||
!isAnyDragActive && 'group-hover:opacity-100'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className='h-[14px] w-[14px] text-[var(--text-tertiary)]' />
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-muted)]' />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -149,6 +149,12 @@ export function WorkspaceHeader({
|
||||
|
||||
const activeWorkspaceFull = workspaces.find((w) => w.id === workspaceId) || null
|
||||
|
||||
const workspaceInitial = (() => {
|
||||
const name = activeWorkspace?.name || ''
|
||||
const stripped = name.replace(/workspace/gi, '').trim()
|
||||
return (stripped[0] || name[0] || 'W').toUpperCase()
|
||||
})()
|
||||
|
||||
/**
|
||||
* Opens the context menu for a workspace at the specified position
|
||||
*/
|
||||
@@ -310,7 +316,7 @@ export function WorkspaceHeader({
|
||||
<button
|
||||
type='button'
|
||||
aria-label='Switch workspace'
|
||||
className='group -mx-[6px] flex h-[28px] min-w-0 max-w-full cursor-pointer items-center gap-[8px] rounded-[8px] bg-transparent px-[8px] transition-colors hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
className='group flex h-[32px] w-full min-w-0 cursor-pointer items-center gap-[8px] rounded-[8px] border border-[var(--border)] bg-[#FCFCFC] px-[8px] transition-colors hover:bg-[var(--surface-5)] dark:bg-[var(--surface-2)] dark:hover:bg-[var(--surface-3)]'
|
||||
title={activeWorkspace?.name || 'Loading...'}
|
||||
onContextMenu={(e) => {
|
||||
if (activeWorkspaceFull) {
|
||||
@@ -318,7 +324,10 @@ export function WorkspaceHeader({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className='truncate font-medium text-[14px] text-[var(--text-primary)]'>
|
||||
<div className='flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center rounded-[4px] bg-[var(--surface-7)] font-medium text-[12px] text-[var(--text-secondary)] leading-none'>
|
||||
{workspaceInitial}
|
||||
</div>
|
||||
<span className='min-w-0 flex-1 truncate text-left font-base text-[14px] text-[var(--text-primary)]'>
|
||||
{activeWorkspace?.name || 'Loading...'}
|
||||
</span>
|
||||
<ChevronDown
|
||||
@@ -395,7 +404,7 @@ export function WorkspaceHeader({
|
||||
{workspaces.map((workspace, index) => (
|
||||
<div key={workspace.id} className={index > 0 ? 'mt-[2px]' : ''}>
|
||||
{editingWorkspaceId === workspace.id ? (
|
||||
<div className='flex h-[26px] items-center gap-[8px] rounded-[8px] bg-[var(--surface-5)] px-[6px]'>
|
||||
<div className='flex h-[26px] items-center gap-[8px] rounded-[8px] bg-[var(--surface-6)] px-[6px]'>
|
||||
<input
|
||||
ref={(el) => {
|
||||
if (el && !hasInputFocusedRef.current) {
|
||||
@@ -481,11 +490,14 @@ export function WorkspaceHeader({
|
||||
<button
|
||||
type='button'
|
||||
aria-label='Switch workspace'
|
||||
className='-mx-[6px] flex h-[28px] min-w-0 max-w-full cursor-pointer items-center gap-[8px] rounded-[8px] bg-transparent px-[8px] transition-colors hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
className='flex h-[32px] w-full min-w-0 items-center gap-[8px] rounded-[8px] border border-[var(--border)] bg-[#FCFCFC] px-[8px] dark:bg-[var(--surface-2)]'
|
||||
title={activeWorkspace?.name || 'Loading...'}
|
||||
disabled
|
||||
>
|
||||
<span className='truncate font-medium text-[14px] text-[var(--text-primary)]'>
|
||||
<div className='flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center rounded-[4px] bg-[var(--surface-7)] font-medium text-[12px] text-[var(--text-secondary)] leading-none'>
|
||||
{workspaceInitial}
|
||||
</div>
|
||||
<span className='min-w-0 flex-1 truncate text-left font-base text-[14px] text-[var(--text-primary)]'>
|
||||
{activeWorkspace?.name || 'Loading...'}
|
||||
</span>
|
||||
<ChevronDown className='h-[8px] w-[10px] flex-shrink-0 text-[var(--text-muted)]' />
|
||||
@@ -546,8 +558,8 @@ export function WorkspaceHeader({
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
Are you sure you want to leave{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{leaveTarget?.name}</span>?
|
||||
You will lose access to all workflows and data in this workspace.{' '}
|
||||
<span className='font-base text-[var(--text-primary)]'>{leaveTarget?.name}</span>? You
|
||||
will lose access to all workflows and data in this workspace.{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
@@ -582,13 +582,13 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<>
|
||||
<aside
|
||||
ref={sidebarRef}
|
||||
className='sidebar-container relative h-full overflow-hidden bg-[#F4F4F4] dark:bg-[var(--surface-1)]'
|
||||
className='sidebar-container relative h-full overflow-hidden bg-[var(--surface-1)]'
|
||||
aria-label='Workspace sidebar'
|
||||
onClick={handleSidebarClick}
|
||||
>
|
||||
<div className='flex h-full flex-col pt-[12px]'>
|
||||
{/* Header */}
|
||||
<div className='flex-shrink-0 px-[16px]'>
|
||||
<div className='flex-shrink-0 px-[8px]'>
|
||||
<WorkspaceHeader
|
||||
activeWorkspace={activeWorkspace}
|
||||
workspaceId={workspaceId}
|
||||
@@ -611,16 +611,17 @@ export const Sidebar = memo(function Sidebar() {
|
||||
</div>
|
||||
|
||||
{/* Top Navigation: Home, Search */}
|
||||
<div className='mt-[14px] flex flex-shrink-0 flex-col gap-[2px] px-[8px]'>
|
||||
<div className='mt-[10px] flex flex-shrink-0 flex-col gap-[2px] px-[8px]'>
|
||||
{topNavItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = item.href ? pathname?.startsWith(item.href) : false
|
||||
const baseClasses =
|
||||
'group flex h-[28px] items-center gap-[8px] rounded-[8px] mx-[2px] px-[8px] text-[14px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
const activeClasses = active ? 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]' : ''
|
||||
'group flex h-[30px] items-center gap-[8px] rounded-[8px] mx-[2px] px-[8px] text-[14px] hover:bg-[var(--surface-active)]'
|
||||
const activeClasses = active ? 'bg-[var(--surface-active)]' : ''
|
||||
const textColor = active
|
||||
? 'text-[var(--text-primary)]'
|
||||
: 'text-[var(--text-secondary)]'
|
||||
const iconColor = active ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)]'
|
||||
|
||||
if (item.onClick) {
|
||||
return (
|
||||
@@ -631,8 +632,8 @@ export const Sidebar = memo(function Sidebar() {
|
||||
className={`${baseClasses} ${activeClasses}`}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<Icon className={`h-[14px] w-[14px] flex-shrink-0 ${textColor}`} />
|
||||
<span className={`truncate font-medium ${textColor}`}>{item.label}</span>
|
||||
<Icon className={`h-[16px] w-[16px] flex-shrink-0 ${iconColor}`} />
|
||||
<span className={`truncate font-base ${textColor}`}>{item.label}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -645,8 +646,8 @@ export const Sidebar = memo(function Sidebar() {
|
||||
className={`${baseClasses} ${activeClasses}`}
|
||||
onContextMenu={(e) => handleNavItemContextMenu(e, item.href!)}
|
||||
>
|
||||
<Icon className={`h-[14px] w-[14px] flex-shrink-0 ${textColor}`} />
|
||||
<span className={`truncate font-medium ${textColor}`}>{item.label}</span>
|
||||
<Icon className={`h-[16px] w-[16px] flex-shrink-0 ${iconColor}`} />
|
||||
<span className={`truncate font-base ${textColor}`}>{item.label}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
@@ -660,20 +661,19 @@ export const Sidebar = memo(function Sidebar() {
|
||||
)}
|
||||
>
|
||||
<div className='px-[16px] pb-[6px]'>
|
||||
<div className='font-medium text-[var(--text-tertiary)] text-small'>Workspace</div>
|
||||
<div className='font-base text-[var(--text-tertiary)] text-small'>Workspace</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-[2px] px-[8px]'>
|
||||
{workspaceNavItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = item.href ? pathname?.startsWith(item.href) : false
|
||||
const baseClasses =
|
||||
'group flex h-[28px] items-center gap-[8px] rounded-[8px] mx-[2px] px-[8px] text-[14px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
const activeClasses = active
|
||||
? 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]'
|
||||
: ''
|
||||
'group flex h-[30px] items-center gap-[8px] rounded-[8px] mx-[2px] px-[8px] text-[14px] hover:bg-[var(--surface-active)]'
|
||||
const activeClasses = active ? 'bg-[var(--surface-active)]' : ''
|
||||
const textColor = active
|
||||
? 'text-[var(--text-primary)]'
|
||||
: 'text-[var(--text-secondary)]'
|
||||
const iconColor = active ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)]'
|
||||
|
||||
return (
|
||||
<Link
|
||||
@@ -683,8 +683,8 @@ export const Sidebar = memo(function Sidebar() {
|
||||
className={`${baseClasses} ${activeClasses}`}
|
||||
onContextMenu={(e) => handleNavItemContextMenu(e, item.href!)}
|
||||
>
|
||||
<Icon className={`h-[14px] w-[14px] flex-shrink-0 ${textColor}`} />
|
||||
<span className={`truncate font-medium ${textColor}`}>{item.label}</span>
|
||||
<Icon className={`h-[16px] w-[16px] flex-shrink-0 ${iconColor}`} />
|
||||
<span className={`truncate font-base ${textColor}`}>{item.label}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
@@ -699,7 +699,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
{/* Tasks */}
|
||||
<div className='flex flex-shrink-0 flex-col'>
|
||||
<div className='px-[16px]'>
|
||||
<div className='font-medium text-[var(--text-tertiary)] text-small'>All tasks</div>
|
||||
<div className='font-base text-[var(--text-tertiary)] text-small'>All tasks</div>
|
||||
</div>
|
||||
<div className='mt-[6px] flex flex-col gap-[2px] px-[8px]'>
|
||||
{tasks.map((task) => {
|
||||
@@ -707,16 +707,19 @@ export const Sidebar = memo(function Sidebar() {
|
||||
const textColor = active
|
||||
? 'text-[var(--text-primary)]'
|
||||
: 'text-[var(--text-secondary)]'
|
||||
const iconColor = active
|
||||
? 'text-[var(--text-primary)]'
|
||||
: 'text-[var(--text-muted)]'
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={task.id}
|
||||
href={task.href}
|
||||
className={`mx-[2px] flex h-[28px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)] ${active ? 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]' : ''}`}
|
||||
className={`mx-[2px] flex h-[30px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px] hover:bg-[var(--surface-active)] ${active ? 'bg-[var(--surface-active)]' : ''}`}
|
||||
onContextMenu={(e) => handleTaskContextMenu(e, task.href, task.id)}
|
||||
>
|
||||
<Blimp className={`h-[14px] w-[14px] flex-shrink-0 ${textColor}`} />
|
||||
<div className={`min-w-0 truncate font-medium ${textColor}`}>{task.name}</div>
|
||||
<Blimp className={`h-[16px] w-[16px] flex-shrink-0 ${iconColor}`} />
|
||||
<div className={`min-w-0 truncate font-base ${textColor}`}>{task.name}</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
@@ -727,9 +730,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<div className='workflows-section mt-[14px] flex flex-col'>
|
||||
<div className='flex flex-shrink-0 flex-col space-y-[4px] px-[16px]'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='font-medium text-[var(--text-tertiary)] text-small'>
|
||||
Workflows
|
||||
</div>
|
||||
<div className='font-base text-[var(--text-tertiary)] text-small'>Workflows</div>
|
||||
<div className='flex items-center justify-center gap-[8px]'>
|
||||
<Popover>
|
||||
<Tooltip.Root>
|
||||
@@ -737,13 +738,13 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-[18px] w-[18px] rounded-[4px] p-0 hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
className='h-[18px] w-[18px] rounded-[4px] p-0 hover:bg-[var(--surface-active)]'
|
||||
disabled={!canEdit}
|
||||
>
|
||||
{isImporting || isCreatingFolder ? (
|
||||
<Loader className='h-[14px] w-[14px]' animate />
|
||||
<Loader className='h-[16px] w-[16px]' animate />
|
||||
) : (
|
||||
<MoreHorizontal className='h-[14px] w-[14px]' />
|
||||
<MoreHorizontal className='h-[16px] w-[16px]' />
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@@ -757,14 +758,14 @@ export const Sidebar = memo(function Sidebar() {
|
||||
onClick={handleImportWorkflow}
|
||||
disabled={!canEdit || isImporting}
|
||||
>
|
||||
<Download className='h-[14px] w-[14px]' />
|
||||
<Download className='h-[16px] w-[16px]' />
|
||||
<span>{isImporting ? 'Importing...' : 'Import workflow'}</span>
|
||||
</PopoverItem>
|
||||
<PopoverItem
|
||||
onClick={handleCreateFolder}
|
||||
disabled={!canEdit || isCreatingFolder}
|
||||
>
|
||||
<FolderPlus className='h-[14px] w-[14px]' />
|
||||
<FolderPlus className='h-[16px] w-[16px]' />
|
||||
<span>{isCreatingFolder ? 'Creating folder...' : 'Create folder'}</span>
|
||||
</PopoverItem>
|
||||
</PopoverContent>
|
||||
@@ -773,11 +774,11 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-[18px] w-[18px] rounded-[4px] p-0 hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
className='h-[18px] w-[18px] rounded-[4px] p-0 hover:bg-[var(--surface-active)]'
|
||||
onClick={handleCreateWorkflow}
|
||||
disabled={isCreatingWorkflow || !canEdit}
|
||||
>
|
||||
<Plus className='h-[14px] w-[14px]' />
|
||||
<Plus className='h-[16px] w-[16px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
@@ -819,11 +820,11 @@ export const Sidebar = memo(function Sidebar() {
|
||||
key={item.id}
|
||||
type='button'
|
||||
data-item-id={item.id}
|
||||
className='group mx-[2px] flex h-[28px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]'
|
||||
className='group mx-[2px] flex h-[30px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px] hover:bg-[var(--surface-active)]'
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<Icon className='h-[14px] w-[14px] flex-shrink-0 text-[var(--text-secondary)]' />
|
||||
<span className='truncate font-medium text-[var(--text-secondary)]'>
|
||||
<Icon className='h-[16px] w-[16px] flex-shrink-0 text-[var(--text-muted)]' />
|
||||
<span className='truncate font-base text-[var(--text-secondary)]'>
|
||||
{item.label}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -62,7 +62,12 @@ export const getExecutionSummaryServerTool: BaseServerTool<
|
||||
|
||||
const clampedLimit = Math.min(Math.max(1, limit), 20)
|
||||
|
||||
logger.info('Fetching execution summary', { workspaceId, workflowId, limit: clampedLimit, status })
|
||||
logger.info('Fetching execution summary', {
|
||||
workspaceId,
|
||||
workflowId,
|
||||
limit: clampedLimit,
|
||||
status,
|
||||
})
|
||||
|
||||
const conditions: SQL[] = [eq(workflowExecutionLogs.workspaceId, workspaceId)]
|
||||
|
||||
@@ -97,8 +102,7 @@ export const getExecutionSummaryServerTool: BaseServerTool<
|
||||
|
||||
const summaries: ExecutionSummary[] = rows.map((row) => {
|
||||
const costData = row.cost as any
|
||||
const errorMsg =
|
||||
row.level === 'error' ? extractErrorMessage(row.executionData) : null
|
||||
const errorMsg = row.level === 'error' ? extractErrorMessage(row.executionData) : null
|
||||
|
||||
return {
|
||||
executionId: row.executionId,
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
} from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, count, desc, eq, isNull } from 'drizzle-orm'
|
||||
import { generateWorkspaceContext } from '@/lib/copilot/workspace-context'
|
||||
import type { DirEntry, GrepMatch, GrepOptions, ReadResult } from '@/lib/copilot/vfs/operations'
|
||||
import * as ops from '@/lib/copilot/vfs/operations'
|
||||
import type { DeploymentData } from '@/lib/copilot/vfs/serializers'
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
serializeTaskSession,
|
||||
serializeWorkflowMeta,
|
||||
} from '@/lib/copilot/vfs/serializers'
|
||||
import { generateWorkspaceContext } from '@/lib/copilot/workspace-context'
|
||||
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace'
|
||||
import { hasWorkflowChanged } from '@/lib/workflows/comparison'
|
||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
||||
|
||||
Reference in New Issue
Block a user