Your Flutter AI Autopilot with Gemini Code & Vision
@@ -11,23 +6,25 @@
-----------------
-CommandDash is a command-based coding assistant. It has built in agents that not only help you write code, but also auto run and debug it - performing various Flutter development tasks for you.
+CommandDash is a command-based coding assistant. It provides built in agents that that can automate various Flutter development tasks for you.
##### ✨ Powered by Gemini
##### 🤝 Dart Analyzer Inside
##### 👨🏼💻 For and by Flutter Engineers
-Currently in Beta, CommandDash is being built in open-sourced with the community.
+Currently in Beta, CommandDash is being built in [open-sourced](https://github.com/CommandDash/commanddash) with the community.
-----------------
## Getting Started
-##### 1. Create Free Gemini API Key
+##### 1. Create Gemini API Key
Visit [Makersuite by Google](https://makersuite.google.com/) and create your free API Key.
+
+*Note: Gemini offers both free and paid plans.*
##### 2. Add the key in CommandDash Panel
-Paste your API key in the input field in CommandDash Panel.
+Paste your API key in the input field in Dash Panel.
-That's it. You're ready to use Dash AI. ✅
+That's it. You're ready to use CommandDash. ✅
## Features
@@ -36,71 +33,35 @@ That's it. You're ready to use Dash AI. ✅
-Attach multiple code snippets from different files in your inline chat. With full-context passed to Gemini, receive accurate responses and amend code across multiple files.
+Select and attach multiple code snippets from different files in your inline chat using **"Attach Snippet to Dash"** from the right-click menu.
+
+🤝 With full-context passed to Gemini, receive accurate responses and update code across multiple files.
### 🚀 @Agents and /Commands
Use built in agents and commands to autopilot different kinds of tasks.
-Currently, we support:
-
-#### 1. `@workspace` agent.
-Ask anything related to Flutter or Dart and get instant answers. Query your workspace using `@workspace` command.
-
-#### 2. `/refactor` command.
-
-
-
-
-More, coming very soon.
+Currently, we offer following agents and commands:
-### ✨ Generate Inline Code
+#### 1. `@workspace`
+Directly query across your workspace and find relevant files related to a feature. Leverage this command to build an understand of the codebase you are working with.
-#### 1. **Code Block Completion**
+#### 2. `@test`
+Generate unit, widget and integration tests with full-context for your Flutter/Dart project.
-Complete methods, classes or any other code blocks by running `Inline Code Generation` from the menu or via `cmd+shift+R`.
+✅ Also, attach previously existing tests as references to help Gemini learn your testing style and choice of libraries.
-Specify details with comments for better accuracy. For example,
+#### 3. `@flutter`
+✨ Use `/doc` command answer your Flutter/Dart questions from trusted sources including official docs.
-```dart
-class Cart {
- // Properties
- List items = [];
+#### 4. `/refactor` and `/document`
- void addItem(Item item) {
- items.add(item);
- }
+Modify your existing code with instructions and apply the changes.
- void removeItem(Item item) {
- items.remove(item);
- }
- // get total price method
- **[cmd+shift+R]**
-}
-```
-
-completes the next lines with:
-``` dart
- double getTotalPrice() {
- double total = 0;
- for (Item item in items) {
- total += item.price;
- }
- return total;
- }
-```
-
-#### 2. **Widget from Image or Description**
-
-Use Gemini's multimodal capabilities to create widget from a image with added description.
-
-Command: `Dash AI Create: Widget from Image or Description`
-
-#### 3. **Code from Blueprint**
-
-Get complete code from a blueprint of a class or function with the behaviour of functions, state management and architecture of your choice.
+
+
+
-Command: `Dash AI Create: Code from Blueprint`
## FAQs
@@ -109,14 +70,14 @@ Command: `Dash AI Create: Code from Blueprint`
2. **Do I need to pay to use CommandDash?**
-- Gemini PRO is currently in early access and is completely free to use for upto 60 requests for minute. Please check the [pricing](https://ai.google.dev/pricing) here.
+- Gemini PRO offers both free and paid plans. Please check the [pricing](https://ai.google.dev/pricing) here.
-3. **I am an Android Studio user. Can I use Dash AI?**
+3. **I am an Android Studio user. Can I use CommandDash?**
- We are coming soon for IntelliJ-based IDEs. *🤫 Secret: most of our core logic is written in Dart, allowing us to ship on any platform very very fast!*
## Contributing
-A coding assistant for all is best built when all of us contribute. You can make contributions to the VSCODE or IntelliJ extension or also to [CommandDash CLI](https://github.com/Welltested-AI/commanddash) shared between the extensions.
+A coding assistant for all is best built when all of us contribute. You can make contributions to the VSCODE or IntelliJ extension or also to [agents engine](https://github.com/CommandDash/packages) shared between the extensions.
### Ways to contribute
@@ -134,4 +95,4 @@ Connect with like minded people building with Flutter and using AI to do so, eve
## License
-Dash AI is released under the Apache License Version 2.0. See the [LICENSE](LICENSE) file for more information.
\ No newline at end of file
+CommandDash is released under the Apache License Version 2.0. See the [LICENSE](LICENSE) file for more information.
\ No newline at end of file
diff --git a/vscode/media/agent-provider/agent-provider.js b/vscode/media/agent-provider/agent-provider.js
new file mode 100644
index 00000000..8ec68206
--- /dev/null
+++ b/vscode/media/agent-provider/agent-provider.js
@@ -0,0 +1,15 @@
+class AgentProvider {
+ constructor(json) {
+ this.json = json;
+ }
+ getInputs(inputString) {
+ for (const item of this.json) {
+ for (const command of item.supported_commands) {
+ if (command.slug === inputString) {
+ return JSON.parse(JSON.stringify(command));
+ }
+ }
+ }
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/vscode/media/agent-ui-builder/agent-ui-builder.js b/vscode/media/agent-ui-builder/agent-ui-builder.js
new file mode 100644
index 00000000..f57ba1cc
--- /dev/null
+++ b/vscode/media/agent-ui-builder/agent-ui-builder.js
@@ -0,0 +1,118 @@
+class AgentUIBuilder {
+ constructor(ref) {
+ this.ref = ref;
+
+ this.onStringInput = this.onStringInput.bind(this);
+ this.buildAgentUI = this.buildAgentUI.bind(this);
+
+ this.container = document.createElement("div");
+
+ this.codeInputIds = [];
+ }
+
+ buildAgentUI() {
+ const { text_field_layout, registered_inputs, slug } = agentInputsJson[0];
+ let textHtml = text_field_layout;
+ registered_inputs.forEach(input => {
+ const inputElement = this.createInputElement(input);
+ this.container.appendChild(inputElement);
+ textHtml = textHtml.replace(`<${input.id}>`, inputElement.outerHTML);
+ });
+
+ this.container.innerHTML = `${slug} ${textHtml}`;
+ this.ref.appendChild(this.container);
+ this.registerCodeInputListener();
+ }
+
+ createInputElement(input) {
+ const { id, display_text, type, optional } = input;
+ const _optional = optional ? "(O)" : "";
+ if (type === "string_input") {
+ const inputContainer = document.createElement("span");
+ const inputSpan = document.createElement("span");
+ inputContainer.innerHTML = `${_optional} ${display_text}`;
+ inputContainer.classList.add("inline-block");
+
+ inputSpan.id = id;
+ inputSpan.contentEditable = true;
+ inputSpan.tabIndex = 0;
+ inputSpan.classList.add("px-2", "inline-block", "rounded-tr-[4px]", "rounded-br-[4px]", "string_input", id, "mb-1", "ml-[1px]", "mr-[1px]");
+ inputSpan.textContent = '\u200B';
+
+ this.ref.addEventListener('input', (event) => this.onStringInput(event, id));
+ this.ref.addEventListener('paste', () => this.onTextPaste(id));
+
+ inputContainer.appendChild(inputSpan);
+
+ requestAnimationFrame(() => {
+ if (!optional) {
+ const input = document.getElementById(id);
+ const selection = window.getSelection();
+ const range = document.createRange();
+ range.selectNodeContents(input);
+ range.collapse(false);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ });
+
+ return inputContainer;
+ }
+
+ if (type === "code_input") {
+ const codeContainer = document.createElement("span");
+ const codePlaceholder = document.createElement("span");
+
+ codeContainer.classList.add("code-input-container");
+
+ codePlaceholder.id = id;
+ codePlaceholder.contentEditable = "false";
+ codePlaceholder.tabIndex = 0;
+ codePlaceholder.classList.add("ml-1", "mb-1", "px-[7px]", "inline-flex", "cursor-pointer", "rounded-[4px]", "mt-1", "code_input", "items-center");
+ codePlaceholder.textContent = `${_optional} ${display_text}`;
+ codeContainer.id = "code-container";
+ codeContainer.appendChild(codePlaceholder);
+ this.codeInputIds.push(id);
+
+ // this.ref.addEventListener("click", this.onCodeInputClick);
+
+ return codeContainer;
+ }
+ }
+
+ registerCodeInputListener() {
+ this.codeInputIds.forEach((_codeInputId) => {
+ const codeInput = document.getElementById(_codeInputId);
+ codeInput.addEventListener("focus", () => {
+ codeInputId = _codeInputId;
+ });
+ });
+ }
+
+ onTextPaste(id) {
+ const inputSpan = document.getElementById(id);
+ inputSpan.dispatchEvent(new Event('input', { bubbles: true }));
+ }
+
+ onStringInput(event, id) {
+ const sel = window.getSelection();
+ const inputSpan = document.getElementById(id);
+ if (event.target === inputSpan || (sel.anchorNode && sel.anchorNode.parentNode && sel.anchorNode.parentNode.classList.contains(id))) {
+ const inputIndex = agentInputsJson[0].registered_inputs.findIndex(_input => _input.id === id);
+ if (inputIndex !== -1) {
+ agentInputsJson[0].registered_inputs[inputIndex].value = inputSpan.textContent.trim();
+ }
+ }
+ }
+
+ onCodeInput(chipsData, chipName) {
+ const firstCodeInput = agentInputsJson[0].registered_inputs.find(input => input.type === "code_input" && ( codeInputId === 0 ? input.value === undefined : input.id === codeInputId));
+
+ if (firstCodeInput) {
+ const codeInputSpan = document.getElementById(firstCodeInput.id);
+ firstCodeInput.value = JSON.stringify(chipsData);
+ codeInputSpan.innerHTML = `${chipName}`;
+ codeInputId = 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/vscode/media/command-deck/command-deck.js b/vscode/media/command-deck/command-deck.js
index 7846795e..25ad0ae6 100644
--- a/vscode/media/command-deck/command-deck.js
+++ b/vscode/media/command-deck/command-deck.js
@@ -75,13 +75,14 @@ function getCaretCoordinates(element, position) {
}
class CommandDeck {
- constructor(ref, menuRef, resolveFn, replaceFn, menuItemFn) {
+ constructor(ref, menuRef, resolveFn, replaceFn, menuItemFn, agentUIBuilder) {
this.ref = ref;
this.menuRef = menuRef;
this.resolveFn = resolveFn;
this.replaceFn = replaceFn;
this.menuItemFn = menuItemFn;
this.options = [];
+ this.agentUIBuilder = agentUIBuilder;
this.makeOptions = this.makeOptions.bind(this);
this.closeMenu = this.closeMenu.bind(this);
@@ -97,10 +98,11 @@ class CommandDeck {
async makeOptions(query) {
let options = [];
if (query.startsWith('@')) {
- options = await this.resolveFn(query.slice(1), 'at');
+ options = await this.resolveFn(query, 'at');
} else if (query.startsWith('/')) {
- options = await this.resolveFn(query.slice(1), 'slash');
+ options = await this.resolveFn(query, 'slash');
}
+
if (options.length !== 0) {
this.options = options;
this.renderMenu();
@@ -122,27 +124,54 @@ class CommandDeck {
selectItem(active) {
return () => {
const option = this.options[active];
-
- const commands = this.extractCommands(option, "/") ?? option;
- const isSlashOptionAvailable = commandsExecution.hasOwnProperty(commands);
-
- if (isSlashOptionAvailable) {
- commandsExecution[commands].exe(this.ref);
+ if (!option?.name.startsWith('/')) {
+ this.ref.textContent = '';
+ }
+ if (option?.name.startsWith('/')) {
+ const textContent = this.ref.innerHTML;
+ const atIndex = textContent.lastIndexOf('/');
+ this.ref.innerHTML = textContent.substring(0, atIndex) + textContent.substring(atIndex + 1);
+ }
+ if (option?.name.startsWith('@')) {
+ const agentSpan = document.createElement('span');
+ const slugSpan = document.createElement('span');
+ agentSpan.classList.add("inline-block", "text-[#287CEB]");
+ agentSpan.contentEditable = false;
+ agentSpan.textContent = `${option?.name}\u00A0`;
+ slugSpan.classList.add("inline-block");
+ slugSpan.contentEditable = false;
+ slugSpan.textContent = "/";
+ this.ref.appendChild(agentSpan);
+ this.ref.appendChild(slugSpan);
+ activeAgent = true;
+ currentActiveAgent = option.name;
+ // this.closeMenu();
+ this.makeOptions("/");
+ // Move the cursor to the end of the word
+ this.ref.focus();
+ // Move the cursor to the end of the text
+ const selection = window.getSelection();
+ const range = document.createRange();
+ range.selectNodeContents(this.ref);
+ range.collapse(false); // false means collapse to the end
+ selection.removeAllRanges();
+ selection.addRange(range);
} else {
- const trigger = this.ref.textContent[this.triggerIdx];
- this.ref.textContent = "";
- const mentionNode = document.createElement("span");
- mentionNode.id = "special-commands";
- mentionNode.classList.add("text-blue-500", "inline-block");
- mentionNode.contentEditable = false;
- mentionNode.textContent = `${trigger}${option}\u200B`;
- this.ref.appendChild(mentionNode);
- this.ref.appendChild(document.createTextNode("\u00A0"));
- setCaretToEnd(this.ref);
+ this.ref.textContent = '';
+ const agentUIBuilder = new AgentUIBuilder(this.ref);
+ const agentProvider = new AgentProvider(data);
+ // agentInputsJson = agentProvider.getInputs(option);
+ agentInputsJson.push(agentProvider.getInputs(option.name));
+ agentUIBuilder.buildAgentUI();
+
+ this.ref.focus();
+ this.closeMenu();
+
+ setTimeout(() => {
+ adjustHeight();
+ commandEnable = true;
+ }, 0);
}
-
- this.ref.focus();
- this.closeMenu();
};
}
@@ -163,18 +192,16 @@ class CommandDeck {
const textBeforeCaret = this.ref.textContent.slice(0, positionIndex);
const tokens = textBeforeCaret.split(/\s/);
const lastToken = tokens[tokens.length - 1];
- const triggerIdx = textBeforeCaret.endsWith(lastToken)
- ? textBeforeCaret.length - lastToken.length
- : -1;
+ const triggerIdx = textBeforeCaret.endsWith(lastToken) ? textBeforeCaret.length - lastToken.length : -1;
const maybeTrigger = textBeforeCaret[triggerIdx];
const keystrokeTriggered = maybeTrigger === '@' || maybeTrigger === '/';
this.ref.style.height = "auto";
this.ref.style.height = this.ref.scrollHeight + "px";
- const isTriggerAtStartOfWord = triggerIdx === 0;
+ // const iscoAtStartOfWord = triggerIdx === 0;
- if (!keystrokeTriggered || !isTriggerAtStartOfWord) {
+ if (!keystrokeTriggered) {
this.closeMenu();
return;
}
@@ -218,26 +245,22 @@ class CommandDeck {
this.selectItem(this.active)();
keyCaught = true;
break;
- case 'Backspace':
- const selection = window.getSelection();
- const range = selection.getRangeAt(0);
- const mentionNode = document.getElementById("special-commands");
-
- if (mentionNode) {
- const prevNode = mentionNode.previousSibling;
- const nextNode = mentionNode.nextSibling;
- this.ref.removeChild(mentionNode);
-
- // Restore the cursor position
- range.setStartAfter(prevNode || nextNode);
- range.collapse(true);
- selection.removeAllRanges();
- selection.addRange(range);
- }
- break;
}
}
+ if (ev.key === "Backspace") {
+ setTimeout(() => {
+ if (this.ref.textContent.trim() === "") {
+ activeAgent = false;
+ commandEnable = false;
+ currentActiveAgent = '';
+ currentActiveSlug = '';
+ agentInputsJson.length = 0;
+ codeInputId = 0;
+ }
+ }, 0);
+ }
+
if (keyCaught) {
ev.preventDefault();
}
diff --git a/vscode/media/header.png b/vscode/media/header.png
new file mode 100644
index 00000000..8577bfbd
Binary files /dev/null and b/vscode/media/header.png differ
diff --git a/vscode/media/input.css b/vscode/media/input.css
new file mode 100644
index 00000000..bd6213e1
--- /dev/null
+++ b/vscode/media/input.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
\ No newline at end of file
diff --git a/vscode/media/onboarding/onboarding.css b/vscode/media/onboarding/onboarding.css
index 039c755a..b8c17b64 100644
--- a/vscode/media/onboarding/onboarding.css
+++ b/vscode/media/onboarding/onboarding.css
@@ -201,11 +201,19 @@ p[contenteditable="true"] {
outline: 1px solid #497BEF;
}
-#text-refactor-input {
+.code_input {
outline: 1px solid var(--vscode-editor-foreground);
}
-#text-refactor-input:focus {
+.code_input:focus {
+ outline: 1px solid #497BEF;
+}
+
+.string_input {
+ outline: 1px solid var(--vscode-editor-foreground);
+}
+
+.string_input:focus {
outline: 1px solid #497BEF;
}
@@ -223,6 +231,12 @@ p[contenteditable="true"] {
z-index: 98;
}
+.questionnaire-card {
+ background-color: var(--vscode-dropdown-background);
+ /* color: var(--vscode-editorWidget-foreground); */
+}
+
+
.tippy-box[data-theme~='flutter-blue'] {
background-color: #287CEB;
color: white;
@@ -242,4 +256,23 @@ p[contenteditable="true"] {
.tippy-box[data-theme~='flutter-blue'][data-placement^='right']>.tippy-arrow::before {
border-right-color: #287CEB;
+}
+
+progress {
+ height: 7px;
+}
+
+@keyframes progress-animation {
+ from {
+ width: 0%;
+ }
+
+ to {
+ width: 100%;
+ }
+}
+
+progress::-webkit-progress-value {
+ background-color: #287CEB;
+ animation: progress-animation 2s linear forwards;
}
\ No newline at end of file
diff --git a/vscode/media/onboarding/onboarding.html b/vscode/media/onboarding/onboarding.html
index d76f10bb..b7c2d4bd 100644
--- a/vscode/media/onboarding/onboarding.html
+++ b/vscode/media/onboarding/onboarding.html
@@ -4,20 +4,19 @@
-
+
-
+