Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ async function render(
'table-row-ttl': false,
'mothership-model-selector': mocks.advanced,
'mothership-plan-mode': mocks.plan,
'org-project-view': false,
}}
>
<Harness />
Expand Down Expand Up @@ -309,6 +310,7 @@ it('keeps restored queued skills scoped when replacing a draft', async () => {
'table-row-ttl': false,
'mothership-model-selector': mocks.advanced,
'mothership-plan-mode': mocks.plan,
'org-project-view': false,
}}
>
<Harness />
Expand Down
34 changes: 31 additions & 3 deletions apps/sim/app/o/[organizationId]/home/organization-home.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useCallback, useEffect, useState, useSyncExternalStore } from 'react'
import { type ReactNode, useCallback, useEffect, useState, useSyncExternalStore } from 'react'
import { toast } from '@sim/emcn'
import { useQueryClient } from '@tanstack/react-query'
import Link from 'next/link'
Expand Down Expand Up @@ -52,10 +52,26 @@ import { useMothershipDraftsStore } from '@/stores/mothership-drafts/store'
import { useOrganizationChatModeStore } from '@/stores/organization-chat-mode/store'
import type { ChatContext } from '@/stores/panel'

/** What a host surface adds to the empty Home for one mode. */
interface OrganizationHomeLanding {
heading?: string
/** Rendered above the composer, such as a picker that scopes the next message. */
accessory?: ReactNode
}

interface OrganizationHomeProps {
userName?: string
chatId?: string
requestMode?: ChatRequestMode
/** Lets a host surface change the empty Home's heading and add a control, per mode. */
landing?: (requestMode: ChatRequestMode) => OrganizationHomeLanding | undefined
/**
* Called with the first message of a new chat before it is sent here; returning true means
* the host sent it elsewhere, so this Home clears the draft and sends nothing.
*/
onBeforeSend?: (message: string, requestMode: ChatRequestMode) => boolean
/** False keeps the address bar where it is when the chat gets its id. */
syncChatUrl?: boolean
}

const subscribeToClient = () => () => {}
Expand All @@ -82,6 +98,9 @@ function OrganizationHomeContent({
userName,
chatId,
requestMode: savedMode,
landing,
onBeforeSend,
syncChatUrl,
}: OrganizationHomeProps) {
const { organization, searchAccess, canBuild, mothershipAvailable } = useOrganizationContext()
const { data: session } = useSession()
Expand Down Expand Up @@ -114,6 +133,7 @@ function OrganizationHomeContent({
requestMode,
onResourceEvent: controller.onResourceEvent,
activeResourceState: controller.activeResourceState,
...(syncChatUrl === false ? { syncChatUrl } : {}),
})
)
const initialDraftKey = `${userId}:organization:${organization.id}:${chatId ?? 'new'}`
Expand Down Expand Up @@ -296,6 +316,11 @@ function OrganizationHomeContent({
path: file.path,
}))
if (!message && !attachments.length) return
if (!hasChat && message && onBeforeSend?.(message, requestMode)) {
useMothershipDraftsStore.getState().clearDraft(draftKey)
files.clearAttachedFiles()
return
}
useMothershipDraftsStore.getState().clearDraft(draftKey)
send(message, attachments.length ? attachments : undefined, contexts)
setRestoredContexts([])
Expand Down Expand Up @@ -334,6 +359,7 @@ function OrganizationHomeContent({
/>
)

const hostLanding = hasChat ? undefined : landing?.(requestMode)
const content = (
<div className='flex h-full min-h-0 min-w-[min(480px,100%)] flex-1 flex-col bg-[var(--bg)]'>
{hasChat ? (
Expand Down Expand Up @@ -399,13 +425,15 @@ function OrganizationHomeContent({
) : (
<OrganizationLanding
heading={
requestMode === 'assistant'
hostLanding?.heading ??
(requestMode === 'assistant'
? `Search ${organization.name}`
: requestMode === 'plan'
? `What should we understand and plan${firstName ? `, ${firstName}` : ''}?`
: `What should we get done${firstName ? `, ${firstName}` : ''}?`
: `What should we get done${firstName ? `, ${firstName}` : ''}?`)
}
>
{hostLanding?.accessory}
{composer}
<div className='absolute inset-x-0 top-full'>
{requestMode === 'agent' ? (
Expand Down
35 changes: 22 additions & 13 deletions apps/sim/app/o/[organizationId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { isDashboardsEnabled } from '@/lib/dashboards/feature-flag'
import { isMothershipModelSelectorEnabled, isPlanModeEnabled } from '@/lib/mothership/feature-flags'
import { organizationRoutes, WORKSPACE_SETTINGS_PATH } from '@/lib/navigation/paths'
import { getOrganizationSurfaceContext } from '@/lib/organizations/surface'
import { isOrgProjectViewEnabled } from '@/lib/projects/feature-flag'
import { isTableRowTtlEnabled } from '@/lib/table/ttl-availability'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { buildAuthCrossLink } from '@/app/(auth)/auth-redirect'
Expand Down Expand Up @@ -55,19 +56,26 @@ export default async function OrganizationLayout({
if (!context.mothershipAvailable && !context.searchAccess.memberScoped)
redirect(WORKSPACE_SETTINGS_PATH)

const [, tableRowTtlEnabled, modelSelectorEnabled, planModeEnabled, dashboardsEnabled] =
await Promise.all([
prefetchOrganizationSidebar(
queryClient,
organizationId,
{ kind: 'session', userId: session.user.id, sessionId: session.session.id },
getActiveOrganizationId(session)
),
isTableRowTtlEnabled(),
isMothershipModelSelectorEnabled(),
isPlanModeEnabled(),
isDashboardsEnabled(organizationId),
])
const [
,
tableRowTtlEnabled,
modelSelectorEnabled,
planModeEnabled,
dashboardsEnabled,
orgProjectViewEnabled,
] = await Promise.all([
prefetchOrganizationSidebar(
queryClient,
organizationId,
{ kind: 'session', userId: session.user.id, sessionId: session.session.id },
getActiveOrganizationId(session)
),
isTableRowTtlEnabled(),
isMothershipModelSelectorEnabled(),
isPlanModeEnabled(),
isDashboardsEnabled(organizationId),
isOrgProjectViewEnabled(organizationId),
])
const initialSidebarCollapsed = cookieStore.get('sidebar_collapsed')?.value === '1'

return (
Expand All @@ -78,6 +86,7 @@ export default async function OrganizationLayout({
'table-row-ttl': tableRowTtlEnabled,
'mothership-model-selector': modelSelectorEnabled,
'mothership-plan-mode': planModeEnabled,
'org-project-view': orgProjectViewEnabled,
}}
>
<OrganizationProvider context={context}>
Expand Down
21 changes: 20 additions & 1 deletion apps/sim/app/playground/org/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { Suspense } from 'react'
import { getSession } from '@/lib/auth'
import { getActiveOrganizationId } from '@/lib/auth/session-response'
import {
getOrganizationSurfaceContext,
type OrganizationSurfaceContext,
resolveOrganizationLanding,
} from '@/lib/organizations/surface'
import { ProtoPage } from '@/app/playground/org/components/proto-page'

/** The organization the home page chats in, resolved the way the app entry picks one. */
async function resolveHomeOrganization(): Promise<OrganizationSurfaceContext | null> {
const session = await getSession()
if (!session?.user) return null
const organizationId = await resolveOrganizationLanding(
session.user.id,
getActiveOrganizationId(session)
)
return organizationId ? getOrganizationSurfaceContext(organizationId, session.user.id) : null
}

export default async function OrgPrototypePage({
params,
}: {
params: Promise<{ slug?: string[] }>
}) {
const { slug } = await params
const organization = slug?.length ? null : await resolveHomeOrganization()
return (
<Suspense
fallback={<p className='p-6 text-[var(--text-muted)] text-caption'>Loading prototype…</p>}
>
<ProtoPage slug={slug} />
<ProtoPage slug={slug} organization={organization} />
</Suspense>
)
}
13 changes: 7 additions & 6 deletions apps/sim/app/playground/org/components/agent-run.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { CircleCheck, Mic, Plus } from '@sim/emcn/icons'
import Link from 'next/link'
import { ShimmerText } from '@/components/ui/shimmer-text'
import { LinkedTickets } from '@/app/playground/org/components/linked-tickets'
import type { RunningWork } from '@/app/playground/org/lib/changelog-data'
import type { Project } from '@/app/playground/org/lib/project'
import { protoRoutes } from '@/app/playground/org/lib/routes'
import type { RunningWork } from '@/app/playground/org/lib/types'

const NARRATION_INTERVAL_S = 7

Expand All @@ -19,14 +20,14 @@ function formatElapsed(total: number): string {

interface AgentRunProps {
work: RunningWork
workspaceId: string
project: Project
}

/**
* A background run seen from inside its chat: elapsed time, what the agent is saying now,
* a Thinking shimmer, and a composer to steer, auto-approve, or stop it.
*/
export function AgentRun({ work, workspaceId }: AgentRunProps) {
export function AgentRun({ work, project }: AgentRunProps) {
const [seconds, setSeconds] = useState(0)
const [autoApprove, setAutoApprove] = useState(false)
const [stopped, setStopped] = useState(false)
Expand Down Expand Up @@ -112,15 +113,15 @@ export function AgentRun({ work, workspaceId }: AgentRunProps) {
<span className='ml-auto flex items-center gap-2 text-[var(--text-muted)] text-small'>
<span className='inline-flex items-center gap-0.5'>
<Link
href={protoRoutes.issue(workspaceId, work.issue)}
href={protoRoutes.issue(project.id, work.issue)}
className='underline-offset-2 hover:text-[var(--text-body)] hover:underline'
>
{work.issue}
</Link>
<LinkedTickets issueKey={work.issue} />
<LinkedTickets project={project} issueKey={work.issue} />
</span>
<Link
href={protoRoutes.chat(work.chat.id)}
href={protoRoutes.chat(project.id, work.chat.id)}
className='flex items-center gap-1.5 rounded-md px-1 hover:text-[var(--text-body)]'
>
<Avatar size='xs' name={work.chat.owner} />
Expand Down
Loading
Loading