Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0946fbd
spec(report-generation): add comprehensive report generation feature …
betterclever Dec 31, 2025
61ef335
feat(report-generation): add core.report.generator component (Phase 1)
betterclever Dec 31, 2025
5d55f0f
feat(report-generation): complete Phases 1-4 with AI SDK integration
betterclever Dec 31, 2025
d040648
fix(report-generation): fix OpenAPI spec generation
betterclever Dec 31, 2025
624860f
docs(report-generation): update spec with OpenAPI completion
betterclever Dec 31, 2025
c4507ca
fix(workflow-call): correctly set dataType for dynamic and static ports
betterclever Dec 31, 2025
d1ef226
feat(ai): upgrade to AI SDK 3.0 and fix typechecks
betterclever Dec 31, 2025
8fd3a92
fix: sync templates api
betterclever Dec 31, 2025
7189cc3
feat: move ai endpoints
betterclever Dec 31, 2025
02a7763
feat(ai-templates): implement local preact+htm preview and tool inter…
betterclever Dec 31, 2025
f703f14
feat: switch to Vercel AI Gateway with multi-provider support and opt…
betterclever Dec 31, 2025
ed581c8
fix(frontend): robust template loading using iframe sandbox fix and j…
betterclever Dec 31, 2025
497b445
feat: Add RJSF with official shadcn theme for Template Editor
betterclever Dec 31, 2025
365b9da
refactor: Template Editor layout - collapsible sidebar, compact header
betterclever Dec 31, 2025
3eb1822
refactor: Fix Template Editor layout with proper top bar
betterclever Dec 31, 2025
a51276d
fix: Template Editor layout matches Workflow Editor pattern
betterclever Dec 31, 2025
1d98da1
fix: Prevent whole-page overscroll in SPA
betterclever Dec 31, 2025
90fcd8b
refactor: Simplify AI chat tool result display
betterclever Dec 31, 2025
7ae1a18
style: Make RJSF form compact (~70% of default size)
betterclever Jan 1, 2026
cf1a27e
feat(frontend): Implement compact RJSF theme v1 with custom array ren…
betterclever Jan 1, 2026
a6ab12b
feat(frontend): Add compact SelectWidget and collapsible array items …
betterclever Jan 1, 2026
4413840
feat(backend): update AI system prompt with ShipSec.ai branding and i…
betterclever Jan 1, 2026
5fc506b
feat(frontend): add animated 'Thinking' indicator for AI chat pending…
betterclever Jan 1, 2026
4829127
feat(frontend): refine AI loading indicator to switch between 'Thinki…
betterclever Jan 1, 2026
492dc87
fix(frontend): Correctly pass event object to RJSF array button handlers
betterclever Jan 1, 2026
f9919b7
fix(backend): update AI system prompt to prevent invalid nested schem…
betterclever Jan 1, 2026
d3c1292
fix(frontend): ensure FieldTemplate consistently renders structure ev…
betterclever Jan 1, 2026
322ffe4
refactor(frontend): switch to 3-column studio layout (Chat - Preview …
betterclever Jan 1, 2026
e9c4e52
fix(frontend): resolve SparklesIcon import and cleanup unused variables
betterclever Jan 1, 2026
4cf5aa7
fix(frontend): simplify chat scrolling and fix flexbox height constraint
betterclever Jan 1, 2026
ee79589
fix(frontend): resolve overscroll in chat empty state by correcting f…
betterclever Jan 1, 2026
eec7ab6
feat(frontend): switch to card layout for templates and fix new templ…
betterclever Jan 1, 2026
b7b180d
style(frontend): refine template cards to be leaner and include usage…
betterclever Jan 1, 2026
3968d49
style(frontend): remove category icon and enhance version badge styli…
betterclever Jan 1, 2026
8cbe184
feat: Add workflow-style save button with dirty state tracking to tem…
betterclever Jan 1, 2026
249044e
debug: Add logging to template editor save flow
betterclever Jan 1, 2026
acdb0e5
debug: Add more logging to template editor component
betterclever Jan 1, 2026
570ce12
debug: Add logging to template store updateTemplate
betterclever Jan 1, 2026
0878923
fix: Add race condition check in dirty state tracking
betterclever Jan 1, 2026
efbfccc
debug: Add more logging to template store isDirty changes
betterclever Jan 1, 2026
de10cce
fix: Update originalValues before API call to prevent race condition
betterclever Jan 1, 2026
555cb5c
feat: Create template immediately navigates to editor without dialog
betterclever Jan 1, 2026
002abb9
fix: Clean up debug logging and ensure save button works correctly
betterclever Jan 1, 2026
3fc46d0
layout: Move schema panel to left and AI assistant to right
betterclever Jan 1, 2026
3203bf8
feat: Add undo functionality for AI template changes with confirmatio…
betterclever Jan 1, 2026
7c6a3fd
fix: Send current template data to AI and fix undo to restore base state
betterclever Jan 1, 2026
a2a7a8b
feat: Implement step-by-step undo stack for AI template changes
betterclever Jan 1, 2026
132172c
refactor: Simplify chat UI to flat message bubbles without personas
betterclever Jan 1, 2026
5d78665
fix: remove core.report.generator component and fix missing startChil…
betterclever Jan 1, 2026
9991f7f
feat: remove AI generation e2e tests
betterclever Jan 1, 2026
4a265c6
chore: remove report generation feature tracking files from .ai folder
betterclever Jan 1, 2026
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
30 changes: 30 additions & 0 deletions backend/drizzle/0019_create-report-templates.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
CREATE TABLE report_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
content JSONB NOT NULL,
input_schema JSONB NOT NULL,
sample_data JSONB,
version INTEGER NOT NULL DEFAULT 1,
is_system BOOLEAN DEFAULT FALSE,
created_by UUID REFERENCES auth_users(id),
org_id UUID REFERENCES organizations(id),
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);

CREATE TABLE generated_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID REFERENCES report_templates(id) ON DELETE SET NULL,
template_version INTEGER,
workflow_run_id UUID REFERENCES workflow_runs(run_id) ON DELETE SET NULL,
input_data JSONB,
artifact_id UUID REFERENCES artifacts(id) ON DELETE SET NULL,
org_id UUID REFERENCES organizations(id),
generated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS report_templates_org_idx ON report_templates(org_id);
CREATE INDEX IF NOT EXISTS report_templates_system_idx ON report_templates(is_system);
CREATE INDEX IF NOT EXISTS generated_reports_template_idx ON generated_reports(template_id);
CREATE INDEX IF NOT EXISTS generated_reports_workflow_idx ON generated_reports(workflow_run_id);
13 changes: 13 additions & 0 deletions backend/drizzle/0020_update-report-template-org-types.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
ALTER TABLE report_templates
DROP CONSTRAINT IF EXISTS report_templates_created_by_fkey,
DROP CONSTRAINT IF EXISTS report_templates_org_id_fkey;

ALTER TABLE generated_reports
DROP CONSTRAINT IF EXISTS generated_reports_org_id_fkey;

ALTER TABLE report_templates
ALTER COLUMN created_by TYPE VARCHAR(191) USING created_by::text,
ALTER COLUMN org_id TYPE VARCHAR(191) USING org_id::text;

ALTER TABLE generated_reports
ALTER COLUMN org_id TYPE VARCHAR(191) USING org_id::text;
5 changes: 4 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"delete:runs": "bun scripts/delete-all-workflow-runs.ts"
},
"dependencies": {
"@ai-sdk/google": "^3.0.2",
"@ai-sdk/openai": "^3.0.2",
"@clerk/backend": "^2.9.4",
"@clerk/types": "^4.81.0",
"@grpc/grpc-js": "^1.14.0",
Expand All @@ -32,12 +34,13 @@
"@temporalio/workflow": "^1.11.3",
"@types/express": "^5.0.3",
"@types/minio": "^7.1.1",
"ai": "^6.0.0-beta.68",
"ai": "^6.0.5",
"bcryptjs": "^3.0.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.44.6",
"handlebars": "^4.7.8",
"ioredis": "^5.4.1",
"kafkajs": "^2.2.4",
"long": "^5.2.4",
Expand Down
5 changes: 4 additions & 1 deletion backend/src/agent-trace/agent-trace-ingest.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export class AgentTraceIngestService implements OnModuleInit, OnModuleDestroy {
}

this.kafkaTopic = process.env.AGENT_TRACE_KAFKA_TOPIC ?? 'telemetry.agent-trace';
this.kafkaGroupId = process.env.AGENT_TRACE_KAFKA_GROUP_ID ?? 'shipsec-agent-trace-ingestor';
const baseGroupId = process.env.AGENT_TRACE_KAFKA_GROUP_ID ?? 'shipsec-agent-trace-ingestor';
this.kafkaGroupId = process.env.NODE_ENV === 'production'
? baseGroupId
: `${baseGroupId}-dev-${Math.random().toString(36).substring(7)}`;
this.kafkaClientId = process.env.AGENT_TRACE_KAFKA_CLIENT_ID ?? 'shipsec-backend-agent-trace';
}

Expand Down
103 changes: 103 additions & 0 deletions backend/src/ai/ai.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
BadRequestException,
Controller,
Post,
Req,
Res,
UnauthorizedException,
UseGuards,
} from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import type { Request, Response } from 'express';
import { UIMessage } from 'ai';

import { AiService } from './ai.service';
import { AuthGuard } from '../auth/auth.guard';
import { CurrentAuth } from '../auth/auth-context.decorator';
import type { AuthContext } from '../auth/types';

/**
* AI Controller - AI SDK v6 compatible
*
* Receives UIMessage[] from useChat's DefaultChatTransport.
* Returns toUIMessageStreamResponse() for streaming.
*/
@ApiTags('AI')
@Controller('ai')
@UseGuards(AuthGuard)
export class AiController {
constructor(private readonly aiService: AiService) {}

/**
* Main chat endpoint
*
* Receives: { messages: UIMessage[], id?, systemPrompt?, context?, model? }
* Returns: UI Message Stream Response
*/
@Post()
@ApiOkResponse({ description: 'AI SDK v6 UI message stream' })
async chat(
@CurrentAuth() auth: AuthContext | null,
@Req() req: Request,
@Res() res: Response,
) {
this.requireAuth(auth);

const body = req.body as {
messages: UIMessage[];
systemPrompt?: string;
context?: string;
model?: string;
};

const { messages, systemPrompt, context, model } = body;

if (!messages || !Array.isArray(messages) || messages.length === 0) {
throw new BadRequestException('Messages are required');
}

const system = systemPrompt || this.aiService.buildSystemPrompt(context);

const result = await this.aiService.streamChat(messages, { system, model, context });

// toUIMessageStreamResponse returns a Response object
// We need to pipe it to Express response
const streamResponse = result.toUIMessageStreamResponse();

// Set headers from the stream response
res.setHeader('Content-Type', streamResponse.headers.get('Content-Type') || 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

// Pipe the body to the response
if (streamResponse.body) {
const reader = streamResponse.body.getReader();
const pump = async () => {
while (true) {
const { done, value } = await reader.read();
if (done) {
res.end();
break;
}
res.write(value);
}
};
pump().catch((err) => {
console.error('Stream error:', err);
res.end();
});
} else {
res.end();
}
}

private requireAuth(auth: AuthContext | null): AuthContext {
if (!auth?.isAuthenticated) {
throw new UnauthorizedException('Authentication required');
}
if (!auth.organizationId) {
throw new BadRequestException('Organization context is required');
}
return auth;
}
}
13 changes: 13 additions & 0 deletions backend/src/ai/ai.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { AiService } from './ai.service';
import { forwardRef } from '@nestjs/common';
import { ApiKeysModule } from '../api-keys/api-keys.module';
import { AiController } from './ai.controller';

@Module({
imports: [forwardRef(() => ApiKeysModule)],
controllers: [AiController],
providers: [AiService],
exports: [AiService],
})
export class AiModule {}
Loading