feat(workspace): add workspaceId to url (#544)

* refactor(kb): use chonkie locally (#475)

* feat(parsers): text and markdown parsers (#473)

* feat: text and markdown parsers

* fix: don't readfile on buffer, convert buffer to string instead

* fix(knowledge-wh): fixed authentication error on webhook trigger

fix(knowledge-wh): fixed authentication error on webhook trigger

* feat(tools): add huggingface tools/blcok  (#472)

* add hugging face tool

* docs: add Hugging Face tool documentation

* fix: format and lint Hugging Face integration files

* docs: add manual intro section to Hugging Face documentation

* feat: replace Record<string, any> with proper HuggingFaceRequestBody interface

* accidental local files added

* restore some docs

* make layout full for model field

* change huggingface logo

* add manual content

* fix lint

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local>

* fix(knowledge-ux): fixed ux for knowledge base (#478)

fix(knowledge-ux): fixed ux for knowledge base (#478)

* fix(billing): bump better-auth version & fix existing subscription issue when adding seats (#484)

* bump better-auth version & fix existing subscription issue Bwhen adding seats

* ack PR comments

* fix(env): added NEXT_PUBLIC_APP_URL to .env.example (#485)

* feat(subworkflows): workflows as a block within workflows (#480)

* feat(subworkflows) workflows in workflows

* revert sync changes

* working output vars

* fix greptile comments

* add cycle detection

* add tests

* working tests

* works

* fix formatting

* fix input var handling

* add images

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>

* fix(kb): fixed kb race condition resulting in no chunks found (#487)

* fix: added all blocks activeExecutionPath (#486)

* refactor(chunker): replace chonkie with custom TextChunker (#479)

* refactor(chunker): replace chonkie with custom TextChunker implementation and update document processing logic

* chore: cleanup unimplemented types

* fix: KB tests updated

* fix(tab-sync): sync between tabs on change (#489)

* fix(tab-sync): sync between tabs on change

* refactor: optimize JSON.stringify operations that are redundant

* fix(file-upload): upload presigned url to kb for file upload instead of the whole file, circumvents 4.5MB serverless func limit (#491)

* feat(folders): folders to manage workflows (#490)

* feat(subworkflows) workflows in workflows

* revert sync changes

* working output vars

* fix greptile comments

* add cycle detection

* add tests

* working tests

* works

* fix formatting

* fix input var handling

* fix(tab-sync): sync between tabs on change

* feat(folders): folders to organize workflows

* address comments

* change schema types

* fix lint error

* fix typing error

* fix race cond

* delete unused files

* improved UI

* updated naming conventions

* revert unrelated changes to db schema

* fixed collapsed sidebar subfolders

* add logs filters for folders

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
Co-authored-by: Waleed Latif <walif6@gmail.com>

* revert tab sync

* improvement(folders): added multi-select for moving folders (#493)

* added multi-select for folders

* allow drag into root

* remove extraneous comments

* instantly create worfklow on plus

* styling improvements, fixed flicker

* small improvement to dragover container

* ack PR comments

* fix(deployed-chat): made the chat mobile friendly (#494)

* improvement(ui/ux): chat deploy (#496)

* improvement(ui/ux): chat deploy experience

* improvement(ui/ux): chat fontweight

* feat(gmail): added option to access raw gmail from gmail polling service (#495)

* added option to grab raw gmail from gmail polling service

* safe json parse for function block execution to prevent vars in raw email from being resolved as sim studio vars

* added tests

* remove extraneous comments

* fix(ui): fix the UI for folder deletion, huggingface icon, workflow block icon, standardized alert dialog (#498)

* fixed folder delete UI

* fixed UI for workflow block, huggingface, & added alert dialog for deleting folders

* consistently style all alert dialogs

* fix(reset-data): remove reset all data button from settings modal along with logic (#499)

* fix(airtable): fixed airtable oauth token refresh, added tests (#502)

* fixed airtable token refresh, added tests

* added helpers for refreshOAuthToken function

* feat(registration): disable registration + handle env booleans (#501)

* feat: disable registration + handle env booleans

* chore: removing pre-process because we need to use util

* chore: format

* feat(providers): added azure openai (#503)

* added azure openai

* fix request params being passed through agent block for azure

* remove o1 from azure-openai models list

* fix: add vscode settings to gitignore

* feat(file-upload): generalized storage to support azure blob, enhanced error logging in kb, added xlsx parser (#506)

* added blob storage option for azure, refactored storage client to be provider agnostic, tested kb & file upload and s3 is undisrupted, still have to test blob

* updated CORS policy for blob, added azure blob-specific headers

* remove extraneous comments

* add file size limit and timeout

* added some extra error handling in kb add documents

* grouped envvars

* ack PR comments

* added sheetjs and xlsx parser

* fix(folders): modified folder deletion to delete subfolders & workflows in it instead of moving to root (#508)

* modified folder deletion to delete subfolders & workflows in it instead of moving to root

* added additional testing utils

* ack PR comments

* feat: api response block and implementation

* improvement(local-storage): remove use of local storage except for oauth and last active workspace id (#497)

* remove local storage usage

* remove migration for last active workspace id

* Update apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx

Add fallback for required scopes

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* add url builder util

* fi

* fix lint

* lint

* modify pre commit hook

* fix oauth

* get last active workspace working again

* new workspace logic works

* fetch locks

* works now

* remove empty useEffect

* fix loading issue

* skip empty workflow syncs

* use isWorkspace in transition flag

* add logging

* add data initialized flag

* fix lint

* fix: build error by create a server-side utils

* remove migration snapshots

* reverse search for workspace based on workflow id

* fix lint

* improvement: loading check and animation

* remove unused utils

* remove console  logs

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>

* feat(multi-select): simplified chat to always return readable stream, can select multiple outputs and get response streamed back in chat panel & deployed chat (#507)

* improvement: all workflow executions return ReadableStream & use sse to support multiple streamed outputs in chats

* fixed build

* remove extraneous comments

* general improvemetns

* ack PR comments

* fixed built

* improvement(workflow-state): split workflow state into separate tables  (#511)

* new tables to track workflow state

* fix lint

* refactor into separate tables

* fix typing

* fix lint

* add tests

* fix lint

* add correct foreign key constraint

* add self ref

* remove unused checks

* fix types

* fix type

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>

* feat(models): added new openai models, updated model pricing, added new groq model (#513)

* fix(autocomplete): fixed extra closing tag on tag dropdown autocomplete (#514)

* chore: enable input format again

* fix: process the input made on api calls with proper extraction

* feat: add json-object for ai generation for response block and others

* chore: add documentation for response block

* chore: rollback temp fix and uncomment original input handler

* chore: add missing mock for response handler

* chore: add missing mock

* chore: greptile recommendations

* added cost tracking for router & evaluator blocks, consolidated model information into a single file, hosted keys for evaluator & router, parallelized unit tests (#516)

* fix(deployState): deploy not persisting bug  (#518)

* fix(undeploy-bug): fix deployment persistence failing bug

* fix lint

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local>

* fix decimal entry issues

* remove unused files

* fix(db): decimal position entry issues (#520)

* fix decimal entry issues

* remove unused files

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>

* fix lint

* fix test

* improvement(kb): added configurability for chunks, query across multiple knowledge bases (#512)

* refactor: consolidate create modal file

* fix: identify dead processes

* fix: mark failed in DB after processing timeout

* improvement: added overlap chunks and fixed modal UI

* feat: multiselect logic

* fix: biome changes for css ordering warn instead of error

* improvement: create chunk ui

* fix: removed unused schema columns

* fix: removed references to deleted columns

* improvement: sped up vector search time

* feat: multi-kb search

* add bulk endpoint to disable/delete multiple chunks

* add bulk endpoint to disable/delete multiple chunks

* fix: removed unused schema columns

* fix: removed references to deleted columns

* made endpoints for knowledge more RESTful, added tests

* added batch operations for delete/enable/disable docs, alr have this for chunks

* added migrations

* added migrations

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>

* fix(models): remove temp from models that don't support it

* feat(sdk): added ts and python SDKs + docs (#524)

* added ts & python sdk, renamed cli from simstudio to cli

* added docs

* ack PR comments

* improvements

* fixed issue where it goes to random workspace when you click reload

fixed lint issue

* feat: better response builder + doc update

* fix(auth): added preview URLs to list of trusted origins (#525)

* trusted origins

* lint error

* removed localhost

* ran lint

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>

* fix(sdk): remove dev script from SDK

* PR: changes for migration

* add changes on top of db migration changes

* fix: allow removing single input field

* improvement(permissions): workspace permissions improvements, added provider and reduced API calls by 85% (#530)

* improved permissions UI & access patterns, show outstanding invites

* added logger

* added provider for workspace permissions, 85% reduction in API calls to get user permissions and improved performance for invitations

* ack PR comments

* cleanup

* fix disabled tooltips

* improvement(tests): parallelized tests and build fixes (#531)

* added provider for workspace permissions, 85% reduction in API calls to get user permissions and improved performance for invitations

* parallelized more tests, fixed test warnings

* removed waitlist verification route, use more utils in tests

* fixed build

* ack PR comments

* fix

* fix(kb): reduced params in kb block, added advanced mode to starter block, updated docs

* feat(realtime): sockets + normalized tables + deprecate sync (#523)

* feat: implement real-time collaborative workflow editing with Socket.IO

- Add Socket.IO server with room-based architecture for workflow collaboration
- Implement socket context for client-side real-time communication
- Add collaborative workflow hook for synchronized state management
- Update CSP to allow socket connections to localhost:3002
- Add fallback authentication for testing collaborative features
- Enable real-time broadcasting of workflow operations between tabs
- Support multi-user editing of blocks, edges, and workflow state

Key components:
- socket-server/: Complete Socket.IO server with authentication and room management
- contexts/socket-context.tsx: Client-side socket connection and state management
- hooks/use-collaborative-workflow.ts: Hook for collaborative workflow operations
- Workflow store integration for real-time state synchronization

Status: Basic collaborative features working, authentication bypass enabled for testing

* feat: complete collaborative subblock editing implementation

 All collaborative features now working perfectly:
- Real-time block movement and positioning
- Real-time subblock value editing (text fields, inputs)
- Real-time edge operations and parent updates
- Multi-user workflow rooms with proper broadcasting
- Socket.IO server with room-based architecture
- Permission bypass system for testing

🔧 Technical improvements:
- Modified useSubBlockValue hook to use collaborative event system
- All subblock setValue calls now dispatch 'update-subblock-value' events
- Collaborative workflow hook handles all real-time operations
- Socket server processes and persists all operations to database
- Clean separation between local and collaborative state management

🧪 Tested and verified:
- Multiple browser tabs with different fallback users
- Block dragging and positioning updates in real-time
- Subblock text editing reflects immediately across tabs
- Workflow room management and user presence
- Database persistence of all collaborative operations

Status: Full collaborative workflow editing working with fallback authentication

* feat: implement proper authentication for collaborative Socket.IO server

 **Authentication System Complete**:
- Removed all fallback authentication code and bypasses
- Socket server now requires valid Better Auth session cookies
- Proper session validation using auth.api.getSession()
- Authentication errors properly handled and logged
- User info extracted from session: userId, userName, email, organizationId

🔧 **Technical Implementation**:
- Updated CSP to allow WebSocket connections (ws://localhost:3002)
- Socket authentication middleware validates session tokens
- Proper error handling for missing/invalid sessions
- Permission system enforces workflow access controls
- Clean separation between authenticated and unauthenticated states

🧪 **Testing Status**:
- Socket server properly rejects unauthenticated connections
- Authentication errors logged with clear messages
- CSP updated to allow both HTTP and WebSocket protocols
- Ready for testing with authenticated users

Status: Production-ready collaborative authentication system

* feat: complete authentication integration for collaborative Socket.IO system

🎉 **PRODUCTION-READY COLLABORATIVE SYSTEM**

 **Authentication Integration Complete**:
- Fixed Socket.IO client to send credentials (withCredentials: true)
- Updated server CORS to accept credentials with specific origin
- Removed all fallback authentication bypasses
- Proper Better Auth session validation working

🔧 **Technical Fixes**:
- Socket client: Enable withCredentials for cookie transmission
- Socket server: Accept credentials with origin 'http://localhost:3000'
- Better Auth cookie utility integration for session parsing
- Comprehensive authentication middleware with proper error handling

🧪 **Verified Working Features**:
-  Real user authentication (Vikhyath Mondreti authenticated)
-  Multi-user workflow rooms (2+ users in same workflow)
-  Permission system enforcing workflow access controls
-  Real-time subblock editing across browser tabs
-  Block movement and positioning updates
-  Automatic room cleanup and management
-  Database persistence of all collaborative operations

🚀 **Status**: Complete enterprise-grade collaborative workflow editing system
- No more fallback users - production authentication
- Multi-tab collaboration working perfectly
- Secure access control with Better Auth integration
- Real-time updates for all workflow operations

* remove sync system and move to server side

* fix lint

* delete unused file

* added socketio dep

* fix subblock persistence bug

* working deletion of workflows

* fix lint

* added railway

* add debug logging for railway deployment

* improve typing

* fix lint

* working subflow persistence

* fix lint

* working cascade deletion

* fix lint

* working subflow inside subflow

* works

* fix lint

* prevent subflow in subflow

* fix lint

* add additional logs, add localhost as allowedOrigin

* add additional logs, add localhost as allowedOrigin

* fix type error

* remove unused code

* fix lint

* fix tests

* fix lint

* fix build error

* workign folder updates

* fix typing issue

* fix lint

* fix typing issues

* lib/

* fix tests

* added old presence component back, updated to use one-time-token better auth plugin for socket server auth, tested

* fix errors

* fix bugs

* add migration scripts to run

* fix lint

* fix deploy tests

* fix lint

* fix minor issues

* fix lint

* fix migration script

* allow comma separateds id file input to migration script

* fix lint

* fixed

* fix lint

* fix fallback case

* fix type errors

* address greptile comments

* fix lint

* fix script to generate new block ids

* fix lint

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local>

* fix(sockets): updated CSP

* remove unecessary logs

* fix lint

* refactor: get started with workspace id in url

* refactor: get rid of the old local way of storing active workspace and depend on params

* fix: import path

* fix: missing function and type issue

* chore: requested changes

* chore: refactor /w/[id] to /w/[workflowId]

* refactor: move knowledge base, logs, marketplace to workspace

* fix: on creation redirect to new workflow, on delete redirect to lastest workflow, update loading animation

* replace generic loading spinner with loading agent on workspace init

* chore: cleanup leftover component

* rm html socket test

* fix in sidebar

* more sidebar fix

* fixed string interpolation issues

* fixed broken invite route

* fixed broken invite route

---------

Co-authored-by: Aditya Tripathi <aditya@climactic.co>
Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>
Co-authored-by: Ajit Kadaveru <ajit.kadaveru@berkeley.edu>
This commit is contained in:
Waleed Latif
2025-06-25 16:28:48 -07:00
committed by GitHub
parent 0a5bf5a821
commit 7182f35702
237 changed files with 615 additions and 504 deletions

19
apps/sim/.env.example Normal file
View File

@@ -0,0 +1,19 @@
# Database (Required)
DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres"
# Authentication (Required)
BETTER_AUTH_SECRET=your_secret_key # Use `openssl rand -hex 32` to generate, or visit https://www.better-auth.com/docs/installation
BETTER_AUTH_URL=http://localhost:3000
# NextJS (Required)
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Security (Required)
ENCRYPTION_KEY=your_encryption_key # Use `openssl rand -hex 32` to generate
# Email Provider (Optional)
# RESEND_API_KEY= # Uncomment and add your key from https://resend.com to send actual emails
# If left commented out, emails will be logged to console instead
# Freestyle API Key (Required for sandboxed code execution for functions/custom-tools)
# FREESTYLE_API_KEY= # Uncomment and add your key from https://docs.freestyle.sh/Getting-Started/run

View File

@@ -17,7 +17,7 @@ interface SocialLoginButtonsProps {
export function SocialLoginButtons({
githubAvailable,
googleAvailable,
callbackURL = '/w',
callbackURL = '/workspace',
isProduction,
}: SocialLoginButtonsProps) {
const [isGithubLoading, setIsGithubLoading] = useState(false)

View File

@@ -3,7 +3,7 @@
import Image from 'next/image'
import Link from 'next/link'
import { GridPattern } from '../(landing)/components/grid-pattern'
import { NotificationList } from '../w/[id]/components/notifications/notifications'
import { NotificationList } from '../workspace/[workspaceId]/w/[workflowId]/components/notifications/notifications'
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -149,7 +149,7 @@ describe('LoginPage', () => {
{
email: 'test@example.com',
password: 'password123',
callbackURL: '/w',
callbackURL: '/workspace',
},
expect.objectContaining({
onError: expect.any(Function),

View File

@@ -125,7 +125,7 @@ export default function LoginPage({
const [showValidationError, setShowValidationError] = useState(false)
// Initialize state for URL parameters
const [callbackUrl, setCallbackUrl] = useState('/w')
const [callbackUrl, setCallbackUrl] = useState('/workspace')
const [isInviteFlow, setIsInviteFlow] = useState(false)
// Forgot password states
@@ -155,7 +155,7 @@ export default function LoginPage({
setCallbackUrl(callback)
} else {
logger.warn('Invalid callback URL detected and blocked:', { url: callback })
// Keep the default safe value ('/w')
// Keep the default safe value ('/workspace')
}
}
@@ -222,7 +222,7 @@ export default function LoginPage({
try {
// Final validation before submission
const safeCallbackUrl = validateCallbackUrl(callbackUrl) ? callbackUrl : '/w'
const safeCallbackUrl = validateCallbackUrl(callbackUrl) ? callbackUrl : '/workspace'
const result = await client.signIn.email(
{

View File

@@ -410,7 +410,7 @@ function SignupFormContent({
<SocialLoginButtons
githubAvailable={githubAvailable}
googleAvailable={googleAvailable}
callbackURL={redirectUrl || '/w'}
callbackURL={redirectUrl || '/workspace'}
isProduction={isProduction}
/>

View File

@@ -148,7 +148,7 @@ export function useVerification({
router.push(redirectUrl)
} else {
// Default redirect to dashboard
router.push('/w')
router.push('/workspace')
}
}, 2000)
} else {
@@ -233,7 +233,7 @@ export function useVerification({
if (isDevOrDocker || !hasResendKey) {
setIsVerified(true)
const timeoutId = setTimeout(() => {
router.push('/w')
router.push('/workspace')
}, 1000)
return () => clearTimeout(timeoutId)

View File

@@ -21,7 +21,7 @@ function Footer() {
if (typeof window !== 'undefined') {
// Check if user has an active session
if (isAuthenticated) {
router.push('/w')
router.push('/workspace')
} else {
// Check if user has logged in before
const hasLoggedInBefore =

View File

@@ -18,7 +18,7 @@ function Hero() {
if (typeof window !== 'undefined') {
// Check if user has an active session
if (isAuthenticated) {
router.push('/w')
router.push('/workspace')
} else {
// Check if user has logged in before
const hasLoggedInBefore =

View File

@@ -2,7 +2,7 @@ import { desc, eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console-logger'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
import { CATEGORIES } from '@/app/w/marketplace/constants/categories'
import { CATEGORIES } from '@/app/workspace/[workspaceId]/marketplace/constants/categories'
import { db } from '@/db'
import * as schema from '@/db/schema'

View File

@@ -149,7 +149,10 @@ export async function GET(req: NextRequest) {
.where(eq(workspaceInvitation.id, invitation.id))
return NextResponse.redirect(
new URL(`/w/${invitation.workspaceId}`, env.NEXT_PUBLIC_APP_URL || 'https://simstudio.ai')
new URL(
`/workspace/${invitation.workspaceId}/w`,
env.NEXT_PUBLIC_APP_URL || 'https://simstudio.ai'
)
)
}
@@ -194,7 +197,10 @@ export async function GET(req: NextRequest) {
// Redirect to the workspace
return NextResponse.redirect(
new URL(`/w/${invitation.workspaceId}`, env.NEXT_PUBLIC_APP_URL || 'https://simstudio.ai')
new URL(
`/workspace/${invitation.workspaceId}/w`,
env.NEXT_PUBLIC_APP_URL || 'https://simstudio.ai'
)
)
} catch (error) {
console.error('Error accepting invitation:', error)

View File

@@ -131,7 +131,7 @@ export default function Invite() {
// Redirect to workspace after a brief delay
setTimeout(() => {
router.push('/w')
router.push('/workspace')
}, 2000)
} else {
// For organization invites, use the client API
@@ -153,7 +153,7 @@ export default function Invite() {
// Redirect to workspace after a brief delay
setTimeout(() => {
router.push('/w')
router.push('/workspace')
}, 2000)
}
} catch (err: any) {

View File

@@ -54,7 +54,7 @@ export default function InviteError() {
<p className='mb-6 text-muted-foreground'>{displayMessage}</p>
<div className='flex w-full flex-col gap-4'>
<Link href='/w' passHref>
<Link href='/workspace' passHref>
<Button variant='default' className='w-full'>
Go to Dashboard
</Button>

View File

@@ -1,5 +0,0 @@
'use client'
import { NextError } from './[id]/components/error'
export default NextError

View File

@@ -1,5 +0,0 @@
'use client'
import { NextGlobalError } from './[id]/components/error'
export default NextGlobalError

View File

@@ -0,0 +1,5 @@
'use client'
import { NextError } from './w/[workflowId]/components/error'
export default NextError

View File

@@ -0,0 +1,5 @@
'use client'
import { NextGlobalError } from './w/[workflowId]/components/error'
export default NextGlobalError

View File

@@ -25,12 +25,12 @@ export function DocumentLoading({
{
id: 'knowledge-root',
label: 'Knowledge',
href: '/w/knowledge',
href: '/knowledge',
},
{
id: `knowledge-base-${knowledgeBaseId}`,
label: knowledgeBaseName,
href: `/w/knowledge/${knowledgeBaseId}`,
href: `/knowledge/${knowledgeBaseId}`,
},
{
id: `document-${knowledgeBaseId}-${documentName}`,

View File

@@ -16,7 +16,7 @@ import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console-logger'
import { ActionBar } from '@/app/w/knowledge/[id]/components/action-bar/action-bar'
import { ActionBar } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar'
import { useDocumentChunks } from '@/hooks/use-knowledge'
import { type ChunkData, type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store'
import { useSidebarStore } from '@/stores/sidebar/store'
@@ -170,10 +170,10 @@ export function Document({
const effectiveDocumentName = document?.filename || documentName || 'Document'
const breadcrumbs = [
{ label: 'Knowledge', href: '/w/knowledge' },
{ label: 'Knowledge', href: '/knowledge' },
{
label: effectiveKnowledgeBaseName,
href: `/w/knowledge/${knowledgeBaseId}`,
href: `/knowledge/${knowledgeBaseId}`,
},
{ label: effectiveDocumentName },
]
@@ -360,10 +360,10 @@ export function Document({
if (combinedError && !isLoadingChunks) {
const errorBreadcrumbs = [
{ label: 'Knowledge', href: '/w/knowledge' },
{ label: 'Knowledge', href: '/knowledge' },
{
label: effectiveKnowledgeBaseName,
href: `/w/knowledge/${knowledgeBaseId}`,
href: `/knowledge/${knowledgeBaseId}`,
},
{ label: 'Error' },
]

View File

@@ -13,7 +13,7 @@ import {
Trash2,
X,
} from 'lucide-react'
import { useRouter } from 'next/navigation'
import { useParams, useRouter } from 'next/navigation'
import {
AlertDialog,
AlertDialogAction,
@@ -28,10 +28,10 @@ import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console-logger'
import { ActionBar } from '@/app/w/knowledge/[id]/components/action-bar/action-bar'
import { getDocumentIcon } from '@/app/w/knowledge/components/icons/document-icons'
import { PrimaryButton } from '@/app/w/knowledge/components/primary-button/primary-button'
import { SearchInput } from '@/app/w/knowledge/components/search-input/search-input'
import { ActionBar } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar'
import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components/icons/document-icons'
import { PrimaryButton } from '@/app/workspace/[workspaceId]/knowledge/components/primary-button/primary-button'
import { SearchInput } from '@/app/workspace/[workspaceId]/knowledge/components/search-input/search-input'
import { useKnowledgeBase, useKnowledgeBaseDocuments } from '@/hooks/use-knowledge'
import { type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store'
import { useSidebarStore } from '@/stores/sidebar/store'
@@ -122,6 +122,8 @@ export function KnowledgeBase({
}: KnowledgeBaseProps) {
const { mode, isExpanded } = useSidebarStore()
const { removeKnowledgeBase } = useKnowledgeStore()
const params = useParams()
const workspaceId = params.workspaceId as string
const {
knowledgeBase,
isLoading: isLoadingKnowledgeBase,
@@ -402,11 +404,11 @@ export function KnowledgeBase({
const handleDocumentClick = (docId: string) => {
// Find the document to get its filename
const document = documents.find((doc) => doc.id === docId)
const params = new URLSearchParams({
const urlParams = new URLSearchParams({
kbName: knowledgeBaseName, // Use the instantly available name
docName: document?.filename || 'Document',
})
router.push(`/w/knowledge/${id}/${docId}?${params.toString()}`)
router.push(`/workspace/${workspaceId}/knowledge/${id}/${docId}?${urlParams.toString()}`)
}
const handleDeleteKnowledgeBase = async () => {
@@ -428,7 +430,7 @@ export function KnowledgeBase({
if (result.success) {
// Remove from store and redirect to knowledge bases list
removeKnowledgeBase(id)
router.push('/w/knowledge')
router.push(`/workspace/${workspaceId}/knowledge`)
} else {
throw new Error(result.error || 'Failed to delete knowledge base')
}
@@ -741,7 +743,7 @@ export function KnowledgeBase({
{
id: 'knowledge-root',
label: 'Knowledge',
href: '/w/knowledge',
href: '/knowledge',
},
{
id: `knowledge-base-${id}`,
@@ -760,7 +762,7 @@ export function KnowledgeBase({
{
id: 'knowledge-root',
label: 'Knowledge',
href: '/w/knowledge',
href: '/knowledge',
},
{
id: 'error',

View File

@@ -19,7 +19,7 @@ export function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoading
{
id: 'knowledge-root',
label: 'Knowledge',
href: '/w/knowledge',
href: '/knowledge',
},
{
id: 'knowledge-base-loading',

View File

@@ -18,7 +18,7 @@ export function BaseOverview({ id, title, docCount, description }: BaseOverviewP
const params = new URLSearchParams({
kbName: title,
})
const href = `/w/knowledge/${id || title.toLowerCase().replace(/\s+/g, '-')}?${params.toString()}`
const href = `/knowledge/${id || title.toLowerCase().replace(/\s+/g, '-')}?${params.toString()}`
const handleCopy = async (e: React.MouseEvent) => {
e.preventDefault()

View File

@@ -12,7 +12,7 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import { createLogger } from '@/lib/logs/console-logger'
import { getDocumentIcon } from '@/app/w/knowledge/components/icons/document-icons'
import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components/icons/document-icons'
import type { DocumentData, KnowledgeBaseData } from '@/stores/knowledge/store'
import { useKnowledgeStore } from '@/stores/knowledge/store'

View File

@@ -1,6 +1,6 @@
import { WorkspaceProvider } from '@/providers/workspace-provider'
import Providers from './components/providers/providers'
import { Sidebar } from './components/sidebar/sidebar'
import Providers from './w/components/providers/providers'
import { Sidebar } from './w/components/sidebar/sidebar'
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { Check, ChevronDown, Folder } from 'lucide-react'
import { useParams } from 'next/navigation'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
@@ -8,9 +9,8 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useFilterStore } from '@/app/w/logs/stores/store'
import { useFilterStore } from '@/app/workspace/[workspaceId]/logs/stores/store'
import { useFolderStore } from '@/stores/folders/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
interface FolderOption {
id: string
@@ -22,7 +22,8 @@ interface FolderOption {
export default function FolderFilter() {
const { folderIds, toggleFolderId, setFolderIds } = useFilterStore()
const { getFolderTree, getFolderPath, fetchFolders } = useFolderStore()
const { activeWorkspaceId } = useWorkflowRegistry()
const params = useParams()
const workspaceId = params.workspaceId as string
const [folders, setFolders] = useState<FolderOption[]>([])
const [loading, setLoading] = useState(true)
@@ -31,9 +32,9 @@ export default function FolderFilter() {
const fetchFoldersData = async () => {
try {
setLoading(true)
if (activeWorkspaceId) {
await fetchFolders(activeWorkspaceId)
const folderTree = getFolderTree(activeWorkspaceId)
if (workspaceId) {
await fetchFolders(workspaceId)
const folderTree = getFolderTree(workspaceId)
// Flatten the folder tree and create options with full paths
const flattenFolders = (nodes: any[], parentPath = ''): FolderOption[] => {
@@ -68,7 +69,7 @@ export default function FolderFilter() {
}
fetchFoldersData()
}, [activeWorkspaceId, fetchFolders, getFolderTree])
}, [workspaceId, fetchFolders, getFolderTree])
// Get display text for the dropdown button
const getSelectedFoldersText = () => {

View File

@@ -6,8 +6,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useFilterStore } from '@/app/w/logs/stores/store'
import type { LogLevel } from '@/app/w/logs/stores/types'
import { useFilterStore } from '@/app/workspace/[workspaceId]/logs/stores/store'
import type { LogLevel } from '@/app/workspace/[workspaceId]/logs/stores/types'
export default function Level() {
const { level, setLevel } = useFilterStore()

View File

@@ -6,8 +6,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useFilterStore } from '@/app/w/logs/stores/store'
import type { TimeRange } from '@/app/w/logs/stores/types'
import { useFilterStore } from '@/app/workspace/[workspaceId]/logs/stores/store'
import type { TimeRange } from '@/app/workspace/[workspaceId]/logs/stores/types'
export default function Timeline() {
const { timeRange, setTimeRange } = useFilterStore()

View File

@@ -7,7 +7,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useFilterStore } from '@/app/w/logs/stores/store'
import { useFilterStore } from '@/app/workspace/[workspaceId]/logs/stores/store'
import type { TriggerType } from '../../../stores/types'
export default function Trigger() {

View File

@@ -8,7 +8,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useFilterStore } from '@/app/w/logs/stores/store'
import { useFilterStore } from '@/app/workspace/[workspaceId]/logs/stores/store'
interface WorkflowOption {
id: string

View File

@@ -7,8 +7,8 @@ import { CopyButton } from '@/components/ui/copy-button'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { redactApiKeys } from '@/lib/utils'
import type { WorkflowLog } from '@/app/w/logs/stores/types'
import { formatDate } from '@/app/w/logs/utils/format-date'
import type { WorkflowLog } from '@/app/workspace/[workspaceId]/logs/stores/types'
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils/format-date'
import { formatCost } from '@/providers/utils'
import { ToolCallsDisplay } from '../tool-calls/tool-calls-display'
import { TraceSpansDisplay } from '../trace-spans/trace-spans-display'

View File

@@ -2,9 +2,9 @@
import { useEffect, useState } from 'react'
import { Eye } from 'lucide-react'
import { useRouter } from 'next/navigation'
import { useParams, useRouter } from 'next/navigation'
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
import { WorkflowPreview } from '@/app/w/components/workflow-preview/workflow-preview'
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import type { Workflow } from '../marketplace'
@@ -28,6 +28,8 @@ interface WorkflowCardProps {
export function WorkflowCard({ workflow, onHover }: WorkflowCardProps) {
const [isPreviewReady, setIsPreviewReady] = useState(!!workflow.workflowState)
const router = useRouter()
const params = useParams()
const workspaceId = params.workspaceId as string
const { createWorkflow } = useWorkflowRegistry()
// When workflow state becomes available, update preview ready state
@@ -71,7 +73,7 @@ export function WorkflowCard({ workflow, onHover }: WorkflowCardProps) {
})
// Navigate to the new workflow
router.push(`/w/${newWorkflowId}`)
router.push(`/workspace/${workspaceId}/w/${newWorkflowId}`)
} else {
console.error('Cannot import workflow: state is not available')
}

View File

@@ -0,0 +1,10 @@
import { redirect } from 'next/navigation'
export default async function WorkspacePage({
params,
}: {
params: Promise<{ workspaceId: string }>
}) {
const { workspaceId } = await params
redirect(`/workspace/${workspaceId}/w`)
}

View File

@@ -33,7 +33,7 @@ import { Textarea } from '@/components/ui/textarea'
import { createLogger } from '@/lib/logs/console-logger'
import { getBaseDomain } from '@/lib/urls/utils'
import { cn } from '@/lib/utils'
import { OutputSelect } from '@/app/w/[id]/components/panel/components/chat/components/output-select/output-select'
import { OutputSelect } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select'
import { useNotificationStore } from '@/stores/notifications/store'
import type { OutputConfig } from '@/stores/panel/chat/types'

View File

@@ -15,10 +15,10 @@ import {
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { ApiEndpoint } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/components/api-endpoint/api-endpoint'
import { ApiKey } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/components/api-key/api-key'
import { DeployStatus } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/components/deploy-status/deploy-status'
import { ExampleCommand } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command'
import { ApiEndpoint } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/api-endpoint/api-endpoint'
import { ApiKey } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/api-key/api-key'
import { DeployStatus } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/deploy-status/deploy-status'
import { ExampleCommand } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command'
import { useNotificationStore } from '@/stores/notifications/store'
import type { WorkflowState } from '@/stores/workflows/workflow/types'
import { DeployedWorkflowModal } from '../../../deployment-controls/components/deployed-workflow-modal'

View File

@@ -20,9 +20,9 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
import { ChatDeploy } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy'
import { DeployForm } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form'
import { DeploymentInfo } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info'
import { ChatDeploy } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy'
import { DeployForm } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form'
import { DeploymentInfo } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info'
import { useNotificationStore } from '@/stores/notifications/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'

View File

@@ -6,7 +6,7 @@ import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
import { WorkflowPreview } from '@/app/w/components/workflow-preview/workflow-preview'
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import type { WorkflowState } from '@/stores/workflows/workflow/types'

View File

@@ -35,7 +35,7 @@ import {
getCategoryColor,
getCategoryIcon,
getCategoryLabel,
} from '@/app/w/marketplace/constants/categories'
} from '@/app/workspace/[workspaceId]/marketplace/constants/categories'
import { useNotificationStore } from '@/stores/notifications/store'
import { getWorkflowWithValues } from '@/stores/workflows'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'

View File

@@ -16,7 +16,7 @@ import {
Trash2,
X,
} from 'lucide-react'
import { useRouter } from 'next/navigation'
import { useParams, useRouter } from 'next/navigation'
import {
AlertDialog,
AlertDialogAction,
@@ -40,8 +40,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { useSession } from '@/lib/auth-client'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/w/components/providers/workspace-permissions-provider'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
import { useExecutionStore } from '@/stores/execution/store'
import { useFolderStore } from '@/stores/folders/store'
import { useNotificationStore } from '@/stores/notifications/store'
import { usePanelStore } from '@/stores/panel/store'
import { useGeneralStore } from '@/stores/settings/general/store'
@@ -84,6 +85,8 @@ interface ControlBarProps {
export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
const router = useRouter()
const { data: session } = useSession()
const params = useParams()
const workspaceId = params.workspaceId as string
// Store hooks
const {
@@ -100,7 +103,6 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
workflows,
updateWorkflow,
activeWorkflowId,
activeWorkspaceId,
removeWorkflow,
duplicateWorkflow,
setDeploymentStatus,
@@ -108,6 +110,7 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
} = useWorkflowRegistry()
const { isExecuting, handleRunWorkflow } = useWorkflowExecution()
const { setActiveTab } = usePanelStore()
const { getFolderTree, expandedFolders } = useFolderStore()
// Get current workflow and workspace ID for permissions
const currentWorkflow = activeWorkflowId ? workflows[activeWorkflowId] : null
@@ -399,33 +402,82 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
}
}
/**
* Get workflows in the exact order they appear in the sidebar
*/
const getSidebarOrderedWorkflows = () => {
// Get and sort regular workflows by last modified (newest first)
const regularWorkflows = Object.values(workflows)
.filter((workflow) => workflow.workspaceId === workspaceId)
.filter((workflow) => workflow.marketplaceData?.status !== 'temp')
.sort((a, b) => {
const dateA =
a.lastModified instanceof Date
? a.lastModified.getTime()
: new Date(a.lastModified).getTime()
const dateB =
b.lastModified instanceof Date
? b.lastModified.getTime()
: new Date(b.lastModified).getTime()
return dateB - dateA
})
// Group workflows by folder
const workflowsByFolder = regularWorkflows.reduce(
(acc, workflow) => {
const folderId = workflow.folderId || 'root'
if (!acc[folderId]) acc[folderId] = []
acc[folderId].push(workflow)
return acc
},
{} as Record<string, typeof regularWorkflows>
)
const orderedWorkflows: typeof regularWorkflows = []
// Recursively collect workflows from expanded folders
const collectFromFolders = (folders: ReturnType<typeof getFolderTree>) => {
folders.forEach((folder) => {
if (expandedFolders.has(folder.id)) {
orderedWorkflows.push(...(workflowsByFolder[folder.id] || []))
if (folder.children.length > 0) {
collectFromFolders(folder.children)
}
}
})
}
// Get workflows from expanded folders first, then root workflows
if (workspaceId) collectFromFolders(getFolderTree(workspaceId))
orderedWorkflows.push(...(workflowsByFolder.root || []))
return orderedWorkflows
}
/**
* Handle deleting the current workflow
*/
const handleDeleteWorkflow = () => {
if (!activeWorkflowId || !userPermissions.canEdit) return
const workflowIds = Object.keys(workflows)
const currentIndex = workflowIds.indexOf(activeWorkflowId)
const sidebarWorkflows = getSidebarOrderedWorkflows()
const currentIndex = sidebarWorkflows.findIndex((w) => w.id === activeWorkflowId)
// Find the next workflow to navigate to
let nextWorkflowId = null
if (workflowIds.length > 1) {
// Try next workflow, then previous, then any other
if (currentIndex < workflowIds.length - 1) {
nextWorkflowId = workflowIds[currentIndex + 1]
// Find next workflow: try next, then previous
let nextWorkflowId: string | null = null
if (sidebarWorkflows.length > 1) {
if (currentIndex < sidebarWorkflows.length - 1) {
nextWorkflowId = sidebarWorkflows[currentIndex + 1].id
} else if (currentIndex > 0) {
nextWorkflowId = workflowIds[currentIndex - 1]
} else {
nextWorkflowId = workflowIds.find((id) => id !== activeWorkflowId) || null
nextWorkflowId = sidebarWorkflows[currentIndex - 1].id
}
}
// Navigate to the next workflow or home
// Navigate to next workflow or workspace home
if (nextWorkflowId) {
router.push(`/w/${nextWorkflowId}`)
router.push(`/workspace/${workspaceId}/w/${nextWorkflowId}`)
} else {
router.push('/')
router.push(`/workspace/${workspaceId}`)
}
// Remove the workflow from the registry
@@ -573,8 +625,17 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
const handleDuplicateWorkflow = async () => {
if (!activeWorkflowId || !userPermissions.canEdit) return
// Duplicate the workflow - no automatic navigation
await duplicateWorkflow(activeWorkflowId)
try {
const newWorkflow = await duplicateWorkflow(activeWorkflowId)
if (newWorkflow) {
router.push(`/workspace/${workspaceId}/w/${newWorkflow}`)
} else {
addNotification('error', 'Failed to duplicate workflow', activeWorkflowId)
}
} catch (error) {
logger.error('Error duplicating workflow:', { error })
addNotification('error', 'Failed to duplicate workflow', activeWorkflowId)
}
}
/**

View File

@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { LoopNodeComponent } from './loop-node'
// Mock dependencies that don't need DOM
vi.mock('@/stores/workflows/workflow/store', () => ({
useWorkflowStore: vi.fn(),
}))
@@ -16,7 +15,6 @@ vi.mock('@/lib/logs/console-logger', () => ({
})),
}))
// Mock ReactFlow components and hooks
vi.mock('reactflow', () => ({
Handle: ({ id, type, position }: any) => ({ id, type, position }),
Position: {
@@ -32,7 +30,6 @@ vi.mock('reactflow', () => ({
memo: (component: any) => component,
}))
// Mock React hooks
vi.mock('react', async () => {
const actual = await vi.importActual('react')
return {
@@ -43,7 +40,6 @@ vi.mock('react', async () => {
}
})
// Mock UI components
vi.mock('@/components/ui/button', () => ({
Button: ({ children, onClick, ...props }: any) => ({ children, onClick, ...props }),
}))
@@ -60,7 +56,6 @@ vi.mock('@/lib/utils', () => ({
cn: (...classes: any[]) => classes.filter(Boolean).join(' '),
}))
// Mock the LoopBadges component
vi.mock('./components/loop-badges', () => ({
LoopBadges: ({ loopId }: any) => ({ loopId }),
}))
@@ -87,8 +82,6 @@ describe('LoopNodeComponent', () => {
beforeEach(() => {
vi.clearAllMocks()
// Mock useWorkflowStore
;(useWorkflowStore as any).mockImplementation((selector: any) => {
const state = {
removeBlock: mockRemoveBlock,
@@ -96,7 +89,6 @@ describe('LoopNodeComponent', () => {
return selector(state)
})
// Mock getNodes
mockGetNodes.mockReturnValue([])
})
@@ -111,14 +103,12 @@ describe('LoopNodeComponent', () => {
})
it('should be a memoized component', () => {
// Since we mocked memo to return the component as-is, we can verify it exists
expect(LoopNodeComponent).toBeDefined()
})
})
describe('Props Validation and Type Safety', () => {
it('should accept NodeProps interface', () => {
// Test that the component accepts the correct prop types
const validProps = {
id: 'test-id',
type: 'loopNode' as const,
@@ -135,9 +125,7 @@ describe('LoopNodeComponent', () => {
dragging: false,
}
// This tests that TypeScript compilation succeeds with these props
expect(() => {
// We're not calling the component, just verifying the types
const _component: typeof LoopNodeComponent = LoopNodeComponent
expect(_component).toBeDefined()
}).not.toThrow()
@@ -163,10 +151,8 @@ describe('LoopNodeComponent', () => {
describe('Store Integration', () => {
it('should integrate with workflow store', () => {
// Test that the component uses the store correctly
expect(useWorkflowStore).toBeDefined()
// Verify the store selector function works
const mockState = { removeBlock: mockRemoveBlock }
const selector = vi.fn((state) => state.removeBlock)
@@ -181,7 +167,6 @@ describe('LoopNodeComponent', () => {
expect(mockRemoveBlock).toBeDefined()
expect(typeof mockRemoveBlock).toBe('function')
// Test calling removeBlock
mockRemoveBlock('test-id')
expect(mockRemoveBlock).toHaveBeenCalledWith('test-id')
})
@@ -189,7 +174,6 @@ describe('LoopNodeComponent', () => {
describe('Component Logic Tests', () => {
it('should handle nesting level calculation logic', () => {
// Test the nesting level calculation logic
const testCases = [
{ nodes: [], parentId: undefined, expectedLevel: 0 },
{ nodes: [{ id: 'parent', data: {} }], parentId: 'parent', expectedLevel: 1 },

View File

@@ -4,8 +4,8 @@ import { type KeyboardEvent, useEffect, useMemo, useRef } from 'react'
import { ArrowUp, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { JSONView } from '@/app/w/[id]/components/panel/components/console/components/json-view/json-view'
import { useWorkflowExecution } from '@/app/w/[id]/hooks/use-workflow-execution'
import { JSONView } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/console/components/json-view/json-view'
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
import { useExecutionStore } from '@/stores/execution/store'
import { useChatStore } from '@/stores/panel/chat/store'
import type { ChatMessage as ChatMessageType } from '@/stores/panel/chat/types'

Some files were not shown because too many files have changed in this diff Show More