Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"version": "0.0.1",
"author": "",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.53",
"@ai-sdk/groq": "^2.0.32",
"@ai-sdk/openai": "^2.0.65",
"@ai-sdk/anthropic": "^3.0.75",
"@ai-sdk/groq": "^3.0.38",
"@ai-sdk/openai": "^3.0.62",
"@aws-sdk/client-acm": "^3.948.0",
"@aws-sdk/client-api-gateway": "^3.948.0",
"@aws-sdk/client-apigatewayv2": "^3.948.0",
Expand Down Expand Up @@ -88,7 +88,7 @@
"@upstash/redis": "^1.34.2",
"@upstash/vector": "^1.2.2",
"adm-zip": "^0.5.16",
"ai": "^5.0.60",
"ai": "^6.0.175",
"archiver": "^7.0.1",
"axios": "^1.12.2",
"better-auth": "^1.4.22",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/assistant-chat/assistant-chat.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Important:
const result = streamText({
model: openai('gpt-5'),
system: systemPrompt,
messages: convertToModelMessages(messages),
messages: await convertToModelMessages(messages),
tools,
stopWhen: stepCountIs(5),
});
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/frameworks/frameworks-scores.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function getOverviewScores(organizationId: string) {
}),
db.onboarding.findUnique({
where: { organizationId },
select: { triggerJobId: true },
select: { triggerJobId: true, triggerJobCompleted: true },
}),
db.organization.findUnique({
where: { id: organizationId },
Expand Down Expand Up @@ -90,7 +90,7 @@ export async function getOverviewScores(organizationId: string) {
incompleteTasks,
},
people,
onboardingTriggerJobId: onboarding?.triggerJobId ?? null,
onboardingTriggerJobId: onboarding?.triggerJobCompleted ? null : (onboarding?.triggerJobId ?? null),
documents: await computeDocumentsScore(organizationId),
findings: await getOrganizationFindings(organizationId),
};
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/policies/policies.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,7 @@ Keep responses helpful and focused on the policy editing task.`;
const result = streamText({
model: openai('gpt-5.5'),
system: systemPrompt,
messages: convertToModelMessages(messages),
messages: await convertToModelMessages(messages),
});

return result.pipeTextStreamToResponse(res);
Expand Down
2 changes: 1 addition & 1 deletion apps/app/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default withSentryConfig(withBotId(config), {
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/

// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
widenClientFileUpload: false,

// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
Expand Down
16 changes: 9 additions & 7 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"version": "0.1.0",
"type": "module",
"dependencies": {
"@ai-sdk/anthropic": "^3.0.0",
"@ai-sdk/groq": "^3.0.0",
"@ai-sdk/openai": "^3.0.0",
"@ai-sdk/provider": "^3.0.0",
"@ai-sdk/react": "^3.0.0",
"@ai-sdk/rsc": "^2.0.0",
"@ai-sdk/anthropic": "^3.0.75",
"@ai-sdk/gateway": "^3.0.110",
"@ai-sdk/google": "^3.0.68",
"@ai-sdk/groq": "^3.0.38",
"@ai-sdk/openai": "^3.0.62",
"@ai-sdk/provider": "^3.0.10",
"@ai-sdk/react": "^3.0.177",
"@ai-sdk/rsc": "^2.0.175",
"@aws-sdk/client-ec2": "^3.948.0",
"@aws-sdk/client-lambda": "^3.948.0",
"@aws-sdk/client-s3": "3.1013.0",
Expand Down Expand Up @@ -84,7 +86,7 @@
"@vercel/sandbox": "^0.0.21",
"@vercel/sdk": "^1.7.1",
"@xyflow/react": "^12.10.0",
"ai": "^6.0.116",
"ai": "^6.0.175",
"ai-elements": "^1.6.1",
"axios": "^1.9.0",
"better-auth": "^1.4.22",
Expand Down
459 changes: 137 additions & 322 deletions apps/app/src/app/(app)/[orgId]/components/OnboardingTracker.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export function FrameworkDetailContent({
const pathname = usePathname();
const searchParams = useSearchParams();
const { hasPermission, permissions } = usePermissions();
const versioningEnabled = useFeatureFlag('is-framework-versioning-enabled');
const complianceTimelineEnabled = useFeatureFlag('is-timeline-enabled');
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [dropdownOpen, setDropdownOpen] = useState(false);
Expand Down Expand Up @@ -82,7 +81,7 @@ export function FrameworkDetailContent({
const validTabsList: string[] = [];
if (complianceTimelineEnabled) validTabsList.push('progress');
validTabsList.push('requirements');
if (versioningEnabled) validTabsList.push('history');
validTabsList.push('history');
const validTabs = new Set(validTabsList);
const activeTab = tabParam && validTabs.has(tabParam) ? tabParam : DEFAULT_TAB;

Expand Down Expand Up @@ -158,7 +157,7 @@ export function FrameworkDetailContent({
<TabsTrigger value="requirements">
Requirements <TabBadge>{requirementsCount}</TabBadge>
</TabsTrigger>
{versioningEnabled && <TabsTrigger value="history">History</TabsTrigger>}
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>
}
>
Expand Down Expand Up @@ -197,14 +196,12 @@ export function FrameworkDetailContent({
/>
</TabsContent>

{versioningEnabled && (
<TabsContent value="history">
<SyncHistorySection
frameworkInstanceId={frameworkInstanceId}
permissions={permissions}
/>
</TabsContent>
)}
<TabsContent value="history">
<SyncHistorySection
frameworkInstanceId={frameworkInstanceId}
permissions={permissions}
/>
</TabsContent>
</PageLayout>

<FrameworkDeleteDialog
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import { useFeatureFlag } from '@trycompai/analytics';
import { useParams, useRouter } from 'next/navigation';
import { useFrameworkUpdateStatus } from '@/hooks/use-framework-update-status';
import { usePermissions } from '@/hooks/use-permissions';
Expand All @@ -18,20 +17,13 @@ export function FrameworkVersioningSection({
initialStatus,
hasActiveAudit,
}: FrameworkVersioningSectionProps) {
const enabled = useFeatureFlag('is-framework-versioning-enabled');
// Thread the flag into SWR so the update-status request doesn't fire for
// orgs that don't have versioning enabled. Without this the request runs
// every mount and we just throw the response away.
const { data } = useFrameworkUpdateStatus(frameworkInstanceId, {
fallbackData: initialStatus,
enabled,
});
const { hasPermission } = usePermissions();
const router = useRouter();
const { orgId } = useParams<{ orgId: string }>();

if (!enabled) return null;

const canUpdate = hasPermission('framework', 'update');

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Card, CardContent, CardHeader, CardTitle } from '@trycompai/ui/card';
import { ScrollArea } from '@trycompai/ui/scroll-area';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@trycompai/ui/tabs';
import { Policy, Task } from '@db';
import { useRealtimeRun } from '@trigger.dev/react-hooks';
import {
ArrowRight,
CheckCircle2,
Expand Down Expand Up @@ -49,25 +48,7 @@ export function ToDoOverview({
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const { run: onboardingRun } = useRealtimeRun(onboardingTriggerJobId || '', {
enabled: !!onboardingTriggerJobId,
});

const IN_PROGRESS_STATUSES = [
'QUEUED',
'EXECUTING',
'WAITING_FOR_DEPLOY',
'REATTEMPTING',
'FROZEN',
'DELAYED',
'WAITING',
'PENDING_VERSION',
'DEQUEUED',
];

const isOnboardingInProgress = onboardingRun
? IN_PROGRESS_STATUSES.includes(onboardingRun.status)
: false;
const isOnboardingInProgress = !!onboardingTriggerJobId;
Comment thread
Marfuen marked this conversation as resolved.

const formatStatus = (status: string) => {
return status.replace('_', ' ').replace(/\b\w/g, (l) => l.toUpperCase());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createGatewayProvider } from '@ai-sdk/gateway';
import type { OpenAIResponsesProviderOptions } from '@ai-sdk/openai';
import type { LanguageModelV2 } from '@ai-sdk/provider';
import type { LanguageModelV3 } from '@ai-sdk/provider';
import type { JSONValue } from 'ai';

export async function getAvailableModels() {
Expand All @@ -10,7 +10,7 @@ export async function getAvailableModels() {
}

export interface ModelOptions {
model: LanguageModelV2;
model: LanguageModelV3;
providerOptions?: Record<string, Record<string, JSONValue>>;
headers?: Record<string, string>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ export function OnboardingStatus({ runId }: { runId: string }) {
useEffect(() => {
if (run?.status === 'COMPLETED') {
router.replace('/');
return;
}
}, [run?.status, router]);
const meta = run?.metadata as Record<string, unknown> | undefined;
if (meta?.readyForDashboard === true) {
router.replace('/');
}
}, [run?.status, run?.metadata, router]);

return (
<div className="flex flex-col items-center justify-center">
Expand Down
15 changes: 6 additions & 9 deletions apps/app/src/hooks/use-framework-update-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ import type { FrameworkUpdateStatus } from '@/types/framework-versioning';

interface UseFrameworkUpdateStatusOptions {
fallbackData?: FrameworkUpdateStatus;
enabled?: boolean;
}

export function useFrameworkUpdateStatus(
frameworkInstanceId: string,
options?: UseFrameworkUpdateStatusOptions,
) {
const key =
frameworkInstanceId && options?.enabled !== false
? `/v1/frameworks/${frameworkInstanceId}/update-status`
: null;
const key = frameworkInstanceId
? `/v1/frameworks/${frameworkInstanceId}/update-status`
: null;

return useSWR<FrameworkUpdateStatus>(
key,
Expand All @@ -30,10 +28,9 @@ export function useFrameworkUpdateStatus(
// Always revalidate on mount, even when fallbackData is provided.
// fallbackData is only a fast first paint — without this, SWR treats
// the server-rendered snapshot as authoritative forever and skips the
// client fetch, so users don't see newly-available upgrades after the
// feature flag is flipped, after a sync on a sibling framework, or
// whenever the Next.js router cache serves a stale RSC. Short of
// signing out and back in.
// client fetch, so users don't see newly-available upgrades after a
// sync on a sibling framework or whenever the Next.js router cache
// serves a stale RSC.
revalidateOnMount: true,
revalidateOnFocus: true,
},
Expand Down
63 changes: 28 additions & 35 deletions apps/app/src/lib/embedding/run-linkage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,10 @@ const AUTONOMOUS_FINAL_TOP_K = 8;
const AUTONOMOUS_MIN_RERANK_SCORE = 5;
const AUTONOMOUS_MIN_LINKS_FLOOR = 3;

// How many risks/vendors to match concurrently in the bulk onboarding path.
// Each iteration makes 1 vector query (Upstash) + 1 OpenAI rerank call + 1
// Prisma update — typical wall-clock per iteration is 3–10 seconds (the
// rerank LLM call dominates). With 32 in-flight at once a 20-entity
// onboarding finishes in roughly one batch, well within gpt-5-mini /
// Upstash rate limits.
//
// NOTE: this is in-process concurrency on a single trigger.dev task. The
// natural next step (true fan-out per entity using `task.batchTrigger`)
// would unlock trigger.dev's queue-level concurrency (50), but requires
// passing the embedded-task metadata to children rather than rebuilding
// taskById per child. Filed as a follow-up.
const MATCH_CONCURRENCY = 32;
// Risk and vendor matching run in parallel, so this limit applies to
// EACH side independently. Keep it at 16 so both sides combined stay
// under ~32 concurrent LLM rerank calls.
const MATCH_CONCURRENCY = 16;

async function mapWithConcurrency<T, R>(
items: T[],
Expand Down Expand Up @@ -604,14 +595,22 @@ export async function runLinkage({

let suggestions: RunLinkageOutput['suggestions'];

// Risk matching — fan out with bounded concurrency. Each iteration is one
// vector query + (rerank | DB update). Order doesn't matter here; we sum
// riskLinks at the end and emit `current` based on completion count.
// Emit initial matching phases before the parallel fan-out so the UI shows
// both phases starting at once.
if (risks.length > 0) {
onPhase?.({ name: 'matching-risks', current: 0, total: risks.length });
}
if (vendors.length > 0) {
onPhase?.({ name: 'matching-vendors', current: 0, total: vendors.length });
}

// Risk + vendor matching run in parallel — they write to separate DB
// tables (Risk.tasks vs Vendor.tasks) and the shared taskById is read-only.
let completedRisks = 0;
const riskOutcomes = await mapWithConcurrency(risks, MATCH_CONCURRENCY, async (risk) => {
let completedVendors = 0;

const [riskOutcomes, vendorOutcomes] = await Promise.all([
mapWithConcurrency(risks, MATCH_CONCURRENCY, async (risk) => {
const similar = await findSimilarTasks({
organizationId,
queryText: riskQueryText(risk),
Expand Down Expand Up @@ -687,23 +686,8 @@ export async function runLinkage({
completedRisks += 1;
onPhase?.({ name: 'matching-risks', current: completedRisks, total: risks.length });
return { count, perEntitySuggestions };
});
const riskLinks = riskOutcomes.reduce((sum, r) => sum + r.count, 0);
// suggestionsOnly endpoints always pass a single riskId, so at most one
// outcome carries suggestions — pick the first non-null.
for (const r of riskOutcomes) {
if (r.perEntitySuggestions) {
suggestions = r.perEntitySuggestions;
break;
}
}

// Vendor matching — same pattern.
if (vendors.length > 0) {
onPhase?.({ name: 'matching-vendors', current: 0, total: vendors.length });
}
let completedVendors = 0;
const vendorOutcomes = await mapWithConcurrency(vendors, MATCH_CONCURRENCY, async (vendor) => {
}),
mapWithConcurrency(vendors, MATCH_CONCURRENCY, async (vendor) => {
const similar = await findSimilarTasks({
organizationId,
queryText: vendorQueryText(vendor),
Expand Down Expand Up @@ -751,8 +735,17 @@ export async function runLinkage({
completedVendors += 1;
onPhase?.({ name: 'matching-vendors', current: completedVendors, total: vendors.length });
return { count, perEntitySuggestions };
});
}),
]);

const riskLinks = riskOutcomes.reduce((sum, r) => sum + r.count, 0);
const vendorLinks = vendorOutcomes.reduce((sum, v) => sum + v.count, 0);
for (const r of riskOutcomes) {
if (r.perEntitySuggestions) {
suggestions = r.perEntitySuggestions;
break;
}
}
if (!suggestions) {
for (const v of vendorOutcomes) {
if (v.perEntitySuggestions) {
Expand Down
10 changes: 7 additions & 3 deletions apps/app/src/lib/rerank-suggestions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { openai } from '@ai-sdk/openai';
import { createGatewayProvider } from '@ai-sdk/gateway';
import { generateObject, jsonSchema } from 'ai';

/**
Expand Down Expand Up @@ -39,7 +39,11 @@ export interface RerankedCandidate {
rerankScore: number;
}

const RERANK_MODEL = 'gpt-5-mini';
const gateway = createGatewayProvider({
baseURL: process.env.AI_GATEWAY_BASE_URL,
});

const RERANK_MODEL = 'google/gemini-3.1-flash-lite-preview' as const;

const SYSTEM_PROMPT = `You are a GRC analyst evaluating which compliance tasks would meaningfully reduce a specific risk or vendor exposure.

Expand Down Expand Up @@ -105,7 +109,7 @@ export async function rerankSuggestions({
.join('\n');

const result = await generateObject({
model: openai(RERANK_MODEL),
model: gateway(RERANK_MODEL),
system: SYSTEM_PROMPT,
prompt: userPrompt,
schema: rerankSchema,
Expand Down
Loading
Loading