Skip to content
Merged

Test #21

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
9 changes: 6 additions & 3 deletions convex/mcp_mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,13 @@ export const acquireConnectionLock = internalMutation({
export const updateConnectionStatus = internalMutation({
args: {
serverName: v.string(),
status: v.string(), // 'connected' | 'failed' | 'disconnected'
status: v.union(
v.literal('connected'),
v.literal('failed'),
v.literal('disconnected')
),
Comment on lines +86 to +90
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Good type constraint, but verify consistency across mutations.

Restricting status to a literal union improves type safety. However, note that storeConnection (line 118) still accepts v.string() for its status parameter.

Verify that:

  1. All callers of updateConnectionStatus pass only these three values
  2. Consider whether storeConnection should use the same constraint for consistency

🏁 Script executed:

#!/bin/bash
# Search for calls to updateConnectionStatus to verify status values
rg -nP '\bupdateConnectionStatus\s*\(' -A 5 -C 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Verify the file exists and examine content around the specified lines
fd -e ts -e js "mcp_mutations" -x cat {}

# Also search for storeConnection definition
rg -nP '\bstoreConnection\s*[=:]' -A 8 -B 2

Length of output: 6880


🏁 Script executed:

#!/bin/bash
# Search for any status field assignments or references
rg -nP '"status"|\'status\'' -A 2 -B 2 --type=ts

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Search for calls to updateConnectionStatus and storeConnection
rg -nP 'updateConnectionStatus|storeConnection' -B 2 -A 3 --type=ts

Length of output: 2861


🏁 Script executed:

#!/bin/bash
# Search for any files importing from mcp_mutations
fd -e ts -e js | xargs rg -l 'mcp_mutations' 2>/dev/null | head -20

Length of output: 40


Fix storeConnection to match updateConnectionStatus type constraints.

The inconsistency is confirmed. While updateConnectionStatus (line 83) uses a strict literal union for status, storeConnection (line 115) accepts v.string(), allowing any value. Although current callers in convex/mcp.ts pass valid values ('connected' and 'disconnected'), the schema should enforce this constraint for consistency and to prevent future bugs.

Update storeConnection's status parameter to use v.union(v.literal('connected'), v.literal('failed'), v.literal('disconnected')) to match updateConnectionStatus, or constrain it to only the values it actually receives: v.union(v.literal('connected'), v.literal('disconnected')).

🤖 Prompt for AI Agents
In convex/mcp_mutations.ts around lines 86-90 and 115-118, the status schema for
updateConnectionStatus is a strict union of 'connected' | 'failed' |
'disconnected', but storeConnection currently uses v.string(); change
storeConnection's status validator to the same union
(v.union(v.literal('connected'), v.literal('failed'),
v.literal('disconnected'))) so both functions enforce identical allowed values
and prevent invalid statuses.

error: v.optional(v.string())
},
handler: async (ctx, args) => {
}, handler: async (ctx, args) => {
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting issue: There should be a line break after the closing brace }, and before handler:. The line currently reads }, handler: async (ctx, args) => { but should have }, on line 91 and handler: async (ctx, args) => { starting on line 92.

Suggested change
}, handler: async (ctx, args) => {
},
handler: async (ctx, args) => {

Copilot uses AI. Check for mistakes.
const existing = await ctx.db
.query('mcpConnections')
.withIndex('by_server', q => q.eq('serverName', args.serverName))
Expand Down
15 changes: 14 additions & 1 deletion src/components/AgenticChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ interface AgenticChatProps {
onSearchResults?: (results: SearchResult[]) => void;
}

/**
* Interactive chat UI that detects search intent, performs agentic multi-model searches,
* and displays interleaved reasoning, search results, and comparison metrics.
*
* This component:
* - Automatically detects when a user message implies a search and runs an agentic search flow.
* - Renders chat messages, explicit reasoning steps, search results, and an advanced comparison dashboard.
* - Updates internal dashboard state with unified search results and may persist search history via a Convex mutation.
* - Gates interactions until a CSRF token is available.
*
* @param onSearchResults - Optional callback invoked with the array of `SearchResult` when an agentic search completes or falls back to fallback results.
* @returns The AgenticChat React element.
*/
export function AgenticChat({ onSearchResults }: AgenticChatProps) {
const { token: csrfToken, error: csrfError } = useCsrfToken();
const saveSearch = useMutation(api.searchHistory.saveSearch);
Expand Down Expand Up @@ -478,4 +491,4 @@ export function AgenticChat({ onSearchResults }: AgenticChatProps) {
</div>
</div>
);
}
}
8 changes: 6 additions & 2 deletions src/components/ComparisonDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,11 @@ function ModelOutputCard({ response }: { response: ModelResponse }) {
}

/**
* Reasoning Step Card Component
* Render a card summarizing a single reasoning step, including its type, validation status, token count, output preview, confidence, and any validation errors.
*
* @param step - The reasoning step to render. Should include `type`, `output`, `confidence`, `validated`, `validationErrors`, and `tokenCount`.
* @param index - Zero-based index of the step used for display (e.g., "Step 1").
* @returns A JSX element representing the rendered reasoning step card.
*/
function ReasoningStepCard({
step,
Expand Down Expand Up @@ -438,4 +442,4 @@ function ScoreBar({
</div>
</div>
);
}
}
10 changes: 9 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import { useState } from "react";
import WorkOSHeader from "./workos-user.tsx";
import { SettingsModal } from "./SettingsModal";

/**
* Renders the application header with controls and a slide-in left navigation menu.
*
* The header includes a menu button, title link, and a quick settings button that opens the settings modal.
* The left aside is a sliding navigation panel with links for Home, Search History, Compare Searches, Export Datasets, and Settings; selecting a link closes the menu.
*
* @returns The header and left-side navigation menu as JSX elements
*/
export default function Header() {
const [isOpen, setIsOpen] = useState(false);
const [showSettings, setShowSettings] = useState(false);
Expand Down Expand Up @@ -144,4 +152,4 @@ export default function Header() {
</aside>
</>
);
}
}
7 changes: 6 additions & 1 deletion src/hooks/useCsrfToken.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { useEffect, useState } from "react";

/**
* Provides the CSRF token fetched from the server and any error encountered while retrieving it.
*
* @returns An object containing `token` — the CSRF token string or `null` if not yet available, and `error` — an `Error` instance if retrieval failed, otherwise `null`.
*/
export function useCsrfToken() {
const [token, setToken] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
Expand All @@ -12,4 +17,4 @@ export function useCsrfToken() {
}, []);

return { token, error };
}
}
18 changes: 13 additions & 5 deletions src/lib/model-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ interface StoredConfig {
}

/**
* Save model configuration to localStorage
* API keys are encrypted and stored separately
* Save a model configuration to localStorage and store any API key in encrypted secure storage.
*
* The configuration's API key (if present) is removed from the stored config and saved separately in encrypted secure storage; the saved config will reference the key by an `apiKeyRef`. The saved configuration becomes the active config and the storage timestamp is updated.
*
* @param id - Identifier for the model configuration
* @param config - The model configuration to save; if `config.apiKey` is present it will be stored in encrypted secure storage and not kept directly in localStorage
* @throws Error if an API key is provided but secure storage is unavailable (unencrypted storage of API keys is not allowed)
* @throws Any error encountered while persisting the configuration or secure storage operations
*/
export async function saveModelConfig(
id: string,
Expand Down Expand Up @@ -70,8 +76,10 @@ export async function saveModelConfig(
}

/**
* Load specific model configuration from localStorage
* Decrypts API key if present
* Load a saved model configuration by id, including decrypting its API key when present.
*
* @param id - The identifier of the model configuration to load
* @returns The reconstructed `ModelConfig` with a decrypted `apiKey` if available, or `null` if the config is not found or loading fails (for example, when an API key reference exists but secure storage is unavailable)
*/
export async function loadModelConfig(id: string): Promise<ModelConfig | null> {
try {
Expand Down Expand Up @@ -387,4 +395,4 @@ export function isStorageAvailable(): boolean {
} catch (error) {
return false;
}
}
}
22 changes: 13 additions & 9 deletions src/lib/s3-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export interface UploadResult {
}

/**
* Upload document to S3
* @param file File buffer or string content
* @param filename Original filename
* @param contentType MIME type
* @returns Upload result with S3 key and URL
* Upload a file to the configured S3 bucket and return its storage details.
*
* @param file - File contents as a `Buffer` or UTF-8 `string` to upload
* @param filename - Original filename used for metadata and to build a sanitized S3 object key
* @param contentType - MIME type to set on the uploaded S3 object
* @returns An object with `key` (S3 object key), `url` (public HTTPS URL assuming the bucket is public), and `size` (bytes)
* @throws If `AWS_ACCESS_KEY_ID` or `AWS_SECRET_ACCESS_KEY` environment variables are not set
*/
export async function uploadDocument(
file: Buffer | string,
Expand Down Expand Up @@ -108,9 +110,11 @@ export async function getPresignedDownloadUrl(
}

/**
* Download document from S3
* @param key S3 object key
* @returns Document content as Buffer
* Retrieve an object from S3 and return its raw contents as a Buffer.
*
* @param key - The S3 object key (path) to download
* @returns A Buffer containing the object's bytes
* @throws Error if the S3 object has no response body
*/
export async function downloadDocument(key: string): Promise<Buffer> {
const command = new GetObjectCommand({
Expand Down Expand Up @@ -169,4 +173,4 @@ export const FILE_SIZE_THRESHOLD = 1024 * 1024; // 1MB

export function shouldUseS3(fileSize: number): boolean {
return fileSize > FILE_SIZE_THRESHOLD && isS3Configured();
}
}
7 changes: 6 additions & 1 deletion src/routes/comparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export const Route = createFileRoute("/comparison")({
component: ComparisonPage,
});

/**
* Renders the comparison page with a full-height gradient background and a centered SearchComparisonDashboard.
*
* @returns The rendered JSX element for the comparison page.
*/
function ComparisonPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
Expand All @@ -13,4 +18,4 @@ function ComparisonPage() {
</div>
</div>
);
}
}
7 changes: 6 additions & 1 deletion src/routes/export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export const Route = createFileRoute("/export")({
component: DatasetExportPage,
});

/**
* Renders the dataset export page layout with a full-height gradient background and centered dashboard.
*
* @returns The JSX element containing a responsive container and the DatasetExportDashboard component.
*/
function DatasetExportPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
Expand All @@ -13,4 +18,4 @@ function DatasetExportPage() {
</div>
</div>
);
}
}
7 changes: 6 additions & 1 deletion src/routes/history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export const Route = createFileRoute("/history")({
component: SearchHistoryPage,
});

/**
* Page component that renders the search history inside a themed, responsive container.
*
* @returns The React element for the history page, containing the SearchHistory component within layout and background styling.
*/
function SearchHistoryPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
Expand All @@ -13,4 +18,4 @@ function SearchHistoryPage() {
</div>
</div>
);
}
}
Loading