diff --git a/README.md b/README.md index a726a85ce..47be052e9 100644 --- a/README.md +++ b/README.md @@ -284,4 +284,6 @@ Cognotik uses a "Bring Your Own Key" (BYOK) model for all AI service integration - OpenAI, Anthropic, and other AI providers for their powerful models - JetBrains for the IntelliJ platform -- The open-source community for various libraries and tools used in the project \ No newline at end of file +- The open-source community for various libraries and tools used in the project +## Improvements +This is a small improvement to the documentation. diff --git a/core/src/main/kotlin/com/simiacryptus/cognotik/util/FileSelectionUtils.kt b/core/src/main/kotlin/com/simiacryptus/cognotik/util/FileSelectionUtils.kt index 1c8019959..cffc97b6a 100644 --- a/core/src/main/kotlin/com/simiacryptus/cognotik/util/FileSelectionUtils.kt +++ b/core/src/main/kotlin/com/simiacryptus/cognotik/util/FileSelectionUtils.kt @@ -5,11 +5,91 @@ import java.io.File import java.io.InputStream import java.nio.file.Path import java.util.* +import kotlin.text.StringBuilder import kotlin.io.path.name class FileSelectionUtils { companion object { val log = LoggerFactory.getLogger(FileSelectionUtils::class.java) + /** + * Creates an ASCII-art formatted compact tree listing of files and directories + * starting from the given root file, subject to filtering and depth constraints. + * + * @param rootFile The starting file or directory. + * @param maxFilesPerDir The maximum number of entries to process in any single directory. + * @param fn A filter function that determines whether a file or directory should be included in the tree. + * @return A string representing the ASCII tree of matching files. + */ + fun filteredWalkAsciiTree( + rootFile: File, + maxFilesPerDir: Int = 20, + fn: (File) -> Boolean = { !isLLMIgnored(it.toPath()) } + ): String { + val sb = StringBuilder() + if (!fn(rootFile)) { + log.debug("Skipping root file for tree: ${rootFile.absolutePath}") + return "" // Root itself doesn't match, so empty tree + } + sb.append(rootFile.name) + if (rootFile.isDirectory) { + sb.append("/") + } + sb.appendLine() + if (rootFile.isDirectory) { + val children = rootFile.listFiles()?.toList() ?: emptyList() + val entriesToConsider = children.take(maxFilesPerDir) + entriesToConsider.forEachIndexed { index, child -> + buildAsciiSubTree( + child, + "", // Initial parentContinuationPrefix for children of the root + index == entriesToConsider.size - 1, + maxFilesPerDir, + fn, + sb + ) + } + } + return sb.toString() + } + private fun buildAsciiSubTree( + currentFile: File, + parentContinuationPrefix: String, // Prefix like "│ " or " " + isLastInSiblings: Boolean, + maxFilesPerDir: Int, + filterFn: (File) -> Boolean, + sb: StringBuilder + ) { + if (!filterFn(currentFile)) { + // If the current file is filtered out, do not display it or its children. + // log.debug("Skipping in tree (sub): ${currentFile.absolutePath}") // Optional: for more verbose logging + return + } + sb.append(parentContinuationPrefix) + sb.append(if (isLastInSiblings) "└── " else "├── ") + sb.append(currentFile.name) + if (currentFile.isDirectory) { + sb.append("/") + } + sb.appendLine() + if (currentFile.isDirectory) { + val children = currentFile.listFiles()?.toList() ?: emptyList() + val entriesToConsider = children.take(maxFilesPerDir) + // The new continuation prefix for the children of currentFile + val childContinuationPrefix = parentContinuationPrefix + (if (isLastInSiblings) " " else "│ ") + entriesToConsider.forEachIndexed { index, child -> + buildAsciiSubTree( + child, + childContinuationPrefix, + index == entriesToConsider.size - 1, + maxFilesPerDir, + filterFn, + sb + ) + } + } + } + + fun filteredWalk( file: File, maxFilesPerDir: Int = 20, @@ -139,6 +219,7 @@ class FileSelectionUtils { path.name == "node_modules" -> return true path.name == "target" -> return true path.name == "build" -> return true + path.name == ".git" -> return true } var currentDir = path.toFile().parentFile currentDir ?: return false diff --git a/demo/demo_projects/DataGnome/src/main/kotlin/src/utils/StringManipulationUtils.js b/demo/demo_projects/DataGnome/src/main/kotlin/src/utils/StringManipulationUtils.js index 458eb4d56..0fc13f306 100644 --- a/demo/demo_projects/DataGnome/src/main/kotlin/src/utils/StringManipulationUtils.js +++ b/demo/demo_projects/DataGnome/src/main/kotlin/src/utils/StringManipulationUtils.js @@ -1,3 +1,4 @@ +// Enhanced on 2025-05-08 17:48:16 - Minor improvements // New utility class for string manipulation class StringManipulationUtils { @@ -147,4 +148,4 @@ class StringManipulationUtils { } } -module.exports = StringManipulationUtils; \ No newline at end of file +module.exports = StringManipulationUtils; diff --git a/desktop/README.md b/desktop/README.md index 66176a636..07b51b3b4 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -3,8 +3,9 @@ ## Overview The Cognotik Application Server is a modular platform for hosting AI-powered applications. It provides a daemon-based -architecture that allows applications to run in the background and be accessed via a web interface. The server includes -system tray integration, socket-based communication for remote control, and various AI-powered applications. +architecture that allows applications to run in the background and be accessed via a web interface or system tray. +The server includes system tray integration, socket-based communication for remote control, automatic updates, +and various AI-powered applications configurable through a setup wizard. ## Open Source & API Key Requirements @@ -20,19 +21,18 @@ model for AI services: ### AppServer -The main server component that hosts web applications and provides the core functionality. +The main server component that hosts web applications, manages the system tray, handles updates, and provides the core functionality. **Key Features:** -- Web server hosting multiple AI applications -- System tray integration for easy access -- Socket-based communication for remote control -- Authentication and authorization management +- Welcome wizard for initial setup and session configuration +- User settings management (API Keys, Local Tools) via web UI +- Socket server for communication with DaemonClient **Usage:** ``` -java -cp com.simiacryptus.cognotik.AppServer server [options] +java -cp com.simiacryptus.cognotik.AppServer [options] ``` **Options:** @@ -69,22 +69,31 @@ Manages the system tray icon and menu for easy access to the applications. **Key Features:** -- System tray icon with context menu -- Quick access to hosted applications -- Server shutdown option +- Quick access to hosted applications via browser +- Option to check for and install updates + +### UpdateManager +Handles checking for new application versions, downloading updates, and launching the appropriate installer for the detected operating system (Windows, macOS, Linux). +**Key Features:** +- Checks GitHub releases for the latest version +- Compares latest version with the current running version +- Downloads platform-specific installers (MSI, DMG, DEB) +- Provides UI prompts for update confirmation and progress +- Manages installer execution (including uninstallation steps where needed) + ## Included Applications -The server hosts several AI-powered applications: +The server hosts several AI-powered applications, primarily accessed through a central welcome wizard or the system tray: -1. **Basic Chat** (`/chat`): A simple chat interface for interacting with AI models. +1. **Basic Chat** (`/chat`): A simple, standalone chat interface accessible via a button in the welcome page header. Allows configuring model, temperature, and budget separately. -2. **Task Runner** (`/singleTask`): A single-task execution environment using the `SingleTaskMode` cognitive strategy. +2. **Task Chat** (`/taskChat`): Launched from the welcome wizard ("Chat" mode). An interactive environment for executing individual tasks using the `TaskChatMode` cognitive strategy. Configurable via the wizard. -3. **Auto Plan** (`/autoPlan`): An application that automatically plans and executes tasks using the `AutoPlanMode` +3. **Autonomous Mode** (`/autoPlan`): Launched from the welcome wizard ("Autonomous" mode). An application that automatically plans and executes tasks using the `AutoPlanMode` cognitive strategy. -4. **Plan Ahead** (`/planAhead`): An application that plans ahead before executing tasks using the `PlanAheadMode` +4. **Plan Ahead Mode** (`/planAhead`): Launched from the welcome wizard ("Plan Ahead" mode). An application that plans ahead before executing tasks using the `PlanAheadMode` cognitive strategy. ## Installation @@ -106,11 +115,11 @@ Each package includes: ### Manual Installation 1. Build the project using Gradle: - ``` + ```bash ./gradlew shadowJar ``` -2. Run the application: +2. Run the application (starts the DaemonClient, which manages the AppServer): ``` java -jar build/libs/cognotik--all.jar ``` @@ -120,18 +129,18 @@ Each package includes: ### Building from Source 1. Clone the repository -2. Build using Gradle: +2. Build the project (including the shadow JAR): ``` - ./gradlew build + ./gradlew shadowJar ``` ### Running in Development Mode ``` -./gradlew runServer +./gradlew run ``` -### Creating Platform Packages +### Creating Platform-Specific Packages ``` ./gradlew package @@ -149,18 +158,19 @@ The application follows a modular architecture: 1. **DaemonClient**: Entry point that manages the server process 2. **AppServer**: Core server that hosts web applications -3. **SystemTrayManager**: UI integration via system tray -4. **Web Applications**: Individual applications hosted by the server +3. **SystemTrayManager**: UI integration via system tray (part of AppServer) +4. **UpdateManager**: Handles application updates (used by SystemTrayManager/AppServer) +5. **Web Applications**: Individual applications hosted by the server (e.g., BasicChatApp, UnifiedPlanApp) +6. **Welcome Wizard (`welcome.html`)**: Initial configuration and session launch UI. Communication between components: -- Socket-based communication between DaemonClient and AppServer -- HTTP/WebSocket for web applications -- System tray for user interaction +- GitHub API for update checks ## Configuration -The server uses sensible defaults but can be configured via command-line options. It automatically finds available ports +The server uses sensible defaults but can be configured via command-line options (passed to `AppServer` via `DaemonClient`) +and a web-based welcome wizard. It automatically finds available ports if the default ports are in use. ### API Key Configuration @@ -168,16 +178,29 @@ if the default ports are in use. You'll need to configure your API keys before using the AI features: 1. Launch the application -2. Access the settings via the system tray menu or web interface -3. Enter your API keys for the services you want to use (OpenAI, Anthropic, etc.) -4. Save your configuration - Your API keys are stored locally and are only used to authenticate with the respective services. +2. The welcome wizard (`/`) will load automatically. +3. Click the "User Settings" button in the top-right menu bar. +4. In the modal dialog: + - Go to the "API Keys" tab. + - Enter your API keys for the services you want to use (OpenAI, Anthropic, Groq, etc.). + - Go to the "Local Tools" tab (optional). + - Add paths to any local command-line tools you want the AI to be able to use. + - Click "Save Settings". +5. Your API keys and tool paths are stored locally and are only used to authenticate with the respective services or execute the specified tools. + +### Session Configuration (via Welcome Wizard) + +Before launching an AI session, the welcome wizard guides you through: +- **Choosing a Cognitive Mode:** Chat, Autonomous, or Plan Ahead. +- **Configuring Settings:** Selecting default and parsing AI models, setting the working directory, adjusting temperature, and enabling/disabling auto-fix. +- **Selecting Tasks:** Enabling specific capabilities like file modification, shell execution, web search, etc. ## Troubleshooting - **Server not starting**: Check if another instance is already running - **Port conflicts**: The server will automatically find an available port - **System tray not showing**: Ensure your system supports system tray icons +To check for updates manually, use the "Update to..." option in the system tray menu (if an update is available). To stop a running server: diff --git a/desktop/src/main/kotlin/com/simiacryptus/cognotik/AppServer.kt b/desktop/src/main/kotlin/com/simiacryptus/cognotik/AppServer.kt index cd621c57c..79c587a89 100644 --- a/desktop/src/main/kotlin/com/simiacryptus/cognotik/AppServer.kt +++ b/desktop/src/main/kotlin/com/simiacryptus/cognotik/AppServer.kt @@ -6,6 +6,7 @@ import com.simiacryptus.cognotik.plan.PlanSettings import com.simiacryptus.cognotik.plan.cognitive.AutoPlanMode import com.simiacryptus.cognotik.plan.cognitive.PlanAheadMode import com.simiacryptus.cognotik.plan.cognitive.TaskChatMode +import com.simiacryptus.cognotik.plan.cognitive.GoalOrientedMode import com.simiacryptus.cognotik.platform.ApplicationServices import com.simiacryptus.cognotik.platform.file.AuthorizationManager import com.simiacryptus.cognotik.platform.model.AuthenticationInterface @@ -76,6 +77,10 @@ open class AppServer( } private var server: AppServer? = null + + + + private fun handleServer(vararg args: String) { log.info("Parsing server options...") val options = parseServerOptions(*args) @@ -83,7 +88,6 @@ open class AppServer( var actualPort = options.port try { - ServerSocket(actualPort).use { log.debug("Port $actualPort is available") } @@ -94,8 +98,8 @@ open class AppServer( log.info("Using alternative port $actualPort") println("Using alternative port $actualPort") } - scheduledExecutorService.scheduleAtFixedRate({checkUpdate()}, - 0, 7*24, TimeUnit.HOURS) + scheduledExecutorService.scheduleAtFixedRate({ checkUpdate() }, + 0, 7 * 24, TimeUnit.HOURS) server = AppServer( localName = options.host, publicName = options.publicName, @@ -104,12 +108,16 @@ open class AppServer( server?.initSystemTray() server?.startSocketServer(actualPort + 1) - Runtime.getRuntime().addShutdownHook(Thread { log.info("Shutdown hook triggered, stopping server...") server?.stopServer() }) - server?._main(*args) + // Call _main with NO server options (strip out --port/--host/--public-name and their values) + val filteredArgs = args.filterIndexed { i, arg -> + arg in listOf("--port", "--host", "--public-name") || + (i > 0 && args[i - 1] in listOf("--port", "--host", "--public-name")) + }.toTypedArray() + server?._main(*filteredArgs) } private fun findAvailablePort(startPort: Int): Int { @@ -250,7 +258,7 @@ open class AppServer( workingDir = "." ) listOf( - ChildWebApp("/chat", BasicChatApp(".".toFile(), model, parsingModel)), + ChildWebApp("/chat", BasicChatApp(File("."), model, parsingModel)), ChildWebApp( "/taskChat", UnifiedPlanApp( path = "/taskChat", @@ -286,6 +294,18 @@ open class AppServer( cognitiveStrategy = PlanAheadMode, describer = describer ) + ), + ChildWebApp( + "/goalOriented", UnifiedPlanApp( + path = "/goalOriented", + applicationName = "Goal-Oriented", + planSettings = planSettings, + model = model, + parsingModel = parsingModel, + api2 = api2, + cognitiveStrategy = GoalOrientedMode, + describer = describer + ) ) ) } @@ -402,14 +422,13 @@ open class AppServer( } -private fun String.toFile(): File = File(this) + + fun String?.urlEncode(): String { return this?.let { - URLEncoder.encode(it, "UTF-8") + URLEncoder.encode(it, Charsets.UTF_8.name()) .replace("+", "%20") - .replace("%7E", "~") - } ?: "" } \ No newline at end of file diff --git a/desktop/src/main/resources/logback.xml b/desktop/src/main/resources/logback.xml index e68cb1ae0..676ad6504 100644 --- a/desktop/src/main/resources/logback.xml +++ b/desktop/src/main/resources/logback.xml @@ -34,8 +34,27 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + chat_traffic.log + + + chat_traffic.%d{yyyy-MM-dd}.log + 30 + true + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/desktop/src/main/resources/welcome/welcome.css b/desktop/src/main/resources/welcome/welcome.css new file mode 100644 index 000000000..e311aa196 --- /dev/null +++ b/desktop/src/main/resources/welcome/welcome.css @@ -0,0 +1,528 @@ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + max-width: 800px; + margin: 0 auto; + padding: 20px; + background-color: #f9f9f9; +} + +h1 { + color: #2c3e50; + border-bottom: 2px solid #3498db; + padding-bottom: 10px; +} + +.logo-container { + display: flex; + align-items: center; + margin-right: 15px; +} + +.logo { + width: 40px; + height: 40px; + margin-right: 10px; +} + +a { + color: #3498db; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + margin-bottom: 5px; + font-weight: bold; +} + +.form-group input[type="text"], +.form-group textarea, +.form-group select { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.form-group textarea { + height: 150px; + font-family: monospace; +} + +.button-group { + display: flex; + justify-content: space-between; + margin-top: 20px; +} + +.wizard-nav { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + padding: 10px 0; + border-bottom: 1px solid #ddd; + background-color: white; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); +} + +.wizard-step { + display: flex; + align-items: center; + margin-right: 15px; + transition: all 0.3s ease; +} + +.step-number { + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 50%; + background-color: #ddd; + color: #555; + margin-right: 10px; + font-weight: bold; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.active .step-number { + background-color: #3498db; + color: white; +} + +.active .step-text { + font-weight: bold; + color: #3498db; +} + +.wizard-content { + display: none; + background-color: white; + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + margin-bottom: 20px; +} + +.wizard-content.active { + display: block; +} + +.api-settings-btn { + position: fixed; + top: 20px; + right: 20px; + background-color: #3498db; + color: white; + border: none; + border-radius: 4px; + padding: 8px 15px; + cursor: pointer; + z-index: 100; +} + +.api-settings-btn:hover { + background-color: #2980b9; +} + +.menu-bar { + position: fixed; + top: 0; + right: 0; + display: flex; + align-items: center; + z-index: 100; + gap: 10px; + background-color: rgba(255, 255, 255, 0.9); + width: 100%; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.menu-button { + background-color: #3498db; + color: white; + border: none; + border-radius: 4px; + padding: 8px 15px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.menu-button:hover { + background-color: #2980b9; +} + +/* Add padding to the top to prevent content from being hidden under the menu bar */ +body { + padding-top: 50px; +} + +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.4); +} + +.modal-content { + background-color: #fefefe; + margin: 5% auto; + padding: 20px; + border-radius: 5px; + width: 80%; + max-width: 800px; + max-height: 80vh; + overflow-y: auto; +} + +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; +} + +.close:hover { + color: black; +} + +.wizard-buttons { + display: flex; + justify-content: space-between; + margin-top: 20px; +} + +.button { + background-color: #3498db; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.2s ease, transform 0.1s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + font-size: 16px; +} + +.button:hover { + background-color: #2980b9; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.button:disabled { + background-color: #bdc3c7; + cursor: not-allowed; +} + +.button.secondary { + background-color: #95a5a6; +} + +.button.secondary:hover { + background-color: #7f8c8d; +} + +.api-key-group { + display: flex; + margin-bottom: 10px; +} + +.api-key-group label { + width: 120px; + padding-top: 8px; +} + +.api-key-group input { + flex-grow: 1; +} +/* Settings Tabs */ +.settings-tabs { + display: flex; + border-bottom: 1px solid #ddd; + margin-bottom: 20px; +} +.tab-button { + background-color: #f1f1f1; + border: none; + outline: none; + cursor: pointer; + padding: 10px 20px; + transition: 0.3s; + font-size: 16px; + border-radius: 5px 5px 0 0; +} +.tab-button:hover { + background-color: #ddd; +} +.tab-button.active { + background-color: #3498db; + color: white; +} +.tab-content { + display: none; + padding: 15px 0; +} +.tab-content.active { + display: block; +} +/* Local Tools */ +#local-tools-container { + border: 1px solid #ddd; + border-radius: 4px; + padding: 10px; + margin-bottom: 15px; +} +#local-tools-list { + max-height: 200px; + overflow-y: auto; + margin-bottom: 10px; +} +.tool-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + border-bottom: 1px solid #eee; +} +.tool-item:last-child { + border-bottom: none; +} +.tool-actions { + display: flex; + gap: 10px; +} +.tool-actions input { + flex-grow: 1; + padding: 8px; +} +.remove-tool { + background-color: #e74c3c; + color: white; + border: none; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; +} +.remove-tool:hover { + background-color: #c0392b; +} + + +.task-toggle { + display: flex; + justify-content: space-between; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + margin-bottom: 5px; + transition: background-color 0.2s ease, transform 0.1s ease; +} + +.task-toggle:hover { + background-color: #f5f5f5; + transform: translateX(2px); +} + +.tooltip { + position: relative; + display: inline-block; + margin-left: 5px; +} + +.tooltip .tooltiptext { + visibility: hidden; + width: 300px; + background-color: #555; + color: #fff; + text-align: left; + border-radius: 6px; + padding: 5px 10px; + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + margin-left: -150px; + opacity: 0; + transition: opacity 0.3s; +} + +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.loading { + display: none; + text-align: center; + padding: 20px; +} + +.spinner { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 2s linear infinite; + margin: 0 auto 10px; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Responsive improvements */ +@media (max-width: 600px) { + .wizard-nav { + flex-direction: column; + } + + .wizard-step { + margin-bottom: 5px; + border-radius: 4px; + } + + .api-key-group { + flex-direction: column; + } + + .api-key-group label { + width: 100%; + margin-bottom: 5px; + } + + .button-group { + flex-direction: column; + } + + .wizard-buttons { + flex-direction: column; + } + + .button { + margin-bottom: 10px; + } + + .logo-container { + margin-right: 5px; + } + + .logo { + width: 30px; + height: 30px; + } + + .menu-bar { + padding: 5px; + } + + .button { + margin-bottom: 10px; + } +} + +/* Accessibility improvements */ +.button:focus, input:focus, select:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.5); +} + +/* Footer styling */ +.footer-content { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.footer-logo { + vertical-align: middle; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + body { + background-color: #1a1a1a; + color: #f0f0f0; + } + + .card, .modal-content, .wizard-content, .wizard-nav { + background-color: #2a2a2a; + color: #f0f0f0; + } + + .menu-bar { + background-color: rgba(42, 42, 42, 0.9); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); + } + + .close { + color: #ddd; + } + + .close:hover { + color: white; + } + + h1 { + color: #3498db; + } + + .task-toggle, .form-group input, .form-group select { + background-color: #333; + color: #f0f0f0; + border-color: #555; + } + + .task-toggle:hover { + background-color: #444; + } + + .button.secondary { + background-color: #555; + } + + .button.secondary:hover { + background-color: #666; + } + + .step-number { + background-color: #444; + color: #ddd; + } + + .active .step-number { + background-color: #3498db; + color: white; + } + + .modal { + background-color: rgba(0, 0, 0, 0.7); + } +} \ No newline at end of file diff --git a/desktop/src/main/resources/welcome/welcome.html b/desktop/src/main/resources/welcome/welcome.html index 9d57410ab..880e92cdf 100644 --- a/desktop/src/main/resources/welcome/welcome.html +++ b/desktop/src/main/resources/welcome/welcome.html @@ -4,537 +4,8 @@ + Cognotik - @@ -589,19 +60,19 @@

Basic Chat Settings

- +
@@ -645,6 +116,10 @@

User Settings

+
+
+ +

Step 1: Choose Cognitive Mode

Select how you want the AI to approach your tasks:

@@ -675,6 +150,15 @@

Step 1: Choose Cognitive Mode

+
+
+ + + ? + Work with AI in a goal-oriented mode, focusing on achieving a specific outcome with iterative planning and execution. + +
+
-
-
- -
+
+ + +

Step 2: Configure Settings

Configure model and environment settings:

@@ -719,7 +203,10 @@

Step 2: Configure Settings

- +
+ + +
@@ -745,36 +232,26 @@

Step 2: Configure Settings

-
- - -
+
+ + +

Step 3: Select Tasks

Choose which tasks you want to enable:

-
- - -
- - - - -
-

Step 4: Review & Launch

-

Review your configuration and launch your AI development session:

-
+

Step 4: Review & Launch

+

Review your configuration and launch your AI development session:

Cognitive Mode

@@ -804,1271 +281,6 @@

API Settings

Cognotik © 2025 SimiaCryptus Software

- + \ No newline at end of file diff --git a/desktop/src/main/resources/welcome/welcome.js b/desktop/src/main/resources/welcome/welcome.js new file mode 100644 index 000000000..1a22b43d7 --- /dev/null +++ b/desktop/src/main/resources/welcome/welcome.js @@ -0,0 +1,1342 @@ +function generateSessionId() { + console.log('[generateSessionId] Called'); + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + + const randomChars = Math.random().toString(36).substring(2, 6); + const sessionId = `U-${year}${month}${day}-${randomChars}`; + console.log('[generateSessionId] Generated sessionId:', sessionId); + return sessionId; +} + +let sessionId = generateSessionId(); +console.log('[Global] Initial sessionId:', sessionId); +let apiSettings = {}; +let taskSettings = { + defaultModel: localStorage.getItem('defaultModel') || 'GPT4o', + parsingModel: localStorage.getItem('parsingModel') || 'GPT4oMini', + workingDir: localStorage.getItem('workingDir') || generateTimestampedDirectory(), + autoFix: localStorage.getItem('autoFix') === 'true', + maxTaskHistoryChars: 20000, + maxTasksPerIteration: 3, + maxIterations: 100, + graphFile: '', + taskSettings: {}, +}; +let cognitiveMode = localStorage.getItem('cognitiveMode') || 'single-task'; +console.log('[Global] Initial taskSettings:', JSON.parse(JSON.stringify(taskSettings))); // Deep copy for logging +console.log('[Global] Initial cognitiveMode:', cognitiveMode); + +const taskTypes = [{ + id: 'InsightTask', + name: 'Insight Task', + description: 'Analyze code and provide detailed explanations of implementation patterns', + tooltip: 'Provides detailed answers and insights about code implementation by analyzing specified files.', +}, { + id: 'FileModificationTask', + name: 'File Modification Task', + description: 'Create new files or modify existing code with AI-powered assistance', + tooltip: 'Creates or modifies source files with AI assistance while maintaining code quality.', +}, { + id: 'RunShellCommandTask', + name: 'Run Shell Command Task', + description: 'Execute shell commands safely', + tooltip: 'Executes shell commands in a controlled environment.', +}, { + id: 'RunCodeTask', + name: 'Run Code Task', + description: 'Execute code snippets with AI assistance', + tooltip: 'Executes code snippets with AI assistance while maintaining code quality.', +}, { + id: 'CommandAutoFixTask', + name: 'Command Auto Fix Task', + description: 'Run a command and automatically fix any issues that arise', + tooltip: 'Executes a command and automatically fixes any issues that arise.', +}, { + id: 'FileSearchTask', + name: 'File Search Task', + description: 'Search project files using patterns with contextual results', + tooltip: 'Performs pattern-based searches across project files with context.', +}, { + id: 'CrawlerAgentTask', + name: 'Web Search Task', + description: 'Search Google, fetch top results, and analyze content', + tooltip: 'Searches Google for specified queries and analyzes the top results.', +}, { + id: 'GitHubSearchTask', + name: 'GitHub Search Task', + description: 'Search GitHub repositories, code, issues and users', + tooltip: 'Performs comprehensive searches across GitHub\'s content.', +}, /* + { + id: 'TaskPlanningTask', + name: 'Task Planning Task', + description: 'Break down and coordinate complex development tasks with dependency management', + tooltip: 'Orchestrates complex development tasks by breaking them down into manageable subtasks.', + }, + { + id: 'ForeachTask', + name: 'Foreach Task', + description: 'Execute subtasks for each item in a list', + tooltip: 'Executes a set of subtasks for each item in a given list.', + }, + { + id: 'KnowledgeIndexingTask', + name: 'Knowledge Indexing Task', + description: 'Index content for semantic search capabilities', + tooltip: 'Indexes documents and code for semantic search capabilities.', + }, + { + id: 'EmbeddingSearchTask', + name: 'Embedding Search Task', + description: 'Perform semantic search using AI embeddings', + tooltip: 'Performs semantic search using AI embeddings across indexed content.', + }, + { + id: 'SeleniumSessionTask', + name: 'Selenium Session Task', + description: 'Automate browser interactions with Selenium', + tooltip: 'Automates browser interactions using Selenium WebDriver.', + }, + { + id: 'CommandSessionTask', + name: 'Command Session Task', + description: 'Manage interactive command-line sessions', + tooltip: 'Manages interactive command-line sessions with state persistence.', + }, + { + id: 'SoftwareGraphPlanningTask', + name: 'Software Graph Planning Task', + description: 'Generate and execute task plans based on software graph structure', + tooltip: 'Creates task plans using software graph context.', + }, + { + id: 'SoftwareGraphModificationTask', + name: 'Software Graph Modification Task', + description: 'Modify an existing software graph representation', + tooltip: 'Loads, modifies and saves software graph representations.', + }, + { + id: 'SoftwareGraphGenerationTask', + name: 'Software Graph Generation Task', + description: 'Generate a SoftwareGraph representation of the codebase', + tooltip: 'Generates a comprehensive SoftwareGraph representation of the codebase.', + }, + { + id: 'DataTableCompilationTask', + name: 'Data Table Compilation Task', + description: 'Compile structured data tables from multiple files', + tooltip: 'Extracts and compiles structured data from multiple files into a unified table.', + }, + */]; + +const apiProviders = [{id: 'OpenAI', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1'}, { + id: 'Anthropic', name: 'Anthropic', baseUrl: 'https://api.anthropic.com/v1' +}, {id: 'Google', name: 'Google', baseUrl: 'https://generativelanguage.googleapis.com'}, { + id: 'Groq', name: 'Groq', baseUrl: 'https://api.groq.com/openai/v1' +}, {id: 'Mistral', name: 'Mistral', baseUrl: 'https://api.mistral.ai/v1'}, {id: 'AWS', name: 'AWS', baseUrl: 'https://api.openai.aws'}, { + id: 'DeepSeek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com' +}, {id: 'Github', name: 'GitHub', baseUrl: 'https://api.github.com'}, {id: 'GoogleSearch', name: 'Google Search', baseUrl: ''},]; + +function generateTimestampedDirectory() { + console.log('[generateTimestampedDirectory] Called'); + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + const dir = `sessions/${year}${month}${day}${hours}${minutes}${seconds}`; + console.log('[generateTimestampedDirectory] Generated directory:', dir); + return dir; +} + +const availableModels = { + OpenAI: [{id: 'GPT4o', name: 'GPT-4o', description: 'OpenAI\'s capable vision model'}, { + id: 'GPT4oMini', name: 'GPT-4o Mini', description: 'Smaller, faster version of GPT-4o' + }, {id: 'O1', name: 'o1', description: 'OpenAI\'s reasoning-focused model'}, { + id: 'O1Mini', name: 'o1-mini', description: 'Smaller version of o1' + }, {id: 'O1Preview', name: 'o1-preview', description: 'Preview version of o1'}, { + id: 'O3', name: 'o3', description: 'OpenAI\'s advanced reasoning model' + }, {id: 'O3Mini', name: 'o3-mini', description: 'Smaller version of o3'}, { + id: 'O4Mini', name: 'o4-mini', description: 'Latest mini reasoning model' + }, {id: 'GPT41', name: 'GPT-4.1', description: 'Latest GPT-4 series model'}, { + id: 'GPT41Mini', name: 'GPT-4.1 Mini', description: 'Smaller version of GPT-4.1' + }, {id: 'GPT41Nano', name: 'GPT-4.1 Nano', description: 'Smallest version of GPT-4.1'}, { + id: 'GPT45', name: 'GPT-4.5', description: 'Advanced preview model' + },], + Anthropic: [{id: 'Claude35Sonnet', name: 'Claude 3.5 Sonnet', description: 'Anthropic\'s advanced model'}, { + id: 'Claude37Sonnet', name: 'Claude 3.7 Sonnet', description: 'Anthropic\'s latest model' + }, {id: 'Claude35Haiku', name: 'Claude 3.5 Haiku', description: 'Smaller, faster Claude model'}, { + id: 'Claude3Opus', name: 'Claude 3 Opus', description: 'Anthropic\'s most capable model' + }, {id: 'Claude3Sonnet', name: 'Claude 3 Sonnet', description: 'Balanced Claude model'}, { + id: 'Claude3Haiku', name: 'Claude 3 Haiku', description: 'Fast, efficient Claude model' + },], + Groq: [{id: 'Llama33_70bVersatile', name: 'Llama 3.3 70B Versatile', description: 'Fast Llama 3.3 inference'}, { + id: 'Llama33_70bSpecDec', name: 'Llama 3.3 70B SpecDec', description: 'Specialized Llama 3.3 model' + }, {id: 'Llama31_8bInstant', name: 'Llama 3.1 8B Instant', description: 'Fast, small Llama model'}, { + id: 'Gemma2_9b', name: 'Gemma 2 9B', description: 'Google\'s Gemma model on Groq' + }, {id: 'MistralSaba24b', name: 'Mistral Saba 24B', description: 'Mistral\'s Saba model'}, { + id: 'Qwen25_32b', name: 'Qwen 2.5 32B', description: 'Qwen model on Groq' + },], + Mistral: [{id: 'Mistral7B', name: 'Mistral 7B', description: 'Mistral\'s base model'}, { + id: 'MistralSmall', name: 'Mistral Small', description: 'Mistral\'s small model' + }, {id: 'MistralMedium', name: 'Mistral Medium', description: 'Mistral\'s medium model'}, { + id: 'MistralLarge', name: 'Mistral Large', description: 'Mistral\'s large model' + }, {id: 'Mixtral8x7B', name: 'Mixtral 8x7B', description: 'Mistral\'s Mixtral model'}, { + id: 'Mixtral8x22B', name: 'Mixtral 8x22B', description: 'Mistral\'s larger Mixtral model' + }, {id: 'Codestral', name: 'Codestral', description: 'Mistral\'s code-focused model'},], + DeepSeek: [{id: 'DeepSeekChat', name: 'DeepSeek Chat', description: 'DeepSeek\'s general chat model'}, { + id: 'DeepSeekCoder', name: 'DeepSeek Coder', description: 'DeepSeek\'s code-focused model' + }, {id: 'DeepSeekReasoner', name: 'DeepSeek Reasoner', description: 'DeepSeek\'s reasoning model'},], + AWS: [{id: 'AWSLLaMA31_405bChat', name: 'Llama 3.1 405B', description: 'Largest Llama model on AWS'}, { + id: 'AWSLLaMA31_70bChat', name: 'Llama 3.1 70B', description: 'Large Llama model on AWS' + }, {id: 'Claude35SonnetAWS', name: 'Claude 3.5 Sonnet (AWS)', description: 'Claude on AWS'}, { + id: 'Claude37SonnetAWS', name: 'Claude 3.7 Sonnet (AWS)', description: 'Latest Claude on AWS' + }, {id: 'MistralLarge2407', name: 'Mistral Large 2407', description: 'Latest Mistral Large on AWS'},], +}; + +function populateModelSelections() { + console.log('[populateModelSelections] Called'); + const modelSelect = document.getElementById('model-selection'); + const parsingModelSelect = document.getElementById('parsing-model'); + if (!modelSelect || !parsingModelSelect) { + console.warn('[populateModelSelections] modelSelect or parsingModelSelect element not found.'); + return; + } + console.log('[populateModelSelections] Current taskSettings.defaultModel:', taskSettings.defaultModel, 'taskSettings.parsingModel:', taskSettings.parsingModel); + + const savedDefaultModel = taskSettings.defaultModel; + const savedParsingModel = taskSettings.parsingModel; + + modelSelect.innerHTML = ''; + parsingModelSelect.innerHTML = ''; + + const addedModels = new Set(); + + if (apiSettings && apiSettings.apiKeys) { + for (const [provider, key] of Object.entries(apiSettings.apiKeys)) { + console.log(`[populateModelSelections] Checking provider: ${provider}, key exists: ${!!key}`); + if (key && availableModels[provider]) { + + availableModels[provider].forEach(model => { + if (!addedModels.has(model.id)) { + console.log(`[populateModelSelections] Adding model ${model.id} from provider ${provider}`); + + const option = document.createElement('option'); + option.value = model.id; + option.textContent = `${model.name} (${provider})`; + option.title = model.description; + modelSelect.appendChild(option); + + const parsingOption = document.createElement('option'); + parsingOption.value = model.id; + parsingOption.textContent = `${model.name} (${provider})`; + parsingOption.title = model.description; + parsingModelSelect.appendChild(parsingOption); + addedModels.add(model.id); + } + }); + } + } + } + + if (modelSelect.options.length === 0) { + console.log('[populateModelSelections] No models available from API keys, adding default OpenAI options.'); + const defaultOption = document.createElement('option'); + defaultOption.value = 'GPT4o'; + defaultOption.textContent = 'GPT-4o (OpenAI) - Configure API key'; + modelSelect.appendChild(defaultOption); + const defaultParsingOption = document.createElement('option'); + defaultParsingOption.value = 'GPT4oMini'; + defaultParsingOption.textContent = 'GPT-4o Mini (OpenAI) - Configure API key'; + parsingModelSelect.appendChild(defaultParsingOption); + } + + if (savedDefaultModel && Array.from(modelSelect.options).some(opt => opt.value === savedDefaultModel)) { + modelSelect.value = savedDefaultModel; + console.log('[populateModelSelections] Restored savedDefaultModel:', savedDefaultModel); + } else if (modelSelect.options.length > 0) { + + modelSelect.selectedIndex = 0; + + taskSettings.defaultModel = modelSelect.value; + console.log('[populateModelSelections] Set defaultModel to first option:', modelSelect.value); + } else { + console.log('[populateModelSelections] No options in modelSelect, defaultModel remains:', taskSettings.defaultModel); + } + + if (savedParsingModel && Array.from(parsingModelSelect.options).some(opt => opt.value === savedParsingModel)) { + parsingModelSelect.value = savedParsingModel; + console.log('[populateModelSelections] Restored savedParsingModel:', savedParsingModel); + } else if (parsingModelSelect.options.length > 0) { + + parsingModelSelect.selectedIndex = 0; + + taskSettings.parsingModel = parsingModelSelect.value; + console.log('[populateModelSelections] Set parsingModel to first option:', parsingModelSelect.value); + } else { + console.log('[populateModelSelections] No options in parsingModelSelect, parsingModel remains:', taskSettings.parsingModel); + } + console.log('[populateModelSelections] Finished. Final modelSelect.value:', modelSelect.value, 'parsingModelSelect.value:', parsingModelSelect.value); +} + +function loadSettingsFromServer() { + console.log('[loadSettingsFromServer] Called'); + fetch('/userSettings/') + .then(response => { + console.log('[loadSettingsFromServer] Received response:', response); + return response.text(); + }) + .then(html => { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const textarea = doc.querySelector('textarea[name="settings"]'); + if (textarea) { + try { + const settings = JSON.parse(textarea.textContent); + apiSettings = settings; + console.log('[loadSettingsFromServer] Parsed settings:', JSON.parse(JSON.stringify(apiSettings))); + + if (apiSettings.apiKeys) { + for (const [provider, key] of Object.entries(apiSettings.apiKeys)) { + const input = document.getElementById(`api-key-${provider}`); + console.log(`[loadSettingsFromServer] Processing API key for provider: ${provider}, key exists: ${!!key}`); + if (input && key) { + input.value = '********'; + } + } + + if (settings.apiBase) { + for (const [provider, baseUrl] of Object.entries(settings.apiBase)) { + const baseInput = document.getElementById(`api-base-${provider}`); + console.log(`[loadSettingsFromServer] Processing API base for provider: ${provider}, baseUrl: ${baseUrl}`); + if (baseInput && baseUrl) { + baseInput.value = baseUrl; + } + } + } + } + // Populate local tools + if (settings.localTools && Array.isArray(settings.localTools)) { + const toolsList = document.getElementById('local-tools-list'); + console.log('[loadSettingsFromServer] Populating local tools:', settings.localTools); + toolsList.innerHTML = ''; + settings.localTools.forEach(toolPath => { + const toolItem = document.createElement('div'); + console.log('[loadSettingsFromServer] Adding local tool:', toolPath); + toolItem.className = 'tool-item'; + toolItem.dataset.path = toolPath; + const toolText = document.createElement('span'); + toolText.textContent = toolPath; + const removeBtn = document.createElement('button'); + removeBtn.className = 'remove-tool'; + removeBtn.textContent = '×'; + removeBtn.addEventListener('click', function () { + toolItem.remove(); + }); + toolItem.appendChild(toolText); + toolItem.appendChild(removeBtn); + toolsList.appendChild(toolItem); + }); + } + + populateModelSelections(); + } catch (e) { + console.error('[loadSettingsFromServer] Error parsing API settings:', e); + } + } + }) + .catch(error => { + console.error('[loadSettingsFromServer] Error loading API settings:', error); + }); + // Removed reference to basicChatBtn here (was causing error) +} + +function populateWorkingDirFromHash() { + console.log('[populateWorkingDirFromHash] Called'); + if (window.location.hash) { + console.log('[populateWorkingDirFromHash] Found hash:', window.location.hash); + + let workingDir = decodeURIComponent(window.location.hash.substring(1)); + console.log('[populateWorkingDirFromHash] Decoded workingDir from hash:', workingDir); + + const workingDirInput = document.getElementById('working-dir'); + if (workingDirInput) { + workingDirInput.value = workingDir; + + taskSettings.workingDir = workingDir; + console.log('[populateWorkingDirFromHash] Set workingDirInput value and taskSettings.workingDir to:', workingDir); + } else { + console.warn('[populateWorkingDirFromHash] working-dir input element not found.'); + } + } +} + +document.addEventListener('DOMContentLoaded', function () { + + setupWizard(); + console.log('[DOMContentLoaded] setupWizard called.'); + + initializeApiSettings(); + console.log('[DOMContentLoaded] initializeApiSettings called.'); + + initializeTaskToggles(); + console.log('[DOMContentLoaded] initializeTaskToggles called.'); + + setupEventListeners(); + console.log('[DOMContentLoaded] setupEventListeners called.'); + + loadSettingsFromServer(); + console.log('[DOMContentLoaded] loadSettingsFromServer called.'); + + populateWorkingDirFromHash(); + console.log('[DOMContentLoaded] populateWorkingDirFromHash called.'); + + populateModelSelections(); + console.log('[DOMContentLoaded] populateModelSelections called.'); + + loadSavedSettings(); // Now defined + console.log('[DOMContentLoaded] loadSavedSettings called.'); + + if (!taskSettings.taskSettings || Object.keys(taskSettings.taskSettings).length === 0) { + saveTaskSelection(); + } + + // --- Basic Chat Modal Setup --- + const basicChatBtn = document.getElementById('open-basic-chat'); + const basicChatModal = document.getElementById('basic-chat-settings-modal'); + const closeBasicChatModal = document.getElementById('close-basic-chat-modal'); + const cancelBasicChatSettings = document.getElementById('cancel-basic-chat-settings'); + const basicChatForm = document.getElementById('basic-chat-settings-form'); + const tempSlider = document.getElementById('basic-chat-temperature'); + const tempValue = document.getElementById('basic-chat-temperature-value'); + + basicChatBtn.addEventListener('click', function () { + console.log('[DOMContentLoaded] basicChatBtn clicked.'); + // Populate model selectors with available models (same as main pipeline) + populateBasicChatModelSelections(); + // Prefill using main pipeline's preferences (shared keys), fallback to legacy basicChat* keys, then default + const model = localStorage.getItem('defaultModel') || localStorage.getItem('basicChatModel') || 'GPT4o'; + console.log(`[DOMContentLoaded] Basic Chat Modal: model determined as ${model} (defaultModel: ${localStorage.getItem('defaultModel')}, basicChatModel: ${localStorage.getItem('basicChatModel')})`); + const parsingModel = localStorage.getItem('parsingModel') || localStorage.getItem('basicChatParsingModel') || 'GPT4oMini'; + const temperature = localStorage.getItem('temperature') || localStorage.getItem('basicChatTemperature') || '0.3'; + const budget = localStorage.getItem('budget') || localStorage.getItem('basicChatBudget') || '2.0'; + document.getElementById('basic-chat-model').value = model; + document.getElementById('basic-chat-parsing-model').value = parsingModel; + document.getElementById('basic-chat-temperature').value = temperature; + document.getElementById('basic-chat-temperature-value').textContent = temperature; + document.getElementById('basic-chat-budget').value = budget; + console.log('[DOMContentLoaded] Basic Chat Modal prefilled with: model:', model, 'parsingModel:', parsingModel, 'temperature:', temperature, 'budget:', budget); + basicChatModal.style.display = "block"; + }); + + // Populate the Basic Chat model selectors with available models based on API keys + function populateBasicChatModelSelections() { + console.log('[populateBasicChatModelSelections] Called'); + const modelSelect = document.getElementById('basic-chat-model'); + const parsingModelSelect = document.getElementById('basic-chat-parsing-model'); + if (!modelSelect || !parsingModelSelect) { + console.warn('[populateBasicChatModelSelections] basic-chat-model or basic-chat-parsing-model element not found.'); + return; + } + + // Save current values to try to preserve selection + const prevModel = modelSelect.value; + const prevParsingModel = parsingModelSelect.value; + console.log('[populateBasicChatModelSelections] Previous selections - model:', prevModel, 'parsingModel:', prevParsingModel); + + modelSelect.innerHTML = ''; + parsingModelSelect.innerHTML = ''; + const addedModels = new Set(); + if (apiSettings && apiSettings.apiKeys) { + for (const [provider, key] of Object.entries(apiSettings.apiKeys)) { + console.log(`[populateBasicChatModelSelections] Checking provider: ${provider}, key exists: ${!!key}`); + if (key && availableModels[provider]) { + availableModels[provider].forEach(model => { + if (!addedModels.has(model.id)) { + console.log(`[populateBasicChatModelSelections] Adding model ${model.id} from provider ${provider}`); + const option = document.createElement('option'); + option.value = model.id; + option.textContent = `${model.name} (${provider})`; + option.title = model.description; + modelSelect.appendChild(option); + + const parsingOption = document.createElement('option'); + parsingOption.value = model.id; + parsingOption.textContent = `${model.name} (${provider})`; + parsingOption.title = model.description; + parsingModelSelect.appendChild(parsingOption); + addedModels.add(model.id); + } + }); + } + } + } + // If no models available, show default + if (modelSelect.options.length === 0) { + console.log('[populateBasicChatModelSelections] No models available from API keys, adding default OpenAI options for basic chat.'); + const defaultOption = document.createElement('option'); + defaultOption.value = 'GPT4o'; + defaultOption.textContent = 'GPT-4o (OpenAI) - Configure API key'; + modelSelect.appendChild(defaultOption); + const defaultParsingOption = document.createElement('option'); + defaultParsingOption.value = 'GPT4oMini'; + defaultParsingOption.textContent = 'GPT-4o Mini (OpenAI) - Configure API key'; + parsingModelSelect.appendChild(defaultParsingOption); + } + // Try to restore previous selection + if (prevModel && Array.from(modelSelect.options).some(opt => opt.value === prevModel)) { + modelSelect.value = prevModel; + console.log('[populateBasicChatModelSelections] Restored previous model selection:', prevModel); + } + if (prevParsingModel && Array.from(parsingModelSelect.options).some(opt => opt.value === prevParsingModel)) { + parsingModelSelect.value = prevParsingModel; + console.log('[populateBasicChatModelSelections] Restored previous parsing model selection:', prevParsingModel); + } + console.log('[populateBasicChatModelSelections] Finished. Final basic-chat-model.value:', modelSelect.value, 'basic-chat-parsing-model.value:', parsingModelSelect.value); + } + + // Update temperature value display + tempSlider.addEventListener('input', function () { + console.log('[DOMContentLoaded] Basic Chat tempSlider input event. New value:', this.value); + tempValue.textContent = this.value; + }); + + closeBasicChatModal.onclick = function () { + console.log('[DOMContentLoaded] closeBasicChatModal clicked.'); + basicChatModal.style.display = "none"; + }; + cancelBasicChatSettings.onclick = function () { + console.log('[DOMContentLoaded] cancelBasicChatSettings clicked.'); + basicChatModal.style.display = "none"; + }; + window.addEventListener('click', function (event) { + if (event.target === basicChatModal) { + console.log('[DOMContentLoaded] Window click event, target is basicChatModal. Closing modal.'); + basicChatModal.style.display = "none"; + } + }); + // Save to localStorage for convenience + basicChatForm.addEventListener('submit', function (e) { + console.log('[DOMContentLoaded] basicChatForm submitted.'); + e.preventDefault(); + // Gather settings + const model = document.getElementById('basic-chat-model').value; + const parsingModel = document.getElementById('basic-chat-parsing-model').value; + const temperature = parseFloat(document.getElementById('basic-chat-temperature').value); + const budget = parseFloat(document.getElementById('basic-chat-budget').value); + console.log('[DOMContentLoaded] Basic Chat Form Save - model:', model, 'parsingModel:', parsingModel, 'temperature:', temperature, 'budget:', budget); + + // Save to localStorage for convenience AND sync with main pipeline preferences + // Always use main pipeline keys for shared params + localStorage.setItem('defaultModel', model); + localStorage.setItem('parsingModel', parsingModel); + localStorage.setItem('temperature', temperature); + localStorage.setItem('budget', budget); + // Also keep legacy basicChat* keys for backward compatibility if needed + localStorage.setItem('basicChatModel', model); + localStorage.setItem('basicChatParsingModel', parsingModel); + localStorage.setItem('basicChatTemperature', temperature); + localStorage.setItem('basicChatBudget', budget); + console.log('[DOMContentLoaded] Saved basic chat settings to localStorage.'); + // Generate session id + const chatSessionId = generateSessionId(); + console.log('[DOMContentLoaded] Generated chatSessionId for basic chat:', chatSessionId); + // Post settings to chat app endpoint + fetch(`/chat/settings`, { + method: 'POST', headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, body: new URLSearchParams({ + sessionId: chatSessionId, action: 'save', settings: JSON.stringify({ + model: model, parsingModel: parsingModel, temperature: temperature, budget: budget + }) + }) + }) + .then(response => { + console.log('[DOMContentLoaded] Basic Chat Save - fetch response:', response); + if (response.ok) { + basicChatModal.style.display = "none"; + console.log('[DOMContentLoaded] Basic Chat settings saved successfully. Redirecting to /chat/#', chatSessionId); + window.location.href = `/chat/#${chatSessionId}`; + } else { + console.error('[DOMContentLoaded] Failed to save chat settings. Status:', response.status); + showNotification('Failed to save chat settings.', 'error'); + } + }) + .catch(error => { + console.error('[DOMContentLoaded] Error saving chat settings:', error); + showNotification('Error saving chat settings: ' + error.message, 'error'); + }); + }); +}); + +// Function to save session settings to the server, similar to welcome_old.html's saveSessionSettings +function saveSessionSettingsToServer() { + console.log('[saveSessionSettingsToServer] Called. Saving session settings to server.'); + console.log('[saveSessionSettingsToServer] Current cognitiveMode:', cognitiveMode); + // Ensure taskSettings reflects the latest from UI elements if not already handled by individual listeners + // For example, workingDir is directly read by launch button, but good to ensure it's in taskSettings + // The workingDir input listener already updates taskSettings.workingDir and localStorage. + // taskSettings.workingDir = document.getElementById('working-dir').value; + // Other settings like defaultModel, parsingModel, temperature, autoFix, auto-plan settings, graphFile + // are expected to be up-to-date in the global taskSettings object and localStorage + // due to their respective event listeners. + // saveTaskSelection() should be called before this if task selections need to be included and are changing. + console.log('[saveSessionSettingsToServer] Current taskSettings:', JSON.parse(JSON.stringify(taskSettings))); + return fetch(`/taskChat/settings`, { // Using the endpoint from old code for session settings + method: 'POST', headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, body: new URLSearchParams({ + sessionId: sessionId, action: 'save', settings: JSON.stringify(taskSettings), cognitiveMode: cognitiveMode, + }) + }) + .then(response => { + if (!response.ok) { + console.error('[saveSessionSettingsToServer] Failed to save session settings. Status:', response.status); + return response.text().then(text => { + throw new Error(`Failed to save session settings: ${response.status} ${text}`); + }); + } + console.log('[saveSessionSettingsToServer] Session settings saved successfully to server.'); + return response; + }); +} + + +function setupWizard() { + console.log('[setupWizard] Called'); + + const modal = document.getElementById('user-settings-modal'); + const btn = document.getElementById('user-settings-btn'); + const closeBtn = document.getElementById('close-user-settings-modal'); + + btn.onclick = function () { + console.log('[setupWizard] user-settings-btn clicked, displaying modal.'); + modal.style.display = "block"; + } + + closeBtn.onclick = function () { + console.log('[setupWizard] close-user-settings-modal clicked, hiding modal.'); + modal.style.display = "none"; + } + + window.onclick = function (event) { + if (event.target == modal) { + console.log('[setupWizard] Window click event, target is user-settings-modal. Hiding modal.'); + modal.style.display = "none"; + } + } + // Tab functionality + document.querySelectorAll('.tab-button').forEach(button => { + button.addEventListener('click', () => { + const tabId = button.dataset.tab; + console.log(`[setupWizard] Tab button clicked: ${tabId}`); + // Remove active class from all tabs + document.querySelectorAll('.tab-button').forEach(btn => { + btn.classList.remove('active'); + }); + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + // Add active class to clicked tab + button.classList.add('active'); + document.getElementById(`${tabId}-tab`).classList.add('active'); + console.log(`[setupWizard] Activated tab content: ${tabId}-tab`); + }); + }); + document.getElementById('back-to-cognitive-mode').addEventListener('click', () => { + console.log('[setupWizard] back-to-cognitive-mode clicked.'); + // No specific save action needed here as per old logic flow for "Back" + navigateToStep('cognitive-mode'); + }); + + + document.getElementById('back-to-task-settings').addEventListener('click', () => { + console.log('[setupWizard] back-to-task-settings clicked.'); + // No specific save action needed here as per old logic flow for "Back" + navigateToStep('task-settings'); + }); + document.getElementById('next-to-task-selection').addEventListener('click', () => { + console.log('[setupWizard] next-to-task-selection clicked.'); + saveSessionSettingsToServer() + .then(() => { + navigateToStep('task-selection'); + }) + .catch(error => { + showNotification('Error saving task settings to server: ' + error.message, 'error'); + // Allow navigation even if server save fails, localStorage is primary for client. + navigateToStep('task-selection'); + }); + }); + + document.getElementById('next-to-task-settings').addEventListener('click', () => { + console.log('[setupWizard] next-to-task-settings clicked.'); + navigateToStep('task-settings'); + }); + + document.getElementById('next-to-launch').addEventListener('click', () => { + console.log('[setupWizard] next-to-launch clicked.'); + saveTaskSelection(); // Updates taskSettings.taskSettings and localStorage for enabled tasks. + saveSessionSettingsToServer() + .then(() => { + updateLaunchSummaries(); + navigateToStep('launch'); + }) + .catch(error => { + showNotification('Error saving task selections to server: ' + error.message, 'error'); + // Allow navigation even if server save fails. + updateLaunchSummaries(); + navigateToStep('launch'); + }); + }); + + document.getElementById('back-to-task-selection').addEventListener('click', () => { + console.log('[setupWizard] back-to-task-selection clicked.'); + navigateToStep('task-selection'); + }); +} + +function initializeApiSettings() { + console.log('[initializeApiSettings] Called'); + const container = document.getElementById('api-keys-container'); + + apiProviders.forEach((provider) => { + const keyGroup = document.createElement('div'); + keyGroup.className = 'api-key-group'; + const label = document.createElement('label'); + label.textContent = provider.name + ':'; + label.setAttribute('for', `api-key-${provider.id}`); + const input = document.createElement('input'); + input.type = 'password'; + input.id = `api-key-${provider.id}`; + console.log(`[initializeApiSettings] Creating input for provider: ${provider.name} (ID: ${provider.id})`); + + if (provider.id === 'GoogleSearch') { + console.log('[initializeApiSettings] Special handling for GoogleSearch provider.'); + input.placeholder = `Enter Google API key for search`; + + const searchEngineGroup = document.createElement('div'); + searchEngineGroup.className = 'api-key-group'; + const searchEngineLabel = document.createElement('label'); + searchEngineLabel.textContent = 'Search Engine ID:'; + searchEngineLabel.setAttribute('for', `api-base-${provider.id}`); + const searchEngineInput = document.createElement('input'); + searchEngineInput.type = 'text'; + searchEngineInput.id = `api-base-${provider.id}`; + searchEngineInput.placeholder = 'Enter Google Search Engine ID'; + searchEngineInput.setAttribute('aria-label', 'Google Search Engine ID'); + searchEngineGroup.appendChild(searchEngineLabel); + searchEngineGroup.appendChild(searchEngineInput); + container.appendChild(searchEngineGroup); + } else { + input.placeholder = `Enter ${provider.name} API key`; + } + + input.setAttribute('aria-label', `${provider.name} API key`); + keyGroup.appendChild(label); + keyGroup.appendChild(input); + container.appendChild(keyGroup); + }); +} + +function initializeTaskToggles() { + console.log('[initializeTaskToggles] Called'); + const container = document.getElementById('task-toggles'); + container.innerHTML = ''; + + + const temperatureSlider = document.getElementById('temperature'); + const temperatureValue = document.getElementById('temperature-value'); + console.log('[initializeTaskToggles] Setting up temperature slider listener.'); + temperatureSlider.addEventListener('input', function () { + temperatureValue.textContent = this.value; + + taskSettings.temperature = parseFloat(this.value); + localStorage.setItem('temperature', this.value); + console.log('[initializeTaskToggles] Temperature changed to:', this.value, 'Updated taskSettings.temperature and localStorage.'); + }); + + taskTypes.forEach((task) => { + const taskToggle = document.createElement('div'); + taskToggle.className = 'task-toggle'; + const toggleContent = document.createElement('div'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `task-${task.id}`; + checkbox.value = task.id; + checkbox.setAttribute('aria-label', `Enable ${task.name}`); + checkbox.checked = false; + console.log(`[initializeTaskToggles] Creating toggle for task: ${task.name} (ID: ${task.id})`); + const label = document.createElement('label'); + label.textContent = task.name; + label.setAttribute('for', `task-${task.id}`); + const tooltip = document.createElement('span'); + tooltip.className = 'tooltip'; + tooltip.textContent = '?'; + tooltip.setAttribute('aria-hidden', 'true'); + const tooltipText = document.createElement('span'); + tooltipText.className = 'tooltiptext'; + tooltipText.innerHTML = task.tooltip; + tooltip.appendChild(tooltipText); + toggleContent.appendChild(checkbox); + toggleContent.appendChild(label); + toggleContent.appendChild(tooltip); + + const description = document.createElement('span'); + description.className = 'sr-only'; + description.id = `desc-${task.id}`; + description.textContent = task.tooltip; + description.style.position = 'absolute'; + description.style.width = '1px'; + description.style.height = '1px'; + description.style.padding = '0'; + description.style.margin = '-1px'; + description.style.overflow = 'hidden'; + description.style.clip = 'rect(0, 0, 0, 0)'; + description.style.whiteSpace = 'nowrap'; + description.style.border = '0'; + checkbox.setAttribute('aria-describedby', `desc-${task.id}`); + toggleContent.appendChild(description); + taskToggle.appendChild(toggleContent); + container.appendChild(taskToggle); + }); +} + +function generateCognotikWorkingDir() { + console.log('[generateCognotikWorkingDir] Called'); + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + const timestamp = `${year}${month}${day}-${hours}${minutes}${seconds}`; + const platform = navigator.platform.toLowerCase(); + let baseDir; + console.log(`[generateCognotikWorkingDir] Detected platform: ${platform}`); + if (platform.includes('win')) { + baseDir = '~\\Documents\\Cognotik'; + } else if (platform.includes('mac')) { + baseDir = '~/Documents/Cognotik'; + } else { + baseDir = '~/Cognotik'; + } + const dir = `${baseDir}/session-${timestamp}`; + console.log(`[generateCognotikWorkingDir] Generated Cognotik working directory: ${dir}`); + return dir; +} + + +function setupEventListeners() { + console.log('[setupEventListeners] Called'); + // Launch Session Button + const launchButton = document.getElementById('launch-session'); + if (launchButton) { + launchButton.addEventListener('click', function () { + console.log('[launch-session] Clicked.'); + taskSettings.workingDir = document.getElementById('working-dir').value; + saveTaskSelection(); // This updates taskSettings.taskSettings and localStorage + console.log('[launch-session] Current cognitiveMode:', cognitiveMode); + console.log('[launch-session] Current taskSettings:', JSON.parse(JSON.stringify(taskSettings))); + console.log('[launch-session] Current apiSettings (relevant parts might be on server):', Object.keys(apiSettings.apiKeys || {})); + if (!validateConfiguration()) { + console.log('[launch-session] Validation failed.'); + return; + } + console.log('[launch-session] Validation passed.'); + let targetPath; + cognitiveMode = document.querySelector('input[name="cognitive-mode"]:checked')?.value || cognitiveMode; + switch (cognitiveMode) { + case 'single-task': + targetPath = '/taskChat'; // Maps to TaskChatMode in UnifiedPlanApp + break; + case 'auto-plan': + targetPath = '/autoPlan'; + break; + case 'plan-ahead': + targetPath = '/planAhead'; + break; + case 'goal-oriented': + targetPath = '/goalOriented'; + break; + // case 'graph': // Example if graph mode is fully re-enabled + // targetPath = '/graphApp'; // Adjust if needed, maps to a specific graph app + // break; + default: + console.error('[launch-session] Unknown cognitive mode:', cognitiveMode); + showNotification(`Error: Unknown cognitive mode selected: ${cognitiveMode}. Please select a valid mode.`, 'error'); + navigateToStep('cognitive-mode'); // Navigate back to mode selection + return; + } + + document.getElementById('loading').style.display = 'block'; + + // 4. Save session settings to server (replicating old launchSession's call to saveSessionSettings) + // and then redirect. + saveSessionSettingsToServer() + .then(() => { + console.log('[launch-session] Session settings successfully saved to server before launch.'); + const targetUrl = `${targetPath}/#${sessionId}`; + console.log('[launch-session] Redirecting to:', targetUrl); + window.location.href = targetUrl; + // No need to hide loading, page navigates. + }) + .catch(error => { + console.error('[launch-session] Failed to save session settings to server before launch:', error); + showNotification('Error saving session settings before launch: ' + error.message, 'error'); + document.getElementById('loading').style.display = 'none'; + }); + + // The old code had a setTimeout for redirect, which is now handled after the async fetch. + // setTimeout(() => { + // window.location.href = targetUrl; + // }, 100); + + }); + console.log('[setupEventListeners] Attached launch-session click listener.'); + } + + console.log('[setupEventListeners] Called'); + document.getElementById('generate-working-dir').addEventListener('click', function () { + console.log('[setupEventListeners] generate-working-dir button clicked.'); + const newDir = generateCognotikWorkingDir(); + document.getElementById('working-dir').value = newDir; + taskSettings.workingDir = newDir; + localStorage.setItem('workingDir', newDir); + console.log('[setupEventListeners] New working directory generated and set:', newDir); + }); + + document.getElementById('save-user-settings').addEventListener('click', saveUserSettings); + console.log('[setupEventListeners] Attached saveUserSettings to save-user-settings click.'); + document.getElementById('reset-user-settings').addEventListener('click', resetUserSettings); + console.log('[setupEventListeners] Attached resetUserSettings to reset-user-settings click.'); + document.getElementById('add-local-tool').addEventListener('click', addLocalTool); + console.log('[setupEventListeners] Attached addLocalTool to add-local-tool click.'); + + apiProviders.forEach(provider => { + const keyInput = document.getElementById(`api-key-${provider.id}`); + if (keyInput) { + console.log(`[setupEventListeners] Adding change listener for API key input: api-key-${provider.id}`); + keyInput.addEventListener('change', () => { + console.log(`[setupEventListeners] API key changed for provider: ${provider.id}. Calling populateModelSelections.`); + populateModelSelections(); + }); + } + }); + + const modelSelect = document.getElementById('model-selection'); + if (modelSelect) { + console.log('[setupEventListeners] Adding change listener for model-selection.'); + modelSelect.addEventListener('change', function () { + taskSettings.defaultModel = this.value; + localStorage.setItem('defaultModel', this.value); + console.log('[setupEventListeners] defaultModel changed to:', this.value, 'Updated taskSettings and localStorage.'); + }); + } + const parsingModelSelect = document.getElementById('parsing-model'); + if (parsingModelSelect) { + console.log('[setupEventListeners] Adding change listener for parsing-model.'); + parsingModelSelect.addEventListener('change', function () { + taskSettings.parsingModel = this.value; + localStorage.setItem('parsingModel', this.value); + console.log('[setupEventListeners] parsingModel changed to:', this.value, 'Updated taskSettings and localStorage.'); + }); + } + const autoFixCheckbox = document.getElementById('auto-fix'); + if (autoFixCheckbox) { + console.log('[setupEventListeners] Adding change listener for auto-fix.'); + autoFixCheckbox.addEventListener('change', function () { + taskSettings.autoFix = this.checked; + localStorage.setItem('autoFix', this.checked); + console.log('[setupEventListeners] autoFix changed to:', this.checked, 'Updated taskSettings and localStorage.'); + }); + } + ['max-task-history', 'max-tasks-per-iteration', 'max-iterations'].forEach(id => { + const input = document.getElementById(id); + if (input) { + console.log(`[setupEventListeners] Adding change listener for ${id}.`); + input.addEventListener('input', function () { // 'input' for responsiveness + const value = parseInt(this.value, 10); + let keyInTaskSettings; + let localStorageKey; + if (id === 'max-task-history') { + keyInTaskSettings = 'maxTaskHistoryChars'; + localStorageKey = 'maxTaskHistoryChars'; + } else if (id === 'max-tasks-per-iteration') { + keyInTaskSettings = 'maxTasksPerIteration'; + localStorageKey = 'maxTasksPerIteration'; + } else if (id === 'max-iterations') { + keyInTaskSettings = 'maxIterations'; + localStorageKey = 'maxIterations'; + } + if (keyInTaskSettings && !isNaN(value)) { + taskSettings[keyInTaskSettings] = value; + if (localStorageKey) { + localStorage.setItem(localStorageKey, String(value)); + } + console.log(`[setupEventListeners] ${keyInTaskSettings} changed to:`, value, 'Updated taskSettings and localStorage.'); + } + }); + } + }); + const graphFileInput = document.getElementById('graph-file'); + if (graphFileInput) { + console.log('[setupEventListeners] Adding change listener for graph-file.'); + graphFileInput.addEventListener('change', function () { + taskSettings.graphFile = this.value; + localStorage.setItem('graphFile', this.value); + console.log('[setupEventListeners] graphFile changed to:', this.value, 'Updated taskSettings and localStorage.'); + }); + } + const workingDirInput = document.getElementById('working-dir'); + if (workingDirInput) { + console.log('[setupEventListeners] Adding change listener for working-dir.'); + // Using 'change' instead of 'input' for file paths is usually better + workingDirInput.addEventListener('change', function () { + taskSettings.workingDir = this.value; + localStorage.setItem('workingDir', this.value); + console.log('[setupEventListeners] workingDir changed to:', this.value, 'Updated taskSettings and localStorage.'); + }); + } + + + document.querySelectorAll('input[name="cognitive-mode"]').forEach(radio => { + console.log(`[setupEventListeners] Adding change listener for cognitive-mode radio: ${radio.value}`); + radio.addEventListener('change', function () { + console.log(`[setupEventListeners] Cognitive mode changed to: ${this.value}`); + document.getElementById('graph-settings').style.display = 'none'; + document.getElementById('auto-plan-settings').style.display = 'none'; + if (this.value === 'graph') { + console.log('[setupEventListeners] Displaying graph-settings.'); + document.getElementById('graph-settings').style.display = 'block'; + } else if (this.value === 'auto-plan') { + document.getElementById('auto-plan-settings').style.display = 'block'; + } + cognitiveMode = this.value; // Update global cognitiveMode variable + localStorage.setItem('cognitiveMode', this.value); + }); + }); +} + +// --- MISSING FUNCTION: saveUserSettings --- +function saveUserSettings() { + console.log('[saveUserSettings] Called'); + // Gather API keys + const apiKeys = {}; + const apiBase = {}; + apiProviders.forEach(provider => { + const keyInput = document.getElementById(`api-key-${provider.id}`); + if (keyInput && keyInput.value && keyInput.value !== '********') { + console.log(`[saveUserSettings] Saving API key for provider: ${provider.id}`); + apiKeys[provider.id] = keyInput.value; + } + // For GoogleSearch, also save the Search Engine ID as "apiBase" + const baseInput = document.getElementById(`api-base-${provider.id}`); + if (baseInput && baseInput.value) { + console.log(`[saveUserSettings] Saving API base for provider: ${provider.id}, value: ${baseInput.value}`); + apiBase[provider.id] = baseInput.value; + } + }); + // Gather local tools + const localTools = []; + const toolsList = document.getElementById('local-tools-list'); + if (toolsList) { + toolsList.querySelectorAll('.tool-item').forEach(item => { + if (item.dataset.path) { + console.log(`[saveUserSettings] Adding local tool from dataset.path: ${item.dataset.path}`); + localTools.push(item.dataset.path); + } else if (item.textContent) { + // fallback if dataset not set + console.log(`[saveUserSettings] Adding local tool from textContent: ${item.textContent.trim()}`); + localTools.push(item.textContent.trim()); + } + }); + } + // Compose settings object + const settings = { + apiKeys, apiBase, localTools + }; + console.log('[saveUserSettings] Settings to save:', JSON.stringify(settings)); // Avoid logging actual keys if sensitive + // Save to server + fetch('/userSettings/', { + method: 'POST', headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, body: new URLSearchParams({ + action: 'save', settings: JSON.stringify(settings) + }) + }) + .then(response => { + console.log('[saveUserSettings] Server response:', response); + if (response.ok) { + console.log('[saveUserSettings] User settings saved successfully.'); + console.log('[saveUserSettings] Updated apiSettings in memory.'); + apiSettings = settings; + populateModelSelections(); + } else { + console.error('[saveUserSettings] Failed to save user settings. Status:', response.status); + showNotification('Failed to save user settings.', 'error'); + } + }) + .catch(error => { + console.error('[saveUserSettings] Error saving user settings:', error); + showNotification('Error saving user settings: ' + error.message, 'error'); + }); +} + +// --- MISSING FUNCTION: resetUserSettings --- +function resetUserSettings() { + console.log('[resetUserSettings] Called'); + // Clear all API key fields + apiProviders.forEach(provider => { + const keyInput = document.getElementById(`api-key-${provider.id}`); + if (keyInput) { + console.log(`[resetUserSettings] Clearing API key for provider: ${provider.id}`); + keyInput.value = ''; + } + const baseInput = document.getElementById(`api-base-${provider.id}`); + if (baseInput) { + console.log(`[resetUserSettings] Clearing API base for provider: ${provider.id}`); + baseInput.value = ''; + } + }); + // Clear local tools + const toolsList = document.getElementById('local-tools-list'); + if (toolsList) toolsList.innerHTML = ''; + console.log('[resetUserSettings] Cleared local tools list.'); + showNotification('User settings reset. Remember to save!', 'info'); +} + +// --- MISSING FUNCTION: addLocalTool --- +function addLocalTool() { + console.log('[addLocalTool] Called'); + const newToolInput = document.getElementById('new-tool-path'); + const toolsList = document.getElementById('local-tools-list'); + if (newToolInput && toolsList && newToolInput.value.trim() !== '') { + const toolPath = newToolInput.value.trim(); + const toolItem = document.createElement('div'); + toolItem.className = 'tool-item'; + toolItem.dataset.path = toolPath; + const toolText = document.createElement('span'); + toolText.textContent = toolPath; + const removeBtn = document.createElement('button'); + removeBtn.className = 'remove-tool'; + removeBtn.textContent = '×'; + removeBtn.addEventListener('click', function () { + console.log(`[addLocalTool] Remove button clicked for tool: ${toolPath}`); + toolItem.remove(); + }); + toolItem.appendChild(toolText); + toolItem.appendChild(removeBtn); + toolsList.appendChild(toolItem); + newToolInput.value = ''; + console.log(`[addLocalTool] Added new local tool: ${toolPath}`); + } else { + console.warn('[addLocalTool] Could not add tool. Input empty, or newToolInput/toolsList not found.'); + } +} + +function showNotification(message, type = 'info') { + console.log(`[showNotification] Called with message: "${message}", type: "${type}"`); + // Simple notification using alert, can be replaced with a fancier UI + if (type === 'error') { + console.error(`[showNotification] Error: ${message}`); + alert('❌ ' + message); + } else if (type === 'success') { + console.log(`[showNotification] Success: ${message}`); + alert('✅ ' + message); + } else { + console.info(`[showNotification] Info: ${message}`); + alert(message); + } +} + +function navigateToStep(stepId) { + console.log(`[navigateToStep] Called with stepId: ${stepId}`); + // Hide all wizard-content + document.querySelectorAll('.wizard-content').forEach(el => el.classList.remove('active')); + // Show the selected step + const stepContent = document.getElementById(stepId); + if (stepContent) { + stepContent.classList.add('active'); + console.log(`[navigateToStep] Activated content for step: ${stepId}`); + } else { + console.warn(`[navigateToStep] Content element for step ${stepId} not found.`); + } + // Update wizard-nav + document.querySelectorAll('.wizard-step').forEach(step => step.classList.remove('active')); + const navStep = document.querySelector(`.wizard-step[data-step="${stepId}"]`); + if (navStep) navStep.classList.add('active'); + console.log(`[navigateToStep] Updated wizard navigation for step: ${stepId}`); +} + +function updateLaunchSummaries() { + // Update the summary sections in the launch step + // Cognitive Mode + const mode = localStorage.getItem('cognitiveMode') || 'single-task'; + const modeMap = { + 'single-task': 'Chat', 'auto-plan': 'Autonomous', 'plan-ahead': 'Plan Ahead', 'goal-oriented': 'Goal Oriented', 'graph': 'Graph Mode' + }; + document.getElementById('cognitive-mode-summary').textContent = modeMap[mode] || mode; + console.log(`[updateLaunchSummaries] Cognitive Mode Summary: ${modeMap[mode] || mode}`); + // Task Settings + let summary = ''; + summary += 'Default Model: ' + (taskSettings.defaultModel || '-') + '\n'; + summary += 'Parsing Model: ' + (taskSettings.parsingModel || '-') + '\n'; // Ensure parsingModel is in taskSettings + summary += 'Working Directory: ' + (taskSettings.workingDir || '-') + '\n'; + summary += 'Temperature: ' + (taskSettings.temperature ?? '-') + '\n'; // Use ?? to handle 0 correctly + summary += 'Auto Fix: ' + (taskSettings.autoFix ? 'Enabled' : 'Disabled') + '\n'; + document.getElementById('task-settings-summary').textContent = summary; + console.log(`[updateLaunchSummaries] Task Settings Summary:\n${summary}`); + // API Settings + let apiSummary = ''; + if (apiSettings.apiKeys) { + for (const [provider, key] of Object.entries(apiSettings.apiKeys)) { + if (key) { + console.log(`[updateLaunchSummaries] API Key configured for: ${provider}`); + apiSummary += provider + ': Configured\n'; + } + } + } + document.getElementById('api-settings-summary').textContent = apiSummary || 'No API keys configured.'; + console.log(`[updateLaunchSummaries] API Settings Summary:\n${apiSummary || 'No API keys configured.'}`); +} + +function validateConfiguration() { + console.log('[validateConfiguration] Called'); + + let hasApiKey = false; + if (apiSettings.apiKeys) { + console.log('[validateConfiguration] Checking API keys:', Object.keys(apiSettings.apiKeys)); + for (const key of Object.values(apiSettings.apiKeys)) { + if (key) { + hasApiKey = true; + break; + } + } + } + if (!hasApiKey) { + console.warn('[validateConfiguration] No API key configured.'); + showNotification('Please configure at least one API key before launching', 'error'); + + document.getElementById('api-settings-btn').click(); + console.log('[validateConfiguration] Clicked api-settings-btn due to missing API key.'); + return false; + } + console.log('[validateConfiguration] API key check passed.'); + + let hasEnabledTask = false; + if (taskSettings.taskSettings) { + console.log('[validateConfiguration] Checking enabled tasks:', taskSettings.taskSettings); + for (const settings of Object.values(taskSettings.taskSettings)) { + if (settings.enabled) { + hasEnabledTask = true; + break; + } + } + } + if (!hasEnabledTask) { + console.warn('[validateConfiguration] No task enabled.'); + showNotification('Please enable at least one task before launching', 'error'); + + navigateToStep('task-selection'); + console.log('[validateConfiguration] Navigated to task-selection due to no enabled tasks.'); + return false; + } + console.log('[validateConfiguration] Enabled task check passed. Configuration is valid.'); + return true; + +} + +function loadSavedSettings() { + console.log('[loadSavedSettings] Called'); + const savedCognitiveMode = localStorage.getItem('cognitiveMode'); + if (savedCognitiveMode) { + cognitiveMode = savedCognitiveMode; + const radioToSelect = document.querySelector(`input[name="cognitive-mode"][value="${savedCognitiveMode}"]`); + if (radioToSelect) { + radioToSelect.checked = true; + radioToSelect.dispatchEvent(new Event('change')); + console.log('[loadSavedSettings] Restored cognitiveMode:', savedCognitiveMode); + } + } + const modelSelect = document.getElementById('model-selection'); + if (modelSelect && localStorage.getItem('defaultModel')) modelSelect.value = localStorage.getItem('defaultModel'); + // taskSettings.defaultModel is already initialized from localStorage or default + const parsingModelSelect = document.getElementById('parsing-model'); + if (parsingModelSelect && localStorage.getItem('parsingModel')) parsingModelSelect.value = localStorage.getItem('parsingModel'); + // taskSettings.parsingModel is already initialized + const workingDirInput = document.getElementById('working-dir'); + if (workingDirInput && localStorage.getItem('workingDir')) workingDirInput.value = localStorage.getItem('workingDir'); + // taskSettings.workingDir is already initialized + const autoFixCheckbox = document.getElementById('auto-fix'); + if (autoFixCheckbox) autoFixCheckbox.checked = localStorage.getItem('autoFix') === 'true'; + // taskSettings.autoFix is already initialized + const temperatureSlider = document.getElementById('temperature'); + const temperatureValue = document.getElementById('temperature-value'); + const savedTemperature = localStorage.getItem('temperature'); + if (savedTemperature) { + taskSettings.temperature = parseFloat(savedTemperature); + if (temperatureSlider) temperatureSlider.value = savedTemperature; + if (temperatureValue) temperatureValue.textContent = savedTemperature; + console.log('[loadSavedSettings] Restored temperature:', savedTemperature); + } else if (temperatureSlider) { + taskSettings.temperature = parseFloat(temperatureSlider.value); // Ensure global taskSettings has default + if (temperatureValue) temperatureValue.textContent = temperatureSlider.value; + } + // Auto-plan settings + const autoPlanFields = { + 'maxTaskHistoryChars': 'max-task-history', 'maxTasksPerIteration': 'max-tasks-per-iteration', 'maxIterations': 'max-iterations' + }; + for (const [key, id] of Object.entries(autoPlanFields)) { + const input = document.getElementById(id); + const savedValue = localStorage.getItem(key); + if (savedValue) { + taskSettings[key] = parseInt(savedValue, 10); + if (input) input.value = savedValue; + } else if (input) { // Ensure global taskSettings has default from input + taskSettings[key] = parseInt(input.value, 10); + } + } + const graphFileInput = document.getElementById('graph-file'); + const savedGraphFile = localStorage.getItem('graphFile'); + if (savedGraphFile) { + taskSettings.graphFile = savedGraphFile; + if (graphFileInput) graphFileInput.value = savedGraphFile; + } else if (graphFileInput && graphFileInput.value) { // Ensure global taskSettings has default from input + taskSettings.graphFile = graphFileInput.value; + } + const savedEnabledTasks = localStorage.getItem('enabledTasks'); + if (savedEnabledTasks) { + try { + taskSettings.taskSettings = JSON.parse(savedEnabledTasks); + console.log('[loadSavedSettings] Restored enabledTasks:', taskSettings.taskSettings); + Object.keys(taskSettings.taskSettings).forEach(taskId => { + const checkbox = document.getElementById(`task-${taskId}`); + if (checkbox && taskSettings.taskSettings[taskId]?.enabled) checkbox.checked = true; + }); + } catch (e) { + console.error('[loadSavedSettings] Error parsing enabledTasks:', e); + taskSettings.taskSettings = {}; + } + } + console.log('[loadSavedSettings] Finished. Initial taskSettings after load:', JSON.parse(JSON.stringify(taskSettings))); +} + +function saveTaskSelection() { + console.log('[saveTaskSelection] Called'); + const currentEnabledTasks = {}; + document.querySelectorAll('#task-toggles .task-toggle input[type="checkbox"]').forEach(checkbox => { + currentEnabledTasks[checkbox.value] = { + enabled: checkbox.checked, + task_type: checkbox.value // Add task_type, which is the task ID + }; + }); + taskSettings.taskSettings = currentEnabledTasks; + localStorage.setItem('enabledTasks', JSON.stringify(currentEnabledTasks)); + console.log('[saveTaskSelection] Saved enabledTasks to localStorage and taskSettings.taskSettings:', JSON.stringify(currentEnabledTasks)); +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 30b7fdf09..0062ea035 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ pluginName=Cognotik pluginRepositoryUrl=https://github.com/SimiaCryptus/Cognotik libraryGroup=com.simiacryptus -libraryVersion=2.0.2 +libraryVersion=2.0.4 gradleVersion=8.13 org.gradle.caching=true diff --git a/intellij/README.md b/intellij/README.md index 1c7959cd0..b26da5257 100644 --- a/intellij/README.md +++ b/intellij/README.md @@ -6,14 +6,9 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -![Cognotik Logo](https://simiacryptus.github.io/intellij_plugins/aicoder/icon.png) +![Cognotik Logo](https://share.simiacrypt.us/cognotik/images/public/icons/icon-512x512.png) **Cognotik** is an open-source AI coding assistant plugin for IntelliJ IDEs, focusing on powerful, high-level agentic tools rather than just code completion. Leverage generative AI directly within your IDE using a "Bring Your Own Key" (BYOK) model for maximum control over data, costs, and model choice. Enhance your development workflow with AI-powered code editing, file manipulation, complex task automation via AI agents, and voice-to-text capabilities. - - -## ✨ Overview - -Welcome to **Cognotik**, your open-source gateway to advanced AI capabilities directly within the IntelliJ ecosystem. Licensed under Apache 2.0, Cognotik empowers developers with sophisticated agentic tools designed to tackle complex coding tasks. ### 🔑 Open Source & Bring Your Own Key (BYOK) @@ -37,6 +32,7 @@ Cognotik differentiates itself by focusing on **high-level agentic capabilities* * Agentic tools - Leverage AI agents to perform complex tasks like refactoring, documentation generation, test creation, and workflow automation across multiple files. * Voice to Text - Dictate code, comments, and commands using your voice for hands-free coding. * Voice to Text - Dictate code and comments using voice commands + ## 🚀 Getting Started @@ -62,15 +58,6 @@ Cognotik differentiates itself by focusing on **high-level agentic capabilities* * **Agentic Tools:** Access agentic features through the `AI Tools` menu or dedicated tool windows. Follow the prompts, which may involve interaction in a separate browser window for complex tasks. * **Voice Control:** Activate voice input (check settings for keybindings/activation method) and speak commands or dictate code. -## 🤝 Contributing - -Cognotik is an open-source project, and contributions are welcome! Please refer to the [CONTRIBUTING.md](CONTRIBUTING.md) file (if available) or the GitHub repository's guidelines for details on how to contribute code, report issues, or suggest features. - -## ❓ Support - -* **Issues:** Report bugs or suggest features via the [GitHub Issues](https://github.com/SimiaCryptus/intellij-aicoder/issues) page. -* **Discussions:** Join the conversation on the [GitHub Discussions](https://github.com/SimiaCryptus/intellij-aicoder/discussions) page. - --- *Keywords: IntelliJ Plugin, AI Coding Assistant, Generative AI, Agentic AI, Open Source, BYOK, Developer Tools, Code Generation, Code Explanation, Refactoring, Voice Coding, JetBrains, OpenAI, Anthropic, Large Language Models (LLM)* \ No newline at end of file diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt index 2a2461021..c524f0a4f 100644 --- a/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt @@ -14,6 +14,7 @@ import com.simiacryptus.cognotik.util.UITools import com.simiacryptus.cognotik.actors.ParsedActor import com.simiacryptus.cognotik.actors.ParsedResponse import com.simiacryptus.cognotik.actors.SimpleActor +import com.simiacryptus.cognotik.apps.general.renderMarkdown import com.simiacryptus.cognotik.diff.IterativePatchUtil.patchFormatPrompt import com.simiacryptus.cognotik.platform.ApplicationServices import com.simiacryptus.cognotik.platform.Session @@ -208,8 +209,8 @@ class MultiStepPatchAction : BaseAction() { AgentPatterns.displayMapInTabs( mapOf( - "Text" to renderMarkdown(design.text, ui = ui), - "JSON" to renderMarkdown("```json\n${toJson(design.obj)/*.indent(" ")*/}\n```", ui = ui), + "Text" to design.text.renderMarkdown, + "JSON" to "```json\n${toJson(design.obj)/*.indent(" ")*/}\n```".renderMarkdown, ) ) }, diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/SimpleCommandAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/SimpleCommandAction.kt index 5431d8aad..40efe4036 100644 --- a/intellij/src/main/kotlin/cognotik/actions/agent/SimpleCommandAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/agent/SimpleCommandAction.kt @@ -278,11 +278,8 @@ class SimpleCommandAction : BaseAction() { task.append( AgentPatterns.displayMapInTabs( mapOf( - "Text" to renderMarkdown(plan.text, ui = ui), - "JSON" to renderMarkdown( - "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde", - ui = ui - ) + "Text" to plan.text.renderMarkdown, + "JSON" to "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde".renderMarkdown ) ), false ) diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/WebDevelopmentAssistantAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/WebDevelopmentAssistantAction.kt index 1c5998ac2..6aa290a64 100644 --- a/intellij/src/main/kotlin/cognotik/actions/agent/WebDevelopmentAssistantAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/agent/WebDevelopmentAssistantAction.kt @@ -11,6 +11,7 @@ import com.simiacryptus.cognotik.util.BrowseUtil.browse import com.simiacryptus.cognotik.util.IdeaOpenAIClient import com.simiacryptus.cognotik.util.UITools import com.simiacryptus.cognotik.actors.* +import com.simiacryptus.cognotik.apps.general.renderMarkdown import com.simiacryptus.cognotik.diff.IterativePatchUtil.patchFormatPrompt import com.simiacryptus.cognotik.platform.ApplicationServices import com.simiacryptus.cognotik.platform.Session @@ -254,11 +255,8 @@ class WebDevelopmentAssistantAction : BaseAction() { AgentPatterns.displayMapInTabs( mapOf( - "Text" to renderMarkdown(design.text, ui = ui), - "JSON" to renderMarkdown( - "```json\n${JsonUtil.toJson(design.obj)/*.indent(" ")*/}\n```", - ui = ui - ), + "Text" to design.text.renderMarkdown, + "JSON" to "```json\n${JsonUtil.toJson(design.obj)/*.indent(" ")*/}\n```".renderMarkdown, ) ) }, @@ -282,10 +280,7 @@ class WebDevelopmentAssistantAction : BaseAction() { var messageWithTools = userMessage task.echo( - renderMarkdown( - "```json\n${JsonUtil.toJson(architectureResponse.obj)/*.indent(" ")*/}\n```", - ui = ui - ) + "```json\n${JsonUtil.toJson(architectureResponse.obj)/*.indent(" ")*/}\n```".renderMarkdown ) val fileTabs = TabbedDisplay(task) architectureResponse.obj.files.filter { @@ -458,14 +453,12 @@ class WebDevelopmentAssistantAction : BaseAction() { }, outputFn = { img -> - renderMarkdown( - "", ui = ui + "".renderMarkdown }, ui = ui, reviseResponse = { userMessages: List> -> @@ -490,14 +483,12 @@ class WebDevelopmentAssistantAction : BaseAction() { }, ).call() task.complete( - renderMarkdown( - "", ui = ui + "".renderMarkdown ) } catch (e: Throwable) { val error = task.error(ui, e) @@ -543,13 +534,13 @@ class WebDevelopmentAssistantAction : BaseAction() { ) }, outputFn = { design: String -> - var design = design - languages.forEach { language -> - if (design.contains("```$language")) { - design = design.substringAfter("```$language").substringBefore("```") - } + var design = design + languages.forEach { language -> + if (design.contains("```$language")) { + design = design.substringAfter("```$language").substringBefore("```") } - renderMarkdown("```${languages.first()}\n${design.let { it }}\n```", ui = ui) + } + "```${languages.first()}\n${design.let { it }}\n```".renderMarkdown }, ui = ui, reviseResponse = { userMessages: List> -> diff --git a/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt b/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt index 696922c49..d09a1dc33 100644 --- a/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt @@ -23,6 +23,7 @@ import com.simiacryptus.cognotik.webui.application.ApplicationServer import com.simiacryptus.cognotik.webui.chat.ChatSocketManager import com.simiacryptus.cognotik.webui.session.SessionTask import com.simiacryptus.jopenai.ChatClient +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.ChatModel import com.simiacryptus.jopenai.models.chatModel import com.simiacryptus.jopenai.util.GPT4Tokenizer @@ -166,7 +167,7 @@ class MultiCodeChatAction : BaseAction() { } }
""" - override fun respond(api: ChatClient, task: SessionTask, userMessage: String): String { + override fun respond(api: ChatClient, task: SessionTask, userMessage: String, currentChatMessages: List): String { val codex = GPT4Tokenizer() task.verbose((codeFiles.joinToString("\n") { path -> @@ -176,7 +177,7 @@ class MultiCodeChatAction : BaseAction() { val settings = MultiStepPatchAction.AutoDevApp.Settings() api.budget = settings.budget ?: 2.00 - return super.respond(api, task, userMessage) + return super.respond(api, task, userMessage, currentChatMessages) } } diff --git a/intellij/src/main/kotlin/cognotik/actions/chat/MultiDiffChatAction.kt b/intellij/src/main/kotlin/cognotik/actions/chat/MultiDiffChatAction.kt index 571b5198d..717a1623d 100644 --- a/intellij/src/main/kotlin/cognotik/actions/chat/MultiDiffChatAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/chat/MultiDiffChatAction.kt @@ -24,6 +24,7 @@ import com.simiacryptus.cognotik.webui.application.ApplicationServer import com.simiacryptus.cognotik.webui.chat.ChatSocketManager import com.simiacryptus.cognotik.webui.session.SessionTask import com.simiacryptus.jopenai.ChatClient +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.ChatModel import com.simiacryptus.jopenai.models.chatModel import com.simiacryptus.jopenai.util.GPT4Tokenizer @@ -183,7 +184,7 @@ open class MultiDiffChatAction( ) } - override fun respond(api: ChatClient, task: SessionTask, userMessage: String): String { + override fun respond(api: ChatClient, task: SessionTask, userMessage: String, currentChatMessages: List): String { val codex = GPT4Tokenizer() task.verbose((getCodeFiles().joinToString("\n") { path -> @@ -193,7 +194,7 @@ open class MultiDiffChatAction( val settings = Settings() api.budget = settings.budget ?: 2.00 - return super.respond(api, task, userMessage) + return super.respond(api, task, userMessage, currentChatMessages) } } diff --git a/intellij/src/main/kotlin/cognotik/actions/git/ReplicateCommitAction.kt b/intellij/src/main/kotlin/cognotik/actions/git/ReplicateCommitAction.kt index d9455d610..a4bd65551 100644 --- a/intellij/src/main/kotlin/cognotik/actions/git/ReplicateCommitAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/git/ReplicateCommitAction.kt @@ -19,6 +19,7 @@ import com.simiacryptus.cognotik.util.BrowseUtil.browse import com.simiacryptus.cognotik.util.UITools import com.simiacryptus.cognotik.actors.ParsedActor import com.simiacryptus.cognotik.actors.SimpleActor +import com.simiacryptus.cognotik.apps.general.renderMarkdown import com.simiacryptus.cognotik.diff.IterativePatchUtil import com.simiacryptus.cognotik.diff.IterativePatchUtil.patchFormatPrompt import com.simiacryptus.cognotik.platform.Session @@ -248,11 +249,8 @@ class ReplicateCommitAction : BaseAction() { task.add( AgentPatterns.displayMapInTabs( mapOf( - "Text" to renderMarkdown(plan.text, ui = ui), - "JSON" to renderMarkdown( - "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde", - ui = ui - ), + "Text" to plan.text.renderMarkdown, + "JSON" to "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde".renderMarkdown, ) ) ) diff --git a/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt b/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt index 000523ae6..25fcf8741 100644 --- a/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt @@ -24,6 +24,7 @@ import com.simiacryptus.cognotik.util.BrowseUtil.browse import com.simiacryptus.cognotik.util.IdeaChatClient import com.simiacryptus.cognotik.actors.ParsedActor import com.simiacryptus.cognotik.actors.SimpleActor +import com.simiacryptus.cognotik.apps.general.renderMarkdown import com.simiacryptus.cognotik.platform.Session import com.simiacryptus.cognotik.platform.model.User import com.simiacryptus.cognotik.util.AddApplyFileDiffLinks @@ -182,11 +183,8 @@ class AnalyzeProblemAction : AnAction() { task.add( AgentPatterns.displayMapInTabs( mapOf( - "Text" to renderMarkdown(plan.text, ui = ui), - "JSON" to renderMarkdown( - "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde", - ui = ui - ), + "Text" to plan.text.renderMarkdown, + "JSON" to "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde".renderMarkdown, ) ) ) diff --git a/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt b/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt index 5a00e9b10..d1c1513c4 100644 --- a/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt +++ b/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt @@ -14,6 +14,7 @@ import com.simiacryptus.cognotik.util.IdeaChatClient import com.simiacryptus.cognotik.util.UITools import com.simiacryptus.cognotik.actors.ParsedActor import com.simiacryptus.cognotik.actors.SimpleActor +import com.simiacryptus.cognotik.apps.general.renderMarkdown import com.simiacryptus.cognotik.platform.Session import com.simiacryptus.cognotik.platform.model.User import com.simiacryptus.cognotik.util.AddApplyFileDiffLinks @@ -225,11 +226,8 @@ class TestResultAutofixAction : BaseAction() { task.add( AgentPatterns.displayMapInTabs( mapOf( - "Text" to renderMarkdown(plan.text, ui = ui), - "JSON" to renderMarkdown( - "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde", - ui = ui - ), + "Text" to plan.text.renderMarkdown, + "JSON" to "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde".renderMarkdown, ) ) ) diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt index f44ed738a..21bd2ea76 100644 --- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt +++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt @@ -42,6 +42,7 @@ class PluginStartupActivity : ProjectActivity { setLogInfo("org.apache.hc.client5.http") setLogInfo("org.eclipse.jetty") setLogInfo("com.simiacryptus") + setLogInfo("TRAFFIC.com.simiacryptus.cognotik.webui.chat") try { @@ -201,5 +202,19 @@ class PluginStartupActivity : ProjectActivity { } } + private fun setLogDebug(name: String) { + try { + LoggerFactory.getLogger(name).apply { + when (this) { + is com.intellij.openapi.diagnostic.Logger -> setLevel(LogLevel.DEBUG) + is ch.qos.logback.classic.Logger -> setLevel(Level.DEBUG) + else -> log.info("Failed to set log level for $name: Unsupported log type (${this::class.java})") + } + } + } catch (e: Exception) { + log.error("Error setting log level for $name", e) + } + } + } } \ No newline at end of file diff --git a/intellij/src/main/resources/META-INF/plugin.xml b/intellij/src/main/resources/META-INF/plugin.xml index 03c6cc908..cde1d5961 100644 --- a/intellij/src/main/resources/META-INF/plugin.xml +++ b/intellij/src/main/resources/META-INF/plugin.xml @@ -41,7 +41,7 @@ text="💬 Cognotik Chat" description="Initiate a general-purpose chat session to discuss coding concepts, get assistance with programming tasks, or explore software development ideas"> - @@ -50,7 +50,7 @@ id="EnhancedOutlineAction" text="📝 Enhanced Outline Tool" description="Create and expand outlines with customizable phases using Cognotik assistance"> - + --> - @@ -116,7 +116,7 @@ description="Print PSI Tree"> - + --> + @@ -194,6 +196,7 @@ text="✓ Validate Code" description="Validate code syntax and structure using Cognotik-powered analysis"> + + + + 50`), allowing for CSS styles to change the navbar appearance (e.g., background, shadow). + +### github-api.js + +* **Purpose:** Interacts with the GitHub API to fetch release information for Cognotik. +* **`fetchLatestRelease()`:** + * Checks `localStorage` for cached data (`CACHE_KEY`) first. Returns cached data if valid and not expired (`CACHE_EXPIRY`). + * If no valid cache, attempts to fetch from `releases/latest` endpoint. + * If `/latest` fails (e.g., pre-release is latest), it falls back to fetching the full `/releases` list and takes the first item (usually the most recent). + * Caches the fetched release data (the single release object) in `localStorage` with a timestamp. + * Returns the release object or `null` on error. +* **`findDownloadAsset(release, os)`:** + * Takes a GitHub release object and a detected OS string ('windows', 'mac', 'linux'). + * Defines preferred file extensions for each OS (`osExtensions`). + * Iterates through preferred extensions, looking for a matching asset (`asset.name.endsWith(ext)`). + * If no extension match, falls back to searching for OS keywords (`osKeywords`) within the asset names (`asset.name.toLowerCase().includes(keyword)`). + * Returns the first matching asset object or `null`. +* **`checkCache()`, `cacheData()`:** Helper functions for managing the `localStorage` cache. +* **`getGitHubReleasesUrl()`:** Returns the URL to the main releases page on GitHub. + +### download-ui.js + +* **Purpose:** Manages the user interface elements related to downloading the application. +* **Initialization (`updateDownloadUI()`):** + * Gets references to relevant DOM elements (`#detected-os`, `#latest-version`, `#download-button`, etc.). + * Sets initial "Checking..." text and disables the download button, setting appropriate ARIA `aria-busy` states. + * Calls `detectOS()` from `os-detection.js`. + * Updates the "Detected OS" text. + * If OS is 'unknown', sets the button to link directly to the GitHub releases page and exits. + * Calls `fetchLatestRelease()` from `github-api.js`. + * **On successful fetch:** + * Updates the "Latest Version" text. + * Calls `findDownloadAsset()` to get the appropriate download link for the OS. + * If an asset is found, updates the download button text, `href`, enables it, and sets the `download` attribute and a descriptive `aria-label`. + * If no asset is found, sets the button to link to the GitHub releases page. + * **On fetch error:** Sets the button to link to GitHub releases and updates text/ARIA attributes to indicate an error. + * Removes `aria-busy` attributes as states are resolved. +* **Auto-initialization:** The `updateDownloadUI` function is called automatically on `DOMContentLoaded`. + +### os-detection.js + +* **Purpose:** Detects the user's operating system based on browser `navigator` properties. +* **`detectOS()`:** + * Checks `navigator.userAgent` and `navigator.platform` strings for keywords associated with Windows, macOS, and Linux. + * Includes checks for 'win', 'mac', 'darwin' (macOS), 'linux', 'x11'. + * Explicitly excludes 'android' from being detected as 'linux'. + * Returns 'windows', 'mac', 'linux', or 'unknown'. +* **`getOSDisplayName()`:** A helper function to return a user-friendly name based on the OS code returned by `detectOS()`. (Note: This function isn't actually used in the provided `download-ui.js`, which has its own `osNames` map). + +### feature-modal.js + +* **Purpose:** Handles the interactive modal dialog for displaying feature details. +* **Initialization:** Runs on `DOMContentLoaded`. +* **Event Listeners:** + * Attaches click listeners to all `.read-more-btn` elements. + * When clicked, it finds the corresponding feature title (`h3`), looks up the content in the `featureDetails` object. + * Populates the modal (`#modal-title`, `#modal-content`) with the details. + * Displays the modal (`modal.style.display = 'block'`) and prevents body scroll. + * Stores the previously focused element to restore focus on close. + * Sets initial focus to the close button (`.close-modal`). + * Adds a `keydown` listener to the modal for focus trapping (`trapFocus`). +* **Closing the Modal (`closeModalHandler()`):** + * Hides the modal. + * Restores body scroll. + * Restores focus to the element that opened the modal. + * Removes the focus trap listener. +* **Closing Mechanisms:** + * Clicking the close button (`.close-modal`). + * Clicking outside the modal content area (`window` click listener checking `event.target === modal`). + * Pressing the Escape key (`document` keydown listener). +* **Focus Trapping (`trapFocus()`):** + * Listens for Tab key presses within the modal. + * Identifies all focusable elements inside the modal. + * If Tab is pressed on the last element, focus moves to the first. + * If Shift+Tab is pressed on the first element, focus moves to the last. + +### logo-animation.js + +* **Purpose:** Creates a complex, interactive animation for the SVG logo in the hero section. +* **Initialization (`initLogoAnimation()`):** + * Finds the `#hero-logo` SVG element. + * Injects SVG `` and a `` if they don't exist. + * Defines multiple color stops for the gradient. + * Applies this gradient fill (`url(#animated-gradient)`) to all `` elements within the logo SVG. +* **Plasma Effect:** + * Uses a `generatePlasmaGrid()` function (implementing the Diamond-Square algorithm) to create a 2D grid of noise values. + * This process utilizes a helper function (`rnd()`) to introduce controlled, pseudo-normal randomness for the displacement in the Diamond-Square algorithm. + * Uses `samplePlasma()` for bilinear interpolation to get a smooth value from the grid at given coordinates. + * The `plasma(x, y, z)` function samples this grid, using the `z` (time) parameter to shift the sampling coordinates, creating the animation effect. +* **Animation Loop (`animateGradient()`):** + * Uses `requestAnimationFrame` for smooth animation. + * Increments a time variable `t`. + * **Auto-Animation (when `isUserInteracting` is false):** Animates the gradient's center (`cx`, `cy`) along a Lissajous curve path. + * **Color Animation:** Iterates through the gradient's `` elements. For each stop, it calculates a `plasmaValue` based on the stop's offset and the current time `t`. This value is mapped to an HSL hue, dynamically updating the `stop-color`. +* **Mouse Interaction:** + * `pointermove`: Updates the gradient's center (`cx`, `cy`) to follow the mouse cursor position relative to the SVG's bounding box. Sets `isUserInteracting` to `true`. Clears the leave timeout. + * `pointerleave`: Sets a timeout (`leaveTimeoutId`) before setting `isUserInteracting` back to `false`, allowing the auto-animation to resume smoothly after a short delay. + * `pointerenter`: Sets `isUserInteracting` to `true` immediately and clears any pending leave timeout. + +## Styling + +* **`styles.css`:** Defines the overall look and feel. + * Uses CSS Custom Properties (variables) extensively for colors, fonts, spacing, etc. + * Includes base resets and styles for common HTML elements. + * Defines styles for major components: header, navigation, hero section, buttons, features, video section, download UI, footer, modal. + * Implements visual effects like gradients, shadows, transitions. + * Includes `:focus-visible` styles for accessibility. +* **`styles/responsive.css`:** Contains `@media` queries to adjust layout and styles for different screen sizes (tablets, mobiles). Overrides styles defined in `styles.css` as needed for smaller viewports. + +## Assets + +* **`logo.svg`:** The main vector logo source file. Its paths are directly embedded in the HTML for easier JS manipulation (animation). +* **`assets/favicon.svg`:** A version of the logo used as the site's favicon. +* **`assets/og-image.png`:** Used for social media link previews. +* **`assets/video-poster.jpg`:** Displayed before the demo video loads. +* **`assets/demo.mp4`:** The product demo video. +* **`assets/hero-pattern.svg`:** A subtle background pattern used in the hero section CSS. + +## Deployment + +Deployment details are not specified within this codebase. However, as a static site, deployment typically involves: + +1. Copying the contents of the `site/cognotik.com` directory (HTML, CSS, JS, assets) to a web server or static hosting provider. +2. Common providers include GitHub Pages, Netlify, Vercel, AWS S3, etc. +3. A CI/CD pipeline could be set up (e.g., using GitHub Actions) to automatically deploy changes merged into the main branch. \ No newline at end of file diff --git a/site/assets/favicon.svg b/site/cognotik.com/assets/favicon.svg similarity index 100% rename from site/assets/favicon.svg rename to site/cognotik.com/assets/favicon.svg diff --git a/site/cognotik.com/contribute.html b/site/cognotik.com/contribute.html new file mode 100644 index 000000000..305119947 --- /dev/null +++ b/site/cognotik.com/contribute.html @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + Contribute to Cognotik + + +
+
+
+
+
+

Contribute to Cognotik

+

+ Cognotik thrives on community collaboration. Whether you're a developer, designer, writer, tester, or enthusiast, your contributions help shape the future of open-source AI development tools. +

+
+
+
+ +
+
+
+
+
+ +
+
+

How You Can Help

+
+
+
+ +
+

Contribute Code

+

+ Help us build new features, fix bugs, or improve performance. Browse open issues or suggest your own ideas! +

+
+
+
+ +
+

Report Bugs & Suggest Features

+

+ Found a bug or have a feature request? Open an issue on GitHub and help us improve. +

+
+
+
+ +
+

Write & Improve Documentation

+

+ Great docs make Cognotik accessible to everyone. Help us write, edit, or translate documentation. +

+
+
+
+ +
+

Join the Community

+

+ Connect with other contributors, ask questions, or share your experience on our GitHub Discussions. +

+
+
+
+ +
+

Build Plugins & Integrations

+

+ Extend Cognotik with your own plugins, or integrate with your favorite tools. See our contribution guide. +

+
+
+
+
+ +
+
+

Get Started

+
    +
  1. + Star & Fork the Repo: github.com/SimiaCryptus/Cognotik +
  2. +
  3. + Join the Discussion: Ask questions or introduce yourself in GitHub Discussions. +
  4. +
  5. + Submit a Pull Request: When you're ready, open a PR and our team will review it! +
  6. +
+ +
+
+ +
+
+

Thank You!

+

+ Every contribution, big or small, makes a difference. Thank you for helping us build the future of open-source AI development! +

+ ← Back to Home +
+
+
+ + + + + + + \ No newline at end of file diff --git a/site/index.html b/site/cognotik.com/index.html similarity index 53% rename from site/index.html rename to site/cognotik.com/index.html index 778bf29a6..f49ee8a02 100644 --- a/site/index.html +++ b/site/cognotik.com/index.html @@ -4,7 +4,7 @@ + content="Cognotik is the ultimate open-source general-purpose AI agent platform empowering everyone—from developers and businesses to educators and individuals. Enhance productivity with intelligent AI assistance, seamless collaboration, and versatile automation. Compatible with OpenAI, Anthropic, Google Gemini, and more. Cross-platform support: Windows, macOS, Linux, IntelliJ, and web. Secure BYOK model ensures privacy and cost control."> @@ -18,53 +18,44 @@ - - - + + -
-
-

Cognotik: Your AI Co-Pilot for Software Development

-

Accelerate your development workflow with intelligent AI planning and code generation.

-
    -
  • Open Source & BYOK: 100% open source under Apache 2.0. Bring your own API key for privacy and flexibility.
  • -
  • Cross-Platform: Available for Windows, macOS, Linux, IntelliJ, and web browsers.
  • -
  • AI Planning & Code Generation: Break down complex tasks and generate high-quality code with advanced AI models.
  • -
  • Interactive UI: Refine AI-generated plans and code in real time.
  • -
  • Supports Multiple AI Providers: Use OpenAI, Anthropic, and more via JOpenAI integration.
  • -
- +

Cognotik: The Ultimate General-Purpose AI Agent Platform

-
- Detected OS: Checking... - Latest Version: Checking... +
+ Detected OS: Checking... + Latest Version: Checking...
-
-
+

Cognotik: Your AI Co-Pilot for Software Development

-
+
-

Why Choose Cognotik?

+

Unlock Your Potential with Cognotik

- Cognotik is the most comprehensive open-source AI-powered development platform for modern software teams and individual developers. It combines intelligent planning, AI code generation, real-time collaboration, and secure extensibility to streamline your workflow, boost productivity, and keep you in control of your data and costs. + Cognotik empowers everyone—developers, businesses, educators, and individuals—with versatile AI agents that streamline tasks, enhance + productivity, and foster seamless collaboration. Open-source, secure, and privacy-first—take control of your workflow, data, and costs.

- - + + + + + +
-

AI-Powered Task Planning

-

Break down complex software tasks into actionable subtasks with advanced AI. Cognotik intelligently manages dependencies and generates step-by-step implementation plans for you or your team.

+

General-Purpose AI Agents

+

Leverage versatile AI agents that can handle a wide range of development tasks. From code generation to debugging, these agents adapt to your needs and learn from interactions to become more effective over time.

+
- - + + + +
-

AI Code Generation & Refactoring

-

Generate high-quality code, refactor existing codebases, and automate repetitive tasks with AI. Supports multiple languages and frameworks. Integrates with your IDE and CI/CD pipeline.

+

Free & Open Source

+

Enjoy complete transparency with our Apache 2.0 licensed codebase. Contribute to the project, customize it for your needs, or inspect the code for security. No hidden costs or proprietary restrictions—just powerful, community-driven software.

+
- - + + + + + + + +
-

Interactive Real-Time Collaboration

-

Collaborate with teammates or AI in real time. Review, refine, and approve AI-generated plans and code interactively. All changes are tracked and auditable.

+

Choose Any LLM Model

+

Freedom to select the AI model that best fits your needs. Connect to OpenAI, Anthropic Claude, Google Gemini, or any other compatible LLM. Switch between models seamlessly to optimize for cost, performance, or specialized capabilities.

+
- - + + + +

Cross-Platform & Extensible

-

Works on Windows, macOS, Linux, IntelliJ, and web. Easily extensible with plugins and APIs. Integrates with your favorite tools and workflows.

-
-
-
- - - -
-

Bring Your Own Key (BYOK) & Privacy

-

Use your own API keys for OpenAI, Anthropic, Google, and more. Your data and API usage stay under your control. No vendor lock-in, no forced cloud, and no hidden costs.

+

Use Cognotik anywhere—Windows, macOS, Linux, IntelliJ plugin, or web app. The extensible architecture allows for custom plugins and integrations with your existing development tools, version control systems, and CI/CD pipelines.

+
- - + + + + +
-

Enterprise-Grade Security & Compliance

-

Designed for teams and organizations. Supports on-premises deployment, SSO, audit logs, and granular access control. Meets privacy and compliance requirements.

+

Bring Your Own Key (BYOK) Cost Model

+

Maintain complete control over your AI costs and data privacy by using your own API keys. Set budgets, monitor usage, and ensure sensitive code never leaves your environment without your knowledge. No intermediary servers or additional markups.

+
-
-
- - - -
-

Knowledge Management & Documentation

-

Index, search, and visualize project knowledge. Extract structured data from documentation, generate docs from code, and keep your team aligned.

-
-
-
- Ready to transform your development workflow? Download Cognotik
-
+
-

See Cognotik in Action

+

Experience the Future of AI-Enhanced Development

+

Experience the Future of AI Assistance

- Watch how Cognotik revolutionizes software development with AI-powered planning, code generation, and interactive collaboration.
- Demo highlights: + Discover how Cognotik transforms your workflow through intelligent task automation, versatile AI assistance, and seamless collaboration.
+ Key highlights include:

    -
  • Automatic task breakdown and dependency management
  • -
  • Real-time AI code suggestions and refactoring
  • -
  • Live collaboration between developers and AI
  • -
  • Integration with multiple AI providers (OpenAI, Anthropic, Google, and more)
  • -
  • Secure, privacy-first workflow with BYOK
  • +
  • Intelligent task automation and workflow optimization
  • +
  • Instant AI-driven suggestions and improvements
  • +
  • Real-time human-AI collaboration
  • +
  • Flexible integration with OpenAI, Anthropic, Google Gemini, and more
  • +
  • Privacy-focused, secure BYOK infrastructure

- -
+
-

Open Source, Secure, and Flexible

-
-
-

Apache 2.0 License

-

- Cognotik is fully open-source under the permissive Apache 2.0 license. Use, modify, and distribute it freely for personal, educational, or commercial projects. No vendor lock-in, no hidden fees. -

- View License -
-
-

Bring Your Own Key (BYOK) Model

-

- Use your own API keys for maximum privacy, cost control, and flexibility. Cognotik never shares your data or API usage with third parties. -

-
    -
  • Supports OpenAI, Anthropic, Google, and more via JOpenAI integration
  • -
  • No forced cloud usage—run on-premises or in your own cloud
  • -
  • All data and API usage stays under your control
  • -
  • Granular access control for teams and organizations
  • -
-
+

Get Involved with Cognotik

+
+
+ Ready to supercharge your software development?Download + Cognotik
-
+ + + + @@ -231,6 +242,7 @@

Bring Your Own Key (BYOK) Model

+ + + + + + + + + + + + + + +
+
+
+
+
+

Cognotik IntelliJ Plugin

+

+ Bring the power of Cognotik's generative AI and agentic tools directly into your JetBrains IDE. Enhance your workflow with intelligent code editing, file manipulation, complex task automation, and voice control. +

+ +
+ +
+ + View on GitHub + +
+
+ +
+ Plugin Screenshot Placeholder +
+
+
+
+
+
+
+

Key Features

+
+
+
+

Editor Actions

+

Edit, paste, and chat about code directly within the editor to streamline your workflow and get instant AI assistance.

+
+
+
+

File Actions

+

Create new files, edit existing ones, or perform mass operations across your project with AI guidance.

+
+
+
+

Agentic Tools

+

Leverage powerful AI agents for complex tasks like refactoring, documentation generation, test creation, and multi-step workflow automation.

+
+
+
+

Voice to Text

+

Dictate code, comments, and commands using your voice for a hands-free coding experience.

+
+
+
+

BYOK Model

+

Use your own API keys (OpenAI, Anthropic, etc.) for full control over data privacy, costs, and model choice.

+
+
+
+

Open Source

+

Inspect, modify, and contribute to the plugin under the permissive Apache 2.0 license.

+
+
+
+
+
+
+

Getting Started

+

Installation

+
    +
  1. Open IntelliJ IDEA, GoLand, PyCharm, or other compatible JetBrains IDE.
  2. +
  3. Go to `Settings/Preferences` > `Plugins`.
  4. +
  5. Select the `Marketplace` tab.
  6. +
  7. Search for "AI Coding Assistant" or "Cognotik".
  8. +
  9. Click `Install` and restart the IDE when prompted.
  10. +
+

Configuration

+
    +
  1. After installation, open `Settings/Preferences`.
  2. +
  3. Navigate to `Tools` > `Cognotik`.
  4. +
  5. Enter your API keys for the desired AI service providers (e.g., OpenAI API Key).
  6. +
  7. Configure other settings like preferred models and agent behavior as needed.
  8. +
+

Basic Usage

+
    +
  • Editor Actions: Right-click in the editor or select code, then use the `AI Tools` context menu.
  • +
  • File Actions: Right-click on files/folders in the Project view, then use the `AI Tools` menu.
  • +
  • Agentic Tools: Access via the `AI Tools` menu or dedicated tool windows. Follow prompts, potentially in a browser window for complex tasks.
  • +
  • Voice Control: Activate voice input (check settings for activation method) and speak commands or dictate.
  • +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/site/logo.svg b/site/cognotik.com/logo.svg similarity index 100% rename from site/logo.svg rename to site/cognotik.com/logo.svg diff --git a/site/cognotik.com/monetization.html b/site/cognotik.com/monetization.html new file mode 100644 index 000000000..0e6d0fed6 --- /dev/null +++ b/site/cognotik.com/monetization.html @@ -0,0 +1,153 @@ + + + + + + Cognotik Monetization & Investor Information + + + + + + + + +
+
+
+
+
+

Monetization & Investor Information

+

+ Cognotik is committed to remaining a powerful, open-source, privacy-first AI development platform. We believe in sustainable growth, + transparency, and community-driven innovation. This page outlines our approach to monetization, sustainability, and opportunities for + investors and sponsors. +

+
+
+
+ +
+
+
+
+
+ +
+
+

Our Monetization Philosophy

+
+
+

Open Source First

+

+ Cognotik is and will always be free and open source under the Apache 2.0 license. The core platform is available to + everyone, with no hidden fees or proprietary lock-in. +

+
+
+

BYOK (Bring Your Own Key) Model

+

+ We do not resell AI APIs or charge for usage. Users connect their own API keys (OpenAI, Anthropic, Google, etc.), ensuring privacy and + cost control. No markups, no data intermediaries. +

+
+
+

Sustainability & Growth

+

+ To ensure long-term sustainability and accelerate development, we are exploring the following monetization and funding options: +

    +
  • Sponsorships & Donations: Community and corporate sponsorships via GitHub Sponsors, OpenCollective, or direct + support. +
  • +
  • Enterprise Support: Paid support, consulting, and integration services for enterprises and large teams.
  • +
  • Premium Plugins: Optional, value-added plugins or integrations (all core features remain free).
  • +
  • Grants & Partnerships: Collaboration with research institutions, foundations, and industry partners.
  • +
+

+
+
+
+
+ +
+
+

Investor & Sponsor Opportunities

+
+
+

Why Invest in Cognotik?

+

+

    +
  • Rapidly growing open-source community and adoption
  • +
  • Unique privacy-first, cross-platform approach
  • +
  • Extensible architecture for enterprise and developer markets
  • +
  • Strong team with proven open-source track record
  • +
+

+

Contact for Investment & Sponsorship

+

+ Interested in supporting Cognotik's mission or discussing investment?
+ Contact Us +

+
+
+
+
+ +
+
+ Support the future of open, secure, and intelligent software development. +
+ Sponsor on + GitHub + View on + GitHub +
+
+ Cognotik is a project by SimiaCryptus. +
+
+ + +
+
+

Thank You!

+

+ Every contribution, big or small, makes a difference. Thank you for helping us build the future of open-source AI development! +

+ ← Back to Home +
+
+
+ + + + + \ No newline at end of file diff --git a/site/cognotik.com/privacy.html b/site/cognotik.com/privacy.html new file mode 100644 index 000000000..ba3a4c41e --- /dev/null +++ b/site/cognotik.com/privacy.html @@ -0,0 +1,58 @@ + + + + + + Privacy Policy - Cognotik + + + + + + +
+
+

Privacy Policy

+

Effective Date: June 1, 2025

+

Introduction

+

+ Cognotik is an open-source AI development platform created by SimiaCryptus. We are committed to protecting your privacy and ensuring transparency about how your data is handled when you use our website (cognotik.com) and our software. +

+

Data Collection

+
    +
  • Website: We do not use cookies, trackers, or analytics services that collect personally identifiable information. We may collect anonymized, aggregate server logs for security and performance monitoring (e.g., IP address, browser type, date/time of access), but these are not used to identify individuals.
  • +
  • Software: Cognotik is open-source and does not collect or transmit your code, prompts, or usage data to our servers. All AI interactions occur directly between your device and the AI provider you configure (e.g., OpenAI, Anthropic, Google), using your own API key ("Bring Your Own Key" model).
  • +
+

Use of Your Data

+

+ We do not sell, rent, or share your personal data. Any information collected (such as anonymized server logs) is used solely to maintain and secure our website. +

+

Third-Party Services

+

+ If you use Cognotik to connect to third-party AI providers, your data is subject to their privacy policies. We recommend reviewing the privacy policies of any AI provider you use with Cognotik (e.g., OpenAI, Anthropic, Google). +

+

Open Source Transparency

+

+ Cognotik is fully open source under the Apache 2.0 license. You can inspect the source code to verify how data is handled. We encourage the community to audit and contribute to our codebase. +

+

Children's Privacy

+

+ Cognotik is not directed at children under 16. We do not knowingly collect or solicit personal information from children. +

+

Changes to This Policy

+

+ We may update this Privacy Policy from time to time. Changes will be posted on this page with a revised effective date. +

+

Contact Us

+

+ For any questions or concerns about this Privacy Policy or your data, please contact us at contact@cognotik.com. +

+
+

+ Last updated: June 1, 2025
+ © 2025 SimiaCryptus. All rights reserved. +

+
+
+ + \ No newline at end of file diff --git a/site/scripts/download-ui.js b/site/cognotik.com/scripts/download-ui.js similarity index 51% rename from site/scripts/download-ui.js rename to site/cognotik.com/scripts/download-ui.js index 73cde4059..d895590f6 100644 --- a/site/scripts/download-ui.js +++ b/site/cognotik.com/scripts/download-ui.js @@ -5,21 +5,36 @@ function updateDownloadUI() { const osText = document.getElementById('detected-os'); const versionText = document.getElementById('latest-version'); const downloadBtn = document.getElementById('download-button'); + const downloadInfoSimple = document.getElementById('download-info-simple'); // For aria-live updates const githubReleasesUrl = 'https://github.com/SimiaCryptus/Cognotik/releases'; const osNames = { windows: 'Windows', mac: 'macOS', linux: 'Linux', unknown: 'Unknown' }; // Initial state - if (osText) osText.textContent = `Detected OS: Checking...`; - if (versionText) versionText.textContent = `Latest Version: Checking...`; + // Set initial text and add aria-busy for screen readers + if (osText) { + osText.textContent = `Detected OS: Checking...`; + osText.setAttribute('aria-busy', 'true'); + } + if (versionText) { + versionText.textContent = `Latest Version: Checking...`; + versionText.setAttribute('aria-busy', 'true'); + } if (downloadBtn) { downloadBtn.textContent = 'Checking for updates...'; downloadBtn.disabled = true; downloadBtn.href = '#'; + downloadBtn.setAttribute('aria-label', 'Checking for updates, please wait.'); + downloadBtn.setAttribute('aria-busy', 'true'); } + // Announce the initial checking state + if (downloadInfoSimple) downloadInfoSimple.setAttribute('aria-live', 'polite'); // Detect OS const os = typeof detectOS === 'function' ? detectOS() : 'unknown'; - if (osText) osText.textContent = `Detected OS: ${osNames[os] || 'Unknown'}`; + if (osText) { + osText.textContent = `Detected OS: ${osNames[os] || 'Unknown'}`; + osText.removeAttribute('aria-busy'); // Done checking OS + } // If unknown, fallback if (os === 'unknown') { @@ -27,8 +42,13 @@ function updateDownloadUI() { downloadBtn.textContent = 'View All Releases on GitHub'; downloadBtn.href = githubReleasesUrl; downloadBtn.disabled = false; + downloadBtn.setAttribute('aria-label', 'View all Cognotik releases on GitHub'); + downloadBtn.removeAttribute('aria-busy'); + } + if (versionText) { + versionText.textContent = 'Could not detect your operating system'; + versionText.removeAttribute('aria-busy'); } - if (versionText) versionText.textContent = 'Could not detect your operating system'; return; } @@ -39,30 +59,43 @@ function updateDownloadUI() { downloadBtn.textContent = 'View All Releases on GitHub'; downloadBtn.href = githubReleasesUrl; downloadBtn.disabled = false; + downloadBtn.setAttribute('aria-label', 'View all Cognotik releases on GitHub'); + downloadBtn.removeAttribute('aria-busy'); + } + if (versionText) { + versionText.textContent = 'Release information unavailable'; + versionText.removeAttribute('aria-busy'); } - if (versionText) versionText.textContent = 'Release information unavailable'; return; } fetchLatestRelease().then(release => { if (!release) throw new Error('No releases found'); const version = release.tag_name ? release.tag_name.replace(/^v/, '') : 'Unknown'; - if (versionText) versionText.textContent = `Latest Version: ${version}`; + if (versionText) { + versionText.textContent = `Latest Version: ${version}`; + versionText.removeAttribute('aria-busy'); // Done checking version + } // Find asset let asset = typeof findDownloadAsset === 'function' ? findDownloadAsset(release, os) : null; if (asset) { if (downloadBtn) { - downloadBtn.textContent = `Download for ${osNames[os]} v${version}`; + const buttonText = `Download for ${osNames[os]} v${version}`; + downloadBtn.textContent = buttonText; downloadBtn.href = asset.browser_download_url; downloadBtn.disabled = false; downloadBtn.setAttribute('download', asset.name); + downloadBtn.setAttribute('aria-label', `${buttonText} (${asset.name})`); + downloadBtn.removeAttribute('aria-busy'); } } else { if (downloadBtn) { downloadBtn.textContent = 'View All Releases on GitHub'; downloadBtn.href = githubReleasesUrl; downloadBtn.disabled = false; + downloadBtn.setAttribute('aria-label', 'View all Cognotik releases on GitHub'); + downloadBtn.removeAttribute('aria-busy'); } } }).catch(err => { @@ -70,8 +103,13 @@ function updateDownloadUI() { downloadBtn.textContent = 'Could not fetch releases. Visit GitHub.'; downloadBtn.href = githubReleasesUrl; downloadBtn.disabled = false; + downloadBtn.setAttribute('aria-label', 'Could not fetch release information. Visit GitHub to view releases.'); + downloadBtn.removeAttribute('aria-busy'); + } + if (versionText) { + versionText.textContent = 'Release information unavailable'; + versionText.removeAttribute('aria-busy'); } - if (versionText) versionText.textContent = 'Release information unavailable'; console.error('Error fetching release info:', err); }); } diff --git a/site/cognotik.com/scripts/feature-modal.js b/site/cognotik.com/scripts/feature-modal.js new file mode 100644 index 000000000..f105be499 --- /dev/null +++ b/site/cognotik.com/scripts/feature-modal.js @@ -0,0 +1,172 @@ +document.addEventListener('DOMContentLoaded', function () { + // Get the modal + const modal = document.getElementById('feature-modal'); + if (!modal) return; // Exit if modal doesn't exist + + const modalTitle = document.getElementById('modal-title'); + const modalContent = document.getElementById('modal-content'); + const closeModal = document.querySelector('.close-modal'); + let previouslyFocusedElement = null; // To restore focus later + + + // Feature details content + const featureDetails = { + 'General-Purpose AI Agents': { + title: 'General-Purpose AI Agents', + content: ` +

Cognotik's AI agents are designed to be versatile and adaptable, capable of handling a wide range of development tasks:

+
    +
  • Flexible Agent Framework: Provides a modular structure for creating agents that can perform diverse tasks.
  • +
  • Cognitive Modes: Choose how the AI approaches tasks: +
      +
    • Chat Mode: Interactive execution of individual tasks, ideal for focused work.
    • +
    • Autonomous Mode: AI independently plans and executes complex development tasks with minimal guidance.
    • +
    • Plan Ahead Mode: Collaboratively create detailed plans with the AI before execution, offering more control.
    • +
    +
  • +
  • The Task Library includes: +
      +
    • InsightTask: Analyze code, answer questions, and provide explanations.
    • +
    • FileModificationTask: Create new files or modify existing code.
    • +
    • RunShellCommandTask: Execute shell command(s).
    • +
    • RunCodeTask: Generate and execute code snippets.
    • +
    • FileSearchTask: Search project files using patterns with contextual results.
    • +
    • WebSearchTask (CrawlerAgentTask): Search Google, fetch, and analyze web content.
    • +
    +
  • +
+

These agents serve as true co-pilots in your development process, reducing cognitive load and handling repetitive tasks while you focus on higher-level design and problem-solving.

+ ` + }, + 'Free & Open Source': { + title: 'Free & Open Source', + content: ` +

Cognotik is committed to the principles of open-source software development:

+
    +
  • Apache 2.0 License: Use, modify, and distribute Cognotik freely for both personal and commercial projects.
  • +
  • Transparent Codebase: Review the code for security, customize it for your specific needs, or learn from its implementation.
  • +
  • Community Contributions: Benefit from improvements and extensions created by a global community of developers.
  • +
  • No Vendor Lock-in: Avoid dependency on proprietary solutions with hidden costs or unexpected changes.
  • +
  • Ethical AI Development: Participate in building AI tools that respect user privacy and promote responsible use.
  • +
+

The open-source nature of Cognotik ensures longevity, security, and alignment with the collaborative spirit of software development.

+

Visit our GitHub repository to explore the code, contribute, or report issues.

+ ` + }, + 'Choose Any LLM Model': { + title: 'Choose Any LLM Model', + content: ` +

Cognotik gives you the freedom to select the AI model that best fits your specific requirements:

+
    +
  • Multiple Provider Support: Connect to OpenAI (ChatGPT4.1, o3, etc), Anthropic Claude, Google Gemini, and other compatible LLMs.
  • +
  • Model Switching: Seamlessly switch between different models based on the task at hand.
  • +
  • Cost Optimization: Use more affordable models for routine tasks and premium models for complex challenges.
  • +
  • Specialized Capabilities: Select models with strengths in specific domains or programming languages.
  • +
  • Future-Proof: As new models emerge, Cognotik's architecture allows for easy integration.
  • +
  • Local Models: Support for running local models for complete privacy and offline work.
  • +
+

This flexibility ensures you're never locked into a single AI provider or model, giving you control over performance, cost, and capabilities.

+ ` + }, + 'Cross-Platform & Extensible': { + title: 'Cross-Platform & Extensible', + content: ` +

Cognotik is designed to fit seamlessly into your existing workflow, regardless of your platform or toolchain:

+
    +
  • Cross-Platform: Available on Windows, macOS, Linux, and as an IntelliJ plugin.
  • +
  • Standalone Package: No dependencies on Java or other runtimes, making it easy to install and run.
  • +
  • Background Daemon: Cognotik is run as a background service accessed via a web UI, allowing for easy integration with various tools.
  • +
  • Auto-update: Automatically update to the latest version with new features and improvements.
  • +
  • Extensible Architecture: Extend functionality with custom providers for task types, service access, and even cognitive modes.
  • +
+

The extensible nature of Cognotik means it can grow with your needs and adapt to new technologies and methodologies as they emerge.

+ ` + }, + 'Bring Your Own Key (BYOK) Cost Model': { + title: 'Bring Your Own Key (BYOK) Cost Model', + content: ` +

Cognotik's BYOK approach puts you in complete control of your AI usage and costs:

+
    +
  • Direct API Access: Use your own API keys from OpenAI, Anthropic, Google, or other providers.
  • +
  • Cost Transparency: Pay only for what you use, with no additional markups or hidden fees.
  • +
  • Budget Control: Set usage limits and monitor consumption to prevent unexpected costs.
  • +
  • Data Privacy: Ensure your code and sensitive information never passes through third-party servers.
  • +
+

This model puts you in charge of your AI usage, allowing you to optimize costs while maintaining flexibility and control.

+ ` + } + }; + + + + // Function to close the modal + function closeModalHandler() { + modal.style.display = 'none'; + document.body.style.overflow = 'auto'; // Re-enable scrolling + if (previouslyFocusedElement) { + previouslyFocusedElement.focus(); // Restore focus + previouslyFocusedElement = null; + } + modal.removeEventListener('keydown', trapFocus); // Remove focus trap listener as per documentation + } + + // Add focus trapping inside the modal + function trapFocus(event) { + if (event.key !== 'Tab') return; + + const focusableElements = modal.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (event.shiftKey) { // Shift + Tab + if (document.activeElement === firstElement) { + lastElement.focus(); + event.preventDefault(); + } + } else { // Tab + if (document.activeElement === lastElement) { + firstElement.focus(); + event.preventDefault(); + } + } + } + + // Open modal logic + document.querySelectorAll('.read-more-btn').forEach(button => { + button.addEventListener('click', function () { + const featureTitle = this.parentElement.querySelector('h3').textContent; + const details = featureDetails[featureTitle]; + + if (details && modalTitle && modalContent && closeModal) { + previouslyFocusedElement = document.activeElement; // Store focus + modalTitle.textContent = details.title; + modalContent.innerHTML = details.content; + modal.style.display = 'block'; + document.body.style.overflow = 'hidden'; // Prevent scrolling + closeModal.focus(); // Set initial focus to the close button + modal.addEventListener('keydown', trapFocus); // Add focus trap listener + } + }); + }); + + // Close modal when clicking the close button + if (closeModal) { + closeModal.addEventListener('click', closeModalHandler); + } + + // Close modal when clicking outside the modal content + window.addEventListener('click', function (event) { + if (event.target === modal) { + closeModalHandler(); + } + }); + + // Close modal with Escape key + document.addEventListener('keydown', function (event) { + if (event.key === 'Escape' && modal.style.display === 'block') { + closeModalHandler(); + } + }); +}); \ No newline at end of file diff --git a/site/scripts/github-api.js b/site/cognotik.com/scripts/github-api.js similarity index 84% rename from site/scripts/github-api.js rename to site/cognotik.com/scripts/github-api.js index 91ff2bff8..3b64c4e8b 100644 --- a/site/scripts/github-api.js +++ b/site/cognotik.com/scripts/github-api.js @@ -5,7 +5,7 @@ // Cache constants const CACHE_KEY = 'cognotik_release_data'; -const CACHE_EXPIRY = 3600000; // 1 hour in milliseconds +const CACHE_EXPIRY = 3600000; // 1 hour in milliseconds (Consider localStorage for longer persistence) /** * Fetches the latest release information from GitHub @@ -15,7 +15,7 @@ const CACHE_EXPIRY = 3600000; // 1 hour in milliseconds async function fetchLatestRelease() { try { // Check cache first - const cachedData = checkCache(); + const cachedData = checkCache(CACHE_KEY, CACHE_EXPIRY); if (cachedData) { console.log('Using cached release data'); return cachedData; @@ -32,6 +32,7 @@ async function fetchLatestRelease() { console.log('Latest endpoint failed, trying releases list'); response = await fetch('https://api.github.com/repos/SimiaCryptus/Cognotik/releases'); + if (!response.ok) { throw new Error(`GitHub API error: ${response.status}`); } @@ -41,15 +42,15 @@ async function fetchLatestRelease() { throw new Error('No releases found'); } - // Cache the data - cacheData(releases[0]); + // Cache the first release from the list + cacheData(CACHE_KEY, releases[0]); return releases[0]; } const latestRelease = await response.json(); // Cache the data - cacheData(latestRelease); + cacheData(CACHE_KEY, latestRelease); return latestRelease; } catch (error) { console.error('Error fetching release data:', error); @@ -122,11 +123,13 @@ function findDownloadAsset(release, os) { /** * Checks if there is valid cached release data + * @param {string} key - The cache key (e.g., CACHE_KEY) + * @param {number} expiry - The cache expiry time in milliseconds (e.g., CACHE_EXPIRY) * @returns {Object|null} The cached release data or null if no valid cache */ -function checkCache() { +function checkCache(key, expiry) { try { - const cachedItem = sessionStorage.getItem(CACHE_KEY); + const cachedItem = localStorage.getItem(key); // Use localStorage for persistence across sessions if (!cachedItem) { return null; } @@ -135,9 +138,9 @@ function checkCache() { const now = new Date().getTime(); // Check if cache is expired - if (now - timestamp > CACHE_EXPIRY) { + if (now - timestamp > expiry) { console.log('Cache expired'); - sessionStorage.removeItem(CACHE_KEY); + localStorage.removeItem(key); return null; } @@ -150,16 +153,17 @@ function checkCache() { /** * Caches the release data with a timestamp + * @param {string} key - The cache key (e.g., CACHE_KEY) * @param {Object} data - The release data to cache */ -function cacheData(data) { +function cacheData(key, data) { try { const cacheItem = { timestamp: new Date().getTime(), data: data }; - sessionStorage.setItem(CACHE_KEY, JSON.stringify(cacheItem)); + localStorage.setItem(key, JSON.stringify(cacheItem)); // Use localStorage console.log('Release data cached'); } catch (error) { console.error('Error caching release data:', error); diff --git a/site/cognotik.com/scripts/logo-animation.js b/site/cognotik.com/scripts/logo-animation.js new file mode 100644 index 000000000..86a24d64f --- /dev/null +++ b/site/cognotik.com/scripts/logo-animation.js @@ -0,0 +1,235 @@ +/** + * Logo animation functionality for Cognotik website + */ + +function rnd() { + let x = 0; + for (let i = 0; i < 6; i++) { + x += Math.random() * 2 - 1; + } + return x; +} + +/** + * Initialize logo animations with animated, multicolored gradient responsive to cursor + */ +function initLogoAnimation() { + const heroLogo = document.getElementById('hero-logo'); + // Optionally, navLogo can be handled similarly if needed + // const navLogo = document.querySelector('.logo svg'); + + if (heroLogo) { + // --- Inject animated gradient defs if not already present --- + let defs = heroLogo.querySelector('defs'); + if (!defs) { + defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); + heroLogo.insertBefore(defs, heroLogo.firstChild); + } + + // Remove any existing gradient + let oldGrad = heroLogo.querySelector('#animated-gradient'); + if (oldGrad) oldGrad.remove(); + + // Create a multicolored, animated radial gradient + const grad = document.createElementNS('http://www.w3.org/2000/svg', 'radialGradient'); + grad.setAttribute('id', 'animated-gradient'); + grad.setAttribute('cx', '50%'); + grad.setAttribute('cy', '50%'); + grad.setAttribute('r', '70%'); + grad.setAttribute('fx', '50%'); + grad.setAttribute('fy', '50%'); + grad.setAttribute('gradientUnits', 'objectBoundingBox'); + + // Multicolored stops + const stops = [ + { offset: '0%', color: '#ff00cc' }, + { offset: '25%', color: '#3333ff' }, + { offset: '50%', color: '#00ffcc' }, + { offset: '75%', color: '#ffcc00' }, + { offset: '100%', color: '#ff0066' } + ]; + stops.forEach(stop => { + const s = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); + s.setAttribute('offset', stop.offset); + s.setAttribute('stop-color', stop.color); + grad.appendChild(s); + }); + defs.appendChild(grad); + + // Apply the gradient to all paths + heroLogo.querySelectorAll('path').forEach(path => { + path.setAttribute('fill', 'url(#animated-gradient)'); + }); + + // Plasma field helper: returns a value between 0 and 1 using a recursive, randomized infilling algorithm (Diamond–Square) + function generatePlasmaGrid(size) { + const grid = []; + for (let i = 0; i < size; i++) { + grid[i] = new Array(size).fill(0); + } + // Initialize corners + grid[0][0] = Math.random() * 0.8 + 0.1; // Keep values away from extremes for smoother transitions + grid[0][size - 1] = Math.random() * 0.8 + 0.1; + grid[size - 1][0] = Math.random() * 0.8 + 0.1; + grid[size - 1][size - 1] = Math.random() * 0.8 + 0.1; + + let step = size - 1; + let offset = 1; + while(step > 1) { + const half = step / 2; + // Diamond step + for (let y = 0; y < size - 1; y += step) { // Iterate y outer for cache coherency (minor) + for (let x = 0; x < size - 1; x += step) { + const avg = (grid[x][y] + grid[x][y + step] + + grid[x + step][y] + grid[x + step][y + step]) / 4; + grid[x + half][y + half] = avg + rnd() * offset; + } + } + // Square step + for (let y = 0; y < size; y += half) { // Iterate y outer + for (let x = (y + half) % step; x < size; x += step) { + let sum = 0, count = 0; + if (x - half >= 0) { sum += grid[x - half][y]; count++; } + if (x + half < size) { sum += grid[x + half][y]; count++; } + if (y - half >= 0) { sum += grid[x][y - half]; count++; } + if (y + half < size) { sum += grid[x][y + half]; count++; } + grid[x][y] = (sum / count) + rnd() * offset; + } + } + step = half; + offset *= 0.5; + } + // Normalize grid values to [0,1] + let min = Infinity, max = -Infinity; + for (let i = 0; i < size; i++) { // Iterate y outer + for (let j = 0; j < size; j++) { // Iterate x inner + if (grid[i][j] < min) min = grid[i][j]; + if (grid[i][j] > max) max = grid[i][j]; + } + } + for (let i = 0; i < size; i++) { + for (let j = 0; j < size; j++) { + grid[i][j] = (grid[i][j] - min) / (max - min); + } + } + return grid; + } + + function samplePlasma(grid, x, y) { + const size = grid.length; + const gx = x * (size - 1); + const gy = y * (size - 1); + const x0 = Math.floor(gx); + const x1 = Math.min(x0 + 1, size - 1); + const y0 = Math.floor(gy); + const y1 = Math.min(y0 + 1, size - 1); + const tx = gx - x0; + const ty = gy - y0; + const a = grid[x0][y0]; + const b = grid[x1][y0]; + const c = grid[x0][y1]; + const d = grid[x1][y1]; + const ab = a * (1 - tx) + b * tx; + const cd = c * (1 - tx) + d * tx; + return ab * (1 - ty) + (cd * ty); + } + + // Generate the plasma grid once (size 129 = 2^7 + 1 for sufficient detail) + const plasmaGrid = generatePlasmaGrid(64 + 1); // Further reduced size for performance, 2^6 + 1 + + // Plasma field function using the generated plasma grid. + // The time parameter z is used to shift the sampling, creating animation. + function plasma(x, y, z) { + const shift = z % 1; + return samplePlasma(plasmaGrid, (x + shift) % 1, (y + shift) % 1); + } + + // --- Animate the gradient's center over time (auto) --- + let t = 0; + let isUserInteracting = false; + let animationFrameId = null; // To potentially cancel the animation frame + let leaveTimeoutId = null; // Timeout for smooth transition on leave + + function animateGradient() { + t += 0.01; + // Only animate gradient center if user is not interacting + if (!isUserInteracting) { + + // Animate gradient center (cx,cy) in a Lissajous curve path for more visual interest + const cx = 50 + 25 * Math.sin(t * 0.8); + const cy = 50 + 25 * Math.cos(t * 1.1); + // Animate focal point (fx,fy) and radius (r) for more dynamism + const fx = 50 + 20 * Math.cos(t * 0.65 + Math.PI / 3); // Different speeds and phases + const fy = 50 + 20 * Math.sin(t * 0.95 + Math.PI / 1.5); + const radius = 65 + 15 * Math.sin(t * 0.45); // Pulsating radius + grad.setAttribute('cx', `${cx}%`); + grad.setAttribute('cy', `${cy}%`); + grad.setAttribute('fx', `${fx}%`); + grad.setAttribute('fy', `${fy}%`); + grad.setAttribute('r', `${radius}%`); + } + + // Update each color stop based on a 2D slice of a 3D plasma field. + grad.querySelectorAll('stop').forEach((stop, i) => { + // Parse the original offset (0-100) and convert to [0,1] coordinates. + const offsetVal = parseFloat(stop.getAttribute('offset')); + const xCoord = offsetVal / 100; + const yCoord = offsetVal / 100; // for this example, using the same value for x and y + + // Compute plasma field value for the (x,y) slice at depth = t. + const plasmaValue = plasma(xCoord, yCoord, t / 12); // Slightly faster plasma evolution + + // Map the plasma value [0,1] to a hue angle [0,360] + const hue = Math.floor(plasmaValue * 360 * 2); + + // Update the stop color dynamically. + stop.setAttribute('stop-color', `hsl(${hue},100%,50%)`); + }); + + animationFrameId = requestAnimationFrame(animateGradient); + } + animateGradient(); + + function onPointerMove(e) { + clearTimeout(leaveTimeoutId); // Clear any pending leave transition + isUserInteracting = true; + // Get bounding rect of SVG + const rect = heroLogo.getBoundingClientRect(); + let x = ((e.clientX - rect.left) / rect.width) * 100; + let y = ((e.clientY - rect.top) / rect.height) * 100; + // Clamp to [0,100] + x = Math.max(0, Math.min(100, x)); + y = Math.max(0, Math.min(100, y)); + // Make the interactive focal point also move + const fxInteractive = 50 + (x - 50) * 0.8; // Focal point follows cursor but less extremely + const fyInteractive = 50 + (y - 50) * 0.8; + grad.setAttribute('cx', `${x}%`); + grad.setAttribute('cy', `${y}%`); + grad.setAttribute('fx', `${fxInteractive}%`); + grad.setAttribute('fy', `${fyInteractive}%`); + } + function onPointerLeave() { + // Delay setting isUserInteracting to false for a smoother transition back to auto-animation + leaveTimeoutId = setTimeout(() => { + isUserInteracting = false; + // Optionally, smoothly animate back to center or let the auto-animation take over + // grad.style.transition = 'cx 0.5s ease, cy 0.5s ease'; // Add transition in CSS if desired + // grad.setAttribute('cx', '50%'); + // grad.setAttribute('cy', '50%'); + }, 200); // 200ms delay + } + // Add a small delay when pointer leaves to make transition smoother + function onPointerEnter() { + clearTimeout(leaveTimeoutId); // Clear timeout if pointer re-enters quickly + isUserInteracting = true; + } + + heroLogo.addEventListener('pointermove', onPointerMove); + heroLogo.addEventListener('pointerleave', onPointerLeave); + heroLogo.addEventListener('pointerenter', onPointerEnter); + } +} + +document.addEventListener('DOMContentLoaded', function() { + initLogoAnimation(); +}); \ No newline at end of file diff --git a/site/scripts/main.js b/site/cognotik.com/scripts/main.js similarity index 77% rename from site/scripts/main.js rename to site/cognotik.com/scripts/main.js index 18f6a97f3..a51821426 100644 --- a/site/scripts/main.js +++ b/site/cognotik.com/scripts/main.js @@ -24,11 +24,20 @@ document.addEventListener('DOMContentLoaded', function() { menuToggle.addEventListener('click', function() { mainNav.classList.toggle('active'); menuToggle.classList.toggle('active'); + // Toggle ARIA attributes for accessibility + const isExpanded = mainNav.classList.contains('active'); + menuToggle.setAttribute('aria-expanded', isExpanded); }); + // Set initial ARIA state + menuToggle.setAttribute('aria-expanded', 'false'); + menuToggle.setAttribute('aria-controls', 'main-nav-list'); // Assuming mainNav ul has id="main-nav-list" + mainNav.setAttribute('id', 'main-nav-list'); + document.addEventListener('click', function(e) { if (!mainNav.contains(e.target) && !menuToggle.contains(e.target)) { mainNav.classList.remove('active'); menuToggle.classList.remove('active'); + menuToggle.setAttribute('aria-expanded', 'false'); } }); } diff --git a/site/scripts/os-detection.js b/site/cognotik.com/scripts/os-detection.js similarity index 100% rename from site/scripts/os-detection.js rename to site/cognotik.com/scripts/os-detection.js diff --git a/site/cognotik.com/styles.css b/site/cognotik.com/styles.css new file mode 100644 index 000000000..32da8733b --- /dev/null +++ b/site/cognotik.com/styles.css @@ -0,0 +1,1009 @@ +/* ===== GLOBAL STYLES ===== */ +/* Enhanced Global Styles for Visual Masterpiece */ +:root { + /* Color Palette - derived from the logo */ + --primary-color: #3b82f6; /* Slightly softer, modern blue */ + --primary-dark: #1d4ed8; /* Deeper blue for hover/active states */ + --secondary-color: #f97316; /* Vibrant orange accent */ + --secondary-dark: #c2410c; /* Darker orange */ + --accent-color: #06b6d4; /* Rich cyan accent */ + --accent-dark: #0e7490; /* Darker cyan */ + --text-color: #23272f; /* Slightly darker text */ + --text-light: #7b8794; /* Softer secondary text */ + --background-light: #f3f6fa; /* Slightly bluer light background */ + --background-white: #ffffff; /* White background */ + --border-color: #e0e6ed; /* Softer border color */ + --success-color: #22c55e; /* Success messages */ + --error-color: #ef4444; /* Error messages */ + --gradient-primary: linear-gradient(110deg, var(--primary-color) 0%, var(--accent-color) 100%); + --gradient-secondary: linear-gradient(110deg, var(--secondary-color) 0%, #fbbf24 100%); /* Orange to Yellow */ + --gradient-accent: linear-gradient(110deg, var(--accent-color) 0%, #67e8f9 100%); /* Cyan to Light Cyan */ + + /* Typography */ + --font-primary: 'Inter', 'Roboto', Arial, 'Liberation Sans', 'Noto Sans', 'DejaVu Sans', 'Helvetica Neue', sans-serif; + --font-heading: 'Poppins', 'Inter', 'Roboto', Arial, 'Liberation Sans', 'Noto Sans', 'DejaVu Sans', 'Helvetica Neue', sans-serif; + --font-mono: 'Fira Code', 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace; + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 2rem; + --spacing-xl: 3rem; + --spacing-xxl: 5rem; + + /* Container widths */ + --container-max-width: 1200px; + --container-narrow: 900px; + + /* Border radius */ + --border-radius-sm: 4px; + --border-radius-md: 14px; + --border-radius-lg: 22px; + + /* Transitions */ + --transition-fast: 0.2s cubic-bezier(0.4, 0, 0.2, 1); /* Smoother ease-out */ + --transition-normal: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 0.5s ease; + + /* Shadows */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05); + --shadow-md: 0 6px 24px rgba(37,99,235,0.08), 0 1.5px 8px rgba(0,0,0,0.08); + --shadow-lg: 0 16px 40px rgba(37,99,235,0.10), 0 4px 24px rgba(0,0,0,0.10); +} +/* Focus Styles for Accessibility */ +*:focus-visible { + outline: 3px solid var(--primary-dark); + outline-offset: 2px; + border-radius: var(--border-radius-sm); /* Optional: match element's border-radius */ +} + +/* Reset and base styles */ +* { + padding: 0; + box-sizing: border-box; +} + +html { + font-size: 16px; + scroll-behavior: smooth; +} + +body { + font-family: var(--font-primary); + color: var(--text-color); + line-height: 1.6; + background: linear-gradient(135deg, #f3f6fa 0%, #e9f1fb 100%); + overflow-x: hidden; + padding-top: 60px; /* Align with fixed navbar height (60px) */ + font-feature-settings: "ss01" on, "ss02" on, "cv01" on; + font-variant-ligatures: contextual; + /* Polish font smoothing */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); + font-weight: 700; + line-height: 1.2; + margin-bottom: var(--spacing-md); + color: var(--primary-dark); + letter-spacing: -0.01em; + text-shadow: 0 2px 8px rgba(37,99,235,0.07); +} + +h1 { + font-size: 3rem; + font-family: var(--font-heading); + letter-spacing: -0.025em; /* Slightly tighter */ + background: linear-gradient(90deg, var(--primary-color) 60%, var(--accent-color) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +h2 { + font-size: 2.5rem; + font-family: var(--font-heading); + color: var(--primary-dark); /* Use darker blue for better contrast */ + letter-spacing: -0.01em; + text-shadow: 0 1px 6px rgba(37,99,235,0.05); +} + +h3 { + font-size: 2rem; + font-family: var(--font-heading); /* Use secondary color for feature titles etc. */ + color: var(--secondary-color); +} + +h4 { + font-size: 1.5rem; + font-family: var(--font-heading); +} + +h5 { + font-size: 1.25rem; + font-family: var(--font-heading); +} + +h6 { + font-size: 1rem; + font-family: var(--font-heading); +} + +/* Polish diacritic support for headings and body */ +[lang="pl"], body[lang="pl"] { + font-family: 'Inter', 'Roboto', Arial, 'Liberation Sans', 'Noto Sans', 'DejaVu Sans', 'Helvetica Neue', sans-serif; +} + +p { + margin-bottom: var(--spacing-md); + font-family: var(--font-primary); +} + +a { + color: var(--primary-color); + text-decoration: underline; /* Default underline for clarity */ + transition: color var(--transition-fast), text-shadow 0.2s; +} + +a:hover { + color: var(--primary-dark); + text-shadow: 0 2px 8px rgba(37,99,235,0.08); + text-decoration: underline; + text-decoration-thickness: 1.5px; /* Slightly thicker underline on hover */ } + +img, svg { + max-width: 100%; + height: auto; +} + +ul, ol { + margin-bottom: var(--spacing-md); + padding-left: var(--spacing-lg); +} + +code { + font-family: var(--font-mono); + background-color: var(--background-light); + padding: 0.2em 0.4em; + border-radius: var(--border-radius-sm); + font-size: 0.9em; +} + +/* Container */ +.container { + width: 100%; + max-width: var(--container-max-width); + margin: 0 auto; + padding: 0 var(--spacing-xl); +} + +.container-narrow { + max-width: var(--container-narrow); +} + +/* Section styling */ +.section { + padding: var(--spacing-xxl) 0; + position: relative; /* Useful for child element positioning or pseudo-elements */ + border-top: 1px solid var(--border-color); +} + +.section-title { + text-align: center; + margin-bottom: var(--spacing-xl); +} + +.section-subtitle { + text-align: center; + font-size: 1.25rem; + color: var(--text-light); + margin-top: -1rem; + margin-bottom: var(--spacing-xl); + max-width: 700px; + margin-left: auto; + margin-right: auto; +} +/* Remove top border from the first section within main, typically following a hero section */ +main > .section:first-of-type { + border-top: none; +} +.open-source-extra { + margin-top: 2em; + color: #666; + text-align: center; + font-size: 1.08em; +} + +/* ===== HEADER & NAVIGATION ===== */ + +/* Styles for .header and .header.scrolled removed. + README specifies .fixed-top and .fixed-top.scrolled for navbar behavior. + The .fixed-top rules below will manage the fixed header. */ + +.navbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-sm) 0; + height: 60px; +} +/* .fixed-top is the primary class for the fixed header as per README */ +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; + background-color: var(--background-white); + box-shadow: var(--shadow-sm); + transition: all var(--transition-normal); +} +.fixed-top.scrolled { + background-color: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: var(--shadow-md); +} + +.logo { + display: flex; + align-items: center; +} + +.logo svg { + height: 40px; + width: auto; + margin-right: var(--spacing-sm); +} + +.logo-text { + font-family: var(--font-heading); + font-weight: 700; + font-size: 1.5rem; + color: var(--text-color); + letter-spacing: -0.01em; +} + +.nav-links { + display: flex; + list-style: none; + margin: 0; + padding: 0; +} + +.nav-links li { + margin-left: var(--spacing-lg); +} + +.nav-links a { + color: var(--text-color); + font-weight: 500; + transition: color var(--transition-fast); +} + +.nav-links a:hover { + color: var(--primary-color); + text-decoration: underline; +} + +.nav-links a.active { + color: var(--primary-color); +} + +.mobile-menu-toggle { + display: none; + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--text-color); +} + +/* ===== HERO SECTION ===== */ +.hero { + padding: var(--spacing-xxl) 0; + background: linear-gradient(120deg, var(--background-light) 0%, #e9f1fb 70%, var(--background-white) 100%); + position: relative; + overflow: hidden; + box-shadow: 0 8px 32px rgba(37,99,235,0.09), 0 1.5px 8px rgba(0,0,0,0.07); +} + +/* Align hero background with README: use hero-pattern.svg */ +.hero-section::before { + content: ''; + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url('assets/hero-pattern.svg'); + background-size: cover; /* Ensure pattern covers the area */ + opacity: 0.15; + pointer-events: none; + z-index: 0; + animation: pulsePattern 10s ease-in-out infinite alternate; /* Subtle pulse */ +} + +.hero-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-lg); + position: relative; + z-index: 2; +} + +.hero-text { + /* The following properties are the effectively applied styles from the original malformed block */ + flex: 1; + max-width: 600px; + background: rgba(255,255,255,0.92); /* Slightly more opaque */ + border-radius: var(--border-radius-lg); + box-shadow: 0 2px 16px rgba(37,99,235,0.04); + padding: 2.5rem 2rem 2rem 2rem; +} + + +.hero-title { + /* The following properties are the effectively applied styles from the original malformed block */ + font-size: 3rem; + margin-bottom: var(--spacing-md); + line-height: 1.1; + font-family: var(--font-heading); + letter-spacing: -0.02em; +} + +.hero-subtitle { + /* The following properties are the effectively applied styles from the original malformed block */ + font-size: 1.5rem; + color: var(--text-light); + margin-bottom: var(--spacing-lg); + font-family: var(--font-primary); +} +.hero-details { + margin: 1.5em 0 1em 1em; + color: #e0e7ef; + font-size: 1.08em; + list-style: disc inside; +} + +.hero-image { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + position: relative; + z-index: 1; + filter: drop-shadow(0 8px 24px rgba(37,99,235,0.10)); +} + +.hero-image img { + max-width: 100%; + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-lg); +} +.hero-logo-container { + position: relative; + animation: float 6s ease-in-out infinite; + background: linear-gradient(135deg, #fff 60%, #e9f1fb 100%); + border-radius: 50%; + box-shadow: 0 4px 32px rgba(37,99,235,0.10); + padding: 2.5rem; +} +@keyframes float { + 0% { + transform: translateY(0px); + } + 50% { + transform: translateY(-20px); + } + 100% { + transform: translateY(0px); + } +} + +/* ===== BUTTONS ===== */ +.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + font-weight: 600; + text-align: center; + border: none; + border-radius: var(--border-radius-md); + cursor: pointer; + transition: all var(--transition-fast); + text-decoration: none; + box-shadow: 0 2px 8px rgba(37,99,235,0.08); +} + +.btn-primary { + background: var(--gradient-primary); + color: white; + border: none; + box-shadow: 0 4px 16px rgba(37,99,235,0.13); +} + +.btn-primary:hover { + background: linear-gradient(110deg, var(--primary-dark) 0%, var(--accent-dark) 100%); + transform: translateY(-3px) scale(1.03); + box-shadow: 0 8px 32px rgba(37,99,235,0.18); + text-decoration: none; + color: white; +} + +.btn-secondary { + background: var(--gradient-secondary); + color: white; + border: none; + box-shadow: 0 4px 16px rgba(255,140,66,0.13); +} + +.btn-secondary:hover { + background: linear-gradient(110deg, var(--secondary-dark) 0%, #f59e0b 100%); /* Darker orange to amber */ + transform: translateY(-3px) scale(1.03); + box-shadow: 0 8px 32px rgba(255,140,66,0.18); + text-decoration: none; + color: white; +} + +.btn-outline { + background-color: transparent; + border: 2px solid var(--primary-color); + color: var(--primary-color); +} + +.btn-outline:hover { + background-color: var(--primary-color); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-md); + text-decoration: none; +} + +.btn-lg { + padding: 1rem 2rem; + font-size: 1.1rem; +} + +.btn-sm { + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +.btn-download { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.btn-download i { + font-size: 1.2em; +} + +/* ===== FEATURES SECTION ===== */ +.features-section { /* Corrected selector from .features to .features-section */ + background-color: var(--background-white); + /* padding is now handled by the .section class (var(--spacing-xxl) 0) */ +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--spacing-lg); + margin-top: 2.5rem; +} + +.feature-card { + background: linear-gradient(120deg, #fff 80%, #e9f1fb 100%); + border-radius: var(--border-radius-xl); /* Use larger radius */ + padding: 2.2rem 2rem 2.3rem 2rem; + box-shadow: 0 8px 32px rgba(37,99,235,0.11), 0 2px 8px rgba(0,0,0,0.06); + transition: transform var(--transition-normal), box-shadow var(--transition-normal), border-color 0.3s; + position: relative; + overflow: hidden; + z-index: 1; + border: 2.5px solid transparent; +} +.feature-card:hover .feature-icon svg, +.feature-card:hover .feature-icon i { + transform: scale(1.15) rotate(-5deg); +} + + +.feature-card:hover { + /* The following properties are the effectively applied styles from the original malformed block */ + transform: translateY(-8px) scale(1.03); + box-shadow: 0 16px 48px rgba(37,99,235,0.18), 0 4px 24px rgba(0,0,0,0.10); + border-color: var(--primary-color); /* Use primary color for border highlight */ +} + +.feature-icon { + margin-bottom: var(--spacing-md); + background: linear-gradient(90deg, var(--primary-color) 60%, var(--accent-color) 100%); + border-radius: 50%; + padding: 0.4em; + box-shadow: 0 2px 8px rgba(37,99,235,0.07); + display: inline-block; + transition: transform 0.3s ease-in-out; /* For icon animation */ +} + +.feature-card::before { /* Animated top border */ + content: ''; + position: absolute; + top: 0; + left: 50%; /* Start from center */ + transform: translateX(-50%); + width: 0; /* Start with no width */ + height: 5px; /* Thicker highlight */ + background: var(--gradient-accent); /* Use accent gradient */ + z-index: 2; + border-radius: 5px 5px 0 0; /* Rounded top corners for the bar */ + transition: width 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); /* Smoother transition */ +} + +.feature-card:hover::before { + width: 80%; /* Animate width on hover, not full width for style */ +} + + +.feature-title { + font-size: 1.5rem; + margin-bottom: var(--spacing-sm); + font-family: var(--font-heading); +} + +.feature-description { + color: var(--text-light); + font-family: var(--font-primary); + margin-bottom: 1.1em; +} + +/* ===== VIDEO SECTION ===== */ +.video-section { + background: linear-gradient(135deg, var(--background-light) 0%, #e9f1fb 100%); + position: relative; + overflow: hidden; + box-shadow: 0 8px 32px rgba(37,99,235,0.07); +} +.video-section::before { + content: ''; + position: absolute; + top: -100px; + left: 0; + width: 100%; + height: 100px; + background: linear-gradient(to bottom, transparent, var(--background-light)); + z-index: 1; +} + +.video-container { + /* The following properties are the effectively applied styles from the original malformed block */ + position: relative; + width: 100%; + padding-bottom: 56.25%; /* 16:9 aspect ratio */ + height: 0; + overflow: hidden; + border-radius: var(--border-radius-xl); /* Larger radius */ + box-shadow: 0 8px 32px rgba(37,99,235,0.12); + transform: perspective(1000px) rotateX(2deg) scale(1.02); + transition: transform var(--transition-normal), box-shadow 0.3s; +} +.video-container:hover { + /* The following properties are the effectively applied styles from the original malformed block */ + transform: perspective(1000px) rotateX(0) scale(1.04); + box-shadow: 0 16px 48px rgba(37,99,235,0.18); +} + +.video-container iframe, +.video-container video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; +} + +.video-description { + text-align: center; + margin-top: var(--spacing-lg); + max-width: 700px; + margin-left: auto; + margin-right: auto; +} + +/* ===== DOWNLOAD SECTION ===== */ + + + + + + + + + + + + + + + +/* --- Simplified Download UI Styles --- */ +.download-ui-simple { + margin: 2rem 0 1.5rem 0; + background: var(--gradient-primary); + color: #fff; + border-radius: var(--border-radius-lg); + padding: 2rem 1.8rem 1.8rem 1.8rem; /* Slightly adjusted padding */ + box-shadow: 0 8px 32px rgba(37,99,235,0.15), 0 2px 8px rgba(0,0,0,0.07); + text-align: left; + max-width: 480px; + border: 2.5px solid var(--accent-color); +} +.download-ui-simple #download-info-simple { + font-size: 1rem; + opacity: 0.95; +} +.download-ui-simple .btn { + margin-top: 0.5em; + background: #fff; + color: var(--primary-color); + font-weight: 700; + font-size: 1.05rem; /* Slightly smaller font */ + border-radius: var(--border-radius-md); + padding: 0.8em 1.8em; /* Adjusted padding */ + box-shadow: 0 2px 8px rgba(37,99,235,0.08); + border: 2px solid var(--primary-color); + transition: all 0.22s; +} +.download-ui-simple .btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} +.download-ui-simple .btn:hover:not(:disabled), .download-ui-simple .btn:focus-visible:not(:disabled) { + background: var(--primary-color); + color: #fff; + border-color: var(--accent-color); + box-shadow: 0 8px 32px rgba(37,99,235,0.18); +} +.download-ui-simple .download-alternatives { + font-size: 0.95em; + opacity: 0.85; + margin-top: 0.5em; +} +.download-ui-simple .download-alternatives a { + color: #fff; + text-decoration: underline; +} +.download-ui-simple .download-alternatives a:hover { + color: #fff; + text-decoration-thickness: 1.5px; +} + +/* ===== OPEN SOURCE SECTION ===== */ +.open-source { + background-color: var(--background-white); +} + +.open-source-container { + display: flex; + align-items: center; + gap: var(--spacing-xl); +} + +.open-source-image { + flex: 1; + display: flex; + justify-content: center; +} + +.open-source-content { + flex: 1; +} + +.open-source-badge { + display: inline-block; + background-color: var(--background-light); + color: var(--text-color); + padding: 0.3rem 0.8rem; + border-radius: 20px; + font-size: 0.9rem; + font-weight: 600; + margin-bottom: var(--spacing-md); +} + +.open-source-links { + margin-top: var(--spacing-lg); +} + +.open-source-links a { + display: inline-flex; + align-items: center; + margin-right: var(--spacing-lg); + color: var(--primary-color); +} + +.open-source-links a i { + margin-right: var(--spacing-xs); +} + +/* ===== FOOTER ===== */ +footer { + background-color: var(--text-color); /* Darker footer */ + padding: var(--spacing-xl) 0; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--spacing-lg); +} + +.footer-logo { + display: flex; + align-items: center; +} + +.footer-logo svg { + height: 30px; + width: auto; + margin-right: var(--spacing-sm); +} + +.footer-links { + display: flex; + gap: var(--spacing-lg); +} + +.footer-links a { + /* Consolidating footer link styles for consistency with dark footer background */ + /* color: var(--text-light); */ /* This would be for light background */ + transition: color var(--transition-fast); + color: rgba(255, 255, 255, 0.7); /* Lighter text on dark background */ +} + +.footer-links a:hover { + opacity: 1; + text-decoration: underline; + /* color: var(--primary-color); */ /* Primary color might be too bright on dark bg */ + color: rgba(255, 255, 255, 0.9); /* Brighter on hover */ +} + +.footer-content p { /* Style copyright text */ + width: 100%; + text-align: center; + margin-top: var(--spacing-lg); + color: var(--text-light); + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.6); /* Lighter copyright text */ +} + +/* ===== UTILITIES ===== */ +.text-center { + text-align: center; +} + +.mt-1 { margin-top: var(--spacing-xs); } +.mt-2 { margin-top: var(--spacing-sm); } +.mt-3 { margin-top: var(--spacing-md); } +.mt-4 { margin-top: var(--spacing-lg); } +.mt-5 { margin-top: var(--spacing-xl); } + +.mb-1 { margin-bottom: var(--spacing-xs); } +.mb-2 { margin-bottom: var(--spacing-sm); } +.mb-3 { margin-bottom: var(--spacing-md); } +.mb-4 { margin-bottom: var(--spacing-lg); } +.mb-5 { margin-bottom: var(--spacing-xl); } + + + + + + + + + + + + + + + + + + + + + +/* Responsive styles are consolidated in styles/responsive.css */ + +/* ===== ANIMATIONS ===== */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@keyframes pulsePattern { /* For hero background pattern */ + 0% { + opacity: 0.12; + } + 100% { + opacity: 0.18; + } +} + +.fade-in { + animation: fadeIn 0.8s ease forwards; +} + +.delay-1 { animation-delay: 0.2s; } +.delay-2 { animation-delay: 0.4s; } +.delay-3 { animation-delay: 0.6s; } +.delay-4 { animation-delay: 0.8s; } +.delay-5 { animation-delay: 1s; } + +/* ===== LOGO ANIMATION ===== */ +.logo svg { + transition: transform var(--transition-normal); +} + +.logo:hover svg { + transform: rotate(10deg); +} + +/* ===== LOADING STATES ===== */ +.loading { + position: relative; +} + +.loading::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + margin: -10px 0 0 -10px; + border: 3px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: white; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-text { + visibility: hidden; +} + +.loading .loading-text { + visibility: visible; +} + + + +/* Feature Card Read More Button */ +.read-more-btn { + background: linear-gradient(90deg, var(--primary-color) 60%, var(--accent-color) 100%); + border: none; + color: #fff; + padding: 10px 20px; /* Slightly adjusted padding */ + border-radius: 22px; + cursor: pointer; + margin-top: 18px; + font-family: var(--font-primary); + font-size: 1.09em; + box-shadow: 0 2px 8px rgba(37,99,235,0.08); + transition: all 0.23s; +} + +.read-more-btn:hover, .read-more-btn:focus-visible { + background: linear-gradient(90deg, var(--primary-dark) 60%, var(--accent-color) 100%); + color: #fff; + transform: translateY(-2px) scale(1.04); + box-shadow: 0 8px 32px rgba(37,99,235,0.13); +} + +/* Modal Styles */ +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); +} + +.modal-content { + background-color: #fff; + margin: 8% auto; /* Adjust margin */ + padding: 30px; + border-radius: 22px; + box-shadow: 0 16px 48px rgba(37,99,235,0.18), 0 4px 24px rgba(0,0,0,0.10); + width: 80%; + max-width: 800px; + position: relative; + animation: modalFadeIn 0.3s ease-in-out; + font-family: var(--font-primary); + border: 2.5px solid var(--accent-color); +} + +@keyframes modalFadeIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.close-modal { + position: absolute; + top: 15px; + right: 25px; + color: #aaa; + font-size: 28px; + font-weight: bold; + cursor: pointer; + transition: color 0.2s; +} + +.close-modal:hover, .close-modal:focus-visible { + color: #333; +} + +#modal-title { + margin-top: 0; + color: var(--primary-color); + border-bottom: 2.5px solid var(--accent-color); + padding-bottom: 10px; + margin-bottom: 20px; + font-family: var(--font-heading); + background: linear-gradient(90deg, var(--primary-color) 60%, var(--accent-color) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +#modal-content { + line-height: 1.6; + font-family: var(--font-primary); +} + +#modal-content ul { + margin-left: 20px; + margin-bottom: 20px; +} + +#modal-content li { + margin-bottom: 10px; +} + +#modal-content a { + color: var(--primary-color); + text-decoration: none; +} + +#modal-content a:hover, #modal-content a:focus-visible { + text-decoration: underline; + color: var(--accent-color); +} \ No newline at end of file diff --git a/site/styles/responsive.css b/site/cognotik.com/styles/responsive.css similarity index 100% rename from site/styles/responsive.css rename to site/cognotik.com/styles/responsive.css diff --git a/site/new_tld.sh b/site/new_tld.sh new file mode 100644 index 000000000..de9462b05 --- /dev/null +++ b/site/new_tld.sh @@ -0,0 +1,323 @@ +#!/bin/bash +# Check if domain name is provided +if [ -z "$1" ]; then + echo "Error: Domain name is required" + echo "Usage: $0 " + exit 1 +fi +DOMAIN=$1 +BUCKET_NAME=$DOMAIN +REGION="us-east-1" # Change this to your preferred AWS region +CLOUDFRONT_ENABLED=true # Set to false if you don't want CloudFront + +echo "Creating S3 bucket for domain: $DOMAIN" +# Create the S3 bucket +aws s3api create-bucket --bucket $BUCKET_NAME --region $REGION +# Disable block public access settings for the bucket +echo "Disabling block public access settings for the bucket..." +aws s3api put-public-access-block --bucket $BUCKET_NAME --public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false" + +# Enable website hosting on the bucket +aws s3 website s3://$BUCKET_NAME/ --index-document index.html --error-document error.html + +# Create bucket policy to allow public read access +echo "Setting bucket policy to allow public read access..." +cat > /tmp/bucket-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadGetObject", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::$BUCKET_NAME/*" + } + ] +} +EOF +# Apply the bucket policy +aws s3api put-bucket-policy --bucket $BUCKET_NAME --policy file:///tmp/bucket-policy.json +# Check if the bucket policy was applied successfully +if [ $? -ne 0 ]; then + echo "Warning: Failed to apply bucket policy. Your bucket may not be publicly accessible." + echo "You may need to manually disable Block Public Access settings in the AWS console:" + echo "1. Go to https://s3.console.aws.amazon.com/s3/buckets/$BUCKET_NAME?region=$REGION&tab=permissions" + echo "2. Under 'Block public access (bucket settings)', click Edit" + echo "3. Uncheck all four options and save changes" + echo "4. Then run this script again" +fi +# Create sample index.html +cat > /tmp/index.html << EOF + + + + Welcome to $DOMAIN + + + +

Welcome to $DOMAIN

+

Your S3 website is now live!

+ + +EOF +# Create sample error.html +cat > /tmp/error.html << EOF + + + + Error - $DOMAIN + + + +

404 - Page Not Found

+

The page you are looking for does not exist.

+ + +EOF +# Upload the sample files to the bucket +aws s3 cp /tmp/index.html s3://$BUCKET_NAME/ +aws s3 cp /tmp/error.html s3://$BUCKET_NAME/ +# Route53 DNS Configuration +echo "Setting up Route53 DNS for $DOMAIN..." +# Check if the domain is registered in Route53 +HOSTED_ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name $DOMAIN --max-items 1 --query "HostedZones[?Name=='$DOMAIN.'].Id" --output text | cut -d'/' -f3) +if [ -z "$HOSTED_ZONE_ID" ]; then + echo "No Route53 hosted zone found for $DOMAIN" + echo "Would you like to create a new hosted zone for $DOMAIN? (y/n)" + read CREATE_ZONE + if [ "$CREATE_ZONE" = "y" ] || [ "$CREATE_ZONE" = "Y" ]; then + echo "Creating new Route53 hosted zone for $DOMAIN..." + ZONE_RESPONSE=$(aws route53 create-hosted-zone --name $DOMAIN --caller-reference $(date +%s) --query "HostedZone.Id" --output text) + HOSTED_ZONE_ID=$(echo $ZONE_RESPONSE | cut -d'/' -f3) + echo "New hosted zone created with ID: $HOSTED_ZONE_ID" + echo "Please update your domain's name servers at your registrar with the following name servers:" + aws route53 get-hosted-zone --id $HOSTED_ZONE_ID --query "DelegationSet.NameServers" --output text + else + echo "Skipping Route53 configuration." + HOSTED_ZONE_ID="" + fi +fi +if [ ! -z "$HOSTED_ZONE_ID" ]; then + # Create a JSON file for the Route53 change batch + cat > /tmp/route53-change-batch.json << EOF +{ + "Changes": [ + { + "Action": "UPSERT", + "ResourceRecordSet": { + "Name": "$DOMAIN", + "Type": "A", + "AliasTarget": { + "HostedZoneId": "Z3AQBSTGFYJSTF", + "DNSName": "s3-website-$REGION.amazonaws.com", + "EvaluateTargetHealth": false + } + } + }, + { + "Action": "UPSERT", + "ResourceRecordSet": { + "Name": "www.$DOMAIN", + "Type": "CNAME", + "TTL": 300, + "ResourceRecords": [ + { + "Value": "$DOMAIN" + } + ] + } + } + ] +} +EOF + # Apply the Route53 changes + echo "Updating Route53 DNS records..." + aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file:///tmp/route53-change-batch.json + if [ $? -eq 0 ]; then + echo "DNS configuration complete! Please allow some time for DNS changes to propagate." + echo "Your website will be accessible at: http://$DOMAIN and http://www.$DOMAIN" + else + echo "Failed to update DNS records. Please check your AWS permissions and try again." + fi +fi + +echo "Website setup complete!" + +# Set up CloudFront distribution with HTTPS if enabled +if [ "$CLOUDFRONT_ENABLED" = true ]; then + echo "Setting up CloudFront distribution with HTTPS..." + + # First, request an SSL certificate from ACM + echo "Requesting SSL certificate for $DOMAIN and www.$DOMAIN..." + CERTIFICATE_ARN=$(aws acm request-certificate \ + --domain-name $DOMAIN \ + --validation-method DNS \ + --subject-alternative-names www.$DOMAIN \ + --region us-east-1 \ + --query CertificateArn \ + --output text) + + if [ -z "$CERTIFICATE_ARN" ]; then + echo "Failed to request SSL certificate. Skipping CloudFront setup." + else + echo "Certificate requested: $CERTIFICATE_ARN" + + # Get certificate validation records and add them to Route53 + if [ ! -z "$HOSTED_ZONE_ID" ]; then + echo "Adding certificate validation records to Route53..." + sleep 5 # Wait a bit for the certificate to be processed + + VALIDATION_RECORDS=$(aws acm describe-certificate \ + --certificate-arn $CERTIFICATE_ARN \ + --region us-east-1 \ + --query "Certificate.DomainValidationOptions[].ResourceRecord") + + if [ ! -z "$VALIDATION_RECORDS" ]; then + # Create validation records in Route53 + for i in $(seq 0 1); do + RECORD_NAME=$(aws acm describe-certificate --certificate-arn $CERTIFICATE_ARN --region us-east-1 --query "Certificate.DomainValidationOptions[$i].ResourceRecord.Name" --output text) + RECORD_VALUE=$(aws acm describe-certificate --certificate-arn $CERTIFICATE_ARN --region us-east-1 --query "Certificate.DomainValidationOptions[$i].ResourceRecord.Value" --output text) + + if [ ! -z "$RECORD_NAME" ] && [ ! -z "$RECORD_VALUE" ]; then + cat > /tmp/validation-change-batch.json << EOF +{ + "Changes": [ + { + "Action": "UPSERT", + "ResourceRecordSet": { + "Name": "$RECORD_NAME", + "Type": "CNAME", + "TTL": 300, + "ResourceRecords": [ + { + "Value": "$RECORD_VALUE" + } + ] + } + } + ] +} +EOF + aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file:///tmp/validation-change-batch.json + fi + done + + echo "Certificate validation records added. Certificate will be validated automatically." + echo "This may take up to 30 minutes. You can check the status in the AWS Certificate Manager console." + + # Create CloudFront distribution configuration file + cat > /tmp/cloudfront-config.json << EOF +{ + "CallerReference": "$(date +%s)", + "Comment": "CloudFront distribution for $DOMAIN", + "Aliases": { + "Quantity": 2, + "Items": ["$DOMAIN", "www.$DOMAIN"] + }, + "DefaultRootObject": "index.html", + "Origins": { + "Quantity": 1, + "Items": [ + { + "Id": "S3-$BUCKET_NAME", + "DomainName": "$BUCKET_NAME.s3.amazonaws.com", + "S3OriginConfig": { + "OriginAccessIdentity": "" + } + } + ] + }, + "DefaultCacheBehavior": { + "TargetOriginId": "S3-$BUCKET_NAME", + "ViewerProtocolPolicy": "redirect-to-https", + "AllowedMethods": { + "Quantity": 2, + "Items": ["GET", "HEAD"], + "CachedMethods": { + "Quantity": 2, + "Items": ["GET", "HEAD"] + } + }, + "ForwardedValues": { + "QueryString": false, + "Cookies": { + "Forward": "none" + } + }, + "MinTTL": 0, + "DefaultTTL": 86400, + "MaxTTL": 31536000 + }, + "ViewerCertificate": { + "ACMCertificateArn": "$CERTIFICATE_ARN", + "SSLSupportMethod": "sni-only", + "MinimumProtocolVersion": "TLSv1.2_2021" + }, + "Enabled": true +} +EOF + + echo "Creating CloudFront distribution (this may take a few minutes)..." + DISTRIBUTION_RESPONSE=$(aws cloudfront create-distribution --distribution-config file:///tmp/cloudfront-config.json) + DISTRIBUTION_ID=$(echo "$DISTRIBUTION_RESPONSE" | grep -o '"Id": "[^"]*"' | cut -d'"' -f4) + DISTRIBUTION_DOMAIN=$(echo "$DISTRIBUTION_RESPONSE" | grep -o '"DomainName": "[^"]*"' | cut -d'"' -f4) + + echo "CloudFront distribution created: $DISTRIBUTION_ID" + echo "CloudFront domain: $DISTRIBUTION_DOMAIN" + + # Update Route53 records to point to CloudFront + if [ ! -z "$HOSTED_ZONE_ID" ] && [ ! -z "$DISTRIBUTION_DOMAIN" ]; then + echo "Updating Route53 records to point to CloudFront..." + cat > /tmp/route53-cloudfront-change.json << EOF +{ + "Changes": [ + { + "Action": "UPSERT", + "ResourceRecordSet": { + "Name": "$DOMAIN", + "Type": "A", + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": "$DISTRIBUTION_DOMAIN", + "EvaluateTargetHealth": false + } + } + } + ] +} +EOF + aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file:///tmp/route53-cloudfront-change.json + fi + fi + fi + fi + + echo "Your website is available at: http://$BUCKET_NAME.s3-website-$REGION.amazonaws.com" + echo "Once CloudFront is deployed and DNS propagates, it will be available at: https://$DOMAIN" +else + echo "Your website is available at: http://$BUCKET_NAME.s3-website-$REGION.amazonaws.com" + echo "" + if [ -z "$HOSTED_ZONE_ID" ]; then + echo "Next steps:" + echo "1. Configure your domain's DNS to point to the S3 website endpoint" + echo "2. For HTTPS support, enable CloudFront by setting CLOUDFRONT_ENABLED=true" + echo "3. Upload your website content to the S3 bucket" + else + echo "Next steps:" + echo "1. For HTTPS support, enable CloudFront by setting CLOUDFRONT_ENABLED=true" + echo "2. Upload your website content to the S3 bucket" + fi +fi \ No newline at end of file diff --git a/site/scripts/logo-animation.js b/site/scripts/logo-animation.js deleted file mode 100644 index e6db128e9..000000000 --- a/site/scripts/logo-animation.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Logo animation functionality for Cognotik website - */ - -/** - * Initialize logo animations with animated, multicolored gradient responsive to cursor - */ -function initLogoAnimation() { - const heroLogo = document.getElementById('hero-logo'); - // Optionally, navLogo can be handled similarly if needed - // const navLogo = document.querySelector('.logo svg'); - - if (heroLogo) { - // --- Inject animated gradient defs if not already present --- - let defs = heroLogo.querySelector('defs'); - if (!defs) { - defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); - heroLogo.insertBefore(defs, heroLogo.firstChild); - } - - // Remove any existing gradient - let oldGrad = heroLogo.querySelector('#animated-gradient'); - if (oldGrad) oldGrad.remove(); - - // Create a multicolored, animated radial gradient - const grad = document.createElementNS('http://www.w3.org/2000/svg', 'radialGradient'); - grad.setAttribute('id', 'animated-gradient'); - grad.setAttribute('cx', '50%'); - grad.setAttribute('cy', '50%'); - grad.setAttribute('r', '70%'); - grad.setAttribute('fx', '50%'); - grad.setAttribute('fy', '50%'); - grad.setAttribute('gradientUnits', 'objectBoundingBox'); - - // Multicolored stops - const stops = [ - { offset: '0%', color: '#ff00cc' }, - { offset: '25%', color: '#3333ff' }, - { offset: '50%', color: '#00ffcc' }, - { offset: '75%', color: '#ffcc00' }, - { offset: '100%', color: '#ff0066' } - ]; - stops.forEach(stop => { - const s = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); - s.setAttribute('offset', stop.offset); - s.setAttribute('stop-color', stop.color); - grad.appendChild(s); - }); - defs.appendChild(grad); - - // Apply the gradient to all paths - heroLogo.querySelectorAll('path').forEach(path => { - path.setAttribute('fill', 'url(#animated-gradient)'); - }); - - // --- Animate the gradient's center over time (auto) --- - let t = 0; - function animateGradient() { - t += 0.01; - // Animate cx/cy in a circle - const cx = 50 + 20 * Math.cos(t * 1.2); - const cy = 50 + 20 * Math.sin(t); - grad.setAttribute('cx', `${cx}%`); - grad.setAttribute('cy', `${cy}%`); - // Animate color stops (optional: hue rotation) - grad.querySelectorAll('stop').forEach((stop, i) => { - // Optionally animate the color stops for a more dynamic effect - // (left as static for now) - }); - requestAnimationFrame(animateGradient); - } - animateGradient(); - - // --- Make gradient center follow cursor when over logo --- - function onPointerMove(e) { - // Get bounding rect of SVG - const rect = heroLogo.getBoundingClientRect(); - let x = ((e.clientX - rect.left) / rect.width) * 100; - let y = ((e.clientY - rect.top) / rect.height) * 100; - // Clamp to [0,100] - x = Math.max(0, Math.min(100, x)); - y = Math.max(0, Math.min(100, y)); - grad.setAttribute('cx', `${x}%`); - grad.setAttribute('cy', `${y}%`); - } - function onPointerLeave() { - // Reset to center - grad.setAttribute('cx', '50%'); - grad.setAttribute('cy', '50%'); - } - heroLogo.addEventListener('pointermove', onPointerMove); - heroLogo.addEventListener('pointerleave', onPointerLeave); - } -} - -document.addEventListener('DOMContentLoaded', function() { - initLogoAnimation(); -}); \ No newline at end of file diff --git a/site/site_plan.md b/site/site_plan.md deleted file mode 100644 index 18ce82c9e..000000000 --- a/site/site_plan.md +++ /dev/null @@ -1,134 +0,0 @@ -**Project:** Cognotik Product Website - -**Goal:** Create a professional, informative, and user-friendly website to promote Cognotik, showcase its features, provide easy access to downloads for the latest version tailored to the user's OS, and build user trust. - -**Target Audience:** Software developers, tech leads, project managers, potential users of AI development tools. - -**I. Key Features & Requirements:** - -1. **Homepage:** Single-page or multi-page structure (TBD, start with single-page focus). -2. **Clear Value Proposition:** Immediately convey what Cognotik is and its benefits. -3. **Feature Highlights:** Showcase core capabilities (referencing README: AI planning, code gen, interactive UI, desktop/plugin integration, BYOK model). -4. **Introductory Video:** Prominently display an introductory/demo video (using a placeholder initially). -5. **Dynamic Download Section:** - * Automatically detect the user's Operating System (Windows, macOS, Linux). - * Fetch the latest release information (version number, download assets) from the GitHub repository (`SimiaCryptus/Cognotik`). - * Present a clear, prominent Call-to-Action (CTA) button that links directly to the appropriate installer/package for the detected OS and latest version. - * Provide fallback options (e.g., link to GitHub Releases page) if OS detection fails or no suitable asset is found. - * Display the latest version number near the download button. -6. **Open Source Information:** Clearly state the open-source nature (Apache 2.0 License) and the "Bring Your Own Key" model. -7. **Links:** Easy access to GitHub repository, documentation (if separate), support channels. -8. **Responsive Design:** Ensure optimal viewing and usability across desktop, tablet, and mobile devices. -9. **Branding:** Consistent use of the Cognotik logo (`logo.svg`) and color scheme. - -**II. Technology Stack:** - -1. **Frontend Framework/Library:** - * **(Simple):** Vanilla HTML5, CSS3, and JavaScript. Sufficient for the required dynamic functionality. -2. **CSS:** - * **Option A:** Plain CSS -3. **Data Source:** GitHub Releases API (`https://api.github.com/repos/SimiaCryptus/Cognotik/releases`). - -**III. Website Structure & Content Outline (Homepage Focus):** - -1. **Navigation Bar:** - * Logo - * Key Sections (Features, Download, Video, GitHub) - * Primary Download CTA (optional, mirrors main download button) - -2. **Hero Section:** - * **Headline:** Catchy title (e.g., "Cognotik: Your AI Co-Pilot for Software Development") - * **Sub-headline:** Briefly explain the core value proposition. - * **Visual:** Screenshot, animation, or abstract graphic. - * **Primary CTA:** "Download Latest Version" (links to Dynamic Download Section). - -3. **Introductory Video Section:** - * **Headline:** "See Cognotik in Action" - * **Video Embed:** - * Use standard HTML `