mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
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:
19
apps/sim/.env.example
Normal file
19
apps/sim/.env.example
Normal 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
|
||||
@@ -17,7 +17,7 @@ interface SocialLoginButtonsProps {
|
||||
export function SocialLoginButtons({
|
||||
githubAvailable,
|
||||
googleAvailable,
|
||||
callbackURL = '/w',
|
||||
callbackURL = '/workspace',
|
||||
isProduction,
|
||||
}: SocialLoginButtonsProps) {
|
||||
const [isGithubLoading, setIsGithubLoading] = useState(false)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('LoginPage', () => {
|
||||
{
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
callbackURL: '/w',
|
||||
callbackURL: '/workspace',
|
||||
},
|
||||
expect.objectContaining({
|
||||
onError: expect.any(Function),
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -410,7 +410,7 @@ function SignupFormContent({
|
||||
<SocialLoginButtons
|
||||
githubAvailable={githubAvailable}
|
||||
googleAvailable={googleAvailable}
|
||||
callbackURL={redirectUrl || '/w'}
|
||||
callbackURL={redirectUrl || '/workspace'}
|
||||
isProduction={isProduction}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { NextError } from './[id]/components/error'
|
||||
|
||||
export default NextError
|
||||
@@ -1,5 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { NextGlobalError } from './[id]/components/error'
|
||||
|
||||
export default NextGlobalError
|
||||
5
apps/sim/app/workspace/[workspaceId]/error.tsx
Normal file
5
apps/sim/app/workspace/[workspaceId]/error.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { NextError } from './w/[workflowId]/components/error'
|
||||
|
||||
export default NextError
|
||||
5
apps/sim/app/workspace/[workspaceId]/global-error.tsx
Normal file
5
apps/sim/app/workspace/[workspaceId]/global-error.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { NextGlobalError } from './w/[workflowId]/components/error'
|
||||
|
||||
export default NextGlobalError
|
||||
@@ -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}`,
|
||||
@@ -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' },
|
||||
]
|
||||
@@ -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',
|
||||
@@ -19,7 +19,7 @@ export function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoading
|
||||
{
|
||||
id: 'knowledge-root',
|
||||
label: 'Knowledge',
|
||||
href: '/w/knowledge',
|
||||
href: '/knowledge',
|
||||
},
|
||||
{
|
||||
id: 'knowledge-base-loading',
|
||||
@@ -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()
|
||||
@@ -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'
|
||||
|
||||
@@ -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 (
|
||||
@@ -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 = () => {
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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() {
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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')
|
||||
}
|
||||
10
apps/sim/app/workspace/[workspaceId]/page.tsx
Normal file
10
apps/sim/app/workspace/[workspaceId]/page.tsx
Normal 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`)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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 },
|
||||
@@ -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
Reference in New Issue
Block a user