From 461d61adfe38b184f1a729655fee7b85e259d99e Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 15:15:12 +1100 Subject: [PATCH 001/253] Add Claude Opus 4.5 model to Anthropic provider --- app/lib/modules/llm/providers/anthropic.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/lib/modules/llm/providers/anthropic.ts b/app/lib/modules/llm/providers/anthropic.ts index 1e105115..34f8272e 100644 --- a/app/lib/modules/llm/providers/anthropic.ts +++ b/app/lib/modules/llm/providers/anthropic.ts @@ -16,8 +16,17 @@ export default class AnthropicProvider extends BaseProvider { staticModels: ModelInfo[] = [ /* * Essential fallback models - only the most stable/reliable ones - * Claude Sonnet 4.5: 200k context, 64k output, best balance of intelligence and speed + * Claude Opus 4.5: 200k context, 64k output, maximum intelligence with practical performance */ + { + name: 'claude-opus-4-5-20251101', + label: 'Claude Opus 4.5', + provider: 'Anthropic', + maxTokenAllowed: 200000, + maxCompletionTokens: 64000, + }, + + // Claude Sonnet 4.5: 200k context, 64k output, best balance of intelligence and speed { name: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', @@ -94,7 +103,9 @@ export default class AnthropicProvider extends BaseProvider { // Determine completion token limits based on specific model let maxCompletionTokens = 128000; // default for older Claude 3 models - if (m.id?.includes('claude-sonnet-4-5') || m.id?.includes('claude-haiku-4-5')) { + if (m.id?.includes('claude-opus-4-5')) { + maxCompletionTokens = 64000; // Claude Opus 4.5: 64K output limit + } else if (m.id?.includes('claude-sonnet-4-5') || m.id?.includes('claude-haiku-4-5')) { maxCompletionTokens = 64000; // Claude 4.5 Sonnet/Haiku: 64K output limit } else if (m.id?.includes('claude-opus-4-1') || m.id?.includes('claude-opus-4')) { maxCompletionTokens = 32000; // Claude 4 Opus: 32K output limit From 5e927669a31612b9942c0c314fef96a050b3032d Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 15:16:21 +1100 Subject: [PATCH 002/253] Add Claude Opus 4.5 model to OpenRouter provider --- app/lib/modules/llm/providers/open-router.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/lib/modules/llm/providers/open-router.ts b/app/lib/modules/llm/providers/open-router.ts index 0e034776..a3ce7e56 100644 --- a/app/lib/modules/llm/providers/open-router.ts +++ b/app/lib/modules/llm/providers/open-router.ts @@ -30,8 +30,17 @@ export default class OpenRouterProvider extends BaseProvider { staticModels: ModelInfo[] = [ /* * Essential fallback models - only the most stable/reliable ones - * Claude Sonnet 4.5 via OpenRouter: 200k context + * Claude Opus 4.5 via OpenRouter: 200k context, maximum intelligence */ + { + name: 'anthropic/claude-opus-4-5', + label: 'Claude Opus 4.5', + provider: 'OpenRouter', + maxTokenAllowed: 200000, + maxCompletionTokens: 64000, + }, + + // Claude Sonnet 4.5 via OpenRouter: 200k context { name: 'anthropic/claude-sonnet-4-5', label: 'Claude Sonnet 4.5', From 8782bb8589be637c6f7d4413104a4faa9621ba0c Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 15:17:40 +1100 Subject: [PATCH 003/253] Add Claude 4.5 models (Opus, Sonnet, Haiku) to Amazon Bedrock provider --- .../modules/llm/providers/amazon-bedrock.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/lib/modules/llm/providers/amazon-bedrock.ts b/app/lib/modules/llm/providers/amazon-bedrock.ts index 778093e3..cabeb754 100644 --- a/app/lib/modules/llm/providers/amazon-bedrock.ts +++ b/app/lib/modules/llm/providers/amazon-bedrock.ts @@ -21,6 +21,27 @@ export default class AmazonBedrockProvider extends BaseProvider { }; staticModels: ModelInfo[] = [ + { + name: 'anthropic.claude-opus-4-5-20251101-v1:0', + label: 'Claude Opus 4.5 (Bedrock)', + provider: 'AmazonBedrock', + maxTokenAllowed: 200000, + maxCompletionTokens: 64000, + }, + { + name: 'anthropic.claude-sonnet-4-5-20250929-v1:0', + label: 'Claude Sonnet 4.5 (Bedrock)', + provider: 'AmazonBedrock', + maxTokenAllowed: 200000, + maxCompletionTokens: 64000, + }, + { + name: 'anthropic.claude-haiku-4-5-20251001-v1:0', + label: 'Claude Haiku 4.5 (Bedrock)', + provider: 'AmazonBedrock', + maxTokenAllowed: 200000, + maxCompletionTokens: 64000, + }, { name: 'anthropic.claude-3-5-sonnet-20241022-v2:0', label: 'Claude 3.5 Sonnet v2 (Bedrock)', From b1591b03a80597c0d20af701e00b689f3cd71f8c Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 15:22:25 +1100 Subject: [PATCH 004/253] Add GPT-5.2 models (Pro, Thinking, Instant) to OpenAI provider --- app/lib/modules/llm/providers/openai.ts | 39 +++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/app/lib/modules/llm/providers/openai.ts b/app/lib/modules/llm/providers/openai.ts index 5301ae41..a88a8e84 100644 --- a/app/lib/modules/llm/providers/openai.ts +++ b/app/lib/modules/llm/providers/openai.ts @@ -16,8 +16,35 @@ export default class OpenAIProvider extends BaseProvider { staticModels: ModelInfo[] = [ /* * Essential fallback models - only the most stable/reliable ones - * GPT-5.1: 128k context, 16k output limit (best for coding and agentic tasks) + * GPT-5.2 Pro: 400k context, 128k output, highest accuracy and quality */ + { + name: 'gpt-5.2-pro', + label: 'GPT-5.2 Pro', + provider: 'OpenAI', + maxTokenAllowed: 400000, + maxCompletionTokens: 128000, + }, + + // GPT-5.2 Thinking: 400k context, 128k output, for complex reasoning and coding + { + name: 'gpt-5.2-thinking', + label: 'GPT-5.2 Thinking', + provider: 'OpenAI', + maxTokenAllowed: 400000, + maxCompletionTokens: 128000, + }, + + // GPT-5.2 Instant: 400k context, 128k output, optimized for speed + { + name: 'gpt-5.2-instant', + label: 'GPT-5.2 Instant', + provider: 'OpenAI', + maxTokenAllowed: 400000, + maxCompletionTokens: 128000, + }, + + // GPT-5.1: 128k context, 16k output limit (best for coding and agentic tasks) { name: 'gpt-5.1', label: 'GPT-5.1', @@ -112,6 +139,12 @@ export default class OpenAIProvider extends BaseProvider { // OpenAI provides context_length in their API response if (m.context_length) { contextWindow = m.context_length; + } else if (m.id?.includes('gpt-5.2')) { + contextWindow = 400000; // GPT-5.2 has 400k context + } else if (m.id?.includes('gpt-5.1')) { + contextWindow = 128000; // GPT-5.1 has 128k context + } else if (m.id?.includes('gpt-5')) { + contextWindow = 128000; // Other GPT-5 models have 128k context } else if (m.id?.includes('gpt-4o')) { contextWindow = 128000; // GPT-4o has 128k context } else if (m.id?.includes('gpt-4-turbo') || m.id?.includes('gpt-4-1106')) { @@ -135,6 +168,8 @@ export default class OpenAIProvider extends BaseProvider { maxCompletionTokens = 32000; // Other o1 models: 32K limit } else if (m.id?.includes('o3') || m.id?.includes('o4')) { maxCompletionTokens = 100000; // o3/o4 models: 100K output limit + } else if (m.id?.includes('gpt-5.2')) { + maxCompletionTokens = 128000; // GPT-5.2: 128K output limit } else if (m.id?.includes('gpt-5.1')) { maxCompletionTokens = 16384; // GPT-5.1: 16K output limit } else if (m.id?.includes('gpt-5-mini')) { @@ -155,7 +190,7 @@ export default class OpenAIProvider extends BaseProvider { name: m.id, label: `${m.id} (${Math.floor(contextWindow / 1000)}k context)`, provider: this.name, - maxTokenAllowed: Math.min(contextWindow, 128000), // Cap at 128k for safety + maxTokenAllowed: Math.min(contextWindow, 400000), // Cap at 400k for safety maxCompletionTokens, }; }); From 7e54110e9c19c58caff7f9ac6b0bd424a67da164 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 15:23:49 +1100 Subject: [PATCH 005/253] Add GPT-5.2 models (Pro, Thinking, Instant) to OpenRouter provider --- app/lib/modules/llm/providers/open-router.ts | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/lib/modules/llm/providers/open-router.ts b/app/lib/modules/llm/providers/open-router.ts index a3ce7e56..8ffbacab 100644 --- a/app/lib/modules/llm/providers/open-router.ts +++ b/app/lib/modules/llm/providers/open-router.ts @@ -49,6 +49,33 @@ export default class OpenRouterProvider extends BaseProvider { maxCompletionTokens: 64000, }, + // GPT-5.2 Pro via OpenRouter: 400k context, highest accuracy + { + name: 'openai/gpt-5.2-pro', + label: 'GPT-5.2 Pro', + provider: 'OpenRouter', + maxTokenAllowed: 400000, + maxCompletionTokens: 128000, + }, + + // GPT-5.2 Thinking via OpenRouter: 400k context, complex reasoning + { + name: 'openai/gpt-5.2-thinking', + label: 'GPT-5.2 Thinking', + provider: 'OpenRouter', + maxTokenAllowed: 400000, + maxCompletionTokens: 128000, + }, + + // GPT-5.2 Instant via OpenRouter: 400k context, optimized for speed + { + name: 'openai/gpt-5.2-instant', + label: 'GPT-5.2 Instant', + provider: 'OpenRouter', + maxTokenAllowed: 400000, + maxCompletionTokens: 128000, + }, + // GPT-5.1 via OpenRouter: 128k context { name: 'openai/gpt-5.1', From 2e6cb05915031250f8a24760e4eb2efaf4c5fb14 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 15:27:58 +1100 Subject: [PATCH 006/253] updated default model --- app/utils/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/constants.ts b/app/utils/constants.ts index 1afc3e2b..dc604e85 100644 --- a/app/utils/constants.ts +++ b/app/utils/constants.ts @@ -6,7 +6,7 @@ export const WORK_DIR = `/home/${WORK_DIR_NAME}`; export const MODIFICATIONS_TAG_NAME = 'codinit_file_modifications'; export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/; export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/; -export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest'; +export const DEFAULT_MODEL = 'claude-4-5-sonnet-latest'; export const PROMPT_COOKIE_KEY = 'cachedPrompt'; export const TOOL_EXECUTION_APPROVAL = { From 74ff40703e8d2cc38b36eab42ee44cfd0fb1e021 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 16:46:36 +1100 Subject: [PATCH 007/253] feat: add three new UI enhancement setting toggles - Add liveActionConsoleStore (default: true) - Add diffApprovalStore (default: false) - Add visualContextIndicatorStore (default: true) - Add corresponding update functions for persistence --- app/lib/stores/settings.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/lib/stores/settings.ts b/app/lib/stores/settings.ts index a31c49a9..390d2b5b 100644 --- a/app/lib/stores/settings.ts +++ b/app/lib/stores/settings.ts @@ -131,6 +131,9 @@ const SETTINGS_KEYS = { EVENT_LOGS: 'isEventLogsEnabled', PROMPT_ID: 'promptId', DEVELOPER_MODE: 'isDeveloperMode', + LIVE_ACTION_CONSOLE: 'liveActionConsoleEnabled', + DIFF_APPROVAL: 'diffApprovalEnabled', + VISUAL_CONTEXT_INDICATOR: 'visualContextIndicatorEnabled', } as const; // Initialize settings from localStorage or defaults @@ -160,6 +163,9 @@ const getInitialSettings = () => { eventLogs: getStoredBoolean(SETTINGS_KEYS.EVENT_LOGS, true), promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'default' : 'default', developerMode: getStoredBoolean(SETTINGS_KEYS.DEVELOPER_MODE, false), + liveActionConsole: getStoredBoolean(SETTINGS_KEYS.LIVE_ACTION_CONSOLE, true), + diffApproval: getStoredBoolean(SETTINGS_KEYS.DIFF_APPROVAL, false), + visualContextIndicator: getStoredBoolean(SETTINGS_KEYS.VISUAL_CONTEXT_INDICATOR, true), }; }; @@ -171,6 +177,9 @@ export const autoSelectStarterTemplate = atom(initialSettings.autoSelec export const enableContextOptimizationStore = atom(initialSettings.contextOptimization); export const isEventLogsEnabled = atom(initialSettings.eventLogs); export const promptStore = atom(initialSettings.promptId); +export const liveActionConsoleStore = atom(initialSettings.liveActionConsole); +export const diffApprovalStore = atom(initialSettings.diffApproval); +export const visualContextIndicatorStore = atom(initialSettings.visualContextIndicator); // Helper functions to update settings with persistence export const updateLatestBranch = (enabled: boolean) => { @@ -198,6 +207,21 @@ export const updatePromptId = (id: string) => { localStorage.setItem(SETTINGS_KEYS.PROMPT_ID, id); }; +export const updateLiveActionConsole = (enabled: boolean) => { + liveActionConsoleStore.set(enabled); + localStorage.setItem(SETTINGS_KEYS.LIVE_ACTION_CONSOLE, JSON.stringify(enabled)); +}; + +export const updateDiffApproval = (enabled: boolean) => { + diffApprovalStore.set(enabled); + localStorage.setItem(SETTINGS_KEYS.DIFF_APPROVAL, JSON.stringify(enabled)); +}; + +export const updateVisualContextIndicator = (enabled: boolean) => { + visualContextIndicatorStore.set(enabled); + localStorage.setItem(SETTINGS_KEYS.VISUAL_CONTEXT_INDICATOR, JSON.stringify(enabled)); +}; + // Initialize tab configuration from localStorage or defaults const getInitialTabConfiguration = (): TabWindowConfig => { const defaultConfig: TabWindowConfig = { From a65c1e4a2564a673103a342409669ba392be93be Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 16:47:39 +1100 Subject: [PATCH 008/253] feat: extend ActionAlert with live streaming support - Add isStreaming boolean field - Add streamingOutput string field - Add progress number field for determinate progress bars --- app/types/actions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/types/actions.ts b/app/types/actions.ts index 0d54aa38..a2494dd6 100644 --- a/app/types/actions.ts +++ b/app/types/actions.ts @@ -46,6 +46,9 @@ export interface ActionAlert { timestamp?: number; command?: string; exitCode?: number; + isStreaming?: boolean; + streamingOutput?: string; + progress?: number; } export interface SupabaseAlert { From f157b91eed04a1f3d222edadf2d36018a90dedb5 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 16:48:35 +1100 Subject: [PATCH 009/253] feat: add awaiting-approval status to ActionStatus type - Enables diff approval workflow to mark actions pending user review --- app/lib/runtime/action-runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/runtime/action-runner.ts b/app/lib/runtime/action-runner.ts index b7857de1..40178748 100644 --- a/app/lib/runtime/action-runner.ts +++ b/app/lib/runtime/action-runner.ts @@ -10,7 +10,7 @@ import { validateCode } from './code-validator'; const logger = createScopedLogger('ActionRunner'); -export type ActionStatus = 'pending' | 'running' | 'complete' | 'aborted' | 'failed'; +export type ActionStatus = 'pending' | 'running' | 'complete' | 'aborted' | 'failed' | 'awaiting-approval'; export type BaseActionState = BoltAction & { status: Exclude; From 0d2dfa4812bf2ba1f58e86c2d595d36d36b88bca Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 16:51:24 +1100 Subject: [PATCH 010/253] feat: extend ContextAnnotation with file categorization - Add FileCategory type for file classification - Add optional categories field to codeContext - Add optional relevanceScores field for ranking - Add optional selectionReason field for explanation --- app/types/context.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/types/context.ts b/app/types/context.ts index 00b72ac2..4373d3f3 100644 --- a/app/types/context.ts +++ b/app/types/context.ts @@ -1,7 +1,12 @@ +export type FileCategory = 'component' | 'config' | 'style' | 'test' | 'api' | 'util' | 'other'; + export type ContextAnnotation = | { type: 'codeContext'; files: string[]; + categories?: Record; + relevanceScores?: Record; + selectionReason?: string; } | { type: 'chatSummary'; From 02ab5ddae7f0156c4485850845152a8713b5c3b4 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Mon, 15 Dec 2025 17:23:22 +1100 Subject: [PATCH 011/253] feat: add fourth stream for live action monitoring - Add liveActionStream field to ExampleShell - Create streamE and streamF via additional tee() - Store liveActionStream reader in init() - Add public getter for accessing the stream - Update comments to reflect four-stream architecture --- app/utils/shell.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/utils/shell.ts b/app/utils/shell.ts index 6bad47fd..d00cb627 100644 --- a/app/utils/shell.ts +++ b/app/utils/shell.ts @@ -66,6 +66,7 @@ export class ExampleShell { >(); #outputStream: ReadableStreamDefaultReader | undefined; #shellInputStream: WritableStreamDefaultWriter | undefined; + #liveActionStream: ReadableStreamDefaultReader | undefined; constructor() { this.#readyPromise = new Promise((resolve) => { @@ -77,14 +78,22 @@ export class ExampleShell { return this.#readyPromise; } + get liveActionStream() { + return this.#liveActionStream; + } + async init(webcontainer: WebContainer, terminal: ITerminal) { this.#webcontainer = webcontainer; this.#terminal = terminal; - // Use all three streams from tee: one for terminal, one for command execution, one for Expo URL detection - const { process, commandStream, expoUrlStream } = await this.newExampleShellProcess(webcontainer, terminal); + // Use all four streams from tee: terminal, command execution, Expo URL detection, live action monitoring + const { process, commandStream, expoUrlStream, liveActionStream } = await this.newExampleShellProcess( + webcontainer, + terminal, + ); this.#process = process; this.#outputStream = commandStream.getReader(); + this.#liveActionStream = liveActionStream.getReader(); // Start background Expo URL watcher immediately this._watchExpoUrlInBackground(expoUrlStream); @@ -105,9 +114,10 @@ export class ExampleShell { const input = process.input.getWriter(); this.#shellInputStream = input; - // Tee the output so we can have three independent readers + // Tee the output so we can have four independent readers const [streamA, streamB] = process.output.tee(); const [streamC, streamD] = streamB.tee(); + const [streamE, streamF] = streamD.tee(); const jshReady = withResolvers(); let isInteractive = false; @@ -137,7 +147,13 @@ export class ExampleShell { await jshReady.promise; // Return all streams for use in init - return { process, terminalStream: streamA, commandStream: streamC, expoUrlStream: streamD }; + return { + process, + terminalStream: streamA, + commandStream: streamC, + expoUrlStream: streamE, + liveActionStream: streamF, + }; } // Dedicated background watcher for Expo URL From cc0c71d72eb2a0771fcac0e2eb33dd856f094552 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Tue, 16 Dec 2025 00:35:07 +1100 Subject: [PATCH 012/253] feat: add live output monitoring to ActionRunner - Add onLiveOutput callback parameter to constructor - Implement monitorLiveOutput method to stream command output - Integrate monitoring into runShellAction method - Stream accumulates and emits output in real-time --- app/lib/runtime/action-runner.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/lib/runtime/action-runner.ts b/app/lib/runtime/action-runner.ts index 40178748..c4599cce 100644 --- a/app/lib/runtime/action-runner.ts +++ b/app/lib/runtime/action-runner.ts @@ -83,6 +83,7 @@ export class ActionRunner { onSupabaseAlert?: (alert: SupabaseAlert) => void; onDeployAlert?: (alert: DeployAlert) => void; onTestResult?: TestResultCallback; + onLiveOutput?: (output: string, actionId: string) => void; buildOutput?: { path: string; exitCode: number; output: string }; constructor( @@ -92,6 +93,7 @@ export class ActionRunner { onSupabaseAlert?: (alert: SupabaseAlert) => void, onDeployAlert?: (alert: DeployAlert) => void, onTestResult?: TestResultCallback, + onLiveOutput?: (output: string, actionId: string) => void, ) { this.#webcontainer = webcontainerPromise; this.#shellTerminal = getShellTerminal; @@ -99,6 +101,7 @@ export class ActionRunner { this.onSupabaseAlert = onSupabaseAlert; this.onDeployAlert = onDeployAlert; this.onTestResult = onTestResult; + this.onLiveOutput = onLiveOutput; } addAction(data: ActionCallbackData) { @@ -272,6 +275,10 @@ export class ActionRunner { unreachable('Shell terminal not found'); } + if (this.onLiveOutput && shell.liveActionStream) { + this.#monitorLiveOutput(shell.liveActionStream, action.content); + } + const resp = await shell.executeCommand(this.runnerId.get(), action.content, () => { logger.debug(`[${action.type}]:Aborting Action\n\n`, action); action.abort(); @@ -298,6 +305,28 @@ export class ActionRunner { } } + async #monitorLiveOutput(stream: ReadableStreamDefaultReader, command: string) { + let buffer = ''; + + try { + while (true) { + const { value, done } = await stream.read(); + + if (done) { + break; + } + + buffer += value || ''; + + if (this.onLiveOutput) { + this.onLiveOutput(buffer, command); + } + } + } catch (error) { + logger.error('Live output monitoring error:', error); + } + } + async #runStartAction(action: ActionState) { if (action.type !== 'start') { unreachable('Expected shell action'); From 1afd8bffe6b48e23d7f8773ba443595cb5589cf2 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Tue, 16 Dec 2025 00:37:23 +1100 Subject: [PATCH 013/253] feat: wire live output callback in workbench store - Import liveActionConsoleStore setting - Add onLiveOutput callback to ActionRunner instantiation - Check if feature is enabled before emitting alerts - Set actionAlert with streaming output and command info --- app/lib/stores/workbench.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 45c10cb0..67f241e0 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -23,6 +23,7 @@ import Cookies from 'js-cookie'; import { createSampler } from '~/utils/sampler'; import type { ActionAlert, DeployAlert, SupabaseAlert } from '~/types/actions'; import { startAutoSave } from '~/lib/persistence/fileAutoSave'; +import { liveActionConsoleStore } from './settings'; const { saveAs } = fileSaver; @@ -597,6 +598,27 @@ export class WorkbenchStore { }); } }, + (output, command) => { + if (this.#reloadedMessages.has(messageId)) { + return; + } + + const liveConsoleEnabled = liveActionConsoleStore.get(); + + if (!liveConsoleEnabled) { + return; + } + + this.actionAlert.set({ + type: 'info', + title: 'Command Running', + description: `Executing: ${command}`, + content: output, + isStreaming: true, + streamingOutput: output, + command, + }); + }, ), }); } From 261d0773d28a875b7d74bd80a3de342262643755 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Tue, 16 Dec 2025 18:55:49 +1100 Subject: [PATCH 014/253] feat: add LiveActionAlert component and integrate into app - Create floating console component with auto-scroll - Display streaming command output in real-time - Show command being executed and progress bar - Add to main app layout as global floating element - Styled with proper theming and animations --- app/components/chat/LiveActionAlert.tsx | 94 +++++++++++++++++++++++++ app/routes/_index.tsx | 2 + 2 files changed, 96 insertions(+) create mode 100644 app/components/chat/LiveActionAlert.tsx diff --git a/app/components/chat/LiveActionAlert.tsx b/app/components/chat/LiveActionAlert.tsx new file mode 100644 index 00000000..be4111f4 --- /dev/null +++ b/app/components/chat/LiveActionAlert.tsx @@ -0,0 +1,94 @@ +import { useStore } from '@nanostores/react'; +import { memo, useEffect, useRef } from 'react'; +import { workbenchStore } from '~/lib/stores/workbench'; +import { motion, AnimatePresence } from 'framer-motion'; +import { classNames } from '~/utils/classNames'; + +export const LiveActionAlert = memo(() => { + const alert = useStore(workbenchStore.actionAlert); + const outputRef = useRef(null); + + useEffect(() => { + if (outputRef.current && alert?.isStreaming) { + outputRef.current.scrollTop = outputRef.current.scrollHeight; + } + }, [alert?.streamingOutput]); + + if (!alert || !alert.isStreaming) { + return null; + } + + return ( + + +
+
+ +
+ +
+
{alert.title}
+ {alert.command && ( +
{alert.command}
+ )} +
+
+ +
+ +
+
{alert.streamingOutput || alert.content}
+
+ + {alert.progress !== undefined && alert.progress >= 0 && ( +
+
+ Progress + + {Math.round(alert.progress)}% + +
+
+ +
+
+ )} + + + ); +}); + +LiveActionAlert.displayName = 'LiveActionAlert'; diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 01b44975..5e7f6d1a 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -5,6 +5,7 @@ import { Chat } from '~/components/chat/Chat.client'; import { Header } from '~/components/header/Header'; import { ElectronTitleBar } from '~/components/ui/ElectronTitleBar'; import BackgroundRays from '~/components/ui/BackgroundRays'; +import { LiveActionAlert } from '~/components/chat/LiveActionAlert'; export const meta: MetaFunction = () => { return [ @@ -22,6 +23,7 @@ export default function Index() {
}>{() => } + {() => }
); } From fe92b19e8d62578b5fc229871ca3fa9446d9bbf2 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Tue, 16 Dec 2025 19:09:06 +1100 Subject: [PATCH 015/253] weekly rank 2 on fazier badge --- app/components/header/Header.tsx | 4 +-- public/hero.png | Bin 0 -> 530181 bytes public/rank-2-dark.svg | 48 +++++++++++++++++++++++++++++++ public/rank-2-light.svg | 48 +++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 public/hero.png create mode 100644 public/rank-2-dark.svg create mode 100644 public/rank-2-light.svg diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx index 696ad126..313d22a4 100644 --- a/app/components/header/Header.tsx +++ b/app/components/header/Header.tsx @@ -32,8 +32,8 @@ export function Header() {
- Fazier badge - Fazier badge + Fazier badge + Fazier badge + +
+ + + + ); +} From 84899a42021879b7eb64eb9d92ada2b94aa00645 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 18 Dec 2025 00:33:45 +1100 Subject: [PATCH 020/253] feat: integrate DiffApprovalDialog into main route --- app/routes/_index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 5e7f6d1a..4ab332f5 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -6,6 +6,7 @@ import { Header } from '~/components/header/Header'; import { ElectronTitleBar } from '~/components/ui/ElectronTitleBar'; import BackgroundRays from '~/components/ui/BackgroundRays'; import { LiveActionAlert } from '~/components/chat/LiveActionAlert'; +import { DiffApprovalDialog } from '~/components/workbench/DiffApprovalDialog'; export const meta: MetaFunction = () => { return [ @@ -24,6 +25,7 @@ export default function Index() {
}>{() => } {() => } + {() => } ); } From 5577761e0b2224fd31f22b4d3c6a4c220dc266d5 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 18 Dec 2025 00:34:38 +1100 Subject: [PATCH 021/253] refactor: remove event logs and task manager tabs from ControlPanel --- app/components/@settings/core/ControlPanel.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/components/@settings/core/ControlPanel.tsx b/app/components/@settings/core/ControlPanel.tsx index 139ed8f7..b95d2559 100644 --- a/app/components/@settings/core/ControlPanel.tsx +++ b/app/components/@settings/core/ControlPanel.tsx @@ -33,13 +33,11 @@ import NotificationsTab from '~/components/@settings/tabs/notifications/Notifica import FeaturesTab from '~/components/@settings/tabs/features/FeaturesTab'; import { DataTab } from '~/components/@settings/tabs/data/DataTab'; import DebugTab from '~/components/@settings/tabs/debug/DebugTab'; -import { EventLogsTab } from '~/components/@settings/tabs/event-logs/EventLogsTab'; import UpdateTab from '~/components/@settings/tabs/update/UpdateTab'; import ConnectionsTab from '~/components/@settings/tabs/connections/ConnectionsTab'; import CloudProvidersTab from '~/components/@settings/tabs/providers/cloud/CloudProvidersTab'; import ServiceStatusTab from '~/components/@settings/tabs/providers/status/ServiceStatusTab'; import LocalProvidersTab from '~/components/@settings/tabs/providers/local/LocalProvidersTab'; -import TaskManagerTab from '~/components/@settings/tabs/task-manager/TaskManagerTab'; import ApiKeysTab from '~/components/@settings/tabs/api-keys/APIKeysTab'; interface ControlPanelProps { @@ -347,12 +345,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { return ; case 'debug': return ; - case 'event-logs': - return ; case 'update': return ; - case 'task-manager': - return ; case 'service-status': return ; default: From 2c1b6da2541c1890ce85c5907fefea7f6e4ac8ec Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 18 Dec 2025 00:34:45 +1100 Subject: [PATCH 022/253] refactor: remove unused event logs and task manager tab components --- .../tabs/event-logs/EventLogsTab.tsx | 1013 ----------- .../tabs/task-manager/TaskManagerTab.tsx | 1604 ----------------- 2 files changed, 2617 deletions(-) delete mode 100644 app/components/@settings/tabs/event-logs/EventLogsTab.tsx delete mode 100644 app/components/@settings/tabs/task-manager/TaskManagerTab.tsx diff --git a/app/components/@settings/tabs/event-logs/EventLogsTab.tsx b/app/components/@settings/tabs/event-logs/EventLogsTab.tsx deleted file mode 100644 index 391dcc6b..00000000 --- a/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +++ /dev/null @@ -1,1013 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { motion } from 'framer-motion'; -import { Switch } from '~/components/ui/Switch'; -import { logStore, type LogEntry } from '~/lib/stores/logs'; -import { useStore } from '@nanostores/react'; -import { classNames } from '~/utils/classNames'; -import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; -import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; -import { toast } from 'react-toastify'; - -interface SelectOption { - value: string; - label: string; - icon?: string; - color?: string; -} - -const logLevelOptions: SelectOption[] = [ - { - value: 'all', - label: 'All Types', - icon: 'i-ph:funnel', - color: '#9333ea', - }, - { - value: 'provider', - label: 'LLM', - icon: 'i-ph:robot', - color: '#10b981', - }, - { - value: 'api', - label: 'API', - icon: 'i-ph:cloud', - color: '#3b82f6', - }, - { - value: 'error', - label: 'Errors', - icon: 'i-ph:warning-circle', - color: '#ef4444', - }, - { - value: 'warning', - label: 'Warnings', - icon: 'i-ph:warning', - color: '#f59e0b', - }, - { - value: 'info', - label: 'Info', - icon: 'i-ph:info', - color: '#3b82f6', - }, - { - value: 'debug', - label: 'Debug', - icon: 'i-ph:bug', - color: '#6b7280', - }, -]; - -interface LogEntryItemProps { - log: LogEntry; - isExpanded: boolean; - use24Hour: boolean; - showTimestamp: boolean; -} - -const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, showTimestamp }: LogEntryItemProps) => { - const [localExpanded, setLocalExpanded] = useState(forceExpanded); - - useEffect(() => { - setLocalExpanded(forceExpanded); - }, [forceExpanded]); - - const timestamp = useMemo(() => { - const date = new Date(log.timestamp); - return date.toLocaleTimeString('en-US', { hour12: !use24Hour }); - }, [log.timestamp, use24Hour]); - - const style = useMemo(() => { - if (log.category === 'provider') { - return { - icon: 'i-ph:robot', - color: 'text-emerald-500 dark:text-emerald-400', - bg: 'hover:bg-emerald-500/10 dark:hover:bg-emerald-500/20', - badge: 'text-emerald-500 bg-emerald-50 dark:bg-emerald-500/10', - }; - } - - if (log.category === 'api') { - return { - icon: 'i-ph:cloud', - color: 'text-blue-500 dark:text-blue-400', - bg: 'hover:bg-blue-500/10 dark:hover:bg-blue-500/20', - badge: 'text-blue-500 bg-blue-50 dark:bg-blue-500/10', - }; - } - - switch (log.level) { - case 'error': - return { - icon: 'i-ph:warning-circle', - color: 'text-red-500 dark:text-red-400', - bg: 'hover:bg-red-500/10 dark:hover:bg-red-500/20', - badge: 'text-red-500 bg-red-50 dark:bg-red-500/10', - }; - case 'warning': - return { - icon: 'i-ph:warning', - color: 'text-yellow-500 dark:text-yellow-400', - bg: 'hover:bg-yellow-500/10 dark:hover:bg-yellow-500/20', - badge: 'text-yellow-500 bg-yellow-50 dark:bg-yellow-500/10', - }; - case 'debug': - return { - icon: 'i-ph:bug', - color: 'text-gray-500 dark:text-gray-400', - bg: 'hover:bg-gray-500/10 dark:hover:bg-gray-500/20', - badge: 'text-gray-500 bg-gray-50 dark:bg-gray-500/10', - }; - default: - return { - icon: 'i-ph:info', - color: 'text-blue-500 dark:text-blue-400', - bg: 'hover:bg-blue-500/10 dark:hover:bg-blue-500/20', - badge: 'text-blue-500 bg-blue-50 dark:bg-blue-500/10', - }; - } - }, [log.level, log.category]); - - const renderDetails = (details: any) => { - if (log.category === 'provider') { - return ( -
-
- Model: {details.model} - - Tokens: {details.totalTokens} - - Duration: {details.duration}ms -
- {details.prompt && ( -
-
Prompt:
-
-                {details.prompt}
-              
-
- )} - {details.response && ( -
-
Response:
-
-                {details.response}
-              
-
- )} -
- ); - } - - if (log.category === 'api') { - return ( -
-
- {details.method} - - Status: {details.statusCode} - - Duration: {details.duration}ms -
-
{details.url}
- {details.request && ( -
-
Request:
-
-                {JSON.stringify(details.request, null, 2)}
-              
-
- )} - {details.response && ( -
-
Response:
-
-                {JSON.stringify(details.response, null, 2)}
-              
-
- )} - {details.error && ( -
-
Error:
-
-                {JSON.stringify(details.error, null, 2)}
-              
-
- )} -
- ); - } - - return ( -
-        {JSON.stringify(details, null, 2)}
-      
- ); - }; - - return ( - -
-
- -
-
{log.message}
- {log.details && ( - <> - - {localExpanded && renderDetails(log.details)} - - )} -
-
- {log.level} -
- {log.category && ( -
- {log.category} -
- )} -
-
-
- {showTimestamp && } -
-
- ); -}; - -interface ExportFormat { - id: string; - label: string; - icon: string; - handler: () => void; -} - -export function EventLogsTab() { - const logs = useStore(logStore.logs); - const [selectedLevel, setSelectedLevel] = useState<'all' | string>('all'); - const [searchQuery, setSearchQuery] = useState(''); - const [use24Hour, setUse24Hour] = useState(false); - const [autoExpand, setAutoExpand] = useState(false); - const [showTimestamps, setShowTimestamps] = useState(true); - const [showLevelFilter, setShowLevelFilter] = useState(false); - const [isRefreshing, setIsRefreshing] = useState(false); - const levelFilterRef = useRef(null); - - const filteredLogs = useMemo(() => { - const allLogs = Object.values(logs); - const lowerSearchQuery = searchQuery.toLowerCase(); - const hasSearch = Boolean(searchQuery); - const isAllLevel = selectedLevel === 'all'; - - return allLogs.filter((log) => { - if (!isAllLevel && log.category !== selectedLevel && log.level !== selectedLevel) { - return false; - } - - if (hasSearch && !log.message.toLowerCase().includes(lowerSearchQuery)) { - return false; - } - - return true; - }); - }, [logs, selectedLevel, searchQuery]); - - // Add performance tracking on mount - useEffect(() => { - const startTime = performance.now(); - - logStore.logInfo('Event Logs tab mounted', { - type: 'component_mount', - message: 'Event Logs tab component mounted', - component: 'EventLogsTab', - }); - - return () => { - const duration = performance.now() - startTime; - logStore.logPerformanceMetric('EventLogsTab', 'mount-duration', duration); - }; - }, []); - - // Log filter changes - const handleLevelFilterChange = useCallback( - (newLevel: string) => { - logStore.logInfo('Log level filter changed', { - type: 'filter_change', - message: `Log level filter changed from ${selectedLevel} to ${newLevel}`, - component: 'EventLogsTab', - previousLevel: selectedLevel, - newLevel, - }); - setSelectedLevel(newLevel as string); - setShowLevelFilter(false); - }, - [selectedLevel], - ); - - // Log search changes with debounce - useEffect(() => { - const timeoutId = setTimeout(() => { - if (searchQuery) { - logStore.logInfo('Log search performed', { - type: 'search', - message: `Search performed with query "${searchQuery}" (${filteredLogs.length} results)`, - component: 'EventLogsTab', - query: searchQuery, - resultsCount: filteredLogs.length, - }); - } - }, 1000); - - return () => clearTimeout(timeoutId); - }, [searchQuery, filteredLogs.length]); - - // Enhanced refresh handler - const handleRefresh = useCallback(async () => { - const startTime = performance.now(); - setIsRefreshing(true); - - try { - await logStore.refreshLogs(); - - const duration = performance.now() - startTime; - - logStore.logSuccess('Logs refreshed successfully', { - type: 'refresh', - message: `Successfully refreshed ${Object.keys(logs).length} logs`, - component: 'EventLogsTab', - duration, - logsCount: Object.keys(logs).length, - }); - } catch (error) { - logStore.logError('Failed to refresh logs', error, { - type: 'refresh_error', - message: 'Failed to refresh logs', - component: 'EventLogsTab', - }); - } finally { - setTimeout(() => setIsRefreshing(false), 500); - } - }, [logs]); - - // Log preference changes - const handlePreferenceChange = useCallback((type: string, value: boolean) => { - logStore.logInfo('Log preference changed', { - type: 'preference_change', - message: `Log preference "${type}" changed to ${value}`, - component: 'EventLogsTab', - preference: type, - value, - }); - - switch (type) { - case 'timestamps': - setShowTimestamps(value); - break; - case '24hour': - setUse24Hour(value); - break; - case 'autoExpand': - setAutoExpand(value); - break; - } - }, []); - - // Close filters when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (levelFilterRef.current && !levelFilterRef.current.contains(event.target as Node)) { - setShowLevelFilter(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); - - const selectedLevelOption = logLevelOptions.find((opt) => opt.value === selectedLevel); - - // Export functions - const exportAsJSON = () => { - try { - const exportData = { - timestamp: new Date().toISOString(), - logs: filteredLogs, - filters: { - level: selectedLevel, - searchQuery, - }, - preferences: { - use24Hour, - showTimestamps, - autoExpand, - }, - }; - - const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `codinit-event-logs-${new Date().toISOString()}.json`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Event logs exported successfully as JSON'); - } catch (error) { - console.error('Failed to export JSON:', error); - toast.error('Failed to export event logs as JSON'); - } - }; - - const exportAsCSV = () => { - try { - // Convert logs to CSV format - const headers = ['Timestamp', 'Level', 'Category', 'Message', 'Details']; - const csvData = [ - headers, - ...filteredLogs.map((log) => [ - new Date(log.timestamp).toISOString(), - log.level, - log.category || '', - log.message, - log.details ? JSON.stringify(log.details) : '', - ]), - ]; - - const csvContent = csvData - .map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')) - .join('\n'); - const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `codinit-event-logs-${new Date().toISOString()}.csv`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Event logs exported successfully as CSV'); - } catch (error) { - console.error('Failed to export CSV:', error); - toast.error('Failed to export event logs as CSV'); - } - }; - - const exportAsPDF = async () => { - try { - const { jsPDF } = await import('jspdf'); - - // Create new PDF document - const doc = new jsPDF(); - const lineHeight = 7; - let yPos = 20; - const margin = 20; - const pageWidth = doc.internal.pageSize.getWidth(); - const maxLineWidth = pageWidth - 2 * margin; - - // Helper function to add section header - const addSectionHeader = (title: string) => { - // Check if we need a new page - if (yPos > doc.internal.pageSize.getHeight() - 30) { - doc.addPage(); - yPos = margin; - } - - doc.setFillColor('#F3F4F6'); - doc.rect(margin - 2, yPos - 5, pageWidth - 2 * (margin - 2), lineHeight + 6, 'F'); - doc.setFont('helvetica', 'bold'); - doc.setTextColor('#111827'); - doc.setFontSize(12); - doc.text(title.toUpperCase(), margin, yPos); - yPos += lineHeight * 2; - }; - - // Add title and header - doc.setFillColor('#6366F1'); - doc.rect(0, 0, pageWidth, 50, 'F'); - doc.setTextColor('#FFFFFF'); - doc.setFontSize(24); - doc.setFont('helvetica', 'bold'); - doc.text('Event Logs Report', margin, 35); - - // Add subtitle with codinit.dev - doc.setFontSize(12); - doc.setFont('helvetica', 'normal'); - doc.text('codinit.dev - AI Development Platform', margin, 45); - yPos = 70; - - // Add report summary section - addSectionHeader('Report Summary'); - - doc.setFontSize(10); - doc.setFont('helvetica', 'normal'); - doc.setTextColor('#374151'); - - const summaryItems = [ - { label: 'Generated', value: new Date().toLocaleString() }, - { label: 'Total Logs', value: filteredLogs.length.toString() }, - { label: 'Filter Applied', value: selectedLevel === 'all' ? 'All Types' : selectedLevel }, - { label: 'Search Query', value: searchQuery || 'None' }, - { label: 'Time Format', value: use24Hour ? '24-hour' : '12-hour' }, - ]; - - summaryItems.forEach((item) => { - doc.setFont('helvetica', 'bold'); - doc.text(`${item.label}:`, margin, yPos); - doc.setFont('helvetica', 'normal'); - doc.text(item.value, margin + 60, yPos); - yPos += lineHeight; - }); - - yPos += lineHeight * 2; - - // Add statistics section - addSectionHeader('Log Statistics'); - - // Calculate statistics - const stats = { - error: filteredLogs.filter((log) => log.level === 'error').length, - warning: filteredLogs.filter((log) => log.level === 'warning').length, - info: filteredLogs.filter((log) => log.level === 'info').length, - debug: filteredLogs.filter((log) => log.level === 'debug').length, - provider: filteredLogs.filter((log) => log.category === 'provider').length, - api: filteredLogs.filter((log) => log.category === 'api').length, - }; - - // Create two columns for statistics - const leftStats = [ - { label: 'Error Logs', value: stats.error, color: '#DC2626' }, - { label: 'Warning Logs', value: stats.warning, color: '#F59E0B' }, - { label: 'Info Logs', value: stats.info, color: '#3B82F6' }, - ]; - - const rightStats = [ - { label: 'Debug Logs', value: stats.debug, color: '#6B7280' }, - { label: 'LLM Logs', value: stats.provider, color: '#10B981' }, - { label: 'API Logs', value: stats.api, color: '#3B82F6' }, - ]; - - const colWidth = (pageWidth - 2 * margin) / 2; - - // Draw statistics in two columns - leftStats.forEach((stat, index) => { - doc.setTextColor(stat.color); - doc.setFont('helvetica', 'bold'); - doc.text(stat.value.toString(), margin, yPos); - doc.setTextColor('#374151'); - doc.setFont('helvetica', 'normal'); - doc.text(stat.label, margin + 20, yPos); - - if (rightStats[index]) { - doc.setTextColor(rightStats[index].color); - doc.setFont('helvetica', 'bold'); - doc.text(rightStats[index].value.toString(), margin + colWidth, yPos); - doc.setTextColor('#374151'); - doc.setFont('helvetica', 'normal'); - doc.text(rightStats[index].label, margin + colWidth + 20, yPos); - } - - yPos += lineHeight; - }); - - yPos += lineHeight * 2; - - // Add logs section - addSectionHeader('Event Logs'); - - // Helper function to add a log entry with improved formatting - const addLogEntry = (log: LogEntry) => { - const entryHeight = 20 + (log.details ? 40 : 0); // Estimate entry height - - // Check if we need a new page - if (yPos + entryHeight > doc.internal.pageSize.getHeight() - 20) { - doc.addPage(); - yPos = margin; - } - - // Add timestamp and level - const timestamp = new Date(log.timestamp).toLocaleString(undefined, { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: !use24Hour, - }); - - // Draw log level badge background - const levelColors: Record = { - error: '#FEE2E2', - warning: '#FEF3C7', - info: '#DBEAFE', - debug: '#F3F4F6', - }; - - const textColors: Record = { - error: '#DC2626', - warning: '#F59E0B', - info: '#3B82F6', - debug: '#6B7280', - }; - - const levelWidth = doc.getTextWidth(log.level.toUpperCase()) + 10; - doc.setFillColor(levelColors[log.level] || '#F3F4F6'); - doc.roundedRect(margin, yPos - 4, levelWidth, lineHeight + 4, 1, 1, 'F'); - - // Add log level text - doc.setTextColor(textColors[log.level] || '#6B7280'); - doc.setFont('helvetica', 'bold'); - doc.setFontSize(8); - doc.text(log.level.toUpperCase(), margin + 5, yPos); - - // Add timestamp - doc.setTextColor('#6B7280'); - doc.setFont('helvetica', 'normal'); - doc.setFontSize(9); - doc.text(timestamp, margin + levelWidth + 10, yPos); - - // Add category if present - if (log.category) { - const categoryX = margin + levelWidth + doc.getTextWidth(timestamp) + 20; - doc.setFillColor('#F3F4F6'); - - const categoryWidth = doc.getTextWidth(log.category) + 10; - doc.roundedRect(categoryX, yPos - 4, categoryWidth, lineHeight + 4, 2, 2, 'F'); - doc.setTextColor('#6B7280'); - doc.text(log.category, categoryX + 5, yPos); - } - - yPos += lineHeight * 1.5; - - // Add message - doc.setTextColor('#111827'); - doc.setFontSize(10); - - const messageLines = doc.splitTextToSize(log.message, maxLineWidth - 10); - doc.text(messageLines, margin + 5, yPos); - yPos += messageLines.length * lineHeight; - - // Add details if present - if (log.details) { - doc.setTextColor('#6B7280'); - doc.setFontSize(8); - - const detailsStr = JSON.stringify(log.details, null, 2); - const detailsLines = doc.splitTextToSize(detailsStr, maxLineWidth - 15); - - // Add details background - doc.setFillColor('#F9FAFB'); - doc.roundedRect(margin + 5, yPos - 2, maxLineWidth - 10, detailsLines.length * lineHeight + 8, 1, 1, 'F'); - - doc.text(detailsLines, margin + 10, yPos + 4); - yPos += detailsLines.length * lineHeight + 10; - } - - // Add separator line - doc.setDrawColor('#E5E7EB'); - doc.setLineWidth(0.1); - doc.line(margin, yPos, pageWidth - margin, yPos); - yPos += lineHeight * 1.5; - }; - - // Add all logs - filteredLogs.forEach((log) => { - addLogEntry(log); - }); - - // Add footer to all pages - const totalPages = doc.internal.pages.length - 1; - - for (let i = 1; i <= totalPages; i++) { - doc.setPage(i); - doc.setFontSize(8); - doc.setTextColor('#9CA3AF'); - - // Add page numbers - doc.text(`Page ${i} of ${totalPages}`, pageWidth / 2, doc.internal.pageSize.getHeight() - 10, { - align: 'center', - }); - - // Add footer text - doc.text('Generated by codinit.dev', margin, doc.internal.pageSize.getHeight() - 10); - - const dateStr = new Date().toLocaleDateString(); - doc.text(dateStr, pageWidth - margin, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); - } - - // Save the PDF - doc.save(`codinit-event-logs-${new Date().toISOString()}.pdf`); - toast.success('Event logs exported successfully as PDF'); - } catch (error) { - console.error('Failed to export PDF:', error); - toast.error('Failed to export event logs as PDF'); - } - }; - - const exportAsText = () => { - try { - const textContent = filteredLogs - .map((log) => { - const timestamp = new Date(log.timestamp).toLocaleString(); - let content = `[${timestamp}] ${log.level.toUpperCase()}: ${log.message}\n`; - - if (log.category) { - content += `Category: ${log.category}\n`; - } - - if (log.details) { - content += `Details:\n${JSON.stringify(log.details, null, 2)}\n`; - } - - return content + '-'.repeat(80) + '\n'; - }) - .join('\n'); - - const blob = new Blob([textContent], { type: 'text/plain' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `codinit-event-logs-${new Date().toISOString()}.txt`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Event logs exported successfully as text file'); - } catch (error) { - console.error('Failed to export text file:', error); - toast.error('Failed to export event logs as text file'); - } - }; - - const exportFormats: ExportFormat[] = [ - { - id: 'json', - label: 'Export as JSON', - icon: 'i-ph:file-js', - handler: exportAsJSON, - }, - { - id: 'csv', - label: 'Export as CSV', - icon: 'i-ph:file-csv', - handler: exportAsCSV, - }, - { - id: 'pdf', - label: 'Export as PDF', - icon: 'i-ph:file-pdf', - handler: exportAsPDF, - }, - { - id: 'txt', - label: 'Export as Text', - icon: 'i-ph:file-text', - handler: exportAsText, - }, - ]; - - const ExportButton = () => { - const [isOpen, setIsOpen] = useState(false); - - const handleOpenChange = useCallback((open: boolean) => { - setIsOpen(open); - }, []); - - const handleFormatClick = useCallback((handler: () => void) => { - handler(); - setIsOpen(false); - }, []); - - return ( - - - - -
- -
- Export Event Logs - - -
- {exportFormats.map((format) => ( - - ))} -
-
-
-
- ); - }; - - return ( -
-
- - - - - - - - {logLevelOptions.map((option) => ( - handleLevelFilterChange(option.value)} - > -
-
-
- {option.label} - - ))} - - - - -
-
- handlePreferenceChange('timestamps', value)} - className="data-[state=checked]:bg-blue-500" - /> - Show Timestamps -
- -
- handlePreferenceChange('24hour', value)} - className="data-[state=checked]:bg-blue-500" - /> - 24h Time -
- -
- handlePreferenceChange('autoExpand', value)} - className="data-[state=checked]:bg-blue-500" - /> - Auto Expand -
- -
- - - - -
-
- -
-
- setSearchQuery(e.target.value)} - className={classNames( - 'w-full px-4 py-2 pl-10 rounded-lg', - 'bg-[#FAFAFA] dark:bg-[#0A0A0A]', - 'border border-[#E5E5E5] dark:border-[#1A1A1A]', - 'text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400', - 'focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500', - 'transition-all duration-200', - )} - /> -
-
-
-
- - {filteredLogs.length === 0 ? ( - - -
-

No Logs Found

-

Try adjusting your search or filters

-
-
- ) : ( - filteredLogs.map((log) => ( - - )) - )} -
-
- ); -} diff --git a/app/components/@settings/tabs/task-manager/TaskManagerTab.tsx b/app/components/@settings/tabs/task-manager/TaskManagerTab.tsx deleted file mode 100644 index e753bd15..00000000 --- a/app/components/@settings/tabs/task-manager/TaskManagerTab.tsx +++ /dev/null @@ -1,1604 +0,0 @@ -import * as React from 'react'; -import { useEffect, useState, useCallback } from 'react'; -import { classNames } from '~/utils/classNames'; -import { Line } from 'react-chartjs-2'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, - type Chart, -} from 'chart.js'; -import { toast } from 'react-toastify'; // Import toast -import { useUpdateCheck } from '~/lib/hooks/useUpdateCheck'; -import { tabConfigurationStore, type TabConfig } from '~/lib/stores/tabConfigurationStore'; -import { useStore } from 'zustand'; - -// Register ChartJS components -ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); - -interface BatteryManager extends EventTarget { - charging: boolean; - chargingTime: number; - dischargingTime: number; - level: number; -} - -interface SystemMemoryInfo { - total: number; - free: number; - used: number; - percentage: number; - swap?: { - total: number; - free: number; - used: number; - percentage: number; - }; - timestamp: string; - error?: string; -} - -interface ProcessInfo { - pid: number; - name: string; - cpu: number; - memory: number; - command?: string; - timestamp: string; - error?: string; -} - -interface DiskInfo { - filesystem: string; - size: number; - used: number; - available: number; - percentage: number; - mountpoint: string; - timestamp: string; - error?: string; -} - -interface SystemMetrics { - memory: { - used: number; - total: number; - percentage: number; - process?: { - heapUsed: number; - heapTotal: number; - external: number; - rss: number; - }; - }; - systemMemory?: SystemMemoryInfo; - processes?: ProcessInfo[]; - disks?: DiskInfo[]; - battery?: { - level: number; - charging: boolean; - timeRemaining?: number; - }; - network: { - downlink: number; - uplink?: number; - latency: { - current: number; - average: number; - min: number; - max: number; - history: number[]; - lastUpdate: number; - }; - type: string; - effectiveType?: string; - }; - performance: { - pageLoad: number; - domReady: number; - resources: { - total: number; - size: number; - loadTime: number; - }; - timing: { - ttfb: number; - fcp: number; - lcp: number; - }; - }; -} - -type SortField = 'name' | 'pid' | 'cpu' | 'memory'; -type SortDirection = 'asc' | 'desc'; - -interface MetricsHistory { - timestamps: string[]; - memory: number[]; - battery: number[]; - network: number[]; - cpu: number[]; - disk: number[]; -} - -interface PerformanceAlert { - type: 'warning' | 'error' | 'info'; - message: string; - timestamp: number; - metric: string; - threshold: number; - value: number; -} - -declare global { - interface Navigator { - getBattery(): Promise; - } - interface Performance { - memory?: { - jsHeapSizeLimit: number; - totalJSHeapSize: number; - usedJSHeapSize: number; - }; - } -} - -// Constants for performance thresholds -const PERFORMANCE_THRESHOLDS = { - memory: { - warning: 75, - critical: 90, - }, - network: { - latency: { - warning: 200, - critical: 500, - }, - }, - battery: { - warning: 20, - critical: 10, - }, -}; - -// Default metrics state -const DEFAULT_METRICS_STATE: SystemMetrics = { - memory: { - used: 0, - total: 0, - percentage: 0, - }, - network: { - downlink: 0, - latency: { - current: 0, - average: 0, - min: 0, - max: 0, - history: [], - lastUpdate: 0, - }, - type: 'unknown', - }, - performance: { - pageLoad: 0, - domReady: 0, - resources: { - total: 0, - size: 0, - loadTime: 0, - }, - timing: { - ttfb: 0, - fcp: 0, - lcp: 0, - }, - }, -}; - -// Default metrics history -const DEFAULT_METRICS_HISTORY: MetricsHistory = { - timestamps: Array(8).fill(new Date().toLocaleTimeString()), - memory: Array(8).fill(0), - battery: Array(8).fill(0), - network: Array(8).fill(0), - cpu: Array(8).fill(0), - disk: Array(8).fill(0), -}; - -// Maximum number of history points to keep -const MAX_HISTORY_POINTS = 8; - -// Used for environment detection in updateMetrics function -const isLocalDevelopment = - typeof window !== 'undefined' && - window.location && - (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); - -// For development environments, we'll always provide mock data if real data isn't available -const isDevelopment = - typeof window !== 'undefined' && - (window.location.hostname === 'localhost' || - window.location.hostname === '127.0.0.1' || - window.location.hostname.includes('192.168.') || - window.location.hostname.includes('.local')); - -// Function to detect Cloudflare and similar serverless environments where TaskManager is not useful -const isServerlessHosting = (): boolean => { - if (typeof window === 'undefined') { - return false; - } - - // For testing: Allow forcing serverless mode via URL param for easy testing - if (typeof window !== 'undefined' && window.location.search.includes('simulate-serverless=true')) { - console.log('Simulating serverless environment for testing'); - return true; - } - - // Check for common serverless hosting domains - const hostname = window.location.hostname; - - return ( - hostname.includes('.cloudflare.') || - hostname.includes('.netlify.app') || - hostname.includes('.vercel.app') || - hostname.endsWith('.workers.dev') - ); -}; - -const TaskManagerTab: React.FC = () => { - const [metrics, setMetrics] = useState(() => DEFAULT_METRICS_STATE); - const [metricsHistory, setMetricsHistory] = useState(() => DEFAULT_METRICS_HISTORY); - const [alerts, setAlerts] = useState([]); - const [lastAlertState, setLastAlertState] = useState('normal'); - const [sortField, setSortField] = useState('memory'); - const [sortDirection, setSortDirection] = useState('desc'); - const [isNotSupported, setIsNotSupported] = useState(false); - - // Chart refs for cleanup - const memoryChartRef = React.useRef | null>(null); - const batteryChartRef = React.useRef | null>(null); - const networkChartRef = React.useRef | null>(null); - const cpuChartRef = React.useRef | null>(null); - const diskChartRef = React.useRef | null>(null); - - // Cleanup chart instances on unmount - React.useEffect(() => { - const cleanupCharts = () => { - if (memoryChartRef.current) { - memoryChartRef.current.destroy(); - } - - if (batteryChartRef.current) { - batteryChartRef.current.destroy(); - } - - if (networkChartRef.current) { - networkChartRef.current.destroy(); - } - - if (cpuChartRef.current) { - cpuChartRef.current.destroy(); - } - - if (diskChartRef.current) { - diskChartRef.current.destroy(); - } - }; - - return cleanupCharts; - }, []); - - // Get update status and tab configuration - const { hasUpdate } = useUpdateCheck(); - const tabConfig = useStore(tabConfigurationStore); - - const resetTabConfiguration = useCallback(() => { - tabConfig.reset(); - return tabConfig.get(); - }, [tabConfig]); - - // Effect to handle tab visibility - useEffect(() => { - const handleTabVisibility = () => { - const currentConfig = tabConfig.get(); - const controlledTabs = ['debug', 'update']; - - // Update visibility based on conditions - const updatedTabs = currentConfig.userTabs.map((tab: TabConfig) => { - if (controlledTabs.includes(tab.id)) { - return { - ...tab, - visible: tab.id === 'debug' ? metrics.memory.percentage > 80 : hasUpdate, - }; - } - - return tab; - }); - - tabConfig.set({ - ...currentConfig, - userTabs: updatedTabs, - }); - }; - - const checkInterval = setInterval(handleTabVisibility, 5000); - - return () => { - clearInterval(checkInterval); - }; - }, [metrics.memory.percentage, hasUpdate, tabConfig]); - - // Effect to handle reset and initialization - useEffect(() => { - const resetToDefaults = () => { - console.log('TaskManagerTab: Resetting to defaults'); - - // Reset metrics and local state - setMetrics(DEFAULT_METRICS_STATE); - setMetricsHistory(DEFAULT_METRICS_HISTORY); - setAlerts([]); - - // Reset tab configuration to ensure proper visibility - const defaultConfig = resetTabConfiguration(); - console.log('TaskManagerTab: Reset tab configuration:', defaultConfig); - }; - - // Listen for both storage changes and custom reset event - const handleReset = (event: Event | StorageEvent) => { - if (event instanceof StorageEvent) { - if (event.key === 'tabConfiguration' && event.newValue === null) { - resetToDefaults(); - } - } else if (event instanceof CustomEvent && event.type === 'tabConfigReset') { - resetToDefaults(); - } - }; - - // Initial setup - const initializeTab = async () => { - try { - await updateMetrics(); - } catch (error) { - console.error('Failed to initialize TaskManagerTab:', error); - resetToDefaults(); - } - }; - - window.addEventListener('storage', handleReset); - window.addEventListener('tabConfigReset', handleReset); - initializeTab(); - - return () => { - window.removeEventListener('storage', handleReset); - window.removeEventListener('tabConfigReset', handleReset); - }; - }, []); - - // Effect to update metrics periodically - useEffect(() => { - const updateInterval = 5000; // Update every 5 seconds instead of 2.5 seconds - let metricsInterval: NodeJS.Timeout; - - // Only run updates when tab is visible - const handleVisibilityChange = () => { - if (document.hidden) { - clearInterval(metricsInterval); - } else { - updateMetrics(); - metricsInterval = setInterval(updateMetrics, updateInterval); - } - }; - - // Initial setup - handleVisibilityChange(); - document.addEventListener('visibilitychange', handleVisibilityChange); - - return () => { - clearInterval(metricsInterval); - document.removeEventListener('visibilitychange', handleVisibilityChange); - }; - }, []); - - // Effect to disable taskmanager on serverless environments - useEffect(() => { - const checkEnvironment = async () => { - // If we're on Cloudflare/Netlify/etc., set not supported - if (isServerlessHosting()) { - setIsNotSupported(true); - return; - } - - // For testing: Allow forcing API failures via URL param - if (typeof window !== 'undefined' && window.location.search.includes('simulate-api-failure=true')) { - console.log('Simulating API failures for testing'); - setIsNotSupported(true); - - return; - } - - // Try to fetch system metrics once as detection - try { - const response = await fetch('/api/system/memory-info'); - const diskResponse = await fetch('/api/system/disk-info'); - const processResponse = await fetch('/api/system/process-info'); - - // If all these return errors or not found, system monitoring is not supported - if (!response.ok && !diskResponse.ok && !processResponse.ok) { - setIsNotSupported(true); - } - } catch (error) { - console.warn('Failed to fetch system metrics. TaskManager features may be limited:', error); - - // Don't automatically disable - we'll show partial data based on what's available - } - }; - - checkEnvironment(); - }, []); - - // Get detailed performance metrics - const getPerformanceMetrics = async (): Promise> => { - try { - // Get page load metrics - const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; - const pageLoad = navigation.loadEventEnd - navigation.startTime; - const domReady = navigation.domContentLoadedEventEnd - navigation.startTime; - - // Get resource metrics - const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[]; - const resourceMetrics = { - total: resources.length, - size: resources.reduce((total, r) => total + (r.transferSize || 0), 0), - loadTime: Math.max(0, ...resources.map((r) => r.duration)), - }; - - // Get Web Vitals - const ttfb = navigation.responseStart - navigation.requestStart; - const paintEntries = performance.getEntriesByType('paint'); - const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0; - - // Get LCP using PerformanceObserver - const lcp = await new Promise((resolve) => { - new PerformanceObserver((list) => { - const entries = list.getEntries(); - const lastEntry = entries[entries.length - 1]; - resolve(lastEntry?.startTime || 0); - }).observe({ entryTypes: ['largest-contentful-paint'] }); - - // Resolve after 3s if no LCP - setTimeout(() => resolve(0), 3000); - }); - - return { - pageLoad, - domReady, - resources: resourceMetrics, - timing: { - ttfb, - fcp, - lcp, - }, - }; - } catch (error) { - console.error('Failed to get performance metrics:', error); - return {}; - } - }; - - // Function to measure endpoint latency - const measureLatency = async (): Promise => { - try { - const headers = new Headers(); - headers.append('Cache-Control', 'no-cache, no-store, must-revalidate'); - headers.append('Pragma', 'no-cache'); - headers.append('Expires', '0'); - - const attemptMeasurement = async (): Promise => { - const start = performance.now(); - const response = await fetch('/api/health', { - method: 'HEAD', - headers, - }); - const end = performance.now(); - - if (!response.ok) { - throw new Error(`Health check failed with status: ${response.status}`); - } - - return Math.round(end - start); - }; - - try { - const latency = await attemptMeasurement(); - console.log(`Measured latency: ${latency}ms`); - - return latency; - } catch (error) { - console.warn(`Latency measurement failed, retrying: ${error}`); - - try { - // Retry once - const latency = await attemptMeasurement(); - console.log(`Measured latency on retry: ${latency}ms`); - - return latency; - } catch (retryError) { - console.error(`Latency measurement failed after retry: ${retryError}`); - - // Return a realistic random latency value for development - const mockLatency = 30 + Math.floor(Math.random() * 120); // 30-150ms - console.log(`Using mock latency: ${mockLatency}ms`); - - return mockLatency; - } - } - } catch (error) { - console.error(`Error in latency measurement: ${error}`); - - // Return a realistic random latency value - const mockLatency = 30 + Math.floor(Math.random() * 120); // 30-150ms - console.log(`Using mock latency due to error: ${mockLatency}ms`); - - return mockLatency; - } - }; - - // Update metrics with real data only - const updateMetrics = async () => { - try { - // If we already determined this environment doesn't support system metrics, don't try fetching - if (isNotSupported) { - console.log('TaskManager: System metrics not supported in this environment'); - return; - } - - // Get system memory info first as it's most important - let systemMemoryInfo: SystemMemoryInfo | undefined; - let memoryMetrics = { - used: 0, - total: 0, - percentage: 0, - }; - - try { - const response = await fetch('/api/system/memory-info'); - - if (response.ok) { - systemMemoryInfo = await response.json(); - console.log('Memory info response:', systemMemoryInfo); - - // Use system memory as primary memory metrics if available - if (systemMemoryInfo && 'used' in systemMemoryInfo) { - memoryMetrics = { - used: systemMemoryInfo.used || 0, - total: systemMemoryInfo.total || 1, - percentage: systemMemoryInfo.percentage || 0, - }; - } - } - } catch (error) { - console.error('Failed to fetch system memory info:', error); - } - - // Get process information - let processInfo: ProcessInfo[] | undefined; - - try { - const response = await fetch('/api/system/process-info'); - - if (response.ok) { - processInfo = await response.json(); - console.log('Process info response:', processInfo); - } - } catch (error) { - console.error('Failed to fetch process info:', error); - } - - // Get disk information - let diskInfo: DiskInfo[] | undefined; - - try { - const response = await fetch('/api/system/disk-info'); - - if (response.ok) { - diskInfo = await response.json(); - console.log('Disk info response:', diskInfo); - } - } catch (error) { - console.error('Failed to fetch disk info:', error); - } - - // Get battery info - let batteryInfo: SystemMetrics['battery'] | undefined; - - try { - if ('getBattery' in navigator) { - const battery = await (navigator as any).getBattery(); - batteryInfo = { - level: battery.level * 100, - charging: battery.charging, - timeRemaining: battery.charging ? battery.chargingTime : battery.dischargingTime, - }; - } else { - // Mock battery data if API not available - batteryInfo = { - level: 75 + Math.floor(Math.random() * 20), - charging: Math.random() > 0.3, - timeRemaining: 7200 + Math.floor(Math.random() * 3600), - }; - console.log('Battery API not available, using mock data'); - } - } catch (error) { - console.log('Battery API error, using mock data:', error); - batteryInfo = { - level: 75 + Math.floor(Math.random() * 20), - charging: Math.random() > 0.3, - timeRemaining: 7200 + Math.floor(Math.random() * 3600), - }; - } - - // Enhanced network metrics - const connection = - (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection; - - // Measure real latency - const measuredLatency = await measureLatency(); - const connectionRtt = connection?.rtt || 0; - - // Use measured latency if available, fall back to connection.rtt - const currentLatency = measuredLatency || connectionRtt || Math.floor(Math.random() * 100); - - // Update network metrics with historical data - const networkInfo = { - downlink: connection?.downlink || 1.5 + Math.random(), - uplink: connection?.uplink || 0.5 + Math.random(), - latency: { - current: currentLatency, - average: - metrics.network.latency.history.length > 0 - ? [...metrics.network.latency.history, currentLatency].reduce((a, b) => a + b, 0) / - (metrics.network.latency.history.length + 1) - : currentLatency, - min: - metrics.network.latency.history.length > 0 - ? Math.min(...metrics.network.latency.history, currentLatency) - : currentLatency, - max: - metrics.network.latency.history.length > 0 - ? Math.max(...metrics.network.latency.history, currentLatency) - : currentLatency, - history: [...metrics.network.latency.history, currentLatency].slice(-30), // Keep last 30 measurements - lastUpdate: Date.now(), - }, - type: connection?.type || 'unknown', - effectiveType: connection?.effectiveType || '4g', - }; - - // Get performance metrics - const performanceMetrics = await getPerformanceMetrics(); - - const updatedMetrics: SystemMetrics = { - memory: memoryMetrics, - systemMemory: systemMemoryInfo, - processes: processInfo || [], - disks: diskInfo || [], - battery: batteryInfo, - network: networkInfo, - performance: performanceMetrics as SystemMetrics['performance'], - }; - - setMetrics(updatedMetrics); - - // Update history with real data - const now = new Date().toLocaleTimeString(); - setMetricsHistory((prev) => { - // Ensure we have valid data or use zeros - const memoryPercentage = systemMemoryInfo?.percentage || 0; - const batteryLevel = batteryInfo?.level || 0; - const networkDownlink = networkInfo.downlink || 0; - - // Calculate CPU usage more accurately - let cpuUsage = 0; - - if (processInfo && processInfo.length > 0) { - // Get the average of the top 3 CPU-intensive processes - const topProcesses = [...processInfo].sort((a, b) => b.cpu - a.cpu).slice(0, 3); - const topCpuUsage = topProcesses.reduce((total, proc) => total + proc.cpu, 0); - - // Get the sum of all processes - const totalCpuUsage = processInfo.reduce((total, proc) => total + proc.cpu, 0); - - // Use the higher of the two values, but cap at 100% - cpuUsage = Math.min(Math.max(topCpuUsage, (totalCpuUsage / processInfo.length) * 3), 100); - } else { - // If no process info, generate random CPU usage between 5-30% - cpuUsage = 5 + Math.floor(Math.random() * 25); - } - - // Calculate disk usage (average of all disks) - let diskUsage = 0; - - if (diskInfo && diskInfo.length > 0) { - diskUsage = diskInfo.reduce((total, disk) => total + disk.percentage, 0) / diskInfo.length; - } else { - // If no disk info, generate random disk usage between 30-70% - diskUsage = 30 + Math.floor(Math.random() * 40); - } - - // Create new arrays with the latest data - const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS); - const memory = [...prev.memory, memoryPercentage].slice(-MAX_HISTORY_POINTS); - const battery = [...prev.battery, batteryLevel].slice(-MAX_HISTORY_POINTS); - const network = [...prev.network, networkDownlink].slice(-MAX_HISTORY_POINTS); - const cpu = [...prev.cpu, cpuUsage].slice(-MAX_HISTORY_POINTS); - const disk = [...prev.disk, diskUsage].slice(-MAX_HISTORY_POINTS); - - console.log('Updated metrics history:', { - timestamps, - memory, - battery, - network, - cpu, - disk, - }); - - return { timestamps, memory, battery, network, cpu, disk }; - }); - - // Check for memory alerts - only show toast when state changes - const currentState = - systemMemoryInfo && systemMemoryInfo.percentage > PERFORMANCE_THRESHOLDS.memory.critical - ? 'critical-memory' - : networkInfo.latency.current > PERFORMANCE_THRESHOLDS.network.latency.critical - ? 'critical-network' - : batteryInfo && !batteryInfo.charging && batteryInfo.level < PERFORMANCE_THRESHOLDS.battery.critical - ? 'critical-battery' - : 'normal'; - - if (currentState === 'critical-memory' && lastAlertState !== 'critical-memory') { - const alert: PerformanceAlert = { - type: 'error', - message: 'Critical system memory usage detected', - timestamp: Date.now(), - metric: 'memory', - threshold: PERFORMANCE_THRESHOLDS.memory.critical, - value: systemMemoryInfo?.percentage || 0, - }; - setAlerts((prev) => { - const newAlerts = [...prev, alert]; - return newAlerts.slice(-10); - }); - toast.warning(alert.message, { - toastId: 'memory-critical', - autoClose: 5000, - }); - } else if (currentState === 'critical-network' && lastAlertState !== 'critical-network') { - const alert: PerformanceAlert = { - type: 'warning', - message: 'High network latency detected', - timestamp: Date.now(), - metric: 'network', - threshold: PERFORMANCE_THRESHOLDS.network.latency.critical, - value: networkInfo.latency.current, - }; - setAlerts((prev) => { - const newAlerts = [...prev, alert]; - return newAlerts.slice(-10); - }); - toast.warning(alert.message, { - toastId: 'network-critical', - autoClose: 5000, - }); - } else if (currentState === 'critical-battery' && lastAlertState !== 'critical-battery') { - const alert: PerformanceAlert = { - type: 'error', - message: 'Critical battery level detected', - timestamp: Date.now(), - metric: 'battery', - threshold: PERFORMANCE_THRESHOLDS.battery.critical, - value: batteryInfo?.level || 0, - }; - setAlerts((prev) => { - const newAlerts = [...prev, alert]; - return newAlerts.slice(-10); - }); - toast.error(alert.message, { - toastId: 'battery-critical', - autoClose: 5000, - }); - } - - setLastAlertState(currentState); - - // Then update the environment detection - const isCloudflare = - !isDevelopment && // Not in development mode - ((systemMemoryInfo?.error && systemMemoryInfo.error.includes('not available')) || - (processInfo?.[0]?.error && processInfo[0].error.includes('not available')) || - (diskInfo?.[0]?.error && diskInfo[0].error.includes('not available'))); - - // If we detect that we're in a serverless environment, set the flag - if (isCloudflare || isServerlessHosting()) { - setIsNotSupported(true); - } - - if (isCloudflare) { - console.log('Running in Cloudflare environment. System metrics not available.'); - } else if (isLocalDevelopment) { - console.log('Running in local development environment. Using real or mock system metrics as available.'); - } else if (isDevelopment) { - console.log('Running in development environment. Using real or mock system metrics as available.'); - } else { - console.log('Running in production environment. Using real system metrics.'); - } - } catch (error) { - console.error('Failed to update metrics:', error); - } - }; - - const getUsageColor = (usage: number): string => { - if (usage > 80) { - return 'text-red-500'; - } - - if (usage > 50) { - return 'text-yellow-500'; - } - - return 'text-gray-500'; - }; - - // Chart rendering function - const renderUsageGraph = React.useMemo( - () => - (data: number[], label: string, color: string, chartRef: React.RefObject>) => { - // Ensure we have valid data - const validData = data.map((value) => (isNaN(value) ? 0 : value)); - - // Ensure we have at least 2 data points - if (validData.length < 2) { - // Add a second point if we only have one - if (validData.length === 1) { - validData.push(validData[0]); - } else { - // Add two points if we have none - validData.push(0, 0); - } - } - - const chartData = { - labels: - metricsHistory.timestamps.length > 0 - ? metricsHistory.timestamps - : Array(validData.length) - .fill('') - .map((_, _i) => new Date().toLocaleTimeString()), - datasets: [ - { - label, - data: validData.slice(-MAX_HISTORY_POINTS), - borderColor: color, - backgroundColor: `${color}33`, // Add slight transparency for fill - fill: true, - tension: 0.4, - pointRadius: 2, // Small points for better UX - borderWidth: 2, - }, - ], - }; - - const options = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true, - max: label === 'Network' ? undefined : 100, // Auto-scale for network, 0-100 for others - grid: { - color: 'rgba(200, 200, 200, 0.1)', - drawBorder: false, - }, - ticks: { - maxTicksLimit: 5, - callback: (value: any) => { - if (label === 'Network') { - return `${value} Mbps`; - } - - return `${value}%`; - }, - }, - }, - x: { - grid: { - display: false, - }, - ticks: { - maxTicksLimit: 4, - maxRotation: 0, - }, - }, - }, - plugins: { - legend: { - display: false, - }, - tooltip: { - enabled: true, - mode: 'index' as const, - intersect: false, - backgroundColor: 'rgba(0, 0, 0, 0.8)', - titleColor: 'white', - bodyColor: 'white', - borderColor: color, - borderWidth: 1, - padding: 10, - cornerRadius: 4, - displayColors: false, - callbacks: { - title: (tooltipItems: any) => { - return tooltipItems[0].label; // Show timestamp - }, - label: (context: any) => { - const value = context.raw; - - if (label === 'Memory') { - return `Memory: ${value.toFixed(1)}%`; - } else if (label === 'CPU') { - return `CPU: ${value.toFixed(1)}%`; - } else if (label === 'Battery') { - return `Battery: ${value.toFixed(1)}%`; - } else if (label === 'Network') { - return `Network: ${value.toFixed(1)} Mbps`; - } else if (label === 'Disk') { - return `Disk: ${value.toFixed(1)}%`; - } - - return `${label}: ${value.toFixed(1)}`; - }, - }, - }, - }, - animation: { - duration: 300, // Short animation for better UX - } as const, - elements: { - line: { - tension: 0.3, - }, - }, - }; - - return ( -
- -
- ); - }, - [metricsHistory.timestamps], - ); - - // Function to handle sorting - const handleSort = (field: SortField) => { - if (sortField === field) { - // Toggle direction if clicking the same field - setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); - } else { - // Set new field and default to descending - setSortField(field); - setSortDirection('desc'); - } - }; - - // Function to sort processes - const getSortedProcesses = () => { - if (!metrics.processes) { - return []; - } - - return [...metrics.processes].sort((a, b) => { - let comparison = 0; - - switch (sortField) { - case 'name': - comparison = a.name.localeCompare(b.name); - break; - case 'pid': - comparison = a.pid - b.pid; - break; - case 'cpu': - comparison = a.cpu - b.cpu; - break; - case 'memory': - comparison = a.memory - b.memory; - break; - } - - return sortDirection === 'asc' ? comparison : -comparison; - }); - }; - - // If we're in an environment where the task manager won't work, show a message - if (isNotSupported) { - return ( -
-
-

System Monitoring Not Available

-

- System monitoring is not available in serverless environments like Cloudflare Pages, Netlify, or Vercel. These - platforms don't provide access to the underlying system resources. -

-
-

- Why is this disabled? -
- Serverless platforms execute your code in isolated environments without access to the server's operating - system metrics like CPU, memory, and disk usage. -

-

- System monitoring features will be available when running in: -

    -
  • Local development environment
  • -
  • Virtual Machines (VMs)
  • -
  • Dedicated servers
  • -
  • Docker containers (with proper permissions)
  • -
-

-
- - {/* Testing controls - only shown in development */} - {isDevelopment && ( -
- )} -
- ); - } - - return ( -
- {/* Summary Header */} -
-
-
CPU
-
- {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% -
-
-
-
Memory
-
- {Math.round(metrics.systemMemory?.percentage || 0)}% -
-
-
-
Disk
-
0 - ? metrics.disks.reduce((total, disk) => total + disk.percentage, 0) / metrics.disks.length - : 0, - ), - )} - > - {metrics.disks && metrics.disks.length > 0 - ? Math.round(metrics.disks.reduce((total, disk) => total + disk.percentage, 0) / metrics.disks.length) - : 0} - % -
-
-
-
Network
-
{metrics.network.downlink.toFixed(1)} Mbps
-
-
- - {/* Memory Usage */} -
-

Memory Usage

-
- {/* System Physical Memory */} -
-
-
- System Memory -
-
-
- Shows your system's physical memory (RAM) usage. -
-
-
- - {Math.round(metrics.systemMemory?.percentage || 0)}% - -
- {renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb', memoryChartRef)} -
- Used: {formatBytes(metrics.systemMemory?.used || 0)} / {formatBytes(metrics.systemMemory?.total || 0)} -
-
- Free: {formatBytes(metrics.systemMemory?.free || 0)} -
-
- - {/* Swap Memory */} - {metrics.systemMemory?.swap && ( -
-
-
- Swap Memory -
-
-
- Virtual memory used when physical RAM is full. -
-
-
- - {Math.round(metrics.systemMemory.swap.percentage)}% - -
-
-
-
-
- Used: {formatBytes(metrics.systemMemory.swap.used)} / {formatBytes(metrics.systemMemory.swap.total)} -
-
- Free: {formatBytes(metrics.systemMemory.swap.free)} -
-
- )} -
-
- - {/* Disk Usage */} -
-

Disk Usage

- {metrics.disks && metrics.disks.length > 0 ? ( -
-
- System Disk - - {(metricsHistory.disk[metricsHistory.disk.length - 1] || 0).toFixed(1)}% - -
- {renderUsageGraph(metricsHistory.disk, 'Disk', '#8b5cf6', diskChartRef)} - - {/* Show only the main system disk (usually the first one) */} - {metrics.disks[0] && ( - <> -
-
-
-
-
Used: {formatBytes(metrics.disks[0].used)}
-
Free: {formatBytes(metrics.disks[0].available)}
-
Total: {formatBytes(metrics.disks[0].size)}
-
- - )} -
- ) : ( -
-
-

Disk information is not available

-

- This feature may not be supported in your environment -

-
- )} -
- - {/* Process Information */} -
-
-

Process Information

- -
-
- {metrics.processes && metrics.processes.length > 0 ? ( - <> - {/* CPU Usage Summary */} - {metrics.processes[0].name !== 'Unknown' && ( -
-
- CPU Usage - - {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% Total - -
-
-
- {metrics.processes.map((process, index) => { - return ( -
- ); - })} -
-
-
-
- System:{' '} - {metrics.processes.reduce((total, proc) => total + (proc.cpu < 10 ? proc.cpu : 0), 0).toFixed(1)}% -
-
- User:{' '} - {metrics.processes.reduce((total, proc) => total + (proc.cpu >= 10 ? proc.cpu : 0), 0).toFixed(1)} - % -
-
- Idle: {(100 - (metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0)).toFixed(1)}% -
-
-
- )} - -
- - - - - - - - - - - {getSortedProcesses().map((process, index) => ( - - - - - - - ))} - -
handleSort('name')} - > - Process {sortField === 'name' && (sortDirection === 'asc' ? '↑' : '↓')} - handleSort('pid')} - > - PID {sortField === 'pid' && (sortDirection === 'asc' ? '↑' : '↓')} - handleSort('cpu')} - > - CPU % {sortField === 'cpu' && (sortDirection === 'asc' ? '↑' : '↓')} - handleSort('memory')} - > - Memory {sortField === 'memory' && (sortDirection === 'asc' ? '↑' : '↓')} -
- {process.name} - {process.pid} -
-
-
-
- {process.cpu.toFixed(1)}% -
-
-
-
-
-
- {/* Calculate approximate MB based on percentage and total system memory */} - {metrics.systemMemory - ? `${formatBytes(metrics.systemMemory.total * (process.memory / 100))}` - : `${process.memory.toFixed(1)}%`} -
-
-
-
- {metrics.processes[0].error ? ( - -
- Error retrieving process information: {metrics.processes[0].error} - - ) : metrics.processes[0].name === 'Browser' ? ( - -
- Showing browser process information. System process information is not available in this - environment. - - ) : ( - Showing top {metrics.processes.length} processes by memory usage - )} -
- - ) : ( -
-
-

Process information is not available

-

- This feature may not be supported in your environment -

- -
- )} -
-
- - {/* CPU Usage Graph */} -
-

CPU Usage History

-
-
- System CPU - - {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% - -
- {renderUsageGraph(metricsHistory.cpu, 'CPU', '#ef4444', cpuChartRef)} -
- Average: {(metricsHistory.cpu.reduce((a, b) => a + b, 0) / metricsHistory.cpu.length || 0).toFixed(1)}% -
-
- Peak: {Math.max(...metricsHistory.cpu).toFixed(1)}% -
-
-
- - {/* Network */} -
-

Network

-
-
-
- Connection - - {metrics.network.downlink.toFixed(1)} Mbps - -
- {renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b', networkChartRef)} -
- Type: {metrics.network.type} - {metrics.network.effectiveType && ` (${metrics.network.effectiveType})`} -
-
- Latency: {Math.round(metrics.network.latency.current)}ms - - (avg: {Math.round(metrics.network.latency.average)}ms) - -
-
- Min: {Math.round(metrics.network.latency.min)}ms / Max: {Math.round(metrics.network.latency.max)}ms -
- {metrics.network.uplink && ( -
- Uplink: {metrics.network.uplink.toFixed(1)} Mbps -
- )} -
-
-
- - {/* Battery */} - {metrics.battery && ( -
-

Battery

-
-
-
- Status -
- {metrics.battery.charging && ( -
- )} - 20 ? 'text-codinit-elements-textPrimary' : 'text-red-500', - )} - > - {Math.round(metrics.battery.level)}% - -
-
- {renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e', batteryChartRef)} - {metrics.battery.timeRemaining && metrics.battery.timeRemaining !== Infinity && ( -
- {metrics.battery.charging ? 'Time to full: ' : 'Time remaining: '} - {formatTime(metrics.battery.timeRemaining)} -
- )} -
-
-
- )} - - {/* Performance */} -
-

Performance

-
-
-
- Page Load: {(metrics.performance.pageLoad / 1000).toFixed(2)}s -
-
- DOM Ready: {(metrics.performance.domReady / 1000).toFixed(2)}s -
-
- TTFB: {(metrics.performance.timing.ttfb / 1000).toFixed(2)}s -
-
- Resources: {metrics.performance.resources.total} ({formatBytes(metrics.performance.resources.size)}) -
-
-
-
- - {/* Alerts */} - {alerts.length > 0 && ( -
-
- Recent Alerts - -
-
- {alerts.slice(-5).map((alert, index) => ( -
-
- {alert.message} - - {new Date(alert.timestamp).toLocaleTimeString()} - -
- ))} -
-
- )} -
- ); -}; - -export default React.memo(TaskManagerTab); - -// Helper function to format bytes -const formatBytes = (bytes: number): string => { - if (bytes === 0) { - return '0 B'; - } - - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - const value = bytes / Math.pow(k, i); - - // Format with 2 decimal places for MB and larger units - const formattedValue = i >= 2 ? value.toFixed(2) : value.toFixed(0); - - return `${formattedValue} ${sizes[i]}`; -}; - -// Helper function to format time -const formatTime = (seconds: number): string => { - if (!isFinite(seconds) || seconds === 0) { - return 'Unknown'; - } - - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (hours > 0) { - return `${hours}h ${minutes}m`; - } - - return `${minutes}m`; -}; From 15d4ce0c346192cd71e955ab139db5b30d3d029e Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 18 Dec 2025 01:59:08 +1100 Subject: [PATCH 023/253] remove mock feature flags from ControlPanel --- app/components/@settings/core/ControlPanel.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/components/@settings/core/ControlPanel.tsx b/app/components/@settings/core/ControlPanel.tsx index b95d2559..c1fd16fd 100644 --- a/app/components/@settings/core/ControlPanel.tsx +++ b/app/components/@settings/core/ControlPanel.tsx @@ -9,7 +9,6 @@ import { TabTile } from '~/components/@settings/shared/components/TabTile'; import { SearchInterface } from '~/components/@settings/shared/components/SearchInterface'; import { initializeSearchIndex } from '~/components/@settings/shared/utils/settingsSearch'; import { useUpdateCheck } from '~/lib/hooks/useUpdateCheck'; -import { useFeatures } from '~/lib/hooks/useFeatures'; import { useNotifications } from '~/lib/hooks/useNotifications'; import { useConnectionStatus } from '~/lib/hooks/useConnectionStatus'; import { useDebugStatus } from '~/lib/hooks/useDebugStatus'; @@ -168,7 +167,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { // Status hooks const { hasUpdate, currentVersion, acknowledgeUpdate } = useUpdateCheck(); - const { hasNewFeatures, unviewedFeatures, acknowledgeAllFeatures } = useFeatures(); const { hasUnreadNotifications, unreadNotifications, markAllAsRead } = useNotifications(); const { hasConnectionIssues, currentIssue, acknowledgeIssue } = useConnectionStatus(); const { hasActiveWarnings, activeIssues, acknowledgeAllIssues } = useDebugStatus(); @@ -358,8 +356,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { switch (tabId) { case 'update': return hasUpdate; - case 'features': - return hasNewFeatures; case 'notifications': return hasUnreadNotifications; case 'connection': @@ -375,8 +371,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { switch (tabId) { case 'update': return `New update available (v${currentVersion})`; - case 'features': - return `${unviewedFeatures.length} new feature${unviewedFeatures.length === 1 ? '' : 's'} to explore`; case 'notifications': return `${unreadNotifications.length} unread notification${unreadNotifications.length === 1 ? '' : 's'}`; case 'connection': @@ -406,9 +400,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { case 'update': acknowledgeUpdate(); break; - case 'features': - acknowledgeAllFeatures(); - break; case 'notifications': markAllAsRead(); break; From 44c61c0bfca9f9864ee03d6f3ad3160ce608eb57 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 18 Dec 2025 03:08:38 +1100 Subject: [PATCH 024/253] delete mock features hook and API files --- app/lib/api/features.ts | 35 ------------------ app/lib/hooks/index.ts | 1 - app/lib/hooks/useFeatures.ts | 72 ------------------------------------ 3 files changed, 108 deletions(-) delete mode 100644 app/lib/api/features.ts delete mode 100644 app/lib/hooks/useFeatures.ts diff --git a/app/lib/api/features.ts b/app/lib/api/features.ts deleted file mode 100644 index c3ec533d..00000000 --- a/app/lib/api/features.ts +++ /dev/null @@ -1,35 +0,0 @@ -export interface Feature { - id: string; - name: string; - description: string; - viewed: boolean; - releaseDate: string; -} - -export const getFeatureFlags = async (): Promise => { - /* - * TODO: Implement actual feature flags logic - * This is a mock implementation - */ - return [ - { - id: 'feature-1', - name: 'Dark Mode', - description: 'Enable dark mode for better night viewing', - viewed: true, - releaseDate: '2024-03-15', - }, - { - id: 'feature-2', - name: 'Tab Management', - description: 'Customize your tab layout', - viewed: false, - releaseDate: '2024-03-20', - }, - ]; -}; - -export const markFeatureViewed = async (featureId: string): Promise => { - /* TODO: Implement actual feature viewed logic */ - console.log(`Marking feature ${featureId} as viewed`); -}; diff --git a/app/lib/hooks/index.ts b/app/lib/hooks/index.ts index e2f61334..f3777421 100644 --- a/app/lib/hooks/index.ts +++ b/app/lib/hooks/index.ts @@ -5,7 +5,6 @@ export * from './StickToBottom'; export * from './useEditChatDescription'; export { default } from './useViewport'; export { useUpdateCheck } from './useUpdateCheck'; -export { useFeatures } from './useFeatures'; export { useNotifications } from './useNotifications'; export { useConnectionStatus } from './useConnectionStatus'; export { useDebugStatus } from './useDebugStatus'; diff --git a/app/lib/hooks/useFeatures.ts b/app/lib/hooks/useFeatures.ts deleted file mode 100644 index d62a03e3..00000000 --- a/app/lib/hooks/useFeatures.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useState, useEffect } from 'react'; -import { getFeatureFlags, markFeatureViewed, type Feature } from '~/lib/api/features'; - -const VIEWED_FEATURES_KEY = 'codinit_viewed_features'; - -const getViewedFeatures = (): string[] => { - try { - const stored = localStorage.getItem(VIEWED_FEATURES_KEY); - return stored ? JSON.parse(stored) : []; - } catch { - return []; - } -}; - -const setViewedFeatures = (featureIds: string[]) => { - try { - localStorage.setItem(VIEWED_FEATURES_KEY, JSON.stringify(featureIds)); - } catch (error) { - console.error('Failed to persist viewed features:', error); - } -}; - -export const useFeatures = () => { - const [hasNewFeatures, setHasNewFeatures] = useState(false); - const [unviewedFeatures, setUnviewedFeatures] = useState([]); - const [viewedFeatureIds, setViewedFeatureIds] = useState(() => getViewedFeatures()); - - useEffect(() => { - const checkNewFeatures = async () => { - try { - const features = await getFeatureFlags(); - const unviewed = features.filter((feature) => !viewedFeatureIds.includes(feature.id)); - setUnviewedFeatures(unviewed); - setHasNewFeatures(unviewed.length > 0); - } catch (error) { - console.error('Failed to check for new features:', error); - } - }; - - checkNewFeatures(); - }, [viewedFeatureIds]); - - const acknowledgeFeature = async (featureId: string) => { - try { - await markFeatureViewed(featureId); - - const newViewedIds = [...viewedFeatureIds, featureId]; - setViewedFeatureIds(newViewedIds); - setViewedFeatures(newViewedIds); - setUnviewedFeatures((prev) => prev.filter((feature) => feature.id !== featureId)); - setHasNewFeatures(unviewedFeatures.length > 1); - } catch (error) { - console.error('Failed to acknowledge feature:', error); - } - }; - - const acknowledgeAllFeatures = async () => { - try { - await Promise.all(unviewedFeatures.map((feature) => markFeatureViewed(feature.id))); - - const newViewedIds = [...viewedFeatureIds, ...unviewedFeatures.map((f) => f.id)]; - setViewedFeatureIds(newViewedIds); - setViewedFeatures(newViewedIds); - setUnviewedFeatures([]); - setHasNewFeatures(false); - } catch (error) { - console.error('Failed to acknowledge all features:', error); - } - }; - - return { hasNewFeatures, unviewedFeatures, acknowledgeFeature, acknowledgeAllFeatures }; -}; From 5482c5df22dc3e0506daf2bdcbe87f05df4cb46f Mon Sep 17 00:00:00 2001 From: Gerome <186273274+Gerome-Elassaad@users.noreply.github.com> Date: Thu, 18 Dec 2025 05:41:03 +1100 Subject: [PATCH 025/253] Revise README for improved content and structure Updated README to enhance clarity and include new links. --- README.md | 125 +++++++++++++++++++++++------------------------------- 1 file changed, 54 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 1c0649a8..c4223c04 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ - -hero-image - -

- - Featured on HuntScreens + CodinIT.dev Hero +

+ + Fazier badge

@@ -14,115 +12,100 @@ height="48" srcset="https://a.fsdn.com/con/app/sf-download-button?button_size=2x 2x"> +

+ ⚡ CodinIT.dev — Open‑Source AI App Builder ⚡
+ Build, manage, and deploy intelligent applications faster — directly from your browser or desktop.

+--- + +✨ What is CodinIT.dev? + +CodinIT.dev is an open‑source, AI‑powered full‑stack development platform designed to help you build modern Node.js applications with speed and precision. It combines code generation, project management, and deployment tools into a single workflow — powered by your choice of AI providers. + +Whether you’re prototyping, scaling a SaaS product, or experimenting with local LLMs, CodinIT.dev adapts to your stack and your workflow. -

- ⚡ CodinIT.dev — OpenSource AI App Builder ⚡
- Build, manage, and deploy intelligent applications directly from your browser or desktop. -

--- -## 🚀 Quick Start +🚀 Quick Start -Get up and running with **CodinIT.dev** in just a few steps. +Get up and running in minutes. -### 1️⃣ Clone the Repository +1️⃣ Clone the Repository -```bash git clone https://github.com/codinit-dev/codinit-dev.git -cd codinit-app -```` +cd codinit-dev -### Install Dependencies +2️⃣ Install Dependencies -```bash # npm npm install # or pnpm pnpm install -# or yarn -yarn install -``` +3️⃣ Configure Environment -### 2️⃣ Set Up the Database +Create a .env file and add your preferred AI provider keys: -Ensure you have a PostgreSQL database running. (Supabase recommended.) +OPENAI_API_KEY=your_key_here +ANTHROPIC_API_KEY=your_key_here -### 3️⃣ Configure Environment +(You can mix and match multiple providers.) -```bash -cp .env.example .env.local -``` +4️⃣ Run the Dev Server -Add your keys: +pnpm run dev -```bash -OPENAI_API_KEY=your_openai_key -ANTHROPIC_API_KEY=your_anthropic_key -SUPABASE_URL=your_supabase_url -SUPABASE_ANON_KEY=your_supabase_anon_key -``` +The app will be available at: 👉 http://localhost:5173 -### 4️⃣ Run the Dev Server -```bash -pnpm run dev -``` +--- -The app will be available at: -👉 [http://localhost:5173](http://localhost:5173) +🧩 Key Features ---- +🧠 AI‑powered full‑stack development for Node.js apps -## 🧩 Key Features +🌐 19+ AI provider integrations (cloud & local) -* 🧠 AI-powered full-stack development for Node.js apps -* 🌐 Integrations with 19+ AI providers -* 🖥️ Web + Desktop (Electron) support -* 🐳 Docker-ready, deployable to Vercel/Netlify/GitHub Pages -* 🔍 Built-in search, diff view, and file locking system -* 🧰 Supabase integration, data visualization, and voice prompting +🖥️ Web + Desktop (Electron) support ---- +🐳 Docker‑ready — deploy to Vercel, Netlify, or GitHub Pages + +🔍 Built‑in search, diff viewer & file‑locking -## 🔑 API Providers +🧰 Supabase integration, data visualization & voice prompting + +🔐 Provider‑agnostic architecture — no vendor lock‑in -**Cloud Providers:** -OpenAI, Anthropic, Google, Groq, xAI, DeepSeek, Cohere, Mistral, Together, Perplexity, HuggingFace, OpenRouter, and more. -**Local:** -Ollama, LM Studio, OpenAI-compatible local endpoints. --- -## 🖥️ Desktop & Docker Options +🔑 Supported AI Providers -### Run via Docker +☁️ Cloud Providers -```bash -npm run dockerbuild -docker compose --profile development up -``` +OpenAI · Anthropic · Google · Groq · xAI · DeepSeek · Cohere · Mistral · Together · Perplexity · HuggingFace · OpenRouter · and more + +🏠 Local Providers + +Ollama · LM Studio · OpenAI‑compatible local endpoints -### Run as Desktop App +Use one provider or switch dynamically per task. -Download the latest release: -👉 [Latest Release](https://github.com/codinit-dev/codinit-dev/releases/latest) --- -## 🤝 Contributing +🖥️ Desktop & Docker Usage -We welcome contributions! -Open an issue, submit a PR, or join discussions to help shape the future of CodinIT.dev. +Run with Docker ---- +npm run dockerbuild +docker compose --profile development up -

- CodinIT.dev — Build Faster. Code Smarter.
- Download the latest version → -

+Run as a Desktop App + +Download the latest prebuilt release: 👉 https://github.com/codinit-dev/codinit-dev/releases/latest +Available for macOS, Windows, and Linux. From 9172e46aef575b8c4b99b489ecc76ba7f046ae8e Mon Sep 17 00:00:00 2001 From: Gerome <186273274+Gerome-Elassaad@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:34:21 +1100 Subject: [PATCH 026/253] Update README with setup instructions Added instructions for cloning the repository and installing dependencies. --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c4223c04..2da2d6fa 100644 --- a/README.md +++ b/README.md @@ -33,29 +33,33 @@ Get up and running in minutes. 1️⃣ Clone the Repository +``` git clone https://github.com/codinit-dev/codinit-dev.git cd codinit-dev +``` 2️⃣ Install Dependencies +``` # npm npm install # or pnpm pnpm install +``` + 3️⃣ Configure Environment Create a .env file and add your preferred AI provider keys: -OPENAI_API_KEY=your_key_here -ANTHROPIC_API_KEY=your_key_here - (You can mix and match multiple providers.) 4️⃣ Run the Dev Server +``` pnpm run dev +``` The app will be available at: 👉 http://localhost:5173 @@ -94,16 +98,15 @@ Ollama · LM Studio · OpenAI‑compatible local endpoints Use one provider or switch dynamically per task. - --- 🖥️ Desktop & Docker Usage Run with Docker - +``` npm run dockerbuild docker compose --profile development up - +``` Run as a Desktop App Download the latest prebuilt release: 👉 https://github.com/codinit-dev/codinit-dev/releases/latest From a3e5109107215d28d5046182bb504ba45ee5aa02 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 18:46:55 +1100 Subject: [PATCH 027/253] fix: check providerSettings for Ollama/LMStudio baseUrl configuration --- app/lib/modules/llm/manager.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/lib/modules/llm/manager.ts b/app/lib/modules/llm/manager.ts index bf1e3fa5..aaaca229 100644 --- a/app/lib/modules/llm/manager.ts +++ b/app/lib/modules/llm/manager.ts @@ -113,6 +113,7 @@ export class LLMManager { provider: BaseProvider, apiKeys?: Record, serverEnv?: Record, + providerSettings?: Record, ): boolean { // Check if provider has API key configuration const config = provider.config; @@ -128,7 +129,10 @@ export class LLMManager { // For local providers like Ollama and LMStudio, check if baseUrl is configured if (provider.name === 'Ollama' || provider.name === 'LMStudio') { const baseUrlKey = provider.name === 'Ollama' ? 'OLLAMA_API_BASE_URL' : 'LMSTUDIO_API_BASE_URL'; - const hasBaseUrl = apiKeys?.[baseUrlKey] || serverEnv?.[baseUrlKey]; + const hasBaseUrl = + providerSettings?.[provider.name]?.baseUrl || + apiKeys?.[baseUrlKey] || + serverEnv?.[baseUrlKey]; if (!hasBaseUrl) { return false; @@ -217,7 +221,7 @@ export class LLMManager { // Check if provider has required configuration before attempting fetch const providerConfig = providerSettings?.[provider.name]; - const hasRequiredConfig = this._hasRequiredConfiguration(provider, apiKeys, serverEnv); + const hasRequiredConfig = this._hasRequiredConfiguration(provider, apiKeys, serverEnv, providerSettings); if (!hasRequiredConfig) { logger.debug(`Skipping ${provider.name}: missing required configuration`); From e98393d866f9bc0c16bc2cbb8a487ef2be584a21 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 18:50:18 +1100 Subject: [PATCH 028/253] fix: add error handling and proper Docker host mapping to Ollama provider --- app/lib/modules/llm/providers/ollama.ts | 42 ++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/app/lib/modules/llm/providers/ollama.ts b/app/lib/modules/llm/providers/ollama.ts index 0c82c987..ac31d39b 100644 --- a/app/lib/modules/llm/providers/ollama.ts +++ b/app/lib/modules/llm/providers/ollama.ts @@ -83,18 +83,36 @@ export default class OllamaProvider extends BaseProvider { */ const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true'; - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + if (isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse Ollama baseUrl for Docker mapping:', error); + } + } } const response = await fetch(`${baseUrl}/api/tags`); + + if (!response.ok) { + throw new Error( + `Failed to fetch Ollama models: HTTP ${response.status} ${response.statusText}`, + ); + } + const data = (await response.json()) as OllamaApiResponse; - // console.log({ ollamamodels: data.models }); + if (!data || !Array.isArray(data.models)) { + throw new Error('Invalid response from Ollama API: missing models array'); + } return data.models.map((model: OllamaModel) => ({ name: model.name, - label: `${model.name} (${model.details.parameter_size})`, + label: `${model.name} (${model.details?.parameter_size || 'unknown'})`, provider: this.name, maxTokenAllowed: 8000, maxCompletionTokens: 8000, @@ -119,14 +137,24 @@ export default class OllamaProvider extends BaseProvider { defaultApiTokenKey: '', }); - // Backend: Check if we're running in Docker if (!baseUrl) { throw new Error('No baseUrl found for OLLAMA provider'); } + // Backend: Check if we're running in Docker const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || envRecord.RUNNING_IN_DOCKER === 'true'; - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + + if (isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse Ollama baseUrl for Docker mapping:', error); + } + } logger.debug('Ollama Base Url used: ', baseUrl); From 4826555c7e4c5a04bbd44adebdecf659b9adff10 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 19:06:20 +1100 Subject: [PATCH 029/253] fix: use provider settings baseUrl in LocalProvidersTab instead of hardcoded URL --- app/components/@settings/core/constants.ts | 4 --- .../providers/cloud/CloudProvidersTab.tsx | 2 +- .../providers/local/LocalProvidersTab.tsx | 25 ++++++++++++++++--- app/lib/modules/llm/manager.ts | 5 +--- app/lib/modules/llm/providers/ollama.ts | 6 ++--- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/components/@settings/core/constants.ts b/app/components/@settings/core/constants.ts index d74c211a..bd0a95ab 100644 --- a/app/components/@settings/core/constants.ts +++ b/app/components/@settings/core/constants.ts @@ -62,13 +62,11 @@ export const DEFAULT_TAB_CONFIG = [ { id: 'local-providers', visible: true, window: 'user' as const, order: 3 }, { id: 'connection', visible: true, window: 'user' as const, order: 4 }, { id: 'notifications', visible: true, window: 'user' as const, order: 5 }, - { id: 'event-logs', visible: true, window: 'user' as const, order: 6 }, // User Window Tabs (In dropdown, initially hidden) { id: 'profile', visible: false, window: 'user' as const, order: 7 }, { id: 'settings', visible: false, window: 'user' as const, order: 8 }, { id: 'api-keys', visible: true, window: 'user' as const, order: 9 }, - { id: 'task-manager', visible: false, window: 'user' as const, order: 10 }, { id: 'service-status', visible: false, window: 'user' as const, order: 11 }, // User Window Tabs (Hidden, controlled by TaskManagerTab) @@ -82,11 +80,9 @@ export const DEFAULT_TAB_CONFIG = [ { id: 'local-providers', visible: true, window: 'developer' as const, order: 3 }, { id: 'connection', visible: true, window: 'developer' as const, order: 4 }, { id: 'notifications', visible: true, window: 'developer' as const, order: 5 }, - { id: 'event-logs', visible: true, window: 'developer' as const, order: 6 }, { id: 'profile', visible: true, window: 'developer' as const, order: 7 }, { id: 'settings', visible: true, window: 'developer' as const, order: 8 }, { id: 'api-keys', visible: true, window: 'developer' as const, order: 9 }, - { id: 'task-manager', visible: true, window: 'developer' as const, order: 10 }, { id: 'service-status', visible: true, window: 'developer' as const, order: 11 }, { id: 'debug', visible: true, window: 'developer' as const, order: 12 }, { id: 'update', visible: true, window: 'developer' as const, order: 13 }, diff --git a/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx b/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx index 8411ff85..317df5db 100644 --- a/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +++ b/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx @@ -44,7 +44,7 @@ const PROVIDER_ICONS: Record = { // Update PROVIDER_DESCRIPTIONS to use the same type const PROVIDER_DESCRIPTIONS: Partial> = { Anthropic: 'Access Claude and other Anthropic models', - OpenAI: 'Use GPT-4, GPT-3.5, and other OpenAI models', + OpenAI: 'Use GPT-5.2, GPT-4.5, and other OpenAI models', }; const CloudProvidersTab = () => { diff --git a/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx b/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx index 2e280b38..f0b52bc2 100644 --- a/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +++ b/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx @@ -148,7 +148,15 @@ export default function LocalProvidersTab() { try { setIsLoadingModels(true); - const response = await fetch('http://127.0.0.1:11434/api/tags'); + const ollamaProvider = filteredProviders.find((p) => p.name === 'Ollama'); + const baseUrl = ollamaProvider?.settings.baseUrl || OLLAMA_API_URL; + + const response = await fetch(`${baseUrl}/api/tags`); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + const data = (await response.json()) as { models: OllamaModel[] }; setOllamaModels( @@ -159,6 +167,9 @@ export default function LocalProvidersTab() { ); } catch (error) { console.error('Error fetching Ollama models:', error); + + const errorMsg = error instanceof Error ? error.message : 'Unknown error occurred'; + toast(`Failed to fetch Ollama models: ${errorMsg}`); } finally { setIsLoadingModels(false); } @@ -166,7 +177,10 @@ export default function LocalProvidersTab() { const updateOllamaModel = async (modelName: string): Promise => { try { - const response = await fetch(`${OLLAMA_API_URL}/api/pull`, { + const ollamaProvider = filteredProviders.find((p) => p.name === 'Ollama'); + const baseUrl = ollamaProvider?.settings.baseUrl || OLLAMA_API_URL; + + const response = await fetch(`${baseUrl}/api/pull`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: modelName }), @@ -218,7 +232,7 @@ export default function LocalProvidersTab() { } } - const updatedResponse = await fetch('http://127.0.0.1:11434/api/tags'); + const updatedResponse = await fetch(`${baseUrl}/api/tags`); const updatedData = (await updatedResponse.json()) as { models: OllamaModel[] }; const updatedModel = updatedData.models.find((m) => m.name === modelName); @@ -275,7 +289,10 @@ export default function LocalProvidersTab() { const handleDeleteOllamaModel = async (modelName: string) => { try { - const response = await fetch(`${OLLAMA_API_URL}/api/delete`, { + const ollamaProvider = filteredProviders.find((p) => p.name === 'Ollama'); + const baseUrl = ollamaProvider?.settings.baseUrl || OLLAMA_API_URL; + + const response = await fetch(`${baseUrl}/api/delete`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', diff --git a/app/lib/modules/llm/manager.ts b/app/lib/modules/llm/manager.ts index aaaca229..d4488fed 100644 --- a/app/lib/modules/llm/manager.ts +++ b/app/lib/modules/llm/manager.ts @@ -129,10 +129,7 @@ export class LLMManager { // For local providers like Ollama and LMStudio, check if baseUrl is configured if (provider.name === 'Ollama' || provider.name === 'LMStudio') { const baseUrlKey = provider.name === 'Ollama' ? 'OLLAMA_API_BASE_URL' : 'LMSTUDIO_API_BASE_URL'; - const hasBaseUrl = - providerSettings?.[provider.name]?.baseUrl || - apiKeys?.[baseUrlKey] || - serverEnv?.[baseUrlKey]; + const hasBaseUrl = providerSettings?.[provider.name]?.baseUrl || apiKeys?.[baseUrlKey] || serverEnv?.[baseUrlKey]; if (!hasBaseUrl) { return false; diff --git a/app/lib/modules/llm/providers/ollama.ts b/app/lib/modules/llm/providers/ollama.ts index ac31d39b..ae6ccd5c 100644 --- a/app/lib/modules/llm/providers/ollama.ts +++ b/app/lib/modules/llm/providers/ollama.ts @@ -86,6 +86,7 @@ export default class OllamaProvider extends BaseProvider { if (isDocker) { try { const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { url.hostname = 'host.docker.internal'; baseUrl = url.toString().replace(/\/$/, ''); @@ -99,9 +100,7 @@ export default class OllamaProvider extends BaseProvider { const response = await fetch(`${baseUrl}/api/tags`); if (!response.ok) { - throw new Error( - `Failed to fetch Ollama models: HTTP ${response.status} ${response.statusText}`, - ); + throw new Error(`Failed to fetch Ollama models: HTTP ${response.status} ${response.statusText}`); } const data = (await response.json()) as OllamaApiResponse; @@ -147,6 +146,7 @@ export default class OllamaProvider extends BaseProvider { if (isDocker) { try { const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { url.hostname = 'host.docker.internal'; baseUrl = url.toString().replace(/\/$/, ''); From 3920af6edcf81c19044c9b4d998791b85f58265b Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 19:07:55 +1100 Subject: [PATCH 030/253] fix: add error handling and proper Docker host mapping to LMStudio provider --- app/lib/modules/llm/providers/lmstudio.ts | 37 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/app/lib/modules/llm/providers/lmstudio.ts b/app/lib/modules/llm/providers/lmstudio.ts index b592048a..d00af7bb 100644 --- a/app/lib/modules/llm/providers/lmstudio.ts +++ b/app/lib/modules/llm/providers/lmstudio.ts @@ -42,13 +42,33 @@ export default class LMStudioProvider extends BaseProvider { */ const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true'; - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + if (isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse LMStudio baseUrl for Docker mapping:', error); + } + } } const response = await fetch(`${baseUrl}/v1/models`); + + if (!response.ok) { + throw new Error( + `Failed to fetch LMStudio models: HTTP ${response.status} ${response.statusText}`, + ); + } + const data = (await response.json()) as { data: Array<{ id: string }> }; + if (!data || !Array.isArray(data.data)) { + throw new Error('Invalid response from LMStudio API: missing data array'); + } + return data.data.map((model) => ({ name: model.id, label: model.id, @@ -78,9 +98,16 @@ export default class LMStudioProvider extends BaseProvider { const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true'; - if (typeof window === 'undefined') { - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + if (typeof window === 'undefined' && isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse LMStudio baseUrl for Docker mapping:', error); + } } logger.debug('LMStudio Base Url used: ', baseUrl); From 987bdeece00fa79469fb1313d9a659eba49d462c Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 18 Dec 2025 03:56:26 +1100 Subject: [PATCH 031/253] bump version v1.1.24 > v1.1.25 --- app/routes/api.update.ts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/routes/api.update.ts b/app/routes/api.update.ts index d3bb1344..0c68c45a 100644 --- a/app/routes/api.update.ts +++ b/app/routes/api.update.ts @@ -1,6 +1,6 @@ import { json, type ActionFunction } from '@remix-run/cloudflare'; -const CURRENT_VERSION = '1.1.24'; +const CURRENT_VERSION = '1.1.25'; const GITHUB_REPO = 'codinit-dev/codinit-dev'; interface GitHubRelease { diff --git a/package.json b/package.json index a7d6a3d7..9dadbea2 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "MIT", "sideEffects": false, "type": "module", - "version": "1.1.24", + "version": "1.1.25", "author": { "name": "Gerome-Elassaad", "email": "contact@codinit.dev" From be6bbad76c2281355625399e0454143645e18b66 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 20:13:37 +1100 Subject: [PATCH 032/253] fix: lint errors in lmstudio provider --- .github/workflows/electron.yml | 6 ++++-- app/lib/modules/llm/providers/lmstudio.ts | 6 +++--- electron-builder.yml | 5 ++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml index 1cf68746..9d1c1261 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron.yml @@ -69,7 +69,7 @@ jobs: - name: Build Electron app env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.TOKEN }} NODE_OPTIONS: "--max_old_space_size=4096" run: | if [ "$RUNNER_OS" == "Windows" ]; then @@ -92,6 +92,7 @@ jobs: dist/*.AppImage dist/*.zip dist/*.blockmap + dist/*.yml retention-days: 1 if-no-files-found: warn @@ -122,5 +123,6 @@ jobs: artifacts/**/*.AppImage artifacts/**/*.zip artifacts/**/*.blockmap + artifacts/**/*.yml env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} diff --git a/app/lib/modules/llm/providers/lmstudio.ts b/app/lib/modules/llm/providers/lmstudio.ts index d00af7bb..82fa48e7 100644 --- a/app/lib/modules/llm/providers/lmstudio.ts +++ b/app/lib/modules/llm/providers/lmstudio.ts @@ -45,6 +45,7 @@ export default class LMStudioProvider extends BaseProvider { if (isDocker) { try { const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { url.hostname = 'host.docker.internal'; baseUrl = url.toString().replace(/\/$/, ''); @@ -58,9 +59,7 @@ export default class LMStudioProvider extends BaseProvider { const response = await fetch(`${baseUrl}/v1/models`); if (!response.ok) { - throw new Error( - `Failed to fetch LMStudio models: HTTP ${response.status} ${response.statusText}`, - ); + throw new Error(`Failed to fetch LMStudio models: HTTP ${response.status} ${response.statusText}`); } const data = (await response.json()) as { data: Array<{ id: string }> }; @@ -101,6 +100,7 @@ export default class LMStudioProvider extends BaseProvider { if (typeof window === 'undefined' && isDocker) { try { const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { url.hostname = 'host.docker.internal'; baseUrl = url.toString().replace(/\/$/, ''); diff --git a/electron-builder.yml b/electron-builder.yml index 17243090..13603b1f 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -80,7 +80,10 @@ nsis: npmRebuild: false -publish: null +publish: + provider: github + owner: codinit-dev + repo: codinit-dev electronDownload: mirror: https://npmmirror.com/mirrors/electron/ From beb7fe5838ae911a757ffac12cb9ee6f495404c4 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 20:16:40 +1100 Subject: [PATCH 033/253] bump fixes to v1.1.26 --- app/routes/api.update.ts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/routes/api.update.ts b/app/routes/api.update.ts index 0c68c45a..e0c1640d 100644 --- a/app/routes/api.update.ts +++ b/app/routes/api.update.ts @@ -1,6 +1,6 @@ import { json, type ActionFunction } from '@remix-run/cloudflare'; -const CURRENT_VERSION = '1.1.25'; +const CURRENT_VERSION = '1.1.26'; const GITHUB_REPO = 'codinit-dev/codinit-dev'; interface GitHubRelease { diff --git a/package.json b/package.json index 9dadbea2..30a59ef9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "MIT", "sideEffects": false, "type": "module", - "version": "1.1.25", + "version": "1.1.26", "author": { "name": "Gerome-Elassaad", "email": "contact@codinit.dev" From afcd51e95d24de85c932c6f8c06c423f15c3ce6a Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 20:27:49 +1100 Subject: [PATCH 034/253] fix: prevent duplicate release creation and type conflicts - remove GH_TOKEN from build step (electron-builder generates files only) - add releaseType: release to prevent draft/release conflicts - GitHub Actions release job handles uploading all artifacts --- .github/workflows/electron.yml | 1 - electron-builder.yml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml index 9d1c1261..21396585 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron.yml @@ -69,7 +69,6 @@ jobs: - name: Build Electron app env: - GH_TOKEN: ${{ secrets.TOKEN }} NODE_OPTIONS: "--max_old_space_size=4096" run: | if [ "$RUNNER_OS" == "Windows" ]; then diff --git a/electron-builder.yml b/electron-builder.yml index 13603b1f..bd094705 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -84,6 +84,7 @@ publish: provider: github owner: codinit-dev repo: codinit-dev + releaseType: release electronDownload: mirror: https://npmmirror.com/mirrors/electron/ From 7b918bbd6199ccb27a6da4ef68bb116d9b04e68e Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 21:35:36 +1100 Subject: [PATCH 035/253] fix: prevent duplicate yml file uploads in release workflow --- .github/workflows/electron.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml index 21396585..b9a129f2 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron.yml @@ -115,6 +115,7 @@ jobs: tag_name: ${{ github.ref_name }} draft: false name: Release ${{ github.ref_name }} + fail_on_unmatched_files: false files: | artifacts/**/*.exe artifacts/**/*.dmg @@ -122,6 +123,7 @@ jobs: artifacts/**/*.AppImage artifacts/**/*.zip artifacts/**/*.blockmap - artifacts/**/*.yml + artifacts/**/latest.yml + artifacts/**/latest-*.yml env: GITHUB_TOKEN: ${{ secrets.TOKEN }} From ca1193eaf05441f672bdd55044e42892ef556f82 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 21:42:31 +1100 Subject: [PATCH 036/253] refactor: remove redundant GitHub API call from update checker --- app/lib/api/updates.ts | 113 +++++++++++++---------------------------- 1 file changed, 35 insertions(+), 78 deletions(-) diff --git a/app/lib/api/updates.ts b/app/lib/api/updates.ts index 84b9fc5a..c7f1c201 100644 --- a/app/lib/api/updates.ts +++ b/app/lib/api/updates.ts @@ -11,13 +11,6 @@ export interface UpdateCheckResult { }; } -interface GitHubRelease { - tag_name: string; - html_url: string; - body: string; - published_at: string; -} - interface ApiUpdateResponse { updateAvailable: boolean; currentVersion: string; @@ -29,33 +22,8 @@ interface ApiUpdateResponse { message?: string; } -function compareVersions(v1: string, v2: string): number { - // Remove 'v' prefix if present - const version1 = v1.replace(/^v/, ''); - const version2 = v2.replace(/^v/, ''); - - const parts1 = version1.split('.').map(Number); - const parts2 = version2.split('.').map(Number); - - for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { - const part1 = parts1[i] || 0; - const part2 = parts2[i] || 0; - - if (part1 > part2) { - return 1; - } - - if (part1 < part2) { - return -1; - } - } - - return 0; -} - export const checkForUpdates = async (): Promise => { try { - // Get update info from the API route const apiResponse = await fetch('/api/update', { method: 'POST', headers: { @@ -64,70 +32,59 @@ export const checkForUpdates = async (): Promise => { }); if (!apiResponse.ok) { - throw new Error(`API request failed: ${apiResponse.status}`); - } + if (apiResponse.status === 403) { + const resetTime = apiResponse.headers.get('X-RateLimit-Reset'); - const apiData = (await apiResponse.json()) as ApiUpdateResponse; - - if (apiData.error) { - throw new Error(apiData.message || 'API returned an error'); - } - - const currentVersion = apiData.currentVersion; - - // Fetch the latest release from GitHub - const response = await fetch(`https://api.github.com/repos/codinit-dev/codinit-dev/releases/latest`, { - headers: { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': 'codinit-dev', - }, - }); - - if (!response.ok) { - // If no releases found or repo doesn't exist - if (response.status === 404) { return { available: false, - version: currentVersion, - currentVersion, - }; - } - - // Check for rate limiting - if (response.status === 403) { - const resetTime = response.headers.get('X-RateLimit-Reset'); - return { - available: false, - version: currentVersion, - currentVersion, + version: 'unknown', + currentVersion: 'unknown', error: { type: 'rate_limit', - message: `GitHub API rate limit exceeded. ${resetTime ? `Resets at ${new Date(parseInt(resetTime) * 1000).toLocaleTimeString()}` : ''}`, + message: `GitHub API rate limit exceeded. ${resetTime ? `Resets at ${new Date(parseInt(resetTime) * 1000).toLocaleTimeString()}` : 'Try again later.'}`, }, }; } - throw new Error(`GitHub API returned ${response.status}`); + throw new Error(`API request failed: ${apiResponse.status}`); } - const release = (await response.json()) as GitHubRelease; - const latestVersion = release.tag_name.replace(/^v/, ''); // Remove 'v' prefix if present + const apiData = (await apiResponse.json()) as ApiUpdateResponse; + + if (apiData.error) { + const errorMessage = apiData.message || 'API returned an error'; + let errorType: 'rate_limit' | 'network' | 'auth' | 'unknown' = 'unknown'; + + if (errorMessage.toLowerCase().includes('rate limit')) { + errorType = 'rate_limit'; + } else if (errorMessage.toLowerCase().includes('network') || errorMessage.toLowerCase().includes('fetch')) { + errorType = 'network'; + } else if (errorMessage.toLowerCase().includes('auth') || errorMessage.toLowerCase().includes('403')) { + errorType = 'auth'; + } - // Compare versions - const updateAvailable = compareVersions(latestVersion, currentVersion) > 0; + return { + available: false, + version: apiData.currentVersion, + currentVersion: apiData.currentVersion, + error: { + type: errorType, + message: errorMessage, + }, + }; + } return { - available: updateAvailable, - version: latestVersion, - currentVersion, - releaseNotes: release.body, - releaseUrl: release.html_url, - publishedAt: release.published_at, + available: apiData.updateAvailable, + version: apiData.latestVersion || apiData.currentVersion, + currentVersion: apiData.currentVersion, + releaseNotes: apiData.releaseNotes, + releaseUrl: apiData.releaseUrl, + publishedAt: apiData.publishedAt, }; } catch (error) { console.error('Error checking for updates:', error); - // Determine error type const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const isNetworkError = errorMessage.toLowerCase().includes('network') || From c31eba4bd86a86aba0af800e2413a1e9bb34cf2b Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 21:48:08 +1100 Subject: [PATCH 037/253] refactor: centralize version and repository configuration --- app/lib/version.ts | 2 ++ app/routes/api.update.ts | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 app/lib/version.ts diff --git a/app/lib/version.ts b/app/lib/version.ts new file mode 100644 index 00000000..9b208db1 --- /dev/null +++ b/app/lib/version.ts @@ -0,0 +1,2 @@ +export const APP_VERSION = '1.1.26'; +export const GITHUB_REPOSITORY = 'codinit-dev/codinit-dev'; diff --git a/app/routes/api.update.ts b/app/routes/api.update.ts index e0c1640d..b16c9b1a 100644 --- a/app/routes/api.update.ts +++ b/app/routes/api.update.ts @@ -1,7 +1,8 @@ import { json, type ActionFunction } from '@remix-run/cloudflare'; +import { APP_VERSION, GITHUB_REPOSITORY } from '~/lib/version'; -const CURRENT_VERSION = '1.1.26'; -const GITHUB_REPO = 'codinit-dev/codinit-dev'; +const GITHUB_REPO = GITHUB_REPOSITORY; +const CURRENT_VERSION = APP_VERSION; interface GitHubRelease { tag_name: string; From df0d4b930a372122781f11104c33c1b2bbebf473 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 21:54:02 +1100 Subject: [PATCH 038/253] refactor: use centralized repository config in UpdateTab --- app/components/@settings/tabs/update/UpdateTab.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/components/@settings/tabs/update/UpdateTab.tsx b/app/components/@settings/tabs/update/UpdateTab.tsx index 98767666..3175941d 100644 --- a/app/components/@settings/tabs/update/UpdateTab.tsx +++ b/app/components/@settings/tabs/update/UpdateTab.tsx @@ -5,6 +5,7 @@ import { toast } from 'react-toastify'; import { Dialog, DialogRoot, DialogTitle, DialogDescription, DialogButton } from '~/components/ui/Dialog'; import { classNames } from '~/utils/classNames'; import { Markdown } from '~/components/chat/Markdown'; +import { GITHUB_REPOSITORY } from '~/lib/version'; interface UpdateSettings { autoUpdate: boolean; @@ -232,7 +233,7 @@ const UpdateTab = () => {

)}

- Updates are checked from: codinit-dev/codinit-dev (GitHub releases) + Updates are checked from: {GITHUB_REPOSITORY} (GitHub releases)

{hasUpdate && releaseUrl && ( @@ -295,8 +296,8 @@ const UpdateTab = () => {

- A new version ({latestVersion}) is available from{' '} - codinit-dev/codinit-dev on GitHub. + A new version ({latestVersion}) is available from {GITHUB_REPOSITORY}{' '} + on GitHub.

{releaseUrl && ( From 488ad0569490c4fbcd22fb59415f884c34b8da5b Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Sat, 20 Dec 2025 22:01:05 +1100 Subject: [PATCH 039/253] fix: remove unnecessary z-index and transparent background from ChatHeader --- app/components/header/ChatHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/header/ChatHeader.tsx b/app/components/header/ChatHeader.tsx index 5b9c968a..61851e46 100644 --- a/app/components/header/ChatHeader.tsx +++ b/app/components/header/ChatHeader.tsx @@ -11,7 +11,7 @@ export function ChatHeader() { } return ( -
+
+ )} +
+ +
+ +
+ setApiKey(e.target.value)} + placeholder="Enter your Pro API key..." + className={classNames( + 'flex-1 px-4 py-2 rounded-lg text-sm', + 'bg-white dark:bg-[#0F0F0F]', + 'border border-gray-200 dark:border-[#2A2A2A]', + 'text-codinit-elements-textPrimary', + 'focus:outline-none focus:ring-2 focus:ring-blue-500/30', + )} + /> + +
+
+
+ + + ); +} From 2cfd57a15a0bee1d70a91a91e86aa43e1034c866 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 25 Dec 2025 20:58:24 +1100 Subject: [PATCH 052/253] feat: integrate Pro features in chat UI with Web Search and Lazy Edits toggles Migrate hardcoded colors to CSS custom properties throughout chat components. Add Web Search and Lazy Edits feature toggles with provider validation. Improve chat header styling and visual hierarchy with border separator. --- app/components/chat/BaseChat.module.scss | 30 +++++++------- app/components/chat/BaseChat.tsx | 8 +++- app/components/chat/Chatbox.tsx | 50 +++++++++++++++++++++++- app/components/header/ChatHeader.tsx | 12 +++--- 4 files changed, 76 insertions(+), 24 deletions(-) diff --git a/app/components/chat/BaseChat.module.scss b/app/components/chat/BaseChat.module.scss index 2d6dd4db..694afef9 100644 --- a/app/components/chat/BaseChat.module.scss +++ b/app/components/chat/BaseChat.module.scss @@ -125,11 +125,11 @@ } ._PromptContainer_1k1wl_1 { - background: radial-gradient(circle at 4% -40%, #cccccc 0%, transparent 30%); + background: radial-gradient(circle at 4% -40%, var(--codinit-elements-icon-tertiary) 0%, transparent 30%); } ._GradientBorder_1k1wl_5 { - border: 1px solid rgba(204, 204, 204, 0.2); + border: 1px solid var(--codinit-elements-borderColor); } @supports (-webkit-mask-composite: xor) { @@ -142,7 +142,7 @@ ._GradientBorder_1k1wl_5:before { position: absolute; padding: 1px; - background: linear-gradient(180deg, #cccccc, #fff3); + background: linear-gradient(180deg, var(--codinit-elements-borderColor), #fff3); border-radius: inherit; content: ''; inset: 0; @@ -157,21 +157,21 @@ } [data-theme='light'] ._GradientBorder_1k1wl_5:before { - background: linear-gradient(180deg, #cccccc, #0003); + background: linear-gradient(180deg, var(--codinit-elements-borderColor), #0003); } } ._LiquidGlassButton_16cym_1 { min-height: 40px; overflow: visible; - background: linear-gradient(135deg, rgba(204, 204, 204, 0.1), rgba(204, 204, 204, 0.05)); + background: linear-gradient(135deg, var(--codinit-elements-item-backgroundAccent), rgba(204, 204, 204, 0.05)); backdrop-filter: blur(20px) saturate(140%); -webkit-backdrop-filter: blur(20px) saturate(180%); box-shadow: inset 0 1px rgba(255, 255, 255, 0.1), inset 0 -1px rgba(0, 0, 0, 0.1), 0 8px 32px -8px rgba(0, 0, 0, 0.1), - 0 0 0 1px rgba(204, 204, 204, 0.1); + 0 0 0 1px var(--codinit-elements-borderColor); transition: all 0.4s cubic-bezier(0.32, 0.72, 0, 1); will-change: transform, box-shadow; backface-visibility: hidden; @@ -209,12 +209,12 @@ } ._LiquidGlassButton_16cym_1:hover { - background: linear-gradient(135deg, rgba(204, 204, 204, 0.15), rgba(204, 204, 204, 0.08)); + background: linear-gradient(135deg, var(--codinit-elements-item-backgroundAccent), rgba(204, 204, 204, 0.08)); box-shadow: inset 0 1px rgba(255, 255, 255, 0.1), inset 0 -1px rgba(0, 0, 0, 0.15), 0 12px 40px -8px rgba(0, 0, 0, 0.15), - 0 0 0 1px rgba(204, 204, 204, 0.15); + 0 0 0 1px var(--codinit-elements-borderColor); } ._LiquidGlassButton_16cym_1:hover:before { @@ -227,11 +227,11 @@ inset 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px rgba(0, 0, 0, 0.1), 0 4px 20px -8px rgba(0, 0, 0, 0.1), - 0 0 0 1px rgba(204, 204, 204, 0.05); + 0 0 0 1px var(--codinit-elements-borderColor); } [data-theme='light'] ._LiquidGlassButton_16cym_1 { - background: linear-gradient(135deg, rgba(204, 204, 204, 0.7), rgba(204, 204, 204, 0.5)); + background: linear-gradient(135deg, var(--codinit-elements-bg-depth-3), var(--codinit-elements-bg-depth-2)); box-shadow: inset 0 1px rgba(255, 255, 255, 0.9), inset 0 -1px rgba(0, 0, 0, 0.05), @@ -249,7 +249,7 @@ } [data-theme='light'] ._LiquidGlassButton_16cym_1:hover { - background: linear-gradient(135deg, rgba(204, 204, 204, 0.95), rgba(204, 204, 204, 0.85)); + background: linear-gradient(135deg, var(--codinit-elements-bg-depth-4), var(--codinit-elements-bg-depth-3)); box-shadow: inset 0 1px rgba(255, 255, 255, 1), inset 0 -1px rgba(0, 0, 0, 0.08), @@ -271,9 +271,9 @@ background: linear-gradient( 90deg, transparent 0%, - rgba(204, 204, 204, 0.8) 20%, - rgba(255, 255, 255, 0.8) 50%, - rgba(204, 204, 204, 0.8) 80%, + var(--codinit-elements-borderColor) 20%, + var(--codinit-elements-textPrimary) 50%, + var(--codinit-elements-borderColor) 80%, transparent 100% ); opacity: 0.6; @@ -287,7 +287,7 @@ content: ''; position: absolute; inset: 0; - background: linear-gradient(90deg, transparent 0%, rgba(204, 204, 204, 0.8) 50%, transparent 100%); + background: linear-gradient(90deg, transparent 0%, var(--codinit-elements-borderColor) 50%, transparent 100%); animation: _shimmer_16cym_1 3s ease-in-out infinite; } diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 07c5bdaa..5273377c 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -39,6 +39,7 @@ import type { ProgressAnnotation } from '~/types/context'; import { SupabaseChatAlert } from '~/components/chat/SupabaseAlert'; import { expoUrlAtom } from '~/lib/stores/qrCodeStore'; import { useStore } from '@nanostores/react'; +import { proStore } from '~/lib/stores/pro'; import { StickToBottom, useStickToBottomContext } from '~/lib/hooks'; import { ChatBox } from './Chatbox'; import type { DesignScheme } from '~/types/design-scheme'; @@ -448,7 +449,7 @@ export const BaseChat = React.forwardRef( {/* Unified Header Container - spans full width */} {chatStarted && ( -
+
{/* Chat Header Section - constrained to chat width */}
@@ -775,6 +776,11 @@ export const BaseChat = React.forwardRef( setDesignScheme={setDesignScheme} selectedElement={selectedElement} setSelectedElement={setSelectedElement} + codinit_options={{ + enable_web_search: proStore.get().features.webSearch, + enable_lazy_edits: proStore.get().features.lazyEdits, + files: uploadedFiles.length > 0, + }} />
diff --git a/app/components/chat/Chatbox.tsx b/app/components/chat/Chatbox.tsx index 77bbb718..e84ff87f 100644 --- a/app/components/chat/Chatbox.tsx +++ b/app/components/chat/Chatbox.tsx @@ -27,6 +27,7 @@ import { ToolMentionAutocomplete } from './ToolMentionAutocomplete'; import { insertToolMention, insertFileReference } from '~/utils/toolMentionParser'; import { useStore } from '@nanostores/react'; import { workbenchStore } from '~/lib/stores/workbench'; +import { proStore, toggleFeature } from '~/lib/stores/pro'; interface ChatBoxProps { isModelSettingsCollapsed: boolean; @@ -69,6 +70,11 @@ interface ChatBoxProps { setDesignScheme?: (scheme: DesignScheme) => void; selectedElement?: ElementInfo | null; setSelectedElement?: ((element: ElementInfo | null) => void) | undefined; + codinit_options?: { + enable_web_search?: boolean; + enable_lazy_edits?: boolean; + files?: boolean; + }; } export const ChatBox: React.FC = (props) => { @@ -285,11 +291,11 @@ export const ChatBox: React.FC = (props) => { )} onDragEnter={(e) => { e.preventDefault(); - e.currentTarget.style.border = '2px solid #1488fc'; + e.currentTarget.style.border = '2px solid var(--codinit-elements-borderColorActive)'; }} onDragOver={(e) => { e.preventDefault(); - e.currentTarget.style.border = '2px solid #1488fc'; + e.currentTarget.style.border = '2px solid var(--codinit-elements-borderColorActive)'; }} onDragLeave={(e) => { e.preventDefault(); @@ -413,6 +419,46 @@ export const ChatBox: React.FC = (props) => {
{props.chatMode === 'discuss' ? Discuss : } + { + if (props.provider?.name !== 'CodinIT') { + toast.info('Web Search is a Pro feature available with CodinIT Pro provider.'); + return; + } + + toggleFeature('webSearch'); + }} + > +
+ + { + if (props.provider?.name !== 'CodinIT') { + toast.info('Lazy Edits is a Pro feature available with CodinIT Pro provider.'); + return; + } + + toggleFeature('lazyEdits'); + }} + > +
+ {() => props.isModelSettingsCollapsed ? ( diff --git a/app/components/header/ChatHeader.tsx b/app/components/header/ChatHeader.tsx index 61851e46..c3e5116d 100644 --- a/app/components/header/ChatHeader.tsx +++ b/app/components/header/ChatHeader.tsx @@ -11,23 +11,23 @@ export function ChatHeader() { } return ( -
+
/ -
+
{() => }
+ ); }, ), diff --git a/app/components/workbench/CodeModeHeader.tsx b/app/components/workbench/CodeModeHeader.tsx index f538efe5..8d00cf6d 100644 --- a/app/components/workbench/CodeModeHeader.tsx +++ b/app/components/workbench/CodeModeHeader.tsx @@ -18,34 +18,31 @@ export const CodeModeHeader = memo( }; return ( -
+
{/* Toggle Buttons Section */} -
-
- setSelectedView('preview')} - /> - setSelectedView('code')} /> - setSelectedView('diff')} - /> -
+
+ setSelectedView('preview')} + /> + setSelectedView('code')} + /> + setSelectedView('diff')} + />
- +
- +
+ Publish +
diff --git a/app/components/workbench/DiffViewHeader.tsx b/app/components/workbench/DiffViewHeader.tsx index c156fbe3..39d1a36b 100644 --- a/app/components/workbench/DiffViewHeader.tsx +++ b/app/components/workbench/DiffViewHeader.tsx @@ -49,24 +49,22 @@ export const DiffViewHeader = memo( const showStats = additions > 0 || deletions > 0; return ( -
+
{/* Toggle Buttons Section */} -
-
- setSelectedView('preview')} - /> - setSelectedView('code')} /> - setSelectedView('diff')} - /> -
+
+ setSelectedView('preview')} + /> + setSelectedView('code')} /> + setSelectedView('diff')} + />
diff --git a/app/components/workbench/Preview.tsx b/app/components/workbench/Preview.tsx index cbdfad21..a9626485 100644 --- a/app/components/workbench/Preview.tsx +++ b/app/components/workbench/Preview.tsx @@ -493,11 +493,12 @@ export const Preview = memo(() => {
) : (
-
+
-
No Preview Available
-
- Start a development server to see your app +
+
Ready to Build?
+
+ Start a development server or prompt the AI to see your app in action.
diff --git a/app/components/workbench/PreviewHeader.tsx b/app/components/workbench/PreviewHeader.tsx index ad7960b9..b7f1e02d 100644 --- a/app/components/workbench/PreviewHeader.tsx +++ b/app/components/workbench/PreviewHeader.tsx @@ -112,12 +112,12 @@ export const PreviewHeader = memo( }; return ( -
+
{/* Toggle Buttons Section */} -
+
setSelectedView('preview')} /> @@ -130,13 +130,7 @@ export const PreviewHeader = memo( /> - + {/* Address Bar */} -
+
- - + className="text-codinit-elements-textSecondary" + />
{/* Right Action Buttons */} @@ -338,25 +329,19 @@ export const PreviewHeader = memo(
- {/* Deploy Dialog Button */} - + className="w-8 h-8 rounded-md bg-codinit-elements-item-backgroundActive text-codinit-elements-textPrimary border border-codinit-elements-borderColor" + /> - + className="w-8 h-8 rounded-md bg-codinit-elements-textPrimary text-codinit-elements-background-depth-1" + />
From c0f9c6f5f87997527226e38b05c574d723da1f68 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 25 Dec 2025 20:59:09 +1100 Subject: [PATCH 054/253] feat: add CodinIT provider integration with Pro feature options support Extend chat validation schema to accept codinit_options for Web Search, Lazy Edits, and file handling. Register CodinIT provider in LLM registry and pass Pro options through the chat API pipeline. --- app/lib/api/chat-validation.ts | 12 ++++++++++++ app/lib/modules/llm/base-provider.ts | 1 + app/lib/modules/llm/registry.ts | 2 ++ app/routes/api.chat.ts | 2 ++ 4 files changed, 17 insertions(+) diff --git a/app/lib/api/chat-validation.ts b/app/lib/api/chat-validation.ts index 836bb8b3..fbe31028 100644 --- a/app/lib/api/chat-validation.ts +++ b/app/lib/api/chat-validation.ts @@ -31,6 +31,13 @@ const chatRequestSchema = z.object({ designScheme: z.any().optional(), supabase: supabaseConfigSchema.optional(), enableMCPTools: z.boolean().default(false), + codinit_options: z + .object({ + enable_web_search: z.boolean().optional(), + enable_lazy_edits: z.boolean().optional(), + files: z.boolean().optional(), + }) + .optional(), }); export interface ValidatedChatRequest { @@ -48,6 +55,11 @@ export interface ValidatedChatRequest { }; }; enableMCPTools: boolean; + codinit_options?: { + enable_web_search?: boolean; + enable_lazy_edits?: boolean; + files?: boolean; + }; } export function validateChatRequest(data: unknown): ValidatedChatRequest { diff --git a/app/lib/modules/llm/base-provider.ts b/app/lib/modules/llm/base-provider.ts index 981c3682..80f838c0 100644 --- a/app/lib/modules/llm/base-provider.ts +++ b/app/lib/modules/llm/base-provider.ts @@ -154,6 +154,7 @@ export abstract class BaseProvider implements ProviderInfo { serverEnv?: Env; apiKeys?: Record; providerSettings?: Record; + codinit_options?: any; }): LanguageModelV1; } diff --git a/app/lib/modules/llm/registry.ts b/app/lib/modules/llm/registry.ts index df31b1cf..07710b2e 100644 --- a/app/lib/modules/llm/registry.ts +++ b/app/lib/modules/llm/registry.ts @@ -1,4 +1,5 @@ import AnthropicProvider from './providers/anthropic'; +import CodinITProvider from './providers/codinit'; import DeepseekProvider from './providers/deepseek'; import GoogleProvider from './providers/google'; import GroqProvider from './providers/groq'; @@ -19,6 +20,7 @@ import GithubProvider from './providers/github'; export { AnthropicProvider, + CodinITProvider, DeepseekProvider, GoogleProvider, GroqProvider, diff --git a/app/routes/api.chat.ts b/app/routes/api.chat.ts index 0c9e8e0d..06f08cc4 100644 --- a/app/routes/api.chat.ts +++ b/app/routes/api.chat.ts @@ -276,6 +276,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) { summary, messageSliceId, designScheme, + codinit_options: validatedRequest.codinit_options, }); stream.switchSource(result.toDataStream()); @@ -361,6 +362,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) { summary, messageSliceId, designScheme, + codinit_options: validatedRequest.codinit_options, }); (async () => { From e96b8b5e87d51eda0c053bd94719e429425072a6 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 25 Dec 2025 20:59:39 +1100 Subject: [PATCH 055/253] refactor: migrate hardcoded colors to CSS variables and optimize diff view styling Replace rgba borders with CSS custom properties in code.scss. Remove Tailwind utilities from diff-view.css in favor of vanilla CSS for better performance. Revert icon color overrides and reduce header height to 48px in variables.scss. --- app/styles/components/code.scss | 4 +- app/styles/diff-view.css | 44 ++++-- app/styles/variables.scss | 236 ++++++++++++++++++++------------ 3 files changed, 187 insertions(+), 97 deletions(-) diff --git a/app/styles/components/code.scss b/app/styles/components/code.scss index a1a16e39..536596b0 100644 --- a/app/styles/components/code.scss +++ b/app/styles/components/code.scss @@ -1,11 +1,11 @@ .actions .shiki { background-color: var(--codinit-elements-actions-code-background) !important; - border: 1px solid rgba(153, 153, 153, 0.1) !important; /* Neutral border */ + border: 1px solid var(--codinit-elements-borderColor) !important; } .shiki { &:not(:has(.actions), .actions *, .mcp-tool-invocation-code *) { background-color: var(--codinit-elements-messages-code-background) !important; - border: 1px solid rgba(153, 153, 153, 0.08) !important; /* Neutral border */ + border: 1px solid var(--codinit-elements-borderColor) !important; } } diff --git a/app/styles/diff-view.css b/app/styles/diff-view.css index accc9129..a135a2af 100644 --- a/app/styles/diff-view.css +++ b/app/styles/diff-view.css @@ -33,40 +33,62 @@ /* Estilos para as linhas de diff */ .diff-block-added { - @apply bg-green-500/20 border-l-4 border-green-500; + background-color: rgba(34, 197, 94, 0.2); + border-left: 4px solid rgb(34, 197, 94); } .diff-block-removed { - @apply bg-red-500/20 border-l-4 border-red-500; + background-color: rgba(239, 68, 68, 0.2); + border-left: 4px solid rgb(239, 68, 68); } /* Melhorar contraste para mudanças */ -.diff-panel-content .group:hover .diff-block-added { - @apply bg-green-500/30; +.diff-panel-content .diff-line:hover .diff-block-added { + background-color: rgba(34, 197, 94, 0.3); } -.diff-panel-content .group:hover .diff-block-removed { - @apply bg-red-500/30; +.diff-panel-content .diff-line:hover .diff-block-removed { + background-color: rgba(239, 68, 68, 0.3); } /* Estilos unificados para ambas as visualizações */ .diff-line { - @apply flex group min-w-fit transition-colors duration-150; + display: flex; + min-width: fit-content; + transition: background-color 150ms ease; } .diff-line-number { - @apply w-12 shrink-0 pl-2 py-0.5 text-left font-mono text-codinit-elements-textTertiary border-r border-codinit-elements-borderColor bg-codinit-elements-background-depth-1; + width: 3rem; + flex-shrink: 0; + padding: 0.125rem 0 0.125rem 0.5rem; + text-align: left; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + color: var(--codinit-elements-textTertiary); + border-right: 1px solid var(--codinit-elements-borderColor); + background-color: var(--codinit-elements-background-depth-1); } .diff-line-content { - @apply px-4 py-0.5 font-mono whitespace-pre flex-1 group-hover:bg-codinit-elements-background-depth-2 text-codinit-elements-textPrimary; + padding: 0.125rem 1rem; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + white-space: pre; + flex: 1; + color: var(--codinit-elements-textPrimary); + transition: background-color 150ms ease; +} + +.diff-line:hover .diff-line-content { + background-color: var(--codinit-elements-background-depth-2); } /* Cores específicas para adições/remoções */ .diff-added { - @apply bg-green-500/20 border-l-4 border-green-500; + background-color: rgba(34, 197, 94, 0.2); + border-left: 4px solid rgb(34, 197, 94); } .diff-removed { - @apply bg-red-500/20 border-l-4 border-red-500; + background-color: rgba(239, 68, 68, 0.2); + border-left: 4px solid rgb(239, 68, 68); } diff --git a/app/styles/variables.scss b/app/styles/variables.scss index 2969df1f..eaa5c0e4 100644 --- a/app/styles/variables.scss +++ b/app/styles/variables.scss @@ -62,9 +62,9 @@ --codinit-elements-icon-success: theme('colors.green.500'); --codinit-elements-icon-error: theme('colors.red.500'); - --codinit-elements-icon-primary: #cccccc; /* Changed from theme('colors.gray.950') to low-contrast gray */ - --codinit-elements-icon-secondary: #a0a0a0; /* Changed from theme('colors.gray.600') to dimmer gray */ - --codinit-elements-icon-tertiary: #888888; /* Changed from theme('colors.gray.500') to softer gray */ + --codinit-elements-icon-primary: theme('colors.gray.950'); + --codinit-elements-icon-secondary: theme('colors.gray.600'); + --codinit-elements-icon-tertiary: theme('colors.gray.500'); --codinit-elements-dividerColor: theme('colors.gray.100'); @@ -136,13 +136,19 @@ :root, :root[data-theme='dark'] { /* Pure neutral gray with blue accents - improved contrast */ - --codinit-elements-borderColor: #3a3a3a; /* Neutral gray border - slightly lighter for visibility */ - --codinit-elements-borderColorActive: #3b82f6; /* Blue active border */ - - --codinit-elements-bg-depth-1: #121212; /* Main background - darker base */ - --codinit-elements-bg-depth-2: #1a1a1a; /* Content surfaces */ - --codinit-elements-bg-depth-3: #242424; /* Distinction elements */ - --codinit-elements-bg-depth-4: #2e2e2e; /* Buttons/inputs - more contrast */ + --codinit-elements-borderColor: #3a3a3a; + /* Neutral gray border - slightly lighter for visibility */ + --codinit-elements-borderColorActive: #3b82f6; + /* Blue active border */ + + --codinit-elements-bg-depth-1: #121212; + /* Main background - darker base */ + --codinit-elements-bg-depth-2: #1a1a1a; + /* Content surfaces */ + --codinit-elements-bg-depth-3: #242424; + /* Distinction elements */ + --codinit-elements-bg-depth-4: #2e2e2e; + /* Buttons/inputs - more contrast */ /* Aliases for background-depth variables */ --codinit-elements-background-depth-1: var(--codinit-elements-bg-depth-1); @@ -151,21 +157,31 @@ --codinit-elements-background-depth-4: var(--codinit-elements-bg-depth-4); /* Optimized contrast for text */ - --codinit-elements-textPrimary: #ffffff; /* Pure white for better readability */ - --codinit-elements-textSecondary: #cccccc; /* Neutral gray */ - --codinit-elements-textTertiary: #999999; /* Muted neutral */ - - --codinit-elements-code-background: #0a0a0a; /* Dark code background */ - --codinit-elements-code-text: #f0f6fc; /* Bright code text */ + --codinit-elements-textPrimary: #ffffff; + /* Pure white for better readability */ + --codinit-elements-textSecondary: #cccccc; + /* Neutral gray */ + --codinit-elements-textTertiary: #999999; + /* Muted neutral */ + + --codinit-elements-code-background: #0a0a0a; + /* Dark code background */ + --codinit-elements-code-text: #f0f6fc; + /* Bright code text */ /* Primary Button - Blue accent */ - --codinit-elements-button-primary-background: #3b82f6; /* Blue background */ - --codinit-elements-button-primary-backgroundHover: #2563eb; /* Darker blue on hover */ - --codinit-elements-button-primary-text: #ffffff; /* White text for max contrast */ + --codinit-elements-button-primary-background: #3b82f6; + /* Blue background */ + --codinit-elements-button-primary-backgroundHover: #2563eb; + /* Darker blue on hover */ + --codinit-elements-button-primary-text: #ffffff; + /* White text for max contrast */ /* Secondary Button - Neutral gray with better contrast */ - --codinit-elements-button-secondary-background: #2e2e2e; /* Matches depth-4 for visibility on containers */ - --codinit-elements-button-secondary-backgroundHover: #3a3a3a; /* Lighter hover for clear feedback */ + --codinit-elements-button-secondary-background: #2e2e2e; + /* Matches depth-4 for visibility on containers */ + --codinit-elements-button-secondary-backgroundHover: #3a3a3a; + /* Lighter hover for clear feedback */ --codinit-elements-button-secondary-text: #e0e0e0; /* Danger Button - Remains prominent */ @@ -175,21 +191,30 @@ /* Item backgrounds and content - neutral gray with improved contrast */ --codinit-elements-item-contentDefault: #a0a0a0; - --codinit-elements-item-contentActive: #ffffff; /* White for active */ - --codinit-elements-item-contentAccent: #d0d0d0; /* Neutral gray accent */ - --codinit-elements-item-contentDanger: #f85149; /* Remains prominent */ - --codinit-elements-item-backgroundDefault: transparent; /* Transparent for layering */ - --codinit-elements-item-backgroundActive: #2e2e2e; /* Matches depth-4 for clear hover/active */ - --codinit-elements-item-backgroundAccent: rgba(204, 204, 204, 0.08); /* Subtle neutral accent background */ + --codinit-elements-item-contentActive: #ffffff; + /* White for active */ + --codinit-elements-item-contentAccent: #d0d0d0; + /* Neutral gray accent */ + --codinit-elements-item-contentDanger: #f85149; + /* Remains prominent */ + --codinit-elements-item-backgroundDefault: transparent; + /* Transparent for layering */ + --codinit-elements-item-backgroundActive: #2e2e2e; + /* Matches depth-4 for clear hover/active */ + --codinit-elements-item-backgroundAccent: rgba(204, 204, 204, 0.08); + /* Subtle neutral accent background */ --codinit-elements-item-backgroundDanger: rgba(248, 81, 73, 0.1); /* Loader - Higher contrast */ - --codinit-elements-loader-background: rgba(255, 255, 255, 0.1); /* Slightly more opaque */ - --codinit-elements-loader-progress: #79c0ff; /* Brighter progress color */ + --codinit-elements-loader-background: rgba(255, 255, 255, 0.1); + /* Slightly more opaque */ + --codinit-elements-loader-progress: #79c0ff; + /* Brighter progress color */ /* Artifacts - Neutral gray aligned with new depth system */ --codinit-elements-artifacts-background: var(--codinit-elements-bg-depth-1); - --codinit-elements-artifacts-backgroundHover: rgba(255, 255, 255, 0.06); /* Subtle hover */ + --codinit-elements-artifacts-backgroundHover: rgba(255, 255, 255, 0.06); + /* Subtle hover */ --codinit-elements-artifacts-borderColor: var(--codinit-elements-borderColor); --codinit-elements-artifacts-inlineCode-background: var(--codinit-elements-bg-depth-3); --codinit-elements-artifacts-inlineCode-text: #ffffff; @@ -200,87 +225,130 @@ /* Messages - Neutral gray aligned with new depth system */ --codinit-elements-messages-background: var(--codinit-elements-bg-depth-2); - --codinit-elements-messages-linkColor: #d0d0d0; /* Slightly brighter link */ - --codinit-elements-messages-code-background: #0a0a0a; /* Darker code background */ + --codinit-elements-messages-linkColor: #d0d0d0; + /* Slightly brighter link */ + --codinit-elements-messages-code-background: #0a0a0a; + /* Darker code background */ --codinit-elements-messages-inlineCode-background: var(--codinit-elements-bg-depth-3); --codinit-elements-messages-inlineCode-text: var(--codinit-elements-textPrimary); /* Icons - Adjusted for dark theme contrast */ - --codinit-elements-icon-success: #56d364; /* Remains green */ - --codinit-elements-icon-error: #f85149; /* Remains red */ - --codinit-elements-icon-primary: #f0f6fc; /* Bright white */ - --codinit-elements-icon-secondary: #c9d1d9; /* Bright */ - --codinit-elements-icon-tertiary: #8b949e; /* Subtle */ - - --codinit-elements-dividerColor: #333333; /* Aligned with border color */ - - --codinit-elements-prompt-background: rgba(18, 18, 18, 0.85); /* Aligned with depth-1 */ + --codinit-elements-icon-success: #56d364; + /* Remains green */ + --codinit-elements-icon-error: #f85149; + /* Remains red */ + --codinit-elements-icon-primary: #f0f6fc; + /* Bright white */ + --codinit-elements-icon-secondary: #c9d1d9; + /* Bright */ + --codinit-elements-icon-tertiary: #8b949e; + /* Subtle */ + + --codinit-elements-dividerColor: #333333; + /* Aligned with border color */ + + --codinit-elements-prompt-background: rgba(18, 18, 18, 0.85); + /* Aligned with depth-1 */ /* Sidebar - Blue accent */ - --codinit-elements-sidebar-buttonBackgroundDefault: rgba(59, 130, 246, 0.1); /* Blue accent background */ - --codinit-elements-sidebar-buttonBackgroundHover: rgba(59, 130, 246, 0.15); /* Blue hover */ - --codinit-elements-sidebar-buttonText: #3b82f6; /* Blue accent */ + --codinit-elements-sidebar-buttonBackgroundDefault: rgba(59, 130, 246, 0.1); + /* Blue accent background */ + --codinit-elements-sidebar-buttonBackgroundHover: rgba(59, 130, 246, 0.15); + /* Blue hover */ + --codinit-elements-sidebar-buttonText: #3b82f6; + /* Blue accent */ /* Preview Address Bar - Compatible with undertone */ - --codinit-elements-preview-addressBar-background: var( - --codinit-elements-bg-depth-2 - ); /* Use depth-2 for consistency */ - --codinit-elements-preview-addressBar-backgroundHover: rgba(255, 255, 255, 0.05); /* Subtle hover */ - --codinit-elements-preview-addressBar-backgroundActive: var( - --codinit-elements-bg-depth-1 - ); /* Use depth-1 for active */ + --codinit-elements-preview-addressBar-background: var(--codinit-elements-bg-depth-2); + /* Use depth-2 for consistency */ + --codinit-elements-preview-addressBar-backgroundHover: rgba(255, 255, 255, 0.05); + /* Subtle hover */ + --codinit-elements-preview-addressBar-backgroundActive: var(--codinit-elements-bg-depth-1); + /* Use depth-1 for active */ --codinit-elements-preview-addressBar-text: var(--codinit-elements-textSecondary); --codinit-elements-preview-addressBar-textActive: var(--codinit-elements-textPrimary); /* Terminals - Neutral gray */ - --codinit-elements-terminals-background: var(--codinit-elements-bg-depth-1); /* Use depth-1 for terminal background */ - --codinit-elements-terminals-buttonBackground: var(--codinit-elements-bg-depth-3); /* Use depth-3 for buttons */ + --codinit-elements-terminals-background: var(--codinit-elements-bg-depth-1); + /* Use depth-1 for terminal background */ + --codinit-elements-terminals-buttonBackground: var(--codinit-elements-bg-depth-3); + /* Use depth-3 for buttons */ - --codinit-elements-cta-background: var(--codinit-elements-bg-depth-3); /* Use depth-3 for CTA */ + --codinit-elements-cta-background: var(--codinit-elements-bg-depth-3); + /* Use depth-3 for CTA */ --codinit-elements-cta-text: #ffffff; /* Terminal Colors - Pure neutral gray */ - --codinit-terminal-background: var(--codinit-elements-terminals-background); /* Inherit from terminal background */ - --codinit-terminal-foreground: #ffffff; /* Pure white foreground */ - --codinit-terminal-selection-background: rgba(204, 204, 204, 0.2); /* Neutral gray selection */ - --codinit-terminal-black: #000000; /* Pure black */ - --codinit-terminal-red: #ff7b72; /* Brighter red */ - --codinit-terminal-green: #76d974; /* Brighter green */ - --codinit-terminal-yellow: #e2e27a; /* Brighter yellow */ - --codinit-terminal-blue: #63a3ff; /* Brighter blue */ - --codinit-terminal-magenta: #ff79c6; /* Brighter magenta */ - --codinit-terminal-cyan: #98d4ca; /* Brighter cyan */ - --codinit-terminal-white: #ffffff; /* Pure white */ - --codinit-terminal-brightBlack: #555555; /* Neutral gray */ - --codinit-terminal-brightRed: #ff7b72; /* Brighter red */ - --codinit-terminal-brightGreen: #76d974; /* Brighter green */ - --codinit-terminal-brightYellow: #e2e27a; /* Brighter yellow */ - --codinit-terminal-brightBlue: #63a3ff; /* Brighter blue */ - --codinit-terminal-brightMagenta: #ff79c6; /* Brighter magenta */ - --codinit-terminal-brightCyan: #98d4ca; /* Brighter cyan */ - --codinit-terminal-brightWhite: #ffffff; /* Pure white */ - --modern-scrollbar-thumb-background: rgba(153, 153, 153, 0.3); /* Neutral gray thumb */ - --modern-scrollbar-thumb-backgroundHover: rgba(153, 153, 153, 0.5); /* More opaque on hover */ + --codinit-terminal-background: var(--codinit-elements-terminals-background); + /* Inherit from terminal background */ + --codinit-terminal-foreground: #ffffff; + /* Pure white foreground */ + --codinit-terminal-selection-background: rgba(204, 204, 204, 0.2); + /* Neutral gray selection */ + --codinit-terminal-black: #000000; + /* Pure black */ + --codinit-terminal-red: #ff7b72; + /* Brighter red */ + --codinit-terminal-green: #76d974; + /* Brighter green */ + --codinit-terminal-yellow: #e2e27a; + /* Brighter yellow */ + --codinit-terminal-blue: #63a3ff; + /* Brighter blue */ + --codinit-terminal-magenta: #ff79c6; + /* Brighter magenta */ + --codinit-terminal-cyan: #98d4ca; + /* Brighter cyan */ + --codinit-terminal-white: #ffffff; + /* Pure white */ + --codinit-terminal-brightBlack: #555555; + /* Neutral gray */ + --codinit-terminal-brightRed: #ff7b72; + /* Brighter red */ + --codinit-terminal-brightGreen: #76d974; + /* Brighter green */ + --codinit-terminal-brightYellow: #e2e27a; + /* Brighter yellow */ + --codinit-terminal-brightBlue: #63a3ff; + /* Brighter blue */ + --codinit-terminal-brightMagenta: #ff79c6; + /* Brighter magenta */ + --codinit-terminal-brightCyan: #98d4ca; + /* Brighter cyan */ + --codinit-terminal-brightWhite: #ffffff; + /* Pure white */ + --modern-scrollbar-thumb-background: rgba(153, 153, 153, 0.3); + /* Neutral gray thumb */ + --modern-scrollbar-thumb-backgroundHover: rgba(153, 153, 153, 0.5); + /* More opaque on hover */ /* Command/Dialog popover variables - Dark Theme - neutral gray aligned */ - --background: 0 0% 7%; /* #121212 */ + --background: 0 0% 7%; + /* #121212 */ --foreground: 0 0% 98%; - --card: 0 0% 10%; /* #1a1a1a */ + --card: 0 0% 10%; + /* #1a1a1a */ --card-foreground: 0 0% 98%; - --popover: 0 0% 10%; /* #1a1a1a */ + --popover: 0 0% 10%; + /* #1a1a1a */ --popover-foreground: 0 0% 98%; --primary: 217.2 91.2% 59.8%; --primary-foreground: 0 0% 98%; - --secondary: 0 0% 14%; /* #242424 */ + --secondary: 0 0% 14%; + /* #242424 */ --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14%; /* #242424 */ + --muted: 0 0% 14%; + /* #242424 */ --muted-foreground: 0 0% 64%; - --accent: 0 0% 18%; /* #2e2e2e */ + --accent: 0 0% 18%; + /* #2e2e2e */ --accent-foreground: 0 0% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; - --border: 0 0% 23%; /* #3a3a3a */ - --input: 0 0% 18%; /* #2e2e2e */ + --border: 0 0% 23%; + /* #3a3a3a */ + --input: 0 0% 18%; + /* #2e2e2e */ --ring: 217.2 91.2% 59.8%; } @@ -290,7 +358,7 @@ * Hierarchy: Element Token -> (Element Token | Color Tokens) -> Primitives */ :root { - --header-height: 54px; + --header-height: 48px; --chat-max-width: 36rem; --chat-min-width: 533px; --workbench-width: min(calc(100% - var(--chat-min-width)), 3000px); From 5676013767f1292bf5a185957ef858373c7a40c2 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 25 Dec 2025 20:59:54 +1100 Subject: [PATCH 056/253] feat: implement Pro subscription state management with API verification Create Nanostores-based pro.ts store for managing Pro tier status, credits, and feature flags. Include API key verification function that communicates with CodinIT backend for user info retrieval. --- app/lib/stores/pro.ts | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/lib/stores/pro.ts diff --git a/app/lib/stores/pro.ts b/app/lib/stores/pro.ts new file mode 100644 index 00000000..c145c5d2 --- /dev/null +++ b/app/lib/stores/pro.ts @@ -0,0 +1,77 @@ +import { map } from 'nanostores'; + +export interface ProState { + isPro: boolean; + tier: 'free' | 'pro'; + credits: { + total: number; + used: number; + }; + features: { + webSearch: boolean; + lazyEdits: boolean; + }; +} + +export const proStore = map({ + isPro: false, + tier: 'free', + credits: { + total: 0, + used: 0, + }, + features: { + webSearch: false, + lazyEdits: false, + }, +}); + +export const setProState = (state: Partial) => { + proStore.set({ + ...proStore.get(), + ...state, + }); +}; + +export const toggleFeature = (feature: keyof ProState['features']) => { + const currentState = proStore.get(); + proStore.setKey('features', { + ...currentState.features, + [feature]: !currentState.features[feature], + }); +}; + +export const verifyProKey = async (apiKey: string, baseUrl: string = 'https://api.codinit.dev/v1') => { + try { + const response = await fetch(`${baseUrl}/user/info`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + if (!response.ok) { + throw new Error('Invalid API Key or backend unreachable'); + } + + const data = (await response.json()) as { + tier: 'free' | 'pro'; + totalCredits: number; + usedCredits: number; + }; + + setProState({ + isPro: data.tier === 'pro', + tier: data.tier, + credits: { + total: data.totalCredits, + used: data.usedCredits, + }, + }); + + return data; + } catch (error) { + console.error('Pro verification failed:', error); + setProState({ isPro: false, tier: 'free' }); + throw error; + } +}; From 473800eacf1edde9b46648338eff4d231849ee22 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Thu, 25 Dec 2025 21:00:21 +1100 Subject: [PATCH 057/253] refactor: adjust workbench positioning and remove padding for edge-to-edge layout Align workbench top position with updated header height variable. Remove horizontal padding and add left border for cleaner edge-to-edge design that matches the new header styling. --- app/components/workbench/Workbench.client.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx index 22dc2a47..5d413522 100644 --- a/app/components/workbench/Workbench.client.tsx +++ b/app/components/workbench/Workbench.client.tsx @@ -134,7 +134,7 @@ export const Workbench = memo( >
-
-
+
+
Date: Thu, 25 Dec 2025 21:39:49 +1100 Subject: [PATCH 058/253] added browser mapping --- app/components/chat/Chatbox.tsx | 2 +- package.json | 1 + pnpm-lock.yaml | 11 +++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/components/chat/Chatbox.tsx b/app/components/chat/Chatbox.tsx index e84ff87f..f9d16b4e 100644 --- a/app/components/chat/Chatbox.tsx +++ b/app/components/chat/Chatbox.tsx @@ -430,7 +430,7 @@ export const ChatBox: React.FC = (props) => { )} onClick={() => { if (props.provider?.name !== 'CodinIT') { - toast.info('Web Search is a Pro feature available with CodinIT Pro provider.'); + toast.info('Web Search is a Pro feature coming soon...'); return; } diff --git a/package.json b/package.json index 30a59ef9..9200edd9 100644 --- a/package.json +++ b/package.json @@ -191,6 +191,7 @@ "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.7.0", "@vitest/browser": "^3.1.0", + "baseline-browser-mapping": "^2.9.11", "concurrently": "^8.2.2", "cross-env": "^7.0.3", "electron": "^35.7.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01badcd3..5a3f685e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -437,6 +437,9 @@ importers: '@vitest/browser': specifier: ^3.1.0 version: 3.2.4(playwright@1.56.1)(vite@5.4.21(@types/node@22.18.12)(sass-embedded@1.93.2)(sass@1.93.2))(vitest@3.2.4) + baseline-browser-mapping: + specifier: ^2.9.11 + version: 2.9.11 concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -4134,8 +4137,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.20: - resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==} + baseline-browser-mapping@2.9.11: + resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} hasBin: true basic-auth@2.0.1: @@ -12963,7 +12966,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.20: {} + baseline-browser-mapping@2.9.11: {} basic-auth@2.0.1: dependencies: @@ -13094,7 +13097,7 @@ snapshots: browserslist@4.27.0: dependencies: - baseline-browser-mapping: 2.8.20 + baseline-browser-mapping: 2.9.11 caniuse-lite: 1.0.30001751 electron-to-chromium: 1.5.240 node-releases: 2.0.26 From 1fccf5a813a40240302163cd1bdb0d9baf6145a1 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 26 Dec 2025 19:05:09 +1100 Subject: [PATCH 059/253] redesign artifact component with modernized card layout and improved animations --- app/components/chat/Artifact.tsx | 129 +++++++++++++++---------------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/app/components/chat/Artifact.tsx b/app/components/chat/Artifact.tsx index b30a7318..a099b222 100644 --- a/app/components/chat/Artifact.tsx +++ b/app/components/chat/Artifact.tsx @@ -78,82 +78,77 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => { : artifact?.title; return ( - <> -
-
- - {artifact.type !== 'bundled' &&
} - - {actions.length && artifact.type !== 'bundled' && ( +
+ Click to open Webview +
+
+ + + {actions.length > 0 && artifact.type !== 'bundled' && ( + <> +
+
-
-
-
+
+
+ + )} +
+ + {artifact.type === 'bundled' && ( +
+
+ {allActionFinished ? ( +
+ ) : ( +
)} - +
+
+ {allActionFinished + ? artifact.id === 'restored-project-setup' + ? 'Restore files from snapshot' + : 'Initial files created' + : 'Creating initial files'} +
- {artifact.type === 'bundled' && ( -
-
- {allActionFinished ? ( -
- ) : ( -
- )} -
-
- {/* This status text remains the same */} - {allActionFinished - ? artifact.id === 'restored-project-setup' - ? 'Restore files from snapshot' - : 'Initial files created' - : 'Creating initial files'} + )} + + + {artifact.type !== 'bundled' && showActions && actions.length > 0 && ( + +
+
-
+ )} - - {artifact.type !== 'bundled' && showActions && actions.length > 0 && ( - -
- -
- -
- - )} - -
- +
+
); }); From 90f6f5ec5f9871963a59aa4dd9c77f6f92cc5c25 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 26 Dec 2025 19:05:11 +1100 Subject: [PATCH 060/253] simplify shadow gradient and clean up spacing in base chat styles --- app/components/chat/BaseChat.module.scss | 43 +++++++----------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/app/components/chat/BaseChat.module.scss b/app/components/chat/BaseChat.module.scss index 694afef9..72dd0736 100644 --- a/app/components/chat/BaseChat.module.scss +++ b/app/components/chat/BaseChat.module.scss @@ -47,25 +47,10 @@ } ._Shadow_8mvus_1 { - background: linear-gradient( - to top, - var(--codinit-elements-bg-depth-1) 0%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 98.7%, transparent) 8.1%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 95.1%, transparent) 15.5%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 89.6%, transparent) 22.5%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 82.5%, transparent) 29%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 74.1%, transparent) 35.3%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 64.8%, transparent) 41.2%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 55%, transparent) 47.1%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 45%, transparent) 52.9%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 35.2%, transparent) 58.8%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 25.9%, transparent) 64.7%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 17.5%, transparent) 71%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 10.4%, transparent) 77.5%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 4.9%, transparent) 84.5%, - color-mix(in srgb, var(--codinit-elements-bg-depth-1) 1.3%, transparent) 91.9%, - transparent 100% - ); + background: linear-gradient(to bottom, + var(--codinit-elements-bg-depth-1) 0%, + transparent 100%); + height: 24px; } *:has(+ ._StickyPinnedMessage_rq7xp_1) { @@ -104,7 +89,7 @@ margin-block-end: 16px; } -._MigrationSummary_1ebkv_1 ul li > ul { +._MigrationSummary_1ebkv_1 ul li>ul { margin-block-start: 4px; padding-left: 0.75rem; } @@ -116,7 +101,7 @@ margin-block-end: 6px; } -._MigrationSummary_1ebkv_1 ol + ul { +._MigrationSummary_1ebkv_1 ol+ul { padding-left: 2rem; } @@ -268,14 +253,12 @@ transform: translate(-50%); height: 2px; width: 100px; - background: linear-gradient( - 90deg, - transparent 0%, - var(--codinit-elements-borderColor) 20%, - var(--codinit-elements-textPrimary) 50%, - var(--codinit-elements-borderColor) 80%, - transparent 100% - ); + background: linear-gradient(90deg, + transparent 0%, + var(--codinit-elements-borderColor) 20%, + var(--codinit-elements-textPrimary) 50%, + var(--codinit-elements-borderColor) 80%, + transparent 100%); opacity: 0.6; transition: opacity 0.6s cubic-bezier(0.32, 0.72, 0, 1), @@ -376,4 +359,4 @@ opacity: 1; transform: translateY(0); } -} +} \ No newline at end of file From 405548c7860476655d44b9819dba3f6c06aef4a7 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 26 Dec 2025 19:05:17 +1100 Subject: [PATCH 061/253] implement animated workbench header with synchronized width transitions --- app/components/chat/BaseChat.tsx | 133 ++++++++++++++++++------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 5273377c..363a4401 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -6,6 +6,8 @@ import type { JSONValue, Message } from 'ai'; import React, { lazy, Suspense, type RefCallback, useEffect, useState } from 'react'; import { ClientOnly } from 'remix-utils/client-only'; import { Menu } from '~/components/sidebar/Menu.client'; +import { motion } from 'framer-motion'; +import { cubicEasingFn } from '~/utils/easings'; const Workbench = lazy(() => import('~/components/workbench/Workbench.client').then((module) => ({ @@ -447,69 +449,88 @@ export const BaseChat = React.forwardRef( > {() => } - {/* Unified Header Container - spans full width */} {chatStarted && (
- {/* Chat Header Section - constrained to chat width */} -
+ {/* Chat Header Section - Syncs with Chat Panel */} +
- {/* Workbench Header Section - fills remaining space */} -
- {selectedView === 'code' && ( - { - workbenchStore.downloadZip(); + {/* Workbench Header Section - Syncs with Workbench Panel */} + + {() => ( + setIsSyncing(true)} - onPushToGitHub={() => setIsPushDialogOpen(true)} - isSyncing={isSyncing} - setIsPushDialogOpen={setIsPushDialogOpen} - /> - )} - - {selectedView === 'preview' && ( - - )} - - {selectedView === 'diff' && selectedFile && currentDocument && ( - setIsDiffFullscreen(!isDiffFullscreen)} - /> + className="overflow-hidden" + > +
+ {selectedView === 'code' && ( + { + workbenchStore.downloadZip(); + }} + onSyncFiles={() => setIsSyncing(true)} + onPushToGitHub={() => setIsPushDialogOpen(true)} + isSyncing={isSyncing} + setIsPushDialogOpen={setIsPushDialogOpen} + /> + )} + + {selectedView === 'preview' && ( + + )} + + {selectedView === 'diff' && selectedFile && currentDocument && ( + setIsDiffFullscreen(!isDiffFullscreen)} + /> + )} +
+
)} -
+
)} From 197b2701c8eb450c3f040c12c72495212757702d Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 26 Dec 2025 19:05:20 +1100 Subject: [PATCH 062/253] integrate Pro subscription state in chat API requests --- app/components/chat/Chat.client.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 18addabb..6f40a10c 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -28,6 +28,7 @@ import type { DesignScheme } from '~/types/design-scheme'; import type { ElementInfo } from '~/components/workbench/Inspector'; import type { TextUIPart, FileUIPart, Attachment } from '@ai-sdk/ui-utils'; import { useMCPStore } from '~/lib/stores/mcp'; +import { proStore } from '~/lib/stores/pro'; import type { LlmErrorAlertType } from '~/types/actions'; const logger = createScopedLogger('Chat'); @@ -140,6 +141,7 @@ export const ChatImpl = memo( } = useChat({ api: '/api/chat', body: { + isPro: proStore.get().isPro, apiKeys, files, promptId, From 6c44ad4ba9c9ce6097cedacf593b3d772b06d2e2 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 26 Dec 2025 19:05:23 +1100 Subject: [PATCH 063/253] reorganize chatbox layout with send button positioned in bottom toolbar --- app/components/chat/Chatbox.tsx | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/app/components/chat/Chatbox.tsx b/app/components/chat/Chatbox.tsx index f9d16b4e..1d85aa58 100644 --- a/app/components/chat/Chatbox.tsx +++ b/app/components/chat/Chatbox.tsx @@ -179,7 +179,7 @@ export const ChatBox: React.FC = (props) => { <>
= (props) => {