@vibeship/devtools
Version:
Comprehensive markdown-based project management system with AI capabilities for Next.js applications
1,453 lines (1,430 loc) • 698 kB
JavaScript
"use client";
#!/usr/bin/env node
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name14 in all)
__defProp(target, name14, { get: all[name14], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/contexts/VibeshipContext.tsx
var import_react, import_jsx_runtime, VibeshipContext;
var init_VibeshipContext = __esm({
"src/contexts/VibeshipContext.tsx"() {
"use strict";
"use client";
import_react = require("react");
import_jsx_runtime = require("react/jsx-runtime");
VibeshipContext = (0, import_react.createContext)(void 0);
}
});
// src/providers/ErrorBoundary.tsx
function withErrorBoundary(Component4, errorBoundaryProps) {
const WrappedComponent = (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component4, { ...props }) });
WrappedComponent.displayName = `withErrorBoundary(${Component4.displayName || Component4.name})`;
return WrappedComponent;
}
var import_react2, import_jsx_runtime2, ErrorBoundary;
var init_ErrorBoundary = __esm({
"src/providers/ErrorBoundary.tsx"() {
"use strict";
"use client";
import_react2 = require("react");
import_jsx_runtime2 = require("react/jsx-runtime");
ErrorBoundary = class extends import_react2.Component {
constructor(props) {
super(props);
this.reset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
return {
hasError: true,
error
};
}
componentDidCatch(error, errorInfo) {
if (true) {
console.error(`[Vibecode ErrorBoundary${this.props.name ? ` - ${this.props.name}` : ""}]:`, error, errorInfo);
}
this.props.onError?.(error, errorInfo);
this.setState({
errorInfo
});
}
render() {
if (this.state.hasError && this.state.error && this.state.errorInfo) {
if (this.props.fallback) {
return this.props.fallback(this.state.error, this.state.errorInfo, this.reset);
}
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vibecode-error-boundary p-4 border border-red-500 rounded-lg bg-red-50 dark:bg-red-950", children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "text-lg font-semibold text-red-700 dark:text-red-300 mb-2", children: "Something went wrong" }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "text-sm text-red-600 dark:text-red-400", children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("summary", { className: "cursor-pointer mb-2", children: "Error details" }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("pre", { className: "whitespace-pre-wrap overflow-auto p-2 bg-red-100 dark:bg-red-900 rounded", children: [
this.state.error.toString(),
this.state.errorInfo.componentStack
] })
] }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"button",
{
onClick: this.reset,
className: "mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors",
children: "Try again"
}
)
] });
}
return this.props.children;
}
};
}
});
// src/providers/LoadingSpinner.tsx
function LoadingSpinner() {
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "vibecode-loading-spinner fixed bottom-4 right-4 p-3 bg-white dark:bg-gray-800 rounded-lg shadow-lg", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-primary" }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Loading Vibecode DevTools..." })
] }) });
}
var import_jsx_runtime3;
var init_LoadingSpinner = __esm({
"src/providers/LoadingSpinner.tsx"() {
"use strict";
"use client";
import_jsx_runtime3 = require("react/jsx-runtime");
}
});
// src/services/security/encryption-service.ts
var EncryptionService;
var init_encryption_service = __esm({
"src/services/security/encryption-service.ts"() {
"use strict";
EncryptionService = class {
constructor() {
this.algorithm = "AES-GCM";
this.keyLength = 256;
this.saltLength = 16;
this.ivLength = 12;
this.tagLength = 128;
this.iterations = 1e5;
}
/**
* Derives a key from a password using PBKDF2
*/
async deriveKey(password, salt) {
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
const importedKey = await crypto.subtle.importKey(
"raw",
passwordBuffer,
"PBKDF2",
false,
["deriveBits", "deriveKey"]
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: this.iterations,
hash: "SHA-256"
},
importedKey,
{
name: this.algorithm,
length: this.keyLength
},
false,
["encrypt", "decrypt"]
);
}
/**
* Gets or generates the encryption password
*/
async getPassword() {
if (typeof window === "undefined") {
return process.env.ENCRYPTION_KEY || "default-dev-key-change-in-production";
}
const storageKey = "vibecode-encryption-id";
let password = localStorage.getItem(storageKey);
if (!password) {
password = this.generatePassword();
localStorage.setItem(storageKey, password);
}
return password;
}
/**
* Generates a secure random password
*/
generatePassword() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
/**
* Encrypts a string
*/
async encrypt(plaintext) {
const encoder = new TextEncoder();
const data = encoder.encode(plaintext);
const salt = crypto.getRandomValues(new Uint8Array(this.saltLength));
const iv = crypto.getRandomValues(new Uint8Array(this.ivLength));
const password = await this.getPassword();
const key = await this.deriveKey(password, salt);
const encrypted = await crypto.subtle.encrypt(
{
name: this.algorithm,
iv,
tagLength: this.tagLength
},
key,
data
);
const combined = new Uint8Array(
salt.length + iv.length + encrypted.byteLength
);
combined.set(salt, 0);
combined.set(iv, salt.length);
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
return btoa(String.fromCharCode.apply(null, Array.from(combined)));
}
/**
* Decrypts a string
*/
async decrypt(ciphertext) {
const combined = Uint8Array.from(atob(ciphertext), (c) => c.charCodeAt(0));
const salt = combined.slice(0, this.saltLength);
const iv = combined.slice(this.saltLength, this.saltLength + this.ivLength);
const encrypted = combined.slice(this.saltLength + this.ivLength);
const password = await this.getPassword();
const key = await this.deriveKey(password, salt);
const decrypted = await crypto.subtle.decrypt(
{
name: this.algorithm,
iv,
tagLength: this.tagLength
},
key,
encrypted
);
const decoder = new TextDecoder();
return decoder.decode(decrypted);
}
/**
* Checks if Web Crypto API is available
*/
isAvailable() {
return typeof crypto !== "undefined" && crypto.subtle !== void 0;
}
};
}
});
// src/services/security/api-key-manager.ts
var BrowserSecureStorage, SecureAPIKeyManager;
var init_api_key_manager = __esm({
"src/services/security/api-key-manager.ts"() {
"use strict";
init_encryption_service();
BrowserSecureStorage = class {
constructor() {
this.prefix = "vibecode-secure-";
}
async get(key) {
if (typeof window === "undefined") return null;
return localStorage.getItem(this.prefix + key);
}
async set(key, value) {
if (typeof window === "undefined") return;
localStorage.setItem(this.prefix + key, value);
}
async remove(key) {
if (typeof window === "undefined") return;
localStorage.removeItem(this.prefix + key);
}
async list() {
if (typeof window === "undefined") return [];
const keys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key?.startsWith(this.prefix)) {
keys.push(key.substring(this.prefix.length));
}
}
return keys;
}
};
SecureAPIKeyManager = class {
// 5 minutes
constructor(encryption, storage) {
this.keyPrefix = "api-key-";
this.validationCache = /* @__PURE__ */ new Map();
this.cacheTimeout = 5 * 60 * 1e3;
this.encryption = encryption || new EncryptionService();
this.storage = storage || new BrowserSecureStorage();
}
async store(provider, key) {
if (!key || !provider) {
throw new Error("Provider and key are required");
}
this.validateKeyFormat(provider, key);
let encrypted;
try {
if (typeof window !== "undefined" && this.encryption.isAvailable()) {
encrypted = await this.encryption.encrypt(key);
} else {
encrypted = Buffer.from(key).toString("base64");
}
} catch (error) {
console.warn("Encryption failed, using base64:", error);
encrypted = Buffer.from(key).toString("base64");
}
await this.storage.set(this.keyPrefix + provider, encrypted);
this.validationCache.delete(provider);
}
async retrieve(provider) {
console.log(`[APIKeyManager] Retrieving key for ${provider}`);
const encrypted = await this.storage.get(this.keyPrefix + provider);
if (!encrypted) {
console.log(`[APIKeyManager] No encrypted key found for ${provider}`);
return null;
}
console.log(`[APIKeyManager] Found encrypted key for ${provider}, attempting to decrypt...`);
try {
if (typeof window === "undefined") {
const decoded = Buffer.from(encrypted, "base64").toString("utf-8");
console.log(`[APIKeyManager] Successfully decoded key for ${provider} (server-side)`);
return decoded;
}
const decrypted = await this.encryption.decrypt(encrypted);
console.log(`[APIKeyManager] Successfully decrypted key for ${provider} (client-side)`);
return decrypted;
} catch (error) {
console.error("Failed to decrypt API key:", error);
try {
const decoded = Buffer.from(encrypted, "base64").toString("utf-8");
console.log(`[APIKeyManager] Used fallback base64 decode for ${provider}`);
return decoded;
} catch {
console.error(`[APIKeyManager] Failed to decode key for ${provider} even with fallback`);
return null;
}
}
}
async remove(provider) {
await this.storage.remove(this.keyPrefix + provider);
this.validationCache.delete(provider);
}
async validate(provider, key) {
const cached = this.validationCache.get(provider);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.valid;
}
let valid = false;
try {
switch (provider) {
case "openai":
valid = await this.validateOpenAIKey(key);
break;
case "anthropic":
valid = await this.validateAnthropicKey(key);
break;
case "xai":
valid = await this.validateXAIKey(key);
break;
default:
valid = this.isValidKeyFormat(key);
}
} catch (error) {
console.error("Key validation error:", error);
valid = false;
}
this.validationCache.set(provider, { valid, timestamp: Date.now() });
return valid;
}
async rotate(provider) {
const newKey = this.generateMockKey(provider);
await this.store(provider, newKey);
return newKey;
}
async listProviders() {
const keys = await this.storage.list();
return keys.filter((key) => key.startsWith(this.keyPrefix)).map((key) => key.substring(this.keyPrefix.length));
}
async hasKey(provider) {
const key = await this.retrieve(provider);
return key !== null;
}
// Private validation methods
validateKeyFormat(provider, key) {
if (!this.isValidKeyFormat(key)) {
throw new Error(`Invalid key format for provider: ${provider}`);
}
switch (provider) {
case "openai":
if (!key.startsWith("sk-") && !key.startsWith("sk-proj-")) {
throw new Error('OpenAI keys must start with "sk-" or "sk-proj-"');
}
break;
case "anthropic":
if (!key.startsWith("sk-ant-")) {
throw new Error('Anthropic keys must start with "sk-ant-"');
}
break;
}
}
isValidKeyFormat(key) {
return /^[a-zA-Z0-9\-_]{20,}$/.test(key);
}
async validateOpenAIKey(key) {
try {
const response = await fetch("https://api.openai.com/v1/models", {
headers: {
"Authorization": `Bearer ${key}`
}
});
return response.status === 200;
} catch {
return false;
}
}
async validateAnthropicKey(key) {
if (typeof window !== "undefined") {
console.log("[APIKeyManager] Skipping Anthropic key validation on client side (CORS)");
return key.startsWith("sk-ant-") && key.length > 40;
}
try {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": key,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
body: JSON.stringify({
model: "claude-3-opus-20240229",
messages: [{ role: "user", content: "test" }],
max_tokens: 1
})
});
return response.status !== 401;
} catch {
return false;
}
}
async validateXAIKey(key) {
return this.isValidKeyFormat(key);
}
generateMockKey(provider) {
const prefix = {
openai: "sk-proj-",
anthropic: "sk-ant-",
xai: "xai-"
}[provider] || "key-";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let key = prefix;
for (let i = 0; i < 48; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key;
}
};
}
});
// src/services/settings/ai-settings-store.ts
var DEFAULT_SETTINGS, LocalStorageAISettingsStore;
var init_ai_settings_store = __esm({
"src/services/settings/ai-settings-store.ts"() {
"use strict";
init_api_key_manager();
DEFAULT_SETTINGS = {
provider: "openai",
model: "gpt-4-turbo-preview",
apiKeys: {},
temperature: 0.7,
maxTokens: 4096,
streaming: true,
advanced: {
timeout: 3e4,
retries: 3,
cache: true,
responseFormat: "text"
},
mcp: {
enabled: false,
transport: "http"
}
};
LocalStorageAISettingsStore = class {
constructor(keyManager) {
this.storageKey = "vibecode-ai-settings";
this.versionKey = "vibecode-ai-settings-version";
this.currentVersion = "1.0.0";
this.keyManager = keyManager || new SecureAPIKeyManager();
}
async load() {
if (typeof window === "undefined") {
return DEFAULT_SETTINGS;
}
try {
const stored = localStorage.getItem(this.storageKey);
if (!stored) {
return DEFAULT_SETTINGS;
}
const settings = JSON.parse(stored);
const version = localStorage.getItem(this.versionKey);
if (version !== this.currentVersion) {
await this.migrate(version || "0.0.0");
}
const providers = await this.keyManager.listProviders();
const apiKeys = {};
for (const provider of providers) {
const key = await this.keyManager.retrieve(provider);
if (key) {
apiKeys[provider] = key;
}
}
return {
...DEFAULT_SETTINGS,
...settings,
apiKeys
};
} catch (error) {
console.error("Failed to load AI settings:", error);
return DEFAULT_SETTINGS;
}
}
async save(settings) {
if (typeof window === "undefined") {
return;
}
try {
for (const [provider, key] of Object.entries(settings.apiKeys)) {
if (key) {
await this.keyManager.store(provider, key);
} else {
await this.keyManager.remove(provider);
}
}
const { apiKeys, ...settingsWithoutKeys } = settings;
localStorage.setItem(this.storageKey, JSON.stringify(settingsWithoutKeys));
localStorage.setItem(this.versionKey, this.currentVersion);
} catch (error) {
console.error("Failed to save AI settings:", error);
throw error;
}
}
async migrate(oldVersion) {
console.log(`Migrating AI settings from version ${oldVersion} to ${this.currentVersion}`);
if (oldVersion < "1.0.0") {
const oldSettings = localStorage.getItem("ai-settings");
if (oldSettings) {
try {
const parsed = JSON.parse(oldSettings);
await this.save({
...DEFAULT_SETTINGS,
...parsed
});
localStorage.removeItem("ai-settings");
} catch (error) {
console.error("Migration failed:", error);
}
}
}
}
async export() {
const settings = await this.load();
const exportData = {
version: this.currentVersion,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
settings: {
...settings,
// Mask API keys for security
apiKeys: Object.keys(settings.apiKeys).reduce((acc, key) => {
acc[key] = "***MASKED***";
return acc;
}, {})
}
};
return JSON.stringify(exportData, null, 2);
}
async import(data) {
try {
const parsed = JSON.parse(data);
if (!parsed.version || !parsed.settings) {
throw new Error("Invalid import data format");
}
const { apiKeys, ...settingsWithoutKeys } = parsed.settings;
await this.save({
...DEFAULT_SETTINGS,
...settingsWithoutKeys,
apiKeys: {}
// Keep existing keys
});
} catch (error) {
console.error("Failed to import AI settings:", error);
throw new Error("Failed to import settings: Invalid format");
}
}
async reset() {
if (typeof window === "undefined") {
return;
}
const providers = await this.keyManager.listProviders();
for (const provider of providers) {
await this.keyManager.remove(provider);
}
localStorage.removeItem(this.storageKey);
localStorage.removeItem(this.versionKey);
}
};
}
});
// src/contexts/AISettingsContext.tsx
function AISettingsProvider({
children,
aiService
}) {
const [settings, setSettings] = (0, import_react3.useState)(null);
const [providers, setProviders] = (0, import_react3.useState)(PROVIDER_INFO);
const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react3.useState)(false);
const settingsStore = typeof window !== "undefined" ? new LocalStorageAISettingsStore() : null;
const keyManager = typeof window !== "undefined" ? new SecureAPIKeyManager() : null;
(0, import_react3.useEffect)(() => {
if (typeof window !== "undefined") {
loadSettings();
}
}, [aiService]);
const loadSettings = async () => {
if (!settingsStore) return;
try {
setIsLoading(true);
const loadedSettings = await settingsStore.load();
setSettings(loadedSettings);
await updateProviderStatuses(loadedSettings);
if (aiService && keyManager) {
const apiKeys = {};
if (await keyManager.hasKey("openai")) {
const key = await keyManager.retrieve("openai");
if (key) apiKeys.openai = { apiKey: key };
}
if (await keyManager.hasKey("anthropic")) {
const key = await keyManager.retrieve("anthropic");
if (key) apiKeys.anthropic = { apiKey: key };
}
if (await keyManager.hasKey("xai")) {
const key = await keyManager.retrieve("xai");
if (key) apiKeys.xai = { apiKey: key };
}
if (Object.keys(apiKeys).length > 0) {
aiService.configure({
providers: apiKeys,
defaultProvider: loadedSettings.provider,
defaultModel: loadedSettings.model
});
}
}
} catch (error) {
console.error("Failed to load AI settings:", error);
} finally {
setIsLoading(false);
}
};
const updateProviderStatuses = async (currentSettings) => {
const updatedProviders = await Promise.all(
providers.map(async (provider) => {
const hasKey = keyManager ? await keyManager.hasKey(provider.id) : false;
return {
...provider,
status: hasKey ? "connected" : "disconnected"
};
})
);
setProviders(updatedProviders);
};
const updateSettings = (0, import_react3.useCallback)((updates) => {
if (!settings) return;
setSettings((prev) => prev ? { ...prev, ...updates } : null);
setHasUnsavedChanges(true);
}, [settings]);
const saveSettings = async () => {
if (!settings || !settingsStore) return;
try {
await settingsStore.save(settings);
setHasUnsavedChanges(false);
await updateProviderStatuses(settings);
if (aiService && keyManager) {
const apiKeys = {};
if (await keyManager.hasKey("openai")) {
const key = await keyManager.retrieve("openai");
if (key) apiKeys.openai = { apiKey: key };
}
if (await keyManager.hasKey("anthropic")) {
const key = await keyManager.retrieve("anthropic");
if (key) apiKeys.anthropic = { apiKey: key };
}
if (await keyManager.hasKey("xai")) {
const key = await keyManager.retrieve("xai");
if (key) apiKeys.xai = { apiKey: key };
}
aiService.configure({
providers: apiKeys,
defaultProvider: settings.provider,
defaultModel: settings.model
});
}
} catch (error) {
console.error("Failed to save settings:", error);
throw error;
}
};
const resetSettings = async () => {
if (!settingsStore) return;
try {
await settingsStore.reset();
await loadSettings();
setHasUnsavedChanges(false);
} catch (error) {
console.error("Failed to reset settings:", error);
throw error;
}
};
const exportSettings = async () => {
if (!settingsStore) throw new Error("Settings store not available");
return settingsStore.export();
};
const importSettings = async (data) => {
if (!settingsStore) throw new Error("Settings store not available");
try {
await settingsStore.import(data);
await loadSettings();
setHasUnsavedChanges(false);
} catch (error) {
console.error("Failed to import settings:", error);
throw error;
}
};
const updateApiKey = async (provider, key) => {
if (!settings || !keyManager) return;
try {
await keyManager.store(provider, key);
setSettings((prev) => prev ? {
...prev,
apiKeys: { ...prev.apiKeys, [provider]: key }
} : null);
setHasUnsavedChanges(true);
setProviders((prev) => prev.map(
(p) => p.id === provider ? { ...p, status: "connected" } : p
));
} catch (error) {
console.error("Failed to update API key:", error);
throw error;
}
};
const removeApiKey = async (provider) => {
if (!settings || !keyManager) return;
try {
await keyManager.remove(provider);
const { [provider]: _, ...remainingKeys } = settings.apiKeys;
setSettings((prev) => prev ? {
...prev,
apiKeys: remainingKeys
} : null);
setHasUnsavedChanges(true);
setProviders((prev) => prev.map(
(p) => p.id === provider ? { ...p, status: "disconnected" } : p
));
} catch (error) {
console.error("Failed to remove API key:", error);
throw error;
}
};
const validateApiKey = async (provider, key) => {
if (!keyManager) return false;
return keyManager.validate(provider, key);
};
const testConnection = async (provider) => {
try {
setProviders((prev) => prev.map(
(p) => p.id === provider ? { ...p, status: "checking" } : p
));
const startTime = Date.now();
const isValid = await validateApiKey(provider, settings?.apiKeys[provider] || "");
const latency = Date.now() - startTime;
const result = {
success: isValid,
latency,
error: isValid ? void 0 : "Invalid API key"
};
setProviders((prev) => prev.map(
(p) => p.id === provider ? {
...p,
status: isValid ? "connected" : "error",
error: result.error
} : p
));
return result;
} catch (error) {
const result = {
success: false,
error: error instanceof Error ? error.message : "Connection test failed"
};
setProviders((prev) => prev.map(
(p) => p.id === provider ? { ...p, status: "error", error: result.error } : p
));
return result;
}
};
const switchProvider = (providerId) => {
if (!settings) return;
const provider = providers.find((p) => p.id === providerId);
if (!provider) return;
const firstModel = provider.models[0];
updateSettings({
provider: providerId,
model: firstModel?.id || ""
});
if (aiService) {
aiService.switchProvider(providerId);
if (firstModel) {
aiService.switchModel(firstModel.id);
}
}
};
const switchModel = (modelId) => {
if (!settings) return;
updateSettings({ model: modelId });
if (aiService) {
aiService.switchModel(modelId);
}
};
const estimateCost = (inputTokens, outputTokens) => {
if (!settings) return null;
const provider = providers.find((p) => p.id === settings.provider);
const model = provider?.models.find((m) => m.id === settings.model);
if (!model?.pricing) return null;
const inputCost = inputTokens / 1e3 * model.pricing.input;
const outputCost = outputTokens / 1e3 * model.pricing.output;
return {
provider: settings.provider,
model: settings.model,
inputTokens,
outputTokens,
inputCost,
outputCost,
totalCost: inputCost + outputCost,
currency: model.pricing.currency
};
};
if (!settings) {
return null;
}
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
AISettingsContext.Provider,
{
value: {
settings,
providers,
isLoading,
hasUnsavedChanges,
updateSettings,
saveSettings,
resetSettings,
exportSettings,
importSettings,
updateApiKey,
removeApiKey,
validateApiKey,
testConnection,
switchProvider,
switchModel,
estimateCost
},
children
}
);
}
function useAISettings() {
const context = (0, import_react3.useContext)(AISettingsContext);
if (context === void 0) {
throw new Error("useAISettings must be used within an AISettingsProvider");
}
return context;
}
var import_react3, import_jsx_runtime4, AISettingsContext, PROVIDER_INFO;
var init_AISettingsContext = __esm({
"src/contexts/AISettingsContext.tsx"() {
"use strict";
"use client";
import_react3 = require("react");
init_ai_settings_store();
init_api_key_manager();
import_jsx_runtime4 = require("react/jsx-runtime");
AISettingsContext = (0, import_react3.createContext)(void 0);
PROVIDER_INFO = [
{
id: "openai",
name: "OpenAI",
icon: "\u{1F916}",
description: "GPT-4, GPT-3.5, and DALL-E models",
website: "https://openai.com",
requiresApiKey: true,
customizable: false,
status: "disconnected",
models: [
{
id: "gpt-4-turbo-preview",
name: "GPT-4 Turbo",
description: "Most capable model with 128k context",
contextWindow: 128e3,
maxTokens: 4096,
supports: {
chat: true,
completion: true,
functionCalling: true,
vision: true,
streaming: true,
jsonMode: true
},
pricing: {
currency: "USD",
input: 0.01,
output: 0.03
}
},
{
id: "gpt-4",
name: "GPT-4",
description: "Advanced reasoning capabilities",
contextWindow: 8192,
maxTokens: 4096,
supports: {
chat: true,
completion: true,
functionCalling: true,
vision: false,
streaming: true,
jsonMode: true
},
pricing: {
currency: "USD",
input: 0.03,
output: 0.06
}
},
{
id: "gpt-3.5-turbo",
name: "GPT-3.5 Turbo",
description: "Fast and cost-effective",
contextWindow: 16384,
maxTokens: 4096,
supports: {
chat: true,
completion: true,
functionCalling: true,
vision: false,
streaming: true,
jsonMode: true
},
pricing: {
currency: "USD",
input: 5e-4,
output: 15e-4
}
}
]
},
{
id: "anthropic",
name: "Anthropic",
icon: "\u{1F9E0}",
description: "Claude 3 family of models",
website: "https://anthropic.com",
requiresApiKey: true,
customizable: false,
status: "disconnected",
models: [
{
id: "claude-3-opus-20240229",
name: "Claude 3 Opus",
description: "Most powerful model for complex tasks",
contextWindow: 2e5,
maxTokens: 4096,
supports: {
chat: true,
completion: true,
functionCalling: true,
vision: true,
streaming: true,
jsonMode: false
},
pricing: {
currency: "USD",
input: 0.015,
output: 0.075
}
},
{
id: "claude-3-5-sonnet-20241022",
name: "Claude 3.5 Sonnet",
description: "Latest Sonnet - balanced performance and cost",
contextWindow: 2e5,
maxTokens: 8192,
supports: {
chat: true,
completion: true,
functionCalling: true,
vision: true,
streaming: true,
jsonMode: false
},
pricing: {
currency: "USD",
input: 3e-3,
output: 0.015
}
},
{
id: "claude-3-5-haiku-20241022",
name: "Claude 3.5 Haiku",
description: "Latest Haiku - fast and affordable",
contextWindow: 2e5,
maxTokens: 8192,
supports: {
chat: true,
completion: true,
functionCalling: true,
vision: true,
streaming: true,
jsonMode: false
},
pricing: {
currency: "USD",
input: 1e-3,
output: 5e-3
}
}
]
},
{
id: "xai",
name: "xAI",
icon: "\u{1F680}",
description: "Grok models from xAI",
website: "https://x.ai",
requiresApiKey: true,
customizable: false,
status: "disconnected",
models: [
{
id: "grok-beta",
name: "Grok Beta",
description: "Advanced reasoning with real-time knowledge",
contextWindow: 8192,
maxTokens: 4096,
supports: {
chat: true,
completion: true,
functionCalling: false,
vision: false,
streaming: true,
jsonMode: false
}
}
]
}
];
}
});
// ../../node_modules/framer-motion/dist/es/context/LayoutGroupContext.mjs
var import_react4, LayoutGroupContext;
var init_LayoutGroupContext = __esm({
"../../node_modules/framer-motion/dist/es/context/LayoutGroupContext.mjs"() {
"use strict";
"use client";
import_react4 = require("react");
LayoutGroupContext = (0, import_react4.createContext)({});
}
});
// ../../node_modules/framer-motion/dist/es/utils/use-constant.mjs
function useConstant(init) {
const ref = (0, import_react5.useRef)(null);
if (ref.current === null) {
ref.current = init();
}
return ref.current;
}
var import_react5;
var init_use_constant = __esm({
"../../node_modules/framer-motion/dist/es/utils/use-constant.mjs"() {
"use strict";
import_react5 = require("react");
}
});
// ../../node_modules/framer-motion/dist/es/context/PresenceContext.mjs
var import_react6, PresenceContext;
var init_PresenceContext = __esm({
"../../node_modules/framer-motion/dist/es/context/PresenceContext.mjs"() {
"use strict";
"use client";
import_react6 = require("react");
PresenceContext = (0, import_react6.createContext)(null);
}
});
// ../../node_modules/framer-motion/dist/es/context/MotionConfigContext.mjs
var import_react7, MotionConfigContext;
var init_MotionConfigContext = __esm({
"../../node_modules/framer-motion/dist/es/context/MotionConfigContext.mjs"() {
"use strict";
"use client";
import_react7 = require("react");
MotionConfigContext = (0, import_react7.createContext)({
transformPagePoint: (p) => p,
isStatic: false,
reducedMotion: "never"
});
}
});
// ../../node_modules/framer-motion/dist/es/components/AnimatePresence/PopChild.mjs
function PopChild({ children, isPresent }) {
const id3 = (0, import_react8.useId)();
const ref = (0, import_react8.useRef)(null);
const size = (0, import_react8.useRef)({
width: 0,
height: 0,
top: 0,
left: 0
});
const { nonce } = (0, import_react8.useContext)(MotionConfigContext);
(0, import_react8.useInsertionEffect)(() => {
const { width, height, top, left } = size.current;
if (isPresent || !ref.current || !width || !height)
return;
ref.current.dataset.motionPopId = id3;
const style = document.createElement("style");
if (nonce)
style.nonce = nonce;
document.head.appendChild(style);
if (style.sheet) {
style.sheet.insertRule(`
[data-motion-pop-id="${id3}"] {
position: absolute !important;
width: ${width}px !important;
height: ${height}px !important;
top: ${top}px !important;
left: ${left}px !important;
}
`);
}
return () => {
document.head.removeChild(style);
};
}, [isPresent]);
return (0, import_jsx_runtime5.jsx)(PopChildMeasure, { isPresent, childRef: ref, sizeRef: size, children: React4.cloneElement(children, { ref }) });
}
var import_jsx_runtime5, React4, import_react8, PopChildMeasure;
var init_PopChild = __esm({
"../../node_modules/framer-motion/dist/es/components/AnimatePresence/PopChild.mjs"() {
"use strict";
"use client";
import_jsx_runtime5 = require("react/jsx-runtime");
React4 = __toESM(require("react"), 1);
import_react8 = require("react");
init_MotionConfigContext();
PopChildMeasure = class extends React4.Component {
getSnapshotBeforeUpdate(prevProps) {
const element = this.props.childRef.current;
if (element && prevProps.isPresent && !this.props.isPresent) {
const size = this.props.sizeRef.current;
size.height = element.offsetHeight || 0;
size.width = element.offsetWidth || 0;
size.top = element.offsetTop;
size.left = element.offsetLeft;
}
return null;
}
/**
* Required with getSnapshotBeforeUpdate to stop React complaining.
*/
componentDidUpdate() {
}
render() {
return this.props.children;
}
};
}
});
// ../../node_modules/framer-motion/dist/es/components/AnimatePresence/PresenceChild.mjs
function newChildrenMap() {
return /* @__PURE__ */ new Map();
}
var import_jsx_runtime6, React5, import_react9, PresenceChild;
var init_PresenceChild = __esm({
"../../node_modules/framer-motion/dist/es/components/AnimatePresence/PresenceChild.mjs"() {
"use strict";
"use client";
import_jsx_runtime6 = require("react/jsx-runtime");
React5 = __toESM(require("react"), 1);
import_react9 = require("react");
init_PresenceContext();
init_use_constant();
init_PopChild();
PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode }) => {
const presenceChildren = useConstant(newChildrenMap);
const id3 = (0, import_react9.useId)();
const memoizedOnExitComplete = (0, import_react9.useCallback)((childId) => {
presenceChildren.set(childId, true);
for (const isComplete of presenceChildren.values()) {
if (!isComplete)
return;
}
onExitComplete && onExitComplete();
}, [presenceChildren, onExitComplete]);
const context = (0, import_react9.useMemo)(
() => ({
id: id3,
initial,
isPresent,
custom,
onExitComplete: memoizedOnExitComplete,
register: (childId) => {
presenceChildren.set(childId, false);
return () => presenceChildren.delete(childId);
}
}),
/**
* If the presence of a child affects the layout of the components around it,
* we want to make a new context value to ensure they get re-rendered
* so they can detect that layout change.
*/
presenceAffectsLayout ? [Math.random(), memoizedOnExitComplete] : [isPresent, memoizedOnExitComplete]
);
(0, import_react9.useMemo)(() => {
presenceChildren.forEach((_, key) => presenceChildren.set(key, false));
}, [isPresent]);
React5.useEffect(() => {
!isPresent && !presenceChildren.size && onExitComplete && onExitComplete();
}, [isPresent]);
if (mode === "popLayout") {
children = (0, import_jsx_runtime6.jsx)(PopChild, { isPresent, children });
}
return (0, import_jsx_runtime6.jsx)(PresenceContext.Provider, { value: context, children });
};
}
});
// ../../node_modules/framer-motion/dist/es/components/AnimatePresence/use-presence.mjs
function usePresence(subscribe = true) {
const context = (0, import_react10.useContext)(PresenceContext);
if (context === null)
return [true, null];
const { isPresent, onExitComplete, register } = context;
const id3 = (0, import_react10.useId)();
(0, import_react10.useEffect)(() => {
if (subscribe)
register(id3);
}, [subscribe]);
const safeToRemove = (0, import_react10.useCallback)(() => subscribe && onExitComplete && onExitComplete(id3), [id3, onExitComplete, subscribe]);
return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
}
var import_react10;
var init_use_presence = __esm({
"../../node_modules/framer-motion/dist/es/components/AnimatePresence/use-presence.mjs"() {
"use strict";
import_react10 = require("react");
init_PresenceContext();
}
});
// ../../node_modules/framer-motion/dist/es/components/AnimatePresence/utils.mjs
function onlyElements(children) {
const filtered = [];
import_react11.Children.forEach(children, (child) => {
if ((0, import_react11.isValidElement)(child))
filtered.push(child);
});
return filtered;
}
var import_react11, getChildKey;
var init_utils = __esm({
"../../node_modules/framer-motion/dist/es/components/AnimatePresence/utils.mjs"() {
"use strict";
import_react11 = require("react");
getChildKey = (child) => child.key || "";
}
});
// ../../node_modules/framer-motion/dist/es/utils/is-browser.mjs
var isBrowser;
var init_is_browser = __esm({
"../../node_modules/framer-motion/dist/es/utils/is-browser.mjs"() {
"use strict";
isBrowser = typeof window !== "undefined";
}
});
// ../../node_modules/framer-motion/dist/es/utils/use-isomorphic-effect.mjs
var import_react12, useIsomorphicLayoutEffect;
var init_use_isomorphic_effect = __esm({
"../../node_modules/framer-motion/dist/es/utils/use-isomorphic-effect.mjs"() {
"use strict";
import_react12 = require("react");
init_is_browser();
useIsomorphicLayoutEffect = isBrowser ? import_react12.useLayoutEffect : import_react12.useEffect;
}
});
// ../../node_modules/framer-motion/dist/es/components/AnimatePresence/index.mjs
var import_jsx_runtime7, import_react13, AnimatePresence;
var init_AnimatePresence = __esm({
"../../node_modules/framer-motion/dist/es/components/AnimatePresence/index.mjs"() {
"use strict";
"use client";
import_jsx_runtime7 = require("react/jsx-runtime");
import_react13 = require("react");
init_LayoutGroupContext();
init_use_constant();
init_PresenceChild();
init_use_presence();
init_utils();
init_use_isomorphic_effect();
AnimatePresence = ({ children, custom, initial = true, onExitComplete, presenceAffectsLayout = true, mode = "sync", propagate = false }) => {
const [isParentPresent, safeToRemove] = usePresence(propagate);
const presentChildren = (0, import_react13.useMemo)(() => onlyElements(children), [children]);
const presentKeys = propagate && !isParentPresent ? [] : presentChildren.map(getChildKey);
const isInitialRender = (0, import_react13.useRef)(true);
const pendingPresentChildren = (0, import_react13.useRef)(presentChildren);
const exitComplete = useConstant(() => /* @__PURE__ */ new Map());
const [diffedChildren, setDiffedChildren] = (0, import_react13.useState)(presentChildren);
const [renderedChildren, setRenderedChildren] = (0, import_react13.useState)(presentChildren);
useIsomorphicLayoutEffect(() => {
isInitialRender.current = false;
pendingPresentChildren.current = presentChildren;
for (let i = 0; i < renderedChildren.length; i++) {
const key = getChildKey(renderedChildren[i]);
if (!presentKeys.includes(key)) {
if (exitComplete.get(key) !== true) {
exitComplete.set(key, false);
}
} else {
exitComplete.delete(key);
}
}
}, [renderedChildren, presentKeys.length, presentKeys.join("-")]);
const exitingChildren = [];
if (presentChildren !== diffedChildren) {
let nextChildren = [...presentChildren];
for (let i = 0; i < renderedChildren.length; i++) {
const child = renderedChildren[i];
const key = getChildKey(child);
if (!presentKeys.includes(key)) {
nextChildren.splice(i, 0, child);
exitingChildren.push(child);
}
}
if (mode === "wait" && exitingChildren.length) {
nextChildren = exitingChildren;
}
setRenderedChildren(onlyElements(nextChildren));
setDiffedChildren(presentChildren);
return;
}
if (mode === "wait" && renderedChildren.length > 1) {
console.warn(`You're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.`);
}
const { forceRender } = (0, import_react13.useContext)(LayoutGroupContext);
return (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: renderedChildren.map((child) => {
const key = getChildKey(child);
const isPresent = propagate && !isParentPresent ? false : presentChildren === renderedChildren || presentKeys.includes(key);
const onExit = () => {
if (exitComplete.has(key)) {
exitComplete.set(key, true);
} else {
return;
}
let isEveryExitComplete = true;
exitComplete.forEach((isExitComplete) => {
if (!isExitComplete)
isEveryExitComplete = false;
});
if (isEveryExitComplete) {
forceRender === null || forceRender === void 0 ? void 0 : forceRender();
setRenderedChildren(pendingPresentChildren.current);
propagate && (safeToRemove === null || safeToRemove === void 0 ? void 0 : safeToRemove());
onExitComplete && onExitComplete();
}
};
return (0, import_jsx_runtime7.jsx)(PresenceChild, { isPresent, initial: !isInitialRender.current || initial ? void 0 : false, custom: isPresent ? void 0 : custom, presenceAffectsLayout, mode, onExitComplete: isPresent ? void 0 : onExit, children: child }, key);
}) });
};
}
});
// ../../node_modules/motion-utils/dist/es/noop.mjs
var noop;
var init_noop = __esm({
"../../node_modules/motion-utils/dist/es/noop.mjs"() {
"use strict";
noop = /* @__NO_SIDE_EFFECTS__ */ (any) => any;
}
});
// ../../node_modules/motion-utils/dist/es/errors.mjs
var warning, invariant;
var init_errors = __esm({
"../../node_modules/motion-utils/dist/es/errors.mjs"() {
"use strict";
init_noop();
warning = noop;
invariant = noop;
if (true) {
warning = (check, message) => {
if (!check && typeof console !== "undefined") {
console.warn(message);
}
};
invariant = (check, message) => {
if (!check) {
throw new Error(message);
}
};
}
}
});
// ../../node_modules/motion-utils/dist/es/memo.mjs
// @__NO_SIDE_EFFECTS__
function memo(callback) {
let result;
return () => {
if (result === void 0)