Merge branch 'ai-christianson:master' into master
This commit is contained in:
commit
3f28ea80aa
|
|
@ -1,7 +1,9 @@
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './card';
|
export * from './card';
|
||||||
export * from './collapsible';
|
export * from './collapsible';
|
||||||
|
export * from './floating-action-button';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
|
export * from './layout';
|
||||||
export * from './sheet';
|
export * from './sheet';
|
||||||
export * from './switch';
|
export * from './switch';
|
||||||
export * from './scroll-area';
|
export * from './scroll-area';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './card';
|
export * from './card';
|
||||||
export * from './collapsible';
|
export * from './collapsible';
|
||||||
|
export * from './floating-action-button';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
|
export * from './layout';
|
||||||
export * from './sheet';
|
export * from './sheet';
|
||||||
export * from './switch';
|
export * from './switch';
|
||||||
export * from './scroll-area';
|
export * from './scroll-area';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import './styles/global.css';
|
import './styles/global.css';
|
||||||
|
export * from './utils/types';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './components/ui';
|
export * from './components/ui';
|
||||||
export * from './components/TimelineStep';
|
export * from './components/TimelineStep';
|
||||||
export * from './components/TimelineFeed';
|
export * from './components/TimelineFeed';
|
||||||
export * from './components/SessionDrawer';
|
export * from './components/SessionDrawer';
|
||||||
export * from './components/SessionSidebar';
|
export * from './components/SessionSidebar';
|
||||||
|
export * from './components/DefaultAgentScreen';
|
||||||
export declare const hello: () => void;
|
export declare const hello: () => void;
|
||||||
export { getSampleAgentSteps, getSampleAgentSessions, type AgentStep, type AgentSession } from './utils/sample-data';
|
export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
// Entry point for @ra-aid/common package
|
// Entry point for @ra-aid/common package
|
||||||
import './styles/global.css';
|
import './styles/global.css';
|
||||||
// Export utility functions (excluding sample data to avoid circular references)
|
// Export types first to avoid circular references
|
||||||
|
export * from './utils/types';
|
||||||
|
// Export utility functions
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
// Export all UI components
|
// Export UI components
|
||||||
export * from './components/ui';
|
export * from './components/ui';
|
||||||
// Export timeline components
|
// Export timeline components
|
||||||
export * from './components/TimelineStep';
|
export * from './components/TimelineStep';
|
||||||
|
|
@ -10,9 +12,11 @@ export * from './components/TimelineFeed';
|
||||||
// Export session navigation components
|
// Export session navigation components
|
||||||
export * from './components/SessionDrawer';
|
export * from './components/SessionDrawer';
|
||||||
export * from './components/SessionSidebar';
|
export * from './components/SessionSidebar';
|
||||||
|
// Export main screens
|
||||||
|
export * from './components/DefaultAgentScreen';
|
||||||
// Export the hello function (temporary example)
|
// Export the hello function (temporary example)
|
||||||
export const hello = () => {
|
export const hello = () => {
|
||||||
console.log("Hello from @ra-aid/common");
|
console.log("Hello from @ra-aid/common");
|
||||||
};
|
};
|
||||||
// Directly export sample data functions and types to avoid circular references
|
// Directly export sample data functions
|
||||||
export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data';
|
export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data';
|
||||||
|
|
|
||||||
|
|
@ -585,6 +585,9 @@ video {
|
||||||
.pointer-events-none {
|
.pointer-events-none {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.visible {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
.fixed {
|
.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
|
@ -594,6 +597,9 @@ video {
|
||||||
.relative {
|
.relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
.inset-0 {
|
.inset-0 {
|
||||||
inset: 0px;
|
inset: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -608,6 +614,9 @@ video {
|
||||||
.bottom-0 {
|
.bottom-0 {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
.bottom-6 {
|
||||||
|
bottom: 1.5rem;
|
||||||
|
}
|
||||||
.left-0 {
|
.left-0 {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -617,21 +626,52 @@ video {
|
||||||
.right-4 {
|
.right-4 {
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
}
|
}
|
||||||
|
.right-6 {
|
||||||
|
right: 1.5rem;
|
||||||
|
}
|
||||||
.top-0 {
|
.top-0 {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
.top-16 {
|
||||||
|
top: 4rem;
|
||||||
|
}
|
||||||
.top-4 {
|
.top-4 {
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
}
|
}
|
||||||
|
.z-20 {
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.z-30 {
|
||||||
|
z-index: 30;
|
||||||
|
}
|
||||||
.z-50 {
|
.z-50 {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
.col-span-full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
.col-start-1 {
|
||||||
|
grid-column-start: 1;
|
||||||
|
}
|
||||||
|
.row-start-2 {
|
||||||
|
grid-row-start: 2;
|
||||||
|
}
|
||||||
|
.mx-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
.mb-2 {
|
.mb-2 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
.mb-5 {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
.mr-1 {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
.mr-2 {
|
.mr-2 {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
@ -644,8 +684,8 @@ video {
|
||||||
.mt-1\.5 {
|
.mt-1\.5 {
|
||||||
margin-top: 0.375rem;
|
margin-top: 0.375rem;
|
||||||
}
|
}
|
||||||
.mt-3 {
|
.mt-4 {
|
||||||
margin-top: 0.75rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
.mt-6 {
|
.mt-6 {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
|
|
@ -653,44 +693,68 @@ video {
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.inline-flex {
|
.inline-flex {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.h-10 {
|
.h-10 {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
.h-14 {
|
||||||
|
height: 3.5rem;
|
||||||
|
}
|
||||||
|
.h-16 {
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
.h-2\.5 {
|
.h-2\.5 {
|
||||||
height: 0.625rem;
|
height: 0.625rem;
|
||||||
}
|
}
|
||||||
.h-3 {
|
.h-3 {
|
||||||
height: 0.75rem;
|
height: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.h-3\.5 {
|
||||||
|
height: 0.875rem;
|
||||||
|
}
|
||||||
.h-4 {
|
.h-4 {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
.h-5 {
|
.h-5 {
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.h-6 {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
.h-8 {
|
.h-8 {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
.h-9 {
|
.h-9 {
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
}
|
}
|
||||||
.h-\[calc\(100vh-5rem\)\] {
|
.h-\[calc\(100vh-64px\)\] {
|
||||||
height: calc(100vh - 5rem);
|
height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
.h-\[calc\(100vh-9rem\)\] {
|
||||||
|
height: calc(100vh - 9rem);
|
||||||
}
|
}
|
||||||
.h-full {
|
.h-full {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.h-screen {
|
.min-h-screen {
|
||||||
height: 100vh;
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.w-14 {
|
||||||
|
width: 3.5rem;
|
||||||
}
|
}
|
||||||
.w-2\.5 {
|
.w-2\.5 {
|
||||||
width: 0.625rem;
|
width: 0.625rem;
|
||||||
|
|
@ -698,21 +762,24 @@ video {
|
||||||
.w-3 {
|
.w-3 {
|
||||||
width: 0.75rem;
|
width: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.w-3\.5 {
|
||||||
|
width: 0.875rem;
|
||||||
|
}
|
||||||
.w-3\/4 {
|
.w-3\/4 {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
}
|
}
|
||||||
.w-4 {
|
.w-4 {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
.w-5 {
|
.w-6 {
|
||||||
width: 1.25rem;
|
width: 1.5rem;
|
||||||
|
}
|
||||||
|
.w-8 {
|
||||||
|
width: 2rem;
|
||||||
}
|
}
|
||||||
.w-9 {
|
.w-9 {
|
||||||
width: 2.25rem;
|
width: 2.25rem;
|
||||||
}
|
}
|
||||||
.w-\[250px\] {
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
.w-\[85\%\] {
|
.w-\[85\%\] {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
}
|
}
|
||||||
|
|
@ -722,8 +789,8 @@ video {
|
||||||
.min-w-0 {
|
.min-w-0 {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
}
|
}
|
||||||
.max-w-xs {
|
.max-w-md {
|
||||||
max-width: 20rem;
|
max-width: 28rem;
|
||||||
}
|
}
|
||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
|
|
@ -745,6 +812,15 @@ video {
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
.resize {
|
||||||
|
resize: both;
|
||||||
|
}
|
||||||
|
.grid-cols-1 {
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.grid-rows-\[64px_1fr\] {
|
||||||
|
grid-template-rows: 64px 1fr;
|
||||||
|
}
|
||||||
.flex-col {
|
.flex-col {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
@ -772,6 +848,11 @@ video {
|
||||||
.gap-4 {
|
.gap-4 {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-x-reverse: 0;
|
||||||
|
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
|
||||||
|
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
|
}
|
||||||
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
|
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
|
margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
|
@ -787,11 +868,19 @@ video {
|
||||||
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
.space-y-5 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(1.25rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
.overflow-auto {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
.overflow-hidden {
|
.overflow-hidden {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.overflow-x-auto {
|
.overflow-y-auto {
|
||||||
overflow-x: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.truncate {
|
.truncate {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -804,9 +893,6 @@ video {
|
||||||
.whitespace-pre-wrap {
|
.whitespace-pre-wrap {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
.rounded {
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
.rounded-\[inherit\] {
|
.rounded-\[inherit\] {
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
@ -831,6 +917,9 @@ video {
|
||||||
.border-b {
|
.border-b {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
.border-b-2 {
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
}
|
||||||
.border-l {
|
.border-l {
|
||||||
border-left-width: 1px;
|
border-left-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
@ -840,15 +929,33 @@ video {
|
||||||
.border-t {
|
.border-t {
|
||||||
border-top-width: 1px;
|
border-top-width: 1px;
|
||||||
}
|
}
|
||||||
|
.border-dashed {
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
.border-blue-300 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(147 197 253 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
.border-border {
|
.border-border {
|
||||||
border-color: hsl(var(--border));
|
border-color: hsl(var(--border));
|
||||||
}
|
}
|
||||||
|
.border-border\/50 {
|
||||||
|
border-color: hsl(var(--border) / 0.5);
|
||||||
|
}
|
||||||
.border-input {
|
.border-input {
|
||||||
border-color: hsl(var(--input));
|
border-color: hsl(var(--input));
|
||||||
}
|
}
|
||||||
.border-transparent {
|
.border-transparent {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
.border-white {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.border-zinc-700 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(63 63 70 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
.border-l-transparent {
|
.border-l-transparent {
|
||||||
border-left-color: transparent;
|
border-left-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
@ -868,12 +975,19 @@ video {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
.bg-blue-600 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
.bg-border {
|
.bg-border {
|
||||||
background-color: hsl(var(--border));
|
background-color: hsl(var(--border));
|
||||||
}
|
}
|
||||||
.bg-card {
|
.bg-card {
|
||||||
background-color: hsl(var(--card));
|
background-color: hsl(var(--card));
|
||||||
}
|
}
|
||||||
|
.bg-card\/50 {
|
||||||
|
background-color: hsl(var(--card) / 0.5);
|
||||||
|
}
|
||||||
.bg-destructive {
|
.bg-destructive {
|
||||||
background-color: hsl(var(--destructive));
|
background-color: hsl(var(--destructive));
|
||||||
}
|
}
|
||||||
|
|
@ -902,12 +1016,42 @@ video {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1));
|
background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
.bg-zinc-800 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(39 39 42 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.bg-gradient-to-r {
|
||||||
|
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
||||||
|
}
|
||||||
|
.from-blue-400 {
|
||||||
|
--tw-gradient-from: #60a5fa var(--tw-gradient-from-position);
|
||||||
|
--tw-gradient-to: rgb(96 165 250 / 0) var(--tw-gradient-to-position);
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||||
|
}
|
||||||
|
.from-purple-100 {
|
||||||
|
--tw-gradient-from: #f3e8ff var(--tw-gradient-from-position);
|
||||||
|
--tw-gradient-to: rgb(243 232 255 / 0) var(--tw-gradient-to-position);
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||||
|
}
|
||||||
|
.to-blue-100 {
|
||||||
|
--tw-gradient-to: #dbeafe var(--tw-gradient-to-position);
|
||||||
|
}
|
||||||
|
.to-purple-500 {
|
||||||
|
--tw-gradient-to: #a855f7 var(--tw-gradient-to-position);
|
||||||
|
}
|
||||||
|
.bg-clip-text {
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
.p-3 {
|
.p-3 {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
.p-4 {
|
.p-4 {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
.p-6 {
|
.p-6 {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
@ -930,21 +1074,22 @@ video {
|
||||||
padding-left: 2rem;
|
padding-left: 2rem;
|
||||||
padding-right: 2rem;
|
padding-right: 2rem;
|
||||||
}
|
}
|
||||||
|
.py-0\.5 {
|
||||||
|
padding-top: 0.125rem;
|
||||||
|
padding-bottom: 0.125rem;
|
||||||
|
}
|
||||||
.py-1 {
|
.py-1 {
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.py-12 {
|
||||||
|
padding-top: 3rem;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
}
|
||||||
.py-2 {
|
.py-2 {
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.py-8 {
|
|
||||||
padding-top: 2rem;
|
|
||||||
padding-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.pb-2 {
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
.pt-0 {
|
.pt-0 {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -957,6 +1102,10 @@ video {
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.text-2xl {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
.text-lg {
|
.text-lg {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
|
|
@ -965,10 +1114,17 @@ video {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.text-xl {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
.text-xs {
|
.text-xs {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
}
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
.font-medium {
|
.font-medium {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
@ -981,6 +1137,9 @@ video {
|
||||||
.leading-none {
|
.leading-none {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
.leading-relaxed {
|
||||||
|
line-height: 1.625;
|
||||||
|
}
|
||||||
.tracking-tight {
|
.tracking-tight {
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
|
|
@ -996,6 +1155,9 @@ video {
|
||||||
.text-muted-foreground {
|
.text-muted-foreground {
|
||||||
color: hsl(var(--muted-foreground));
|
color: hsl(var(--muted-foreground));
|
||||||
}
|
}
|
||||||
|
.text-muted-foreground\/50 {
|
||||||
|
color: hsl(var(--muted-foreground) / 0.5);
|
||||||
|
}
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: hsl(var(--primary));
|
color: hsl(var(--primary));
|
||||||
}
|
}
|
||||||
|
|
@ -1005,6 +1167,17 @@ video {
|
||||||
.text-secondary-foreground {
|
.text-secondary-foreground {
|
||||||
color: hsl(var(--secondary-foreground));
|
color: hsl(var(--secondary-foreground));
|
||||||
}
|
}
|
||||||
|
.text-transparent {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.text-white {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.text-zinc-100 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(244 244 245 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
.underline-offset-4 {
|
.underline-offset-4 {
|
||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
}
|
}
|
||||||
|
|
@ -1026,6 +1199,11 @@ video {
|
||||||
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
.shadow-xl {
|
||||||
|
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||||
|
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
}
|
||||||
.outline {
|
.outline {
|
||||||
outline-style: solid;
|
outline-style: solid;
|
||||||
}
|
}
|
||||||
|
|
@ -1034,6 +1212,14 @@ video {
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
}
|
}
|
||||||
|
.ring-1 {
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
|
}
|
||||||
|
.ring-ring\/20 {
|
||||||
|
--tw-ring-color: hsl(var(--ring) / 0.2);
|
||||||
|
}
|
||||||
.ring-offset-background {
|
.ring-offset-background {
|
||||||
--tw-ring-offset-color: hsl(var(--background));
|
--tw-ring-offset-color: hsl(var(--background));
|
||||||
}
|
}
|
||||||
|
|
@ -1120,9 +1306,16 @@ video {
|
||||||
.hover\:bg-accent:hover {
|
.hover\:bg-accent:hover {
|
||||||
background-color: hsl(var(--accent));
|
background-color: hsl(var(--accent));
|
||||||
}
|
}
|
||||||
|
.hover\:bg-accent\/30:hover {
|
||||||
|
background-color: hsl(var(--accent) / 0.3);
|
||||||
|
}
|
||||||
.hover\:bg-accent\/50:hover {
|
.hover\:bg-accent\/50:hover {
|
||||||
background-color: hsl(var(--accent) / 0.5);
|
background-color: hsl(var(--accent) / 0.5);
|
||||||
}
|
}
|
||||||
|
.hover\:bg-blue-700:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
.hover\:bg-destructive\/90:hover {
|
.hover\:bg-destructive\/90:hover {
|
||||||
background-color: hsl(var(--destructive) / 0.9);
|
background-color: hsl(var(--destructive) / 0.9);
|
||||||
}
|
}
|
||||||
|
|
@ -1132,6 +1325,10 @@ video {
|
||||||
.hover\:bg-secondary\/80:hover {
|
.hover\:bg-secondary\/80:hover {
|
||||||
background-color: hsl(var(--secondary) / 0.8);
|
background-color: hsl(var(--secondary) / 0.8);
|
||||||
}
|
}
|
||||||
|
.hover\:bg-zinc-700:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
.hover\:text-accent-foreground:hover {
|
.hover\:text-accent-foreground:hover {
|
||||||
color: hsl(var(--accent-foreground));
|
color: hsl(var(--accent-foreground));
|
||||||
}
|
}
|
||||||
|
|
@ -1141,6 +1338,11 @@ video {
|
||||||
.hover\:opacity-100:hover {
|
.hover\:opacity-100:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
.hover\:shadow-md:hover {
|
||||||
|
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
}
|
||||||
.focus\:outline-none:focus {
|
.focus\:outline-none:focus {
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
|
|
@ -1188,6 +1390,11 @@ video {
|
||||||
.disabled\:opacity-50:disabled {
|
.disabled\:opacity-50:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
.group:hover .group-hover\:scale-110 {
|
||||||
|
--tw-scale-x: 1.1;
|
||||||
|
--tw-scale-y: 1.1;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
.data-\[state\=checked\]\:translate-x-4[data-state="checked"] {
|
.data-\[state\=checked\]\:translate-x-4[data-state="checked"] {
|
||||||
--tw-translate-x: 1rem;
|
--tw-translate-x: 1rem;
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
|
@ -1291,6 +1498,26 @@ video {
|
||||||
.data-\[state\=open\]\:duration-500[data-state="open"] {
|
.data-\[state\=open\]\:duration-500[data-state="open"] {
|
||||||
animation-duration: 500ms;
|
animation-duration: 500ms;
|
||||||
}
|
}
|
||||||
|
.dark\:border-blue-700:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(29 78 216 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.dark\:border-gray-800:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(31 41 55 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.dark\:border-zinc-600:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(82 82 91 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.dark\:from-purple-900:is(.dark *) {
|
||||||
|
--tw-gradient-from: #581c87 var(--tw-gradient-from-position);
|
||||||
|
--tw-gradient-to: rgb(88 28 135 / 0) var(--tw-gradient-to-position);
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||||
|
}
|
||||||
|
.dark\:to-blue-900:is(.dark *) {
|
||||||
|
--tw-gradient-to: #1e3a8a var(--tw-gradient-to-position);
|
||||||
|
}
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
|
||||||
.sm\:max-w-md {
|
.sm\:max-w-md {
|
||||||
|
|
@ -1321,6 +1548,10 @@ video {
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|
||||||
|
.md\:col-start-2 {
|
||||||
|
grid-column-start: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.md\:block {
|
.md\:block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
@ -1328,10 +1559,14 @@ video {
|
||||||
.md\:hidden {
|
.md\:hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md\:grid-cols-\[250px_1fr\] {
|
||||||
|
grid-template-columns: 250px 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|
||||||
.lg\:w-\[300px\] {
|
.lg\:grid-cols-\[300px_1fr\] {
|
||||||
width: 300px;
|
grid-template-columns: 300px 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
"name": "@ra-aid/common",
|
"name": "@ra-aid/common",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/index.js",
|
"main": "src/index.ts",
|
||||||
"types": "dist/index.d.ts",
|
"types": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && postcss src/styles/global.css -o dist/styles/global.css",
|
"build": "tsc && postcss src/styles/global.css -o dist/styles/global.css",
|
||||||
"dev": "concurrently \"tsc --watch\" \"postcss src/styles/global.css -o dist/styles/global.css --watch\""
|
"dev": "tsc --watch",
|
||||||
|
"watch:css": "postcss src/styles/global.css -o dist/styles/global.css --watch",
|
||||||
|
"watch": "concurrently \"npm run dev\" \"npm run watch:css\"",
|
||||||
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-collapsible": "^1.1.3",
|
"@radix-ui/react-collapsible": "^1.1.3",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { PanelLeft } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Layout
|
||||||
|
} from './ui';
|
||||||
|
import { SessionDrawer } from './SessionDrawer';
|
||||||
|
import { SessionSidebar } from './SessionSidebar';
|
||||||
|
import { TimelineFeed } from './TimelineFeed';
|
||||||
|
import { getSampleAgentSessions, getSampleAgentSteps } from '../utils/sample-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultAgentScreen component
|
||||||
|
*
|
||||||
|
* Main application screen for displaying agent sessions and their steps.
|
||||||
|
* Handles state management, responsive design, and UI interactions.
|
||||||
|
*/
|
||||||
|
export const DefaultAgentScreen: React.FC = () => {
|
||||||
|
// State for drawer open/close
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
|
||||||
|
// State for selected session
|
||||||
|
const [selectedSessionId, setSelectedSessionId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// State for theme (dark is default)
|
||||||
|
const [isDarkTheme, setIsDarkTheme] = useState(true);
|
||||||
|
|
||||||
|
// Get sample data
|
||||||
|
const sessions = getSampleAgentSessions();
|
||||||
|
const allSteps = getSampleAgentSteps();
|
||||||
|
|
||||||
|
// Set up theme on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
const isDark = setupTheme();
|
||||||
|
setIsDarkTheme(isDark);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Set initial selected session if none selected
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedSessionId && sessions.length > 0) {
|
||||||
|
setSelectedSessionId(sessions[0].id);
|
||||||
|
}
|
||||||
|
}, [sessions, selectedSessionId]);
|
||||||
|
|
||||||
|
// Close drawer when window resizes to desktop width
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
// Check if we're at desktop size (corresponds to md: breakpoint in Tailwind)
|
||||||
|
if (window.innerWidth >= 768 && isDrawerOpen) {
|
||||||
|
setIsDrawerOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// Clean up event listener on component unmount
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [isDrawerOpen]);
|
||||||
|
|
||||||
|
// Filter steps for selected session
|
||||||
|
const selectedSessionSteps = selectedSessionId
|
||||||
|
? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Handle session selection
|
||||||
|
const handleSessionSelect = (sessionId: string) => {
|
||||||
|
setSelectedSessionId(sessionId);
|
||||||
|
setIsDrawerOpen(false); // Close drawer on selection (mobile)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle theme function
|
||||||
|
const toggleTheme = () => {
|
||||||
|
const newIsDark = !isDarkTheme;
|
||||||
|
setIsDarkTheme(newIsDark);
|
||||||
|
|
||||||
|
// Update document element class
|
||||||
|
if (newIsDark) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to localStorage
|
||||||
|
localStorage.setItem('theme', newIsDark ? 'dark' : 'light');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render header content
|
||||||
|
const headerContent = (
|
||||||
|
<div className="flex justify-between items-center h-full px-4">
|
||||||
|
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 inline-block text-transparent bg-clip-text">
|
||||||
|
RA.Aid
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{/* Theme toggle button */}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
aria-label={isDarkTheme ? "Switch to light mode" : "Switch to dark mode"}
|
||||||
|
className="mr-2"
|
||||||
|
>
|
||||||
|
{isDarkTheme ? (
|
||||||
|
// Sun icon for light mode toggle
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="5" />
|
||||||
|
<line x1="12" y1="1" x2="12" y2="3" />
|
||||||
|
<line x1="12" y1="21" x2="12" y2="23" />
|
||||||
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||||
|
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||||
|
<line x1="1" y1="12" x2="3" y2="12" />
|
||||||
|
<line x1="21" y1="12" x2="23" y2="12" />
|
||||||
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||||
|
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
// Moon icon for dark mode toggle
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render sidebar content
|
||||||
|
const sidebarContent = (
|
||||||
|
<SessionSidebar
|
||||||
|
sessions={sessions}
|
||||||
|
currentSessionId={selectedSessionId || undefined}
|
||||||
|
onSelectSession={handleSessionSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render drawer
|
||||||
|
const drawerContent = (
|
||||||
|
<SessionDrawer
|
||||||
|
sessions={sessions}
|
||||||
|
currentSessionId={selectedSessionId || undefined}
|
||||||
|
onSelectSession={handleSessionSelect}
|
||||||
|
isOpen={isDrawerOpen}
|
||||||
|
onClose={() => setIsDrawerOpen(false)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render main content
|
||||||
|
const mainContent = (
|
||||||
|
selectedSessionId ? (
|
||||||
|
<>
|
||||||
|
<h2 className="text-xl font-semibold mb-4">
|
||||||
|
Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'}
|
||||||
|
</h2>
|
||||||
|
<TimelineFeed
|
||||||
|
steps={selectedSessionSteps}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<p className="text-muted-foreground">Select a session to view details</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create floating action button for mobile sidebar toggle
|
||||||
|
const floatingAction = (
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setIsDrawerOpen(true)}
|
||||||
|
aria-label="Toggle sessions panel"
|
||||||
|
className="h-14 w-14 rounded-full shadow-xl bg-zinc-800 hover:bg-zinc-700 text-zinc-100 flex items-center justify-center border-2 border-zinc-700 dark:border-zinc-600"
|
||||||
|
>
|
||||||
|
<PanelLeft className="h-6 w-6" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout
|
||||||
|
header={headerContent}
|
||||||
|
sidebar={sidebarContent}
|
||||||
|
drawer={drawerContent}
|
||||||
|
>
|
||||||
|
{mainContent}
|
||||||
|
</Layout>
|
||||||
|
<div className="fixed bottom-6 right-6 z-50 md:hidden" style={{zIndex: 9999}}>
|
||||||
|
{floatingAction}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function for theme setup
|
||||||
|
const setupTheme = () => {
|
||||||
|
// Check if theme preference is stored in localStorage
|
||||||
|
const storedTheme = localStorage.getItem('theme');
|
||||||
|
|
||||||
|
// Default to dark mode unless explicitly set to light
|
||||||
|
const isDark = storedTheme ? storedTheme === 'dark' : true;
|
||||||
|
|
||||||
|
// Apply theme to document
|
||||||
|
if (isDark) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDark;
|
||||||
|
};
|
||||||
|
|
@ -1,89 +1,46 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Menu } from 'lucide-react';
|
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetTrigger,
|
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetClose
|
SheetClose
|
||||||
} from './ui/sheet';
|
} from './ui/sheet';
|
||||||
import { Button } from './ui/button';
|
import { AgentSession } from '../utils/types';
|
||||||
import { ScrollArea } from './ui/scroll-area';
|
import { getSampleAgentSessions } from '../utils/sample-data';
|
||||||
import { AgentSession, getSampleAgentSessions } from '../utils/sample-data';
|
import { SessionList } from './SessionList';
|
||||||
|
|
||||||
interface SessionDrawerProps {
|
interface SessionDrawerProps {
|
||||||
onSelectSession?: (sessionId: string) => void;
|
onSelectSession?: (sessionId: string) => void;
|
||||||
currentSessionId?: string;
|
currentSessionId?: string;
|
||||||
sessions?: AgentSession[];
|
sessions?: AgentSession[];
|
||||||
|
isOpen?: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SessionDrawer: React.FC<SessionDrawerProps> = ({
|
export const SessionDrawer: React.FC<SessionDrawerProps> = ({
|
||||||
onSelectSession,
|
onSelectSession,
|
||||||
currentSessionId,
|
currentSessionId,
|
||||||
sessions = getSampleAgentSessions()
|
sessions = getSampleAgentSessions(),
|
||||||
|
isOpen = false,
|
||||||
|
onClose
|
||||||
}) => {
|
}) => {
|
||||||
// Get status color
|
|
||||||
const getStatusColor = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'active':
|
|
||||||
return 'bg-blue-500';
|
|
||||||
case 'completed':
|
|
||||||
return 'bg-green-500';
|
|
||||||
case 'error':
|
|
||||||
return 'bg-red-500';
|
|
||||||
default:
|
|
||||||
return 'bg-gray-500';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format timestamp
|
|
||||||
const formatDate = (date: Date) => {
|
|
||||||
return date.toLocaleDateString([], {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet>
|
<Sheet open={isOpen} onOpenChange={onClose}>
|
||||||
<SheetTrigger asChild>
|
<SheetContent
|
||||||
<Button variant="ghost" size="icon" className="md:hidden">
|
side="left"
|
||||||
<Menu className="h-5 w-5" />
|
className="w-[85%] sm:max-w-md border-r border-border"
|
||||||
<span className="sr-only">Toggle navigation</span>
|
>
|
||||||
</Button>
|
|
||||||
</SheetTrigger>
|
|
||||||
<SheetContent side="left" className="w-[85%] sm:max-w-md">
|
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Sessions</SheetTitle>
|
<SheetTitle>Sessions</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<ScrollArea className="h-[calc(100vh-5rem)] mt-6">
|
<SessionList
|
||||||
<div className="space-y-4">
|
sessions={sessions}
|
||||||
{sessions.map((session) => (
|
currentSessionId={currentSessionId}
|
||||||
<SheetClose key={session.id} asChild>
|
onSelectSession={onSelectSession}
|
||||||
<button
|
className="h-[calc(100vh-9rem)] mt-6"
|
||||||
onClick={() => onSelectSession?.(session.id)}
|
wrapperComponent={SheetClose}
|
||||||
className={`w-full flex items-start p-3 text-left rounded-md transition-colors hover:bg-accent/50 ${
|
/>
|
||||||
currentSessionId === session.id ? 'bg-accent' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(session.status)} mt-1.5 mr-3 flex-shrink-0`} />
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="font-medium truncate">{session.name}</div>
|
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
|
||||||
{session.steps.length} steps • {formatDate(session.updated)}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
|
||||||
<span className="capitalize">{session.status}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</SheetClose>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ScrollArea } from './ui/scroll-area';
|
||||||
|
import { AgentSession } from '../utils/types';
|
||||||
|
import { getSampleAgentSessions } from '../utils/sample-data';
|
||||||
|
|
||||||
|
interface SessionListProps {
|
||||||
|
onSelectSession?: (sessionId: string) => void;
|
||||||
|
currentSessionId?: string;
|
||||||
|
sessions?: AgentSession[];
|
||||||
|
className?: string;
|
||||||
|
wrapperComponent?: React.ElementType;
|
||||||
|
closeAction?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SessionList: React.FC<SessionListProps> = ({
|
||||||
|
onSelectSession,
|
||||||
|
currentSessionId,
|
||||||
|
sessions = getSampleAgentSessions(),
|
||||||
|
className = '',
|
||||||
|
wrapperComponent: WrapperComponent = 'button',
|
||||||
|
closeAction
|
||||||
|
}) => {
|
||||||
|
// Get status color
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active':
|
||||||
|
return 'bg-blue-500';
|
||||||
|
case 'completed':
|
||||||
|
return 'bg-green-500';
|
||||||
|
case 'error':
|
||||||
|
return 'bg-red-500';
|
||||||
|
default:
|
||||||
|
return 'bg-gray-500';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format timestamp
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
return date.toLocaleDateString([], {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea className={`${className}`}>
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
{sessions.map((session) => {
|
||||||
|
const buttonContent = (
|
||||||
|
<>
|
||||||
|
<div className={`w-3 h-3 rounded-full ${getStatusColor(session.status)} mt-1.5 mr-3 flex-shrink-0`} />
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="font-medium truncate">{session.name}</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
{session.steps.length} steps • {formatDate(session.updated)}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
<span className="capitalize">{session.status}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
WrapperComponent,
|
||||||
|
{
|
||||||
|
key: session.id,
|
||||||
|
onClick: () => onSelectSession?.(session.id),
|
||||||
|
className: `w-full flex items-start p-3 text-left rounded-md transition-colors hover:bg-accent/50 ${
|
||||||
|
currentSessionId === session.id ? 'bg-accent' : ''
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
closeAction ? (
|
||||||
|
<>
|
||||||
|
{buttonContent}
|
||||||
|
{React.cloneElement(closeAction as React.ReactElement, {
|
||||||
|
onClick: (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSelectSession?.(session.id);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : buttonContent
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollArea } from './ui/scroll-area';
|
import { AgentSession } from '../utils/types';
|
||||||
import { AgentSession, getSampleAgentSessions } from '../utils/sample-data';
|
import { getSampleAgentSessions } from '../utils/sample-data';
|
||||||
|
import { SessionList } from './SessionList';
|
||||||
|
|
||||||
interface SessionSidebarProps {
|
interface SessionSidebarProps {
|
||||||
onSelectSession?: (sessionId: string) => void;
|
onSelectSession?: (sessionId: string) => void;
|
||||||
|
|
@ -15,59 +16,17 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||||
sessions = getSampleAgentSessions(),
|
sessions = getSampleAgentSessions(),
|
||||||
className = ''
|
className = ''
|
||||||
}) => {
|
}) => {
|
||||||
// Get status color
|
|
||||||
const getStatusColor = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'active':
|
|
||||||
return 'bg-blue-500';
|
|
||||||
case 'completed':
|
|
||||||
return 'bg-green-500';
|
|
||||||
case 'error':
|
|
||||||
return 'bg-red-500';
|
|
||||||
default:
|
|
||||||
return 'bg-gray-500';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format timestamp
|
|
||||||
const formatDate = (date: Date) => {
|
|
||||||
return date.toLocaleDateString([], {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`hidden md:block w-[250px] lg:w-[300px] h-screen border-r border-border ${className}`}>
|
<div className={`flex flex-col h-full ${className}`}>
|
||||||
<div className="p-4 border-b border-border">
|
<div className="p-4 border-b border-border">
|
||||||
<h3 className="font-medium text-lg">Sessions</h3>
|
<h3 className="font-medium text-lg">Sessions</h3>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className="h-[calc(100vh-5rem)]">
|
<SessionList
|
||||||
<div className="p-4 space-y-4">
|
sessions={sessions}
|
||||||
{sessions.map((session) => (
|
currentSessionId={currentSessionId}
|
||||||
<button
|
onSelectSession={onSelectSession}
|
||||||
key={session.id}
|
className="flex-1"
|
||||||
onClick={() => onSelectSession?.(session.id)}
|
/>
|
||||||
className={`w-full flex items-start p-3 text-left rounded-md transition-colors hover:bg-accent/50 ${
|
|
||||||
currentSessionId === session.id ? 'bg-accent' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(session.status)} mt-1.5 mr-3 flex-shrink-0`} />
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="font-medium truncate">{session.name}</div>
|
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
|
||||||
{session.steps.length} steps • {formatDate(session.updated)}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
|
||||||
<span className="capitalize">{session.status}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -1,123 +1,45 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { ScrollArea } from './ui/scroll-area';
|
|
||||||
import { TimelineStep } from './TimelineStep';
|
import { TimelineStep } from './TimelineStep';
|
||||||
import { AgentStep } from '../utils/sample-data';
|
import { AgentStep } from '../utils/types';
|
||||||
|
|
||||||
interface TimelineFeedProps {
|
interface TimelineFeedProps {
|
||||||
steps: AgentStep[];
|
steps: AgentStep[];
|
||||||
maxHeight?: string;
|
maxHeight?: string;
|
||||||
filter?: {
|
|
||||||
types?: string[];
|
|
||||||
status?: string[];
|
|
||||||
};
|
|
||||||
sortOrder?: 'asc' | 'desc';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TimelineFeed: React.FC<TimelineFeedProps> = ({
|
export const TimelineFeed: React.FC<TimelineFeedProps> = ({
|
||||||
steps,
|
steps,
|
||||||
maxHeight = '500px',
|
maxHeight
|
||||||
filter,
|
|
||||||
sortOrder = 'desc'
|
|
||||||
}) => {
|
}) => {
|
||||||
// State for filtered and sorted steps
|
// Always use 'desc' (newest first) sort order
|
||||||
const [activeFilter, setActiveFilter] = useState(filter);
|
const sortOrder = 'desc';
|
||||||
const [activeSortOrder, setActiveSortOrder] = useState<'asc' | 'desc'>(sortOrder);
|
|
||||||
|
// Sort steps with newest first (desc order)
|
||||||
// Apply filters and sorting
|
const sortedSteps = useMemo(() => {
|
||||||
const filteredSteps = steps.filter(step => {
|
return [...steps].sort((a, b) => {
|
||||||
if (!activeFilter) return true;
|
|
||||||
|
|
||||||
const typeMatch = !activeFilter.types || activeFilter.types.length === 0 ||
|
|
||||||
activeFilter.types.includes(step.type);
|
|
||||||
|
|
||||||
const statusMatch = !activeFilter.status || activeFilter.status.length === 0 ||
|
|
||||||
activeFilter.status.includes(step.status);
|
|
||||||
|
|
||||||
return typeMatch && statusMatch;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort steps
|
|
||||||
const sortedSteps = [...filteredSteps].sort((a, b) => {
|
|
||||||
if (activeSortOrder === 'asc') {
|
|
||||||
return a.timestamp.getTime() - b.timestamp.getTime();
|
|
||||||
} else {
|
|
||||||
return b.timestamp.getTime() - a.timestamp.getTime();
|
return b.timestamp.getTime() - a.timestamp.getTime();
|
||||||
}
|
});
|
||||||
});
|
}, [steps]);
|
||||||
|
|
||||||
// Toggle sort order
|
|
||||||
const toggleSortOrder = () => {
|
|
||||||
setActiveSortOrder(prevOrder => prevOrder === 'asc' ? 'desc' : 'asc');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter by type
|
|
||||||
const filterTypes = [
|
|
||||||
'all',
|
|
||||||
'tool-execution',
|
|
||||||
'thinking',
|
|
||||||
'planning',
|
|
||||||
'implementation',
|
|
||||||
'user-input'
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleFilterChange = (type: string) => {
|
|
||||||
if (type === 'all') {
|
|
||||||
setActiveFilter({
|
|
||||||
...activeFilter,
|
|
||||||
types: []
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setActiveFilter({
|
|
||||||
...activeFilter,
|
|
||||||
types: [type]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full border border-border rounded-md bg-background">
|
<div className="w-full rounded-md bg-background">
|
||||||
<div className="p-3 border-b border-border">
|
<div
|
||||||
<div className="flex justify-between items-center mb-2">
|
className="p-4 space-y-5 overflow-auto"
|
||||||
<h3 className="font-medium">Timeline Feed</h3>
|
style={{ maxHeight: maxHeight || undefined }}
|
||||||
<button
|
>
|
||||||
onClick={toggleSortOrder}
|
{sortedSteps.length > 0 ? (
|
||||||
className="text-xs bg-secondary hover:bg-secondary/80 text-secondary-foreground px-2 py-1 rounded"
|
sortedSteps.map((step) => (
|
||||||
>
|
<TimelineStep key={step.id} step={step} />
|
||||||
{activeSortOrder === 'asc' ? '⬆️ Oldest first' : '⬇️ Newest first'}
|
))
|
||||||
</button>
|
) : (
|
||||||
</div>
|
<div className="text-center text-muted-foreground py-12 border border-dashed border-border rounded-md">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mx-auto mb-2 text-muted-foreground/50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<div className="flex gap-2 overflow-x-auto pb-2 text-xs">
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
{filterTypes.map(type => (
|
</svg>
|
||||||
<button
|
<p>No steps to display</p>
|
||||||
key={type}
|
</div>
|
||||||
onClick={() => handleFilterChange(type)}
|
)}
|
||||||
className={`px-2 py-1 rounded whitespace-nowrap ${
|
|
||||||
type === 'all' && (!activeFilter?.types || activeFilter.types.length === 0) ||
|
|
||||||
activeFilter?.types?.includes(type)
|
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'bg-secondary/50 text-secondary-foreground hover:bg-secondary/80'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{type === 'all' ? 'All types' : type}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="h-full" style={{ maxHeight }}>
|
|
||||||
<div className="p-3">
|
|
||||||
{sortedSteps.length > 0 ? (
|
|
||||||
sortedSteps.map((step) => (
|
|
||||||
<TimelineStep key={step.id} step={step} />
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="text-center text-muted-foreground py-8">
|
|
||||||
No steps to display
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
|
||||||
import { AgentStep } from '../utils/sample-data';
|
import { AgentStep } from '../utils/types';
|
||||||
|
|
||||||
interface TimelineStepProps {
|
interface TimelineStepProps {
|
||||||
step: AgentStep;
|
step: AgentStep;
|
||||||
|
|
@ -47,34 +47,47 @@ export const TimelineStep: React.FC<TimelineStepProps> = ({ step }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible className="w-full mb-4 border border-border rounded-md overflow-hidden transition-all duration-200">
|
<Collapsible className="w-full mb-5 border border-border rounded-md overflow-hidden shadow-sm hover:shadow-md transition-all duration-200">
|
||||||
<CollapsibleTrigger className="w-full flex items-center justify-between p-3 text-left hover:bg-accent/50 cursor-pointer">
|
<CollapsibleTrigger className="w-full flex items-center justify-between p-4 text-left hover:bg-accent/30 cursor-pointer group">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center space-x-3">
|
||||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(step.status)} mr-3`} />
|
<div className={`w-3 h-3 rounded-full ${getStatusColor(step.status)} ring-1 ring-ring/20`} />
|
||||||
<div className="mr-2">{getTypeIcon(step.type)}</div>
|
<div className="text-lg group-hover:scale-110 transition-transform">{getTypeIcon(step.type)}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">{step.title}</div>
|
<div className="font-medium text-foreground">{step.title}</div>
|
||||||
<div className="text-sm text-muted-foreground truncate max-w-xs">
|
<div className="text-sm text-muted-foreground truncate max-w-md">
|
||||||
{step.type === 'tool-execution' ? 'Run tool' : step.content.substring(0, 60)}
|
{step.type === 'tool-execution' ? 'Run tool' : step.content.substring(0, 60)}
|
||||||
{step.content.length > 60 ? '...' : ''}
|
{step.content.length > 60 ? '...' : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground flex flex-col items-end">
|
<div className="text-xs text-muted-foreground flex flex-col items-end">
|
||||||
<span>{formatTime(step.timestamp)}</span>
|
<span className="font-medium">{formatTime(step.timestamp)}</span>
|
||||||
{step.duration && (
|
{step.duration && (
|
||||||
<span className="mt-1">{(step.duration / 1000).toFixed(1)}s</span>
|
<span className="mt-1 px-2 py-0.5 bg-secondary/50 rounded-full">
|
||||||
|
{(step.duration / 1000).toFixed(1)}s
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="p-4 bg-card border-t border-border">
|
<div className="p-5 bg-card/50 border-t border-border">
|
||||||
<div className="text-sm whitespace-pre-wrap">
|
<div className="text-sm whitespace-pre-wrap text-foreground leading-relaxed">
|
||||||
{step.content}
|
{step.content}
|
||||||
</div>
|
</div>
|
||||||
{step.duration && (
|
{step.duration && (
|
||||||
<div className="mt-3 pt-3 border-t border-border">
|
<div className="mt-4 pt-3 border-t border-border/50">
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground flex items-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-3.5 w-3.5 mr-1"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={2}
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
Duration: {(step.duration / 1000).toFixed(1)} seconds
|
Duration: {(step.duration / 1000).toFixed(1)} seconds
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { Button } from './button';
|
||||||
|
|
||||||
|
export interface FloatingActionButtonProps {
|
||||||
|
icon: ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
ariaLabel?: string;
|
||||||
|
className?: string;
|
||||||
|
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FloatingActionButton component
|
||||||
|
*
|
||||||
|
* A button typically used for primary actions on mobile layouts
|
||||||
|
* Designed to be used with the Layout component's floatingAction prop
|
||||||
|
*/
|
||||||
|
export const FloatingActionButton: React.FC<FloatingActionButtonProps> = ({
|
||||||
|
icon,
|
||||||
|
onClick,
|
||||||
|
ariaLabel = 'Action button',
|
||||||
|
className = '',
|
||||||
|
variant = 'default'
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={variant}
|
||||||
|
size="icon"
|
||||||
|
onClick={onClick}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
className={`h-14 w-14 rounded-full shadow-xl bg-blue-600 hover:bg-blue-700 text-white flex items-center justify-center border-2 border-white dark:border-gray-800 ${className}`}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './card';
|
export * from './card';
|
||||||
export * from './collapsible';
|
export * from './collapsible';
|
||||||
|
export * from './floating-action-button';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
|
export * from './layout';
|
||||||
export * from './sheet';
|
export * from './sheet';
|
||||||
export * from './switch';
|
export * from './switch';
|
||||||
export * from './scroll-area';
|
export * from './scroll-area';
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout component using Tailwind Grid utilities
|
||||||
|
* This component creates a responsive layout with:
|
||||||
|
* - Sticky header at the top (z-index 30)
|
||||||
|
* - Sidebar on desktop (hidden on mobile)
|
||||||
|
* - Main content area with proper positioning
|
||||||
|
* - Optional floating action button for mobile navigation
|
||||||
|
*/
|
||||||
|
export interface LayoutProps {
|
||||||
|
header: React.ReactNode;
|
||||||
|
sidebar?: React.ReactNode;
|
||||||
|
drawer?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
floatingAction?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Layout: React.FC<LayoutProps> = ({
|
||||||
|
header,
|
||||||
|
sidebar,
|
||||||
|
drawer,
|
||||||
|
children,
|
||||||
|
floatingAction
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="grid min-h-screen grid-cols-1 grid-rows-[64px_1fr] md:grid-cols-[250px_1fr] lg:grid-cols-[300px_1fr] bg-background text-foreground relative">
|
||||||
|
{/* Header - always visible, spans full width */}
|
||||||
|
<header className="sticky top-0 z-30 h-16 flex items-center bg-background border-b border-border col-span-full">
|
||||||
|
{header}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Sidebar - hidden on mobile, visible on tablet/desktop */}
|
||||||
|
{sidebar && (
|
||||||
|
<aside className="hidden md:block sticky top-16 h-[calc(100vh-64px)] overflow-y-auto z-20 bg-background border-r border-border row-start-2 col-start-1">
|
||||||
|
{sidebar}
|
||||||
|
</aside>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main content area */}
|
||||||
|
<main className="overflow-y-auto p-4 row-start-2 col-start-1 md:col-start-2">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Mobile drawer - rendered outside grid */}
|
||||||
|
{drawer}
|
||||||
|
|
||||||
|
{/* Floating action button for mobile */}
|
||||||
|
{floatingAction && (
|
||||||
|
<div className="fixed bottom-6 right-6 z-50 md:hidden">
|
||||||
|
{floatingAction}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -19,7 +19,7 @@ const SheetOverlay = React.forwardRef<
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SheetPrimitive.Overlay
|
<SheetPrimitive.Overlay
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
"fixed inset-0 z-70 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -29,7 +29,7 @@ const SheetOverlay = React.forwardRef<
|
||||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
const sheetVariants = cva(
|
const sheetVariants = cva(
|
||||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
"fixed z-70 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
side: {
|
side: {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
// Entry point for @ra-aid/common package
|
// Entry point for @ra-aid/common package
|
||||||
import './styles/global.css';
|
import './styles/global.css';
|
||||||
|
|
||||||
// Export utility functions (excluding sample data to avoid circular references)
|
// Export types first to avoid circular references
|
||||||
|
export * from './utils/types';
|
||||||
|
|
||||||
|
// Export utility functions
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|
||||||
// Export all UI components
|
// Export UI components
|
||||||
export * from './components/ui';
|
export * from './components/ui';
|
||||||
|
|
||||||
// Export timeline components
|
// Export timeline components
|
||||||
|
|
@ -15,15 +18,16 @@ export * from './components/TimelineFeed';
|
||||||
export * from './components/SessionDrawer';
|
export * from './components/SessionDrawer';
|
||||||
export * from './components/SessionSidebar';
|
export * from './components/SessionSidebar';
|
||||||
|
|
||||||
|
// Export main screens
|
||||||
|
export * from './components/DefaultAgentScreen';
|
||||||
|
|
||||||
// Export the hello function (temporary example)
|
// Export the hello function (temporary example)
|
||||||
export const hello = (): void => {
|
export const hello = (): void => {
|
||||||
console.log("Hello from @ra-aid/common");
|
console.log("Hello from @ra-aid/common");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Directly export sample data functions and types to avoid circular references
|
// Directly export sample data functions
|
||||||
export {
|
export {
|
||||||
getSampleAgentSteps,
|
getSampleAgentSteps,
|
||||||
getSampleAgentSessions,
|
getSampleAgentSessions
|
||||||
type AgentStep,
|
|
||||||
type AgentSession
|
|
||||||
} from './utils/sample-data';
|
} from './utils/sample-data';
|
||||||
|
|
@ -2,30 +2,7 @@
|
||||||
* Sample data utility for agent UI components demonstration
|
* Sample data utility for agent UI components demonstration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
import { AgentStep, AgentSession } from './types';
|
||||||
* Represents a single step in the agent process
|
|
||||||
*/
|
|
||||||
export interface AgentStep {
|
|
||||||
id: string;
|
|
||||||
timestamp: Date;
|
|
||||||
status: 'completed' | 'in-progress' | 'error' | 'pending';
|
|
||||||
type: 'tool-execution' | 'thinking' | 'planning' | 'implementation' | 'user-input';
|
|
||||||
title: string;
|
|
||||||
content: string;
|
|
||||||
duration?: number; // in milliseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a session with multiple steps
|
|
||||||
*/
|
|
||||||
export interface AgentSession {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
created: Date;
|
|
||||||
updated: Date;
|
|
||||||
status: 'active' | 'completed' | 'error';
|
|
||||||
steps: AgentStep[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of sample agent steps
|
* Returns an array of sample agent steps
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Common types for agent UI components
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single step in the agent process
|
||||||
|
*/
|
||||||
|
export interface AgentStep {
|
||||||
|
id: string;
|
||||||
|
timestamp: Date;
|
||||||
|
status: 'completed' | 'in-progress' | 'error' | 'pending';
|
||||||
|
type: 'tool-execution' | 'thinking' | 'planning' | 'implementation' | 'user-input';
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
duration?: number; // in milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a session with multiple steps
|
||||||
|
*/
|
||||||
|
export interface AgentSession {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
created: Date;
|
||||||
|
updated: Date;
|
||||||
|
status: 'active' | 'completed' | 'error';
|
||||||
|
steps: AgentStep[];
|
||||||
|
}
|
||||||
|
|
@ -1,211 +1,16 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import {
|
import { DefaultAgentScreen } from '@ra-aid/common';
|
||||||
Button,
|
|
||||||
SessionDrawer,
|
|
||||||
SessionSidebar,
|
|
||||||
TimelineFeed,
|
|
||||||
getSampleAgentSessions,
|
|
||||||
getSampleAgentSteps
|
|
||||||
} from '@ra-aid/common';
|
|
||||||
// The CSS import happens through the common package's index.ts
|
|
||||||
|
|
||||||
// Theme management helper function
|
|
||||||
const setupTheme = () => {
|
|
||||||
// Check if theme preference is stored in localStorage
|
|
||||||
const storedTheme = localStorage.getItem('theme');
|
|
||||||
|
|
||||||
// Default to dark mode unless explicitly set to light
|
|
||||||
const isDark = storedTheme ? storedTheme === 'dark' : true;
|
|
||||||
|
|
||||||
// Apply theme class to document element (html) for better CSS specificity
|
|
||||||
if (isDark) {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the current theme preference
|
|
||||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
||||||
|
|
||||||
return isDark;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application entry point
|
||||||
|
* Simply renders the DefaultAgentScreen component from the common package
|
||||||
|
*/
|
||||||
const App = () => {
|
const App = () => {
|
||||||
// State for drawer open/close
|
return <DefaultAgentScreen />;
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
||||||
|
|
||||||
// State for selected session
|
|
||||||
const [selectedSessionId, setSelectedSessionId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// State for theme (dark is default)
|
|
||||||
const [isDarkTheme, setIsDarkTheme] = useState(true);
|
|
||||||
|
|
||||||
// Get sample data
|
|
||||||
const sessions = getSampleAgentSessions();
|
|
||||||
const allSteps = getSampleAgentSteps();
|
|
||||||
|
|
||||||
// Set up theme on component mount
|
|
||||||
useEffect(() => {
|
|
||||||
const isDark = setupTheme();
|
|
||||||
setIsDarkTheme(isDark);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Set initial selected session if none selected
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selectedSessionId && sessions.length > 0) {
|
|
||||||
setSelectedSessionId(sessions[0].id);
|
|
||||||
}
|
|
||||||
}, [sessions, selectedSessionId]);
|
|
||||||
|
|
||||||
// Filter steps for selected session
|
|
||||||
const selectedSessionSteps = selectedSessionId
|
|
||||||
? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Handle session selection
|
|
||||||
const handleSessionSelect = (sessionId: string) => {
|
|
||||||
setSelectedSessionId(sessionId);
|
|
||||||
setIsDrawerOpen(false); // Close drawer on selection (mobile)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Toggle theme function
|
|
||||||
const toggleTheme = () => {
|
|
||||||
const newIsDark = !isDarkTheme;
|
|
||||||
setIsDarkTheme(newIsDark);
|
|
||||||
|
|
||||||
// Update document element class
|
|
||||||
if (newIsDark) {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to localStorage
|
|
||||||
localStorage.setItem('theme', newIsDark ? 'dark' : 'light');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background text-foreground flex flex-col">
|
|
||||||
{/* Header */}
|
|
||||||
<header className="border-b border-border py-4 px-4 sticky top-0 z-10 bg-background">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 inline-block text-transparent bg-clip-text">
|
|
||||||
RA-Aid
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{/* Theme toggle button */}
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={toggleTheme}
|
|
||||||
aria-label={isDarkTheme ? "Switch to light mode" : "Switch to dark mode"}
|
|
||||||
className="mr-2"
|
|
||||||
>
|
|
||||||
{isDarkTheme ? (
|
|
||||||
// Sun icon for light mode toggle
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<circle cx="12" cy="12" r="5" />
|
|
||||||
<line x1="12" y1="1" x2="12" y2="3" />
|
|
||||||
<line x1="12" y1="21" x2="12" y2="23" />
|
|
||||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
||||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
||||||
<line x1="1" y1="12" x2="3" y2="12" />
|
|
||||||
<line x1="21" y1="12" x2="23" y2="12" />
|
|
||||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
||||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
// Moon icon for dark mode toggle
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* Mobile drawer toggle - show only on small screens */}
|
|
||||||
<div className="md:hidden">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => setIsDrawerOpen(true)}
|
|
||||||
aria-label="Open menu"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* Main content */}
|
|
||||||
<div className="flex flex-1 overflow-hidden">
|
|
||||||
{/* Desktop sidebar - hidden on mobile */}
|
|
||||||
<SessionSidebar
|
|
||||||
sessions={sessions}
|
|
||||||
currentSessionId={selectedSessionId || undefined}
|
|
||||||
onSelectSession={handleSessionSelect}
|
|
||||||
className="shrink-0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Mobile drawer */}
|
|
||||||
<SessionDrawer
|
|
||||||
sessions={sessions}
|
|
||||||
currentSessionId={selectedSessionId || undefined}
|
|
||||||
onSelectSession={handleSessionSelect}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Main content area */}
|
|
||||||
<main className="flex-1 overflow-auto p-4">
|
|
||||||
{selectedSessionId ? (
|
|
||||||
<>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">
|
|
||||||
Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'}
|
|
||||||
</h2>
|
|
||||||
<TimelineFeed
|
|
||||||
steps={selectedSessionSteps}
|
|
||||||
maxHeight="calc(100vh - 14rem)"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center h-full">
|
|
||||||
<p className="text-muted-foreground">Select a session to view details</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer className="border-t border-border py-4 px-4 text-center text-muted-foreground text-sm">
|
|
||||||
<p>Built with shadcn/ui components from the RA-Aid common package</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize theme before rendering the app
|
// Mount the app to the root element
|
||||||
setupTheme();
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,40 @@
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Get all component files from common package
|
||||||
|
const commonSrcDir = path.resolve(__dirname, '../common/src');
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
// Ensure that Vite treats symlinked packages as local, so HMR works correctly.
|
|
||||||
alias: {
|
alias: {
|
||||||
'@ra-aid/common': path.resolve(__dirname, '../common/dist')
|
// Direct alias to the source directory
|
||||||
}
|
'@ra-aid/common': path.resolve(__dirname, '../common/src')
|
||||||
|
},
|
||||||
|
preserveSymlinks: true
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
// Exclude the common package from optimization so it can trigger hot reload
|
||||||
|
exclude: ['@ra-aid/common']
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
hmr: true,
|
||||||
watch: {
|
watch: {
|
||||||
// Watch for changes in the common package.
|
usePolling: true,
|
||||||
// This pattern forces Vite to notice file changes in the shared library.
|
interval: 100,
|
||||||
paths: ['../common/src/**', '../common/dist/**']
|
// Make sure to explicitly NOT ignore the common package
|
||||||
|
ignored: [
|
||||||
|
'**/node_modules/**',
|
||||||
|
'**/dist/**',
|
||||||
|
'!**/common/src/**'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
css: {
|
build: {
|
||||||
// PostCSS configuration is loaded from postcss.config.js
|
commonjsOptions: {
|
||||||
// This ensures proper processing of Tailwind directives
|
transformMixedEsModules: true
|
||||||
devSourcemap: true,
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue