mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
Merge remote-tracking branch 'origin/main' into feat/extend-models-support
This commit is contained in:
70
.automaker/context/gemini.md
Normal file
70
.automaker/context/gemini.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
You are a very strong reasoner and planner. Use these critical instructions to structure your plans, thoughts, and responses.
|
||||||
|
|
||||||
|
Before taking any action (either tool calls or responses to the user), you must proactively, methodically, and independently plan and reason about:
|
||||||
|
|
||||||
|
1. Logical dependencies and constraints:
|
||||||
|
|
||||||
|
Analyze the intended action against the following factors. Resolve conflicts in order of importance:
|
||||||
|
|
||||||
|
1.1) Policy-based rules, mandatory prerequisites, and constraints.
|
||||||
|
1.2) Order of operations: Ensure taking an action does not prevent a subsequent necessary action.
|
||||||
|
1.2.1) The user may request actions in a random order, but you may need to reorder operations to maximize successful completion of the task.
|
||||||
|
1.3) Other prerequisites (information and/or actions needed).
|
||||||
|
1.4) Explicit user constraints or preferences.
|
||||||
|
|
||||||
|
2. Risk assessment:
|
||||||
|
|
||||||
|
What are the consequences of taking the action? Will the new state cause any future issues?
|
||||||
|
|
||||||
|
2.1) For exploratory tasks (like searches), missing optional parameters is a LOW risk.
|
||||||
|
Prefer calling the tool with the available information over asking the user, unless your Rule 1 (Logical Dependencies) reasoning determines that optional information is required for a later step in your plan.
|
||||||
|
|
||||||
|
3. Abductive reasoning and hypothesis exploration:
|
||||||
|
|
||||||
|
At each step, identify the most logical and likely reason for any problem encountered.
|
||||||
|
|
||||||
|
3.1) Look beyond immediate or obvious causes. The most likely reason may not be the simplest and may require deeper inference.
|
||||||
|
3.2) Hypotheses may require additional research. Each hypothesis may take multiple steps to test.
|
||||||
|
3.3) Prioritize hypotheses based on likelihood, but do not discard less likely ones prematurely. A low-probability event may still be the root cause.
|
||||||
|
|
||||||
|
4. Outcome evaluation and adaptability:
|
||||||
|
|
||||||
|
Does the previous observation require any changes to your plan?
|
||||||
|
|
||||||
|
4.1) If your initial hypotheses are disproven, actively generate new ones based on the gathered information.
|
||||||
|
|
||||||
|
5. Information availability:
|
||||||
|
|
||||||
|
Incorporate all applicable and alternative sources of information, including:
|
||||||
|
|
||||||
|
5.1) Using available tools and their capabilities
|
||||||
|
5.2) All policies, rules, checklists, and constraints
|
||||||
|
5.3) Previous observations and conversation history
|
||||||
|
5.4) Information only available by asking the user
|
||||||
|
|
||||||
|
6. Precision and Grounding:
|
||||||
|
|
||||||
|
Ensure your reasoning is extremely precise and relevant to each exact ongoing situation.
|
||||||
|
|
||||||
|
6.1) Verify your claims by quoting the exact applicable information (including policies) when referring to them.
|
||||||
|
|
||||||
|
7. Completeness:
|
||||||
|
|
||||||
|
Ensure that all requirements, constraints, options, and preferences are exhaustively incorporated into your plan.
|
||||||
|
|
||||||
|
7.1) Resolve conflicts using the order of importance in #1.
|
||||||
|
7.2) Avoid premature conclusions: There may be multiple relevant options for a given situation.
|
||||||
|
7.2.1) To check for whether an option is relevant, reason about all information sources from #5.
|
||||||
|
7.2.2) You may need to consult the user to even know whether something is applicable. Do not assume it is not applicable without checking.
|
||||||
|
7.3) Review applicable sources of information from #5 to confirm which are relevant to the current state.
|
||||||
|
|
||||||
|
8. Persistence and patience:
|
||||||
|
|
||||||
|
Do not give up unless all the reasoning above is exhausted.
|
||||||
|
|
||||||
|
8.1) Don't be dissuaded by time taken or user frustration.
|
||||||
|
8.2) This persistence must be intelligent: On transient errors (e.g. please try again), you must retry unless an explicit retry limit (e.g., max x tries) has been reached. If such a limit is hit, you must stop. On other errors, you must change your strategy or arguments, not repeat the same failed call.
|
||||||
|
|
||||||
|
9. Inhibit your response:
|
||||||
|
|
||||||
|
Only take an action after all the above reasoning is completed. Once you've taken an action, you cannot take it back.
|
||||||
@@ -32,7 +32,7 @@ class AutoModeService {
|
|||||||
query: null,
|
query: null,
|
||||||
projectPath: null,
|
projectPath: null,
|
||||||
sendToRenderer: null,
|
sendToRenderer: null,
|
||||||
isActive: () => this.runningFeatures.has(featureId)
|
isActive: () => this.runningFeatures.has(featureId),
|
||||||
};
|
};
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,11 @@ class AutoModeService {
|
|||||||
console.log(`[AutoMode] Running feature: ${feature.description}`);
|
console.log(`[AutoMode] Running feature: ${feature.description}`);
|
||||||
|
|
||||||
// Update feature status to in_progress
|
// Update feature status to in_progress
|
||||||
await featureLoader.updateFeatureStatus(featureId, "in_progress", projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
featureId,
|
||||||
|
"in_progress",
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
sendToRenderer({
|
sendToRenderer({
|
||||||
type: "auto_mode_feature_start",
|
type: "auto_mode_feature_start",
|
||||||
@@ -135,7 +139,12 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Implement the feature
|
// Implement the feature
|
||||||
const result = await featureExecutor.implementFeature(feature, projectPath, sendToRenderer, execution);
|
const result = await featureExecutor.implementFeature(
|
||||||
|
feature,
|
||||||
|
projectPath,
|
||||||
|
sendToRenderer,
|
||||||
|
execution
|
||||||
|
);
|
||||||
|
|
||||||
// Update feature status based on result
|
// Update feature status based on result
|
||||||
// For skipTests features, go to waiting_approval on success instead of verified
|
// For skipTests features, go to waiting_approval on success instead of verified
|
||||||
@@ -145,7 +154,11 @@ class AutoModeService {
|
|||||||
} else {
|
} else {
|
||||||
newStatus = "backlog";
|
newStatus = "backlog";
|
||||||
}
|
}
|
||||||
await featureLoader.updateFeatureStatus(feature.id, newStatus, projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
feature.id,
|
||||||
|
newStatus,
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
// Delete context file only if verified (not for waiting_approval)
|
// Delete context file only if verified (not for waiting_approval)
|
||||||
if (newStatus === "verified") {
|
if (newStatus === "verified") {
|
||||||
@@ -214,11 +227,20 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Verify the feature by running tests
|
// Verify the feature by running tests
|
||||||
const result = await featureVerifier.verifyFeatureTests(feature, projectPath, sendToRenderer, execution);
|
const result = await featureVerifier.verifyFeatureTests(
|
||||||
|
feature,
|
||||||
|
projectPath,
|
||||||
|
sendToRenderer,
|
||||||
|
execution
|
||||||
|
);
|
||||||
|
|
||||||
// Update feature status based on result
|
// Update feature status based on result
|
||||||
const newStatus = result.passes ? "verified" : "in_progress";
|
const newStatus = result.passes ? "verified" : "in_progress";
|
||||||
await featureLoader.updateFeatureStatus(featureId, newStatus, projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
featureId,
|
||||||
|
newStatus,
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
// Delete context file if verified
|
// Delete context file if verified
|
||||||
if (newStatus === "verified") {
|
if (newStatus === "verified") {
|
||||||
@@ -287,10 +309,19 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Read existing context
|
// Read existing context
|
||||||
const previousContext = await contextManager.readContextFile(projectPath, featureId);
|
const previousContext = await contextManager.readContextFile(
|
||||||
|
projectPath,
|
||||||
|
featureId
|
||||||
|
);
|
||||||
|
|
||||||
// Resume implementation with context
|
// Resume implementation with context
|
||||||
const result = await featureExecutor.resumeFeatureWithContext(feature, projectPath, sendToRenderer, previousContext, execution);
|
const result = await featureExecutor.resumeFeatureWithContext(
|
||||||
|
feature,
|
||||||
|
projectPath,
|
||||||
|
sendToRenderer,
|
||||||
|
previousContext,
|
||||||
|
execution
|
||||||
|
);
|
||||||
|
|
||||||
// If the agent ends early without finishing, automatically re-run
|
// If the agent ends early without finishing, automatically re-run
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
@@ -304,11 +335,16 @@ class AutoModeService {
|
|||||||
|
|
||||||
if (updatedFeature && updatedFeature.status === "in_progress") {
|
if (updatedFeature && updatedFeature.status === "in_progress") {
|
||||||
attempts++;
|
attempts++;
|
||||||
console.log(`[AutoMode] Feature ended early, auto-retrying (attempt ${attempts}/${maxAttempts})...`);
|
console.log(
|
||||||
|
`[AutoMode] Feature ended early, auto-retrying (attempt ${attempts}/${maxAttempts})...`
|
||||||
|
);
|
||||||
|
|
||||||
// Update context file with retry message
|
// Update context file with retry message
|
||||||
await contextManager.writeToContextFile(projectPath, featureId,
|
await contextManager.writeToContextFile(
|
||||||
`\n\n🔄 Auto-retry #${attempts} - Continuing implementation...\n\n`);
|
projectPath,
|
||||||
|
featureId,
|
||||||
|
`\n\n🔄 Auto-retry #${attempts} - Continuing implementation...\n\n`
|
||||||
|
);
|
||||||
|
|
||||||
sendToRenderer({
|
sendToRenderer({
|
||||||
type: "auto_mode_progress",
|
type: "auto_mode_progress",
|
||||||
@@ -317,10 +353,19 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Read updated context
|
// Read updated context
|
||||||
const retryContext = await contextManager.readContextFile(projectPath, featureId);
|
const retryContext = await contextManager.readContextFile(
|
||||||
|
projectPath,
|
||||||
|
featureId
|
||||||
|
);
|
||||||
|
|
||||||
// Resume again with full context
|
// Resume again with full context
|
||||||
finalResult = await featureExecutor.resumeFeatureWithContext(feature, projectPath, sendToRenderer, retryContext, execution);
|
finalResult = await featureExecutor.resumeFeatureWithContext(
|
||||||
|
feature,
|
||||||
|
projectPath,
|
||||||
|
sendToRenderer,
|
||||||
|
retryContext,
|
||||||
|
execution
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -334,7 +379,11 @@ class AutoModeService {
|
|||||||
} else {
|
} else {
|
||||||
newStatus = "in_progress";
|
newStatus = "in_progress";
|
||||||
}
|
}
|
||||||
await featureLoader.updateFeatureStatus(featureId, newStatus, projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
featureId,
|
||||||
|
newStatus,
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
// Delete context file only if verified (not for waiting_approval)
|
// Delete context file only if verified (not for waiting_approval)
|
||||||
if (newStatus === "verified") {
|
if (newStatus === "verified") {
|
||||||
@@ -389,7 +438,9 @@ class AutoModeService {
|
|||||||
|
|
||||||
// Skip if this feature is already running (via manual trigger)
|
// Skip if this feature is already running (via manual trigger)
|
||||||
if (this.runningFeatures.has(currentFeatureId)) {
|
if (this.runningFeatures.has(currentFeatureId)) {
|
||||||
console.log(`[AutoMode] Skipping ${currentFeatureId} - already running`);
|
console.log(
|
||||||
|
`[AutoMode] Skipping ${currentFeatureId} - already running`
|
||||||
|
);
|
||||||
await this.sleep(3000);
|
await this.sleep(3000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -409,7 +460,12 @@ class AutoModeService {
|
|||||||
this.runningFeatures.set(currentFeatureId, execution);
|
this.runningFeatures.set(currentFeatureId, execution);
|
||||||
|
|
||||||
// Implement the feature
|
// Implement the feature
|
||||||
const result = await featureExecutor.implementFeature(nextFeature, projectPath, sendToRenderer, execution);
|
const result = await featureExecutor.implementFeature(
|
||||||
|
nextFeature,
|
||||||
|
projectPath,
|
||||||
|
sendToRenderer,
|
||||||
|
execution
|
||||||
|
);
|
||||||
|
|
||||||
// Update feature status based on result
|
// Update feature status based on result
|
||||||
// For skipTests features, go to waiting_approval on success instead of verified
|
// For skipTests features, go to waiting_approval on success instead of verified
|
||||||
@@ -419,7 +475,11 @@ class AutoModeService {
|
|||||||
} else {
|
} else {
|
||||||
newStatus = "backlog";
|
newStatus = "backlog";
|
||||||
}
|
}
|
||||||
await featureLoader.updateFeatureStatus(nextFeature.id, newStatus, projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
nextFeature.id,
|
||||||
|
newStatus,
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
// Delete context file only if verified (not for waiting_approval)
|
// Delete context file only if verified (not for waiting_approval)
|
||||||
if (newStatus === "verified") {
|
if (newStatus === "verified") {
|
||||||
@@ -495,7 +555,12 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Perform the analysis
|
// Perform the analysis
|
||||||
const result = await projectAnalyzer.runProjectAnalysis(projectPath, analysisId, sendToRenderer, execution);
|
const result = await projectAnalyzer.runProjectAnalysis(
|
||||||
|
projectPath,
|
||||||
|
analysisId,
|
||||||
|
sendToRenderer,
|
||||||
|
execution
|
||||||
|
);
|
||||||
|
|
||||||
sendToRenderer({
|
sendToRenderer({
|
||||||
type: "auto_mode_feature_complete",
|
type: "auto_mode_feature_complete",
|
||||||
@@ -543,13 +608,21 @@ class AutoModeService {
|
|||||||
* Follow-up on a feature with additional prompt
|
* Follow-up on a feature with additional prompt
|
||||||
* This continues work on a feature that's in waiting_approval status
|
* This continues work on a feature that's in waiting_approval status
|
||||||
*/
|
*/
|
||||||
async followUpFeature({ projectPath, featureId, prompt, imagePaths, sendToRenderer }) {
|
async followUpFeature({
|
||||||
|
projectPath,
|
||||||
|
featureId,
|
||||||
|
prompt,
|
||||||
|
imagePaths,
|
||||||
|
sendToRenderer,
|
||||||
|
}) {
|
||||||
// Check if this feature is already running
|
// Check if this feature is already running
|
||||||
if (this.runningFeatures.has(featureId)) {
|
if (this.runningFeatures.has(featureId)) {
|
||||||
throw new Error(`Feature ${featureId} is already running`);
|
throw new Error(`Feature ${featureId} is already running`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[AutoMode] Follow-up on feature: ${featureId} with prompt: ${prompt}`);
|
console.log(
|
||||||
|
`[AutoMode] Follow-up on feature: ${featureId} with prompt: ${prompt}`
|
||||||
|
);
|
||||||
|
|
||||||
// Register this feature as running
|
// Register this feature as running
|
||||||
const execution = this.createExecutionContext(featureId);
|
const execution = this.createExecutionContext(featureId);
|
||||||
@@ -559,7 +632,14 @@ class AutoModeService {
|
|||||||
|
|
||||||
// Start the async work in the background (don't await)
|
// Start the async work in the background (don't await)
|
||||||
// This allows the API to return immediately so the modal can close
|
// This allows the API to return immediately so the modal can close
|
||||||
this.runFollowUpWork({ projectPath, featureId, prompt, imagePaths, sendToRenderer, execution }).catch((error) => {
|
this.runFollowUpWork({
|
||||||
|
projectPath,
|
||||||
|
featureId,
|
||||||
|
prompt,
|
||||||
|
imagePaths,
|
||||||
|
sendToRenderer,
|
||||||
|
execution,
|
||||||
|
}).catch((error) => {
|
||||||
console.error("[AutoMode] Follow-up work error:", error);
|
console.error("[AutoMode] Follow-up work error:", error);
|
||||||
this.runningFeatures.delete(featureId);
|
this.runningFeatures.delete(featureId);
|
||||||
});
|
});
|
||||||
@@ -571,7 +651,14 @@ class AutoModeService {
|
|||||||
/**
|
/**
|
||||||
* Internal method to run follow-up work asynchronously
|
* Internal method to run follow-up work asynchronously
|
||||||
*/
|
*/
|
||||||
async runFollowUpWork({ projectPath, featureId, prompt, imagePaths, sendToRenderer, execution }) {
|
async runFollowUpWork({
|
||||||
|
projectPath,
|
||||||
|
featureId,
|
||||||
|
prompt,
|
||||||
|
imagePaths,
|
||||||
|
sendToRenderer,
|
||||||
|
execution,
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
// Load features
|
// Load features
|
||||||
const features = await featureLoader.loadFeatures(projectPath);
|
const features = await featureLoader.loadFeatures(projectPath);
|
||||||
@@ -584,7 +671,11 @@ class AutoModeService {
|
|||||||
console.log(`[AutoMode] Following up on feature: ${feature.description}`);
|
console.log(`[AutoMode] Following up on feature: ${feature.description}`);
|
||||||
|
|
||||||
// Update status to in_progress
|
// Update status to in_progress
|
||||||
await featureLoader.updateFeatureStatus(featureId, "in_progress", projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
featureId,
|
||||||
|
"in_progress",
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
sendToRenderer({
|
sendToRenderer({
|
||||||
type: "auto_mode_feature_start",
|
type: "auto_mode_feature_start",
|
||||||
@@ -593,11 +684,18 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Read existing context and append follow-up prompt
|
// Read existing context and append follow-up prompt
|
||||||
const previousContext = await contextManager.readContextFile(projectPath, featureId);
|
const previousContext = await contextManager.readContextFile(
|
||||||
|
projectPath,
|
||||||
|
featureId
|
||||||
|
);
|
||||||
|
|
||||||
// Append follow-up prompt to context
|
// Append follow-up prompt to context
|
||||||
const followUpContext = `${previousContext}\n\n## Follow-up Instructions\n\n${prompt}`;
|
const followUpContext = `${previousContext}\n\n## Follow-up Instructions\n\n${prompt}`;
|
||||||
await contextManager.writeToContextFile(projectPath, featureId, `\n\n## Follow-up Instructions\n\n${prompt}`);
|
await contextManager.writeToContextFile(
|
||||||
|
projectPath,
|
||||||
|
featureId,
|
||||||
|
`\n\n## Follow-up Instructions\n\n${prompt}`
|
||||||
|
);
|
||||||
|
|
||||||
// Resume implementation with follow-up context and optional images
|
// Resume implementation with follow-up context and optional images
|
||||||
const result = await featureExecutor.resumeFeatureWithContext(
|
const result = await featureExecutor.resumeFeatureWithContext(
|
||||||
@@ -610,10 +708,16 @@ class AutoModeService {
|
|||||||
|
|
||||||
// For skipTests features, go to waiting_approval on success instead of verified
|
// For skipTests features, go to waiting_approval on success instead of verified
|
||||||
const newStatus = result.passes
|
const newStatus = result.passes
|
||||||
? (feature.skipTests ? "waiting_approval" : "verified")
|
? feature.skipTests
|
||||||
|
? "waiting_approval"
|
||||||
|
: "verified"
|
||||||
: "in_progress";
|
: "in_progress";
|
||||||
|
|
||||||
await featureLoader.updateFeatureStatus(feature.id, newStatus, projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
feature.id,
|
||||||
|
newStatus,
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
// Delete context file if verified (only for non-skipTests)
|
// Delete context file if verified (only for non-skipTests)
|
||||||
if (newStatus === "verified") {
|
if (newStatus === "verified") {
|
||||||
@@ -674,10 +778,19 @@ class AutoModeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Run git commit via the agent
|
// Run git commit via the agent
|
||||||
const commitResult = await featureExecutor.commitChangesOnly(feature, projectPath, sendToRenderer, execution);
|
const commitResult = await featureExecutor.commitChangesOnly(
|
||||||
|
feature,
|
||||||
|
projectPath,
|
||||||
|
sendToRenderer,
|
||||||
|
execution
|
||||||
|
);
|
||||||
|
|
||||||
// Update status to verified
|
// Update status to verified
|
||||||
await featureLoader.updateFeatureStatus(featureId, "verified", projectPath);
|
await featureLoader.updateFeatureStatus(
|
||||||
|
featureId,
|
||||||
|
"verified",
|
||||||
|
projectPath
|
||||||
|
);
|
||||||
|
|
||||||
// Delete context file
|
// Delete context file
|
||||||
await contextManager.deleteContextFile(projectPath, featureId);
|
await contextManager.deleteContextFile(projectPath, featureId);
|
||||||
|
|||||||
@@ -25,17 +25,25 @@ ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
|
|||||||
|
|
||||||
1. Read the project files to understand the current codebase structure
|
1. Read the project files to understand the current codebase structure
|
||||||
2. Implement the feature according to the description and steps
|
2. Implement the feature according to the description and steps
|
||||||
${feature.skipTests
|
${
|
||||||
? "3. Test the implementation manually (no automated tests needed for skipTests features)"
|
feature.skipTests
|
||||||
: "3. Write Playwright tests to verify the feature works correctly\n4. Run the tests and ensure they pass\n5. **DELETE the test file(s) you created** - tests are only for immediate verification"}
|
? "3. Test the implementation manually (no automated tests needed for skipTests features)"
|
||||||
${feature.skipTests ? "4" : "6"}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json
|
: "3. Write Playwright tests to verify the feature works correctly\n4. Run the tests and ensure they pass\n5. **DELETE the test file(s) you created** - tests are only for immediate verification"
|
||||||
${feature.skipTests
|
}
|
||||||
? "5. **DO NOT commit changes** - the user will review and commit manually"
|
${
|
||||||
: "7. Commit your changes with git"}
|
feature.skipTests ? "4" : "6"
|
||||||
|
}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json
|
||||||
|
${
|
||||||
|
feature.skipTests
|
||||||
|
? "5. **DO NOT commit changes** - the user will review and commit manually"
|
||||||
|
: "7. Commit your changes with git"
|
||||||
|
}
|
||||||
|
|
||||||
**IMPORTANT - Updating Feature Status:**
|
**IMPORTANT - Updating Feature Status:**
|
||||||
|
|
||||||
When you have completed the feature${feature.skipTests ? "" : " and all tests pass"}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status:
|
When you have completed the feature${
|
||||||
|
feature.skipTests ? "" : " and all tests pass"
|
||||||
|
}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status:
|
||||||
- Call the tool with: featureId="${feature.id}" and status="verified"
|
- Call the tool with: featureId="${feature.id}" and status="verified"
|
||||||
- **You can also include a summary parameter** to describe what was done: summary="Brief summary of changes"
|
- **You can also include a summary parameter** to describe what was done: summary="Brief summary of changes"
|
||||||
- **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions
|
- **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions
|
||||||
@@ -51,7 +59,9 @@ When calling UpdateFeatureStatus, you MUST include a summary parameter that desc
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
\`\`\`
|
\`\`\`
|
||||||
UpdateFeatureStatus(featureId="${feature.id}", status="verified", summary="Added dark mode toggle to settings. Modified: settings.tsx, theme-provider.tsx. Created new useTheme hook.")
|
UpdateFeatureStatus(featureId="${
|
||||||
|
feature.id
|
||||||
|
}", status="verified", summary="Added dark mode toggle to settings. Modified: settings.tsx, theme-provider.tsx. Created new useTheme hook.")
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
The summary will be displayed on the Kanban card so the user can see what was done without checking the code.
|
The summary will be displayed on the Kanban card so the user can see what was done without checking the code.
|
||||||
@@ -61,14 +71,18 @@ The summary will be displayed on the Kanban card so the user can see what was do
|
|||||||
- Focus ONLY on implementing this specific feature
|
- Focus ONLY on implementing this specific feature
|
||||||
- Write clean, production-quality code
|
- Write clean, production-quality code
|
||||||
- Add proper error handling
|
- Add proper error handling
|
||||||
${feature.skipTests
|
${
|
||||||
? "- Skip automated testing (skipTests=true) - user will manually verify"
|
feature.skipTests
|
||||||
: "- Write comprehensive Playwright tests\n- Ensure all existing tests still pass\n- Mark the feature as passing only when all tests are green\n- **CRITICAL: Delete test files after verification** - tests accumulate and become brittle"}
|
? "- Skip automated testing (skipTests=true) - user will manually verify"
|
||||||
|
: "- Write comprehensive Playwright tests\n- Ensure all existing tests still pass\n- Mark the feature as passing only when all tests are green\n- **CRITICAL: Delete test files after verification** - tests accumulate and become brittle"
|
||||||
|
}
|
||||||
- **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly**
|
- **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly**
|
||||||
- **CRITICAL: Always include a summary when marking feature as verified**
|
- **CRITICAL: Always include a summary when marking feature as verified**
|
||||||
${feature.skipTests
|
${
|
||||||
? "- **DO NOT commit changes** - user will review and commit manually"
|
feature.skipTests
|
||||||
: "- Make a git commit when complete"}
|
? "- **DO NOT commit changes** - user will review and commit manually"
|
||||||
|
: "- Make a git commit when complete"
|
||||||
|
}
|
||||||
|
|
||||||
**Testing Utilities (CRITICAL):**
|
**Testing Utilities (CRITICAL):**
|
||||||
|
|
||||||
@@ -119,9 +133,10 @@ ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
|
|||||||
|
|
||||||
1. Read the project files to understand the current implementation
|
1. Read the project files to understand the current implementation
|
||||||
2. If the feature is not fully implemented, continue implementing it
|
2. If the feature is not fully implemented, continue implementing it
|
||||||
${feature.skipTests
|
${
|
||||||
? "3. Test the implementation manually (no automated tests needed for skipTests features)"
|
feature.skipTests
|
||||||
: `3. Write or update Playwright tests to verify the feature works correctly
|
? "3. Test the implementation manually (no automated tests needed for skipTests features)"
|
||||||
|
: `3. Write or update Playwright tests to verify the feature works correctly
|
||||||
4. Run the Playwright tests: npx playwright test tests/[feature-name].spec.ts
|
4. Run the Playwright tests: npx playwright test tests/[feature-name].spec.ts
|
||||||
5. Check if all tests pass
|
5. Check if all tests pass
|
||||||
6. **If ANY tests fail:**
|
6. **If ANY tests fail:**
|
||||||
@@ -131,15 +146,22 @@ ${feature.skipTests
|
|||||||
- Re-run the tests to verify the fixes
|
- Re-run the tests to verify the fixes
|
||||||
- **REPEAT this process until ALL tests pass**
|
- **REPEAT this process until ALL tests pass**
|
||||||
7. **If ALL tests pass:**
|
7. **If ALL tests pass:**
|
||||||
- **DELETE the test file(s) for this feature** - tests are only for immediate verification`}
|
- **DELETE the test file(s) for this feature** - tests are only for immediate verification`
|
||||||
${feature.skipTests ? "4" : "8"}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json
|
}
|
||||||
${feature.skipTests
|
${
|
||||||
? "5. **DO NOT commit changes** - the user will review and commit manually"
|
feature.skipTests ? "4" : "8"
|
||||||
: "9. Explain what was implemented/fixed and that all tests passed\n10. Commit your changes with git"}
|
}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json
|
||||||
|
${
|
||||||
|
feature.skipTests
|
||||||
|
? "5. **DO NOT commit changes** - the user will review and commit manually"
|
||||||
|
: "9. Explain what was implemented/fixed and that all tests passed\n10. Commit your changes with git"
|
||||||
|
}
|
||||||
|
|
||||||
**IMPORTANT - Updating Feature Status:**
|
**IMPORTANT - Updating Feature Status:**
|
||||||
|
|
||||||
When you have completed the feature${feature.skipTests ? "" : " and all tests pass"}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status:
|
When you have completed the feature${
|
||||||
|
feature.skipTests ? "" : " and all tests pass"
|
||||||
|
}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status:
|
||||||
- Call the tool with: featureId="${feature.id}" and status="verified"
|
- Call the tool with: featureId="${feature.id}" and status="verified"
|
||||||
- **You can also include a summary parameter** to describe what was done: summary="Brief summary of changes"
|
- **You can also include a summary parameter** to describe what was done: summary="Brief summary of changes"
|
||||||
- **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions
|
- **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions
|
||||||
@@ -155,7 +177,9 @@ When calling UpdateFeatureStatus, you MUST include a summary parameter that desc
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
\`\`\`
|
\`\`\`
|
||||||
UpdateFeatureStatus(featureId="${feature.id}", status="verified", summary="Added dark mode toggle to settings. Modified: settings.tsx, theme-provider.tsx. Created new useTheme hook.")
|
UpdateFeatureStatus(featureId="${
|
||||||
|
feature.id
|
||||||
|
}", status="verified", summary="Added dark mode toggle to settings. Modified: settings.tsx, theme-provider.tsx. Created new useTheme hook.")
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
The summary will be displayed on the Kanban card so the user can see what was done without checking the code.
|
The summary will be displayed on the Kanban card so the user can see what was done without checking the code.
|
||||||
@@ -173,9 +197,11 @@ rm tests/[feature-name].spec.ts
|
|||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
**Important:**
|
**Important:**
|
||||||
${feature.skipTests
|
${
|
||||||
? "- Skip automated testing (skipTests=true) - user will manually verify\n- **DO NOT commit changes** - user will review and commit manually"
|
feature.skipTests
|
||||||
: "- **CONTINUE IMPLEMENTING until all tests pass** - don't stop at the first failure\n- Only mark as verified if Playwright tests pass\n- **CRITICAL: Delete test files after they pass** - tests should not accumulate\n- Update test utilities if functionality changed\n- Make a git commit when the feature is complete\n- Be thorough and persistent in fixing issues"}
|
? "- Skip automated testing (skipTests=true) - user will manually verify\n- **DO NOT commit changes** - user will review and commit manually"
|
||||||
|
: "- **CONTINUE IMPLEMENTING until all tests pass** - don't stop at the first failure\n- Only mark as verified if Playwright tests pass\n- **CRITICAL: Delete test files after they pass** - tests should not accumulate\n- Update test utilities if functionality changed\n- Make a git commit when the feature is complete\n- Be thorough and persistent in fixing issues"
|
||||||
|
}
|
||||||
- **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly**
|
- **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly**
|
||||||
- **CRITICAL: Always include a summary when marking feature as verified**
|
- **CRITICAL: Always include a summary when marking feature as verified**
|
||||||
|
|
||||||
@@ -211,17 +237,25 @@ Continue where you left off and complete the feature implementation:
|
|||||||
|
|
||||||
1. Review the previous work context above to understand what has been done
|
1. Review the previous work context above to understand what has been done
|
||||||
2. Continue implementing the feature according to the description and steps
|
2. Continue implementing the feature according to the description and steps
|
||||||
${feature.skipTests
|
${
|
||||||
? "3. Test the implementation manually (no automated tests needed for skipTests features)"
|
feature.skipTests
|
||||||
: "3. Write Playwright tests to verify the feature works correctly (if not already done)\n4. Run the tests and ensure they pass\n5. **DELETE the test file(s) you created** - tests are only for immediate verification"}
|
? "3. Test the implementation manually (no automated tests needed for skipTests features)"
|
||||||
${feature.skipTests ? "4" : "6"}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json
|
: "3. Write Playwright tests to verify the feature works correctly (if not already done)\n4. Run the tests and ensure they pass\n5. **DELETE the test file(s) you created** - tests are only for immediate verification"
|
||||||
${feature.skipTests
|
}
|
||||||
? "5. **DO NOT commit changes** - the user will review and commit manually"
|
${
|
||||||
: "7. Commit your changes with git"}
|
feature.skipTests ? "4" : "6"
|
||||||
|
}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json
|
||||||
|
${
|
||||||
|
feature.skipTests
|
||||||
|
? "5. **DO NOT commit changes** - the user will review and commit manually"
|
||||||
|
: "7. Commit your changes with git"
|
||||||
|
}
|
||||||
|
|
||||||
**IMPORTANT - Updating Feature Status:**
|
**IMPORTANT - Updating Feature Status:**
|
||||||
|
|
||||||
When you have completed the feature${feature.skipTests ? "" : " and all tests pass"}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status:
|
When you have completed the feature${
|
||||||
|
feature.skipTests ? "" : " and all tests pass"
|
||||||
|
}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status:
|
||||||
- Call the tool with: featureId="${feature.id}" and status="verified"
|
- Call the tool with: featureId="${feature.id}" and status="verified"
|
||||||
- **You can also include a summary parameter** to describe what was done: summary="Brief summary of changes"
|
- **You can also include a summary parameter** to describe what was done: summary="Brief summary of changes"
|
||||||
- **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions
|
- **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions
|
||||||
@@ -237,7 +271,9 @@ When calling UpdateFeatureStatus, you MUST include a summary parameter that desc
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
\`\`\`
|
\`\`\`
|
||||||
UpdateFeatureStatus(featureId="${feature.id}", status="verified", summary="Added dark mode toggle to settings. Modified: settings.tsx, theme-provider.tsx. Created new useTheme hook.")
|
UpdateFeatureStatus(featureId="${
|
||||||
|
feature.id
|
||||||
|
}", status="verified", summary="Added dark mode toggle to settings. Modified: settings.tsx, theme-provider.tsx. Created new useTheme hook.")
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
The summary will be displayed on the Kanban card so the user can see what was done without checking the code.
|
The summary will be displayed on the Kanban card so the user can see what was done without checking the code.
|
||||||
@@ -247,14 +283,18 @@ The summary will be displayed on the Kanban card so the user can see what was do
|
|||||||
- Review what was already done in the previous context
|
- Review what was already done in the previous context
|
||||||
- Don't redo work that's already complete - continue from where it left off
|
- Don't redo work that's already complete - continue from where it left off
|
||||||
- Focus on completing any remaining tasks
|
- Focus on completing any remaining tasks
|
||||||
${feature.skipTests
|
${
|
||||||
? "- Skip automated testing (skipTests=true) - user will manually verify"
|
feature.skipTests
|
||||||
: "- Write comprehensive Playwright tests if not already done\n- Ensure all tests pass before marking as verified\n- **CRITICAL: Delete test files after verification**"}
|
? "- Skip automated testing (skipTests=true) - user will manually verify"
|
||||||
|
: "- Write comprehensive Playwright tests if not already done\n- Ensure all tests pass before marking as verified\n- **CRITICAL: Delete test files after verification**"
|
||||||
|
}
|
||||||
- **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly**
|
- **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly**
|
||||||
- **CRITICAL: Always include a summary when marking feature as verified**
|
- **CRITICAL: Always include a summary when marking feature as verified**
|
||||||
${feature.skipTests
|
${
|
||||||
? "- **DO NOT commit changes** - user will review and commit manually"
|
feature.skipTests
|
||||||
: "- Make a git commit when complete"}
|
? "- **DO NOT commit changes** - user will review and commit manually"
|
||||||
|
: "- Make a git commit when complete"
|
||||||
|
}
|
||||||
|
|
||||||
Begin by assessing what's been done and what remains to be completed.`;
|
Begin by assessing what's been done and what remains to be completed.`;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased dark`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<Toaster richColors position="top-right" />
|
<Toaster richColors position="top-right" />
|
||||||
|
|||||||
@@ -41,17 +41,52 @@ export default function Home() {
|
|||||||
// Apply theme class to document
|
// Apply theme class to document
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
|
root.classList.remove(
|
||||||
|
"dark",
|
||||||
|
"retro",
|
||||||
|
"light",
|
||||||
|
"dracula",
|
||||||
|
"nord",
|
||||||
|
"monokai",
|
||||||
|
"tokyonight",
|
||||||
|
"solarized",
|
||||||
|
"gruvbox",
|
||||||
|
"catppuccin",
|
||||||
|
"onedark",
|
||||||
|
"synthwave"
|
||||||
|
);
|
||||||
|
|
||||||
if (theme === "dark") {
|
if (theme === "dark") {
|
||||||
root.classList.add("dark");
|
root.classList.add("dark");
|
||||||
|
} else if (theme === "retro") {
|
||||||
|
root.classList.add("retro");
|
||||||
|
} else if (theme === "dracula") {
|
||||||
|
root.classList.add("dracula");
|
||||||
|
} else if (theme === "nord") {
|
||||||
|
root.classList.add("nord");
|
||||||
|
} else if (theme === "monokai") {
|
||||||
|
root.classList.add("monokai");
|
||||||
|
} else if (theme === "tokyonight") {
|
||||||
|
root.classList.add("tokyonight");
|
||||||
|
} else if (theme === "solarized") {
|
||||||
|
root.classList.add("solarized");
|
||||||
|
} else if (theme === "gruvbox") {
|
||||||
|
root.classList.add("gruvbox");
|
||||||
|
} else if (theme === "catppuccin") {
|
||||||
|
root.classList.add("catppuccin");
|
||||||
|
} else if (theme === "onedark") {
|
||||||
|
root.classList.add("onedark");
|
||||||
|
} else if (theme === "synthwave") {
|
||||||
|
root.classList.add("synthwave");
|
||||||
} else if (theme === "light") {
|
} else if (theme === "light") {
|
||||||
root.classList.remove("dark");
|
root.classList.add("light");
|
||||||
} else {
|
} else if (theme === "system") {
|
||||||
// System theme
|
// System theme
|
||||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
if (isDark) {
|
if (isDark) {
|
||||||
root.classList.add("dark");
|
root.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
root.classList.remove("dark");
|
root.classList.add("light");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export function Sidebar() {
|
|||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-shrink-0 border-r border-white/10 bg-zinc-950/50 backdrop-blur-md flex flex-col z-30 transition-all duration-300 relative",
|
"flex-shrink-0 border-r border-sidebar-border bg-sidebar backdrop-blur-md flex flex-col z-30 transition-all duration-300 relative",
|
||||||
sidebarOpen ? "w-16 lg:w-60" : "w-16"
|
sidebarOpen ? "w-16 lg:w-60" : "w-16"
|
||||||
)}
|
)}
|
||||||
data-testid="sidebar"
|
data-testid="sidebar"
|
||||||
@@ -272,7 +272,7 @@ export function Sidebar() {
|
|||||||
{/* Floating Collapse Toggle Button - Desktop only - At border intersection */}
|
{/* Floating Collapse Toggle Button - Desktop only - At border intersection */}
|
||||||
<button
|
<button
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
className="hidden lg:flex absolute top-[68px] -right-3 z-[9999] group/toggle items-center justify-center w-6 h-6 rounded-full bg-zinc-800 border border-white/10 text-zinc-400 hover:text-white hover:bg-zinc-700 hover:border-white/20 transition-all shadow-lg titlebar-no-drag"
|
className="hidden lg:flex absolute top-[68px] -right-3 z-9999 group/toggle items-center justify-center w-6 h-6 rounded-full bg-sidebar-accent border border-border text-muted-foreground hover:text-foreground hover:bg-accent hover:border-border transition-all shadow-lg titlebar-no-drag"
|
||||||
data-testid="sidebar-collapse-button"
|
data-testid="sidebar-collapse-button"
|
||||||
>
|
>
|
||||||
{sidebarOpen ? (
|
{sidebarOpen ? (
|
||||||
@@ -282,12 +282,12 @@ export function Sidebar() {
|
|||||||
)}
|
)}
|
||||||
{/* Tooltip */}
|
{/* Tooltip */}
|
||||||
<div
|
<div
|
||||||
className="absolute left-full ml-2 px-2 py-1 bg-zinc-800 text-white text-xs rounded opacity-0 group-hover/toggle:opacity-100 transition-opacity whitespace-nowrap z-50 border border-zinc-700 pointer-events-none"
|
className="absolute left-full ml-2 px-2 py-1 bg-popover text-popover-foreground text-xs rounded opacity-0 group-hover/toggle:opacity-100 transition-opacity whitespace-nowrap z-50 border border-border pointer-events-none"
|
||||||
data-testid="sidebar-toggle-tooltip"
|
data-testid="sidebar-toggle-tooltip"
|
||||||
>
|
>
|
||||||
{sidebarOpen ? "Collapse sidebar" : "Expand sidebar"}{" "}
|
{sidebarOpen ? "Collapse sidebar" : "Expand sidebar"}{" "}
|
||||||
<span
|
<span
|
||||||
className="ml-1 px-1 py-0.5 bg-white/10 rounded text-[10px] font-mono"
|
className="ml-1 px-1 py-0.5 bg-sidebar-accent/10 rounded text-[10px] font-mono"
|
||||||
data-testid="sidebar-toggle-shortcut"
|
data-testid="sidebar-toggle-shortcut"
|
||||||
>
|
>
|
||||||
{UI_SHORTCUTS.toggleSidebar}
|
{UI_SHORTCUTS.toggleSidebar}
|
||||||
@@ -299,7 +299,7 @@ export function Sidebar() {
|
|||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-20 pt-8 flex items-center justify-center border-b border-zinc-800 flex-shrink-0 titlebar-drag-region",
|
"h-20 pt-8 flex items-center justify-center border-b border-sidebar-border shrink-0 titlebar-drag-region",
|
||||||
sidebarOpen ? "px-3 lg:px-6" : "px-3"
|
sidebarOpen ? "px-3 lg:px-6" : "px-3"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -308,12 +308,12 @@ export function Sidebar() {
|
|||||||
onClick={() => setCurrentView("welcome")}
|
onClick={() => setCurrentView("welcome")}
|
||||||
data-testid="logo-button"
|
data-testid="logo-button"
|
||||||
>
|
>
|
||||||
<div className="relative flex items-center justify-center w-8 h-8 bg-gradient-to-br from-brand-500 to-purple-600 rounded-lg shadow-lg shadow-brand-500/20 group">
|
<div className="relative flex items-center justify-center w-8 h-8 bg-linear-to-br from-brand-500 to-brand-600 rounded-lg shadow-lg shadow-brand-500/20 group">
|
||||||
<Cpu className="text-white w-5 h-5 group-hover:rotate-12 transition-transform" />
|
<Cpu className="text-primary-foreground w-5 h-5 group-hover:rotate-12 transition-transform" />
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-3 font-bold text-white text-base tracking-tight",
|
"ml-3 font-bold text-sidebar-foreground text-base tracking-tight",
|
||||||
sidebarOpen ? "hidden lg:block" : "hidden"
|
sidebarOpen ? "hidden lg:block" : "hidden"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -327,7 +327,7 @@ export function Sidebar() {
|
|||||||
<div className="flex items-center gap-2 titlebar-no-drag px-2 mt-3">
|
<div className="flex items-center gap-2 titlebar-no-drag px-2 mt-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentView("welcome")}
|
onClick={() => setCurrentView("welcome")}
|
||||||
className="group flex items-center justify-center flex-1 px-3 py-2.5 rounded-lg relative overflow-hidden transition-all text-zinc-400 hover:text-white hover:bg-white/5 border border-white/10"
|
className="group flex items-center justify-center flex-1 px-3 py-2.5 rounded-lg relative overflow-hidden transition-all text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 border border-sidebar-border"
|
||||||
title="New Project"
|
title="New Project"
|
||||||
data-testid="new-project-button"
|
data-testid="new-project-button"
|
||||||
>
|
>
|
||||||
@@ -338,11 +338,11 @@ export function Sidebar() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleOpenFolder}
|
onClick={handleOpenFolder}
|
||||||
className="group flex items-center justify-center flex-1 px-3 py-2.5 rounded-lg relative overflow-hidden transition-all text-zinc-400 hover:text-white hover:bg-white/5 border border-white/10"
|
className="group flex items-center justify-center flex-1 px-3 py-2.5 rounded-lg relative overflow-hidden transition-all text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 border border-sidebar-border"
|
||||||
title={`Open Folder (${ACTION_SHORTCUTS.openProject})`}
|
title={`Open Folder (${ACTION_SHORTCUTS.openProject})`}
|
||||||
data-testid="open-project-button"
|
data-testid="open-project-button"
|
||||||
>
|
>
|
||||||
<FolderOpen className="w-4 h-4 flex-shrink-0" />
|
<FolderOpen className="w-4 h-4 shrink-0" />
|
||||||
<span className="ml-2 text-sm font-medium hidden lg:block whitespace-nowrap">
|
<span className="ml-2 text-sm font-medium hidden lg:block whitespace-nowrap">
|
||||||
Open
|
Open
|
||||||
</span>
|
</span>
|
||||||
@@ -362,28 +362,28 @@ export function Sidebar() {
|
|||||||
>
|
>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className="w-full flex items-center justify-between px-3 py-2.5 rounded-lg bg-white/5 border border-white/10 hover:bg-white/10 transition-all text-white titlebar-no-drag"
|
className="w-full flex items-center justify-between px-3 py-2.5 rounded-lg bg-sidebar-accent/10 border border-sidebar-border hover:bg-sidebar-accent/20 transition-all text-foreground titlebar-no-drag"
|
||||||
data-testid="project-selector"
|
data-testid="project-selector"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
<Folder className="h-4 w-4 text-brand-500 flex-shrink-0" />
|
<Folder className="h-4 w-4 text-brand-500 shrink-0" />
|
||||||
<span className="text-sm font-medium truncate">
|
<span className="text-sm font-medium truncate">
|
||||||
{currentProject?.name || "Select Project"}
|
{currentProject?.name || "Select Project"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span
|
<span
|
||||||
className="hidden lg:flex items-center justify-center w-5 h-5 text-[10px] font-mono rounded bg-white/5 border border-white/10 text-zinc-500"
|
className="hidden lg:flex items-center justify-center w-5 h-5 text-[10px] font-mono rounded bg-sidebar-accent/10 border border-sidebar-border text-muted-foreground"
|
||||||
data-testid="project-picker-shortcut"
|
data-testid="project-picker-shortcut"
|
||||||
>
|
>
|
||||||
{ACTION_SHORTCUTS.projectPicker}
|
{ACTION_SHORTCUTS.projectPicker}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="h-4 w-4 text-zinc-400 flex-shrink-0" />
|
<ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="w-56 bg-zinc-800 border-zinc-700"
|
className="w-56 bg-popover border-border"
|
||||||
align="start"
|
align="start"
|
||||||
data-testid="project-picker-dropdown"
|
data-testid="project-picker-dropdown"
|
||||||
>
|
>
|
||||||
@@ -394,12 +394,12 @@ export function Sidebar() {
|
|||||||
setCurrentProject(project);
|
setCurrentProject(project);
|
||||||
setIsProjectPickerOpen(false);
|
setIsProjectPickerOpen(false);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2 cursor-pointer text-zinc-300 hover:text-white hover:bg-zinc-700/50"
|
className="flex items-center gap-2 cursor-pointer text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
data-testid={`project-option-${project.id}`}
|
data-testid={`project-option-${project.id}`}
|
||||||
>
|
>
|
||||||
{index < 9 && (
|
{index < 9 && (
|
||||||
<span
|
<span
|
||||||
className="flex items-center justify-center w-5 h-5 text-[10px] font-mono rounded bg-white/5 border border-white/10 text-zinc-400"
|
className="flex items-center justify-center w-5 h-5 text-[10px] font-mono rounded bg-sidebar-accent/10 border border-sidebar-border text-muted-foreground"
|
||||||
data-testid={`project-hotkey-${index + 1}`}
|
data-testid={`project-hotkey-${index + 1}`}
|
||||||
>
|
>
|
||||||
{index + 1}
|
{index + 1}
|
||||||
@@ -422,7 +422,7 @@ export function Sidebar() {
|
|||||||
{!currentProject && sidebarOpen ? (
|
{!currentProject && sidebarOpen ? (
|
||||||
// Placeholder when no project is selected (only in expanded state)
|
// Placeholder when no project is selected (only in expanded state)
|
||||||
<div className="flex items-center justify-center h-full px-4">
|
<div className="flex items-center justify-center h-full px-4">
|
||||||
<p className="text-zinc-500 text-sm text-center">
|
<p className="text-muted-foreground text-sm text-center">
|
||||||
<span className="hidden lg:block">
|
<span className="hidden lg:block">
|
||||||
Select or create a project above
|
Select or create a project above
|
||||||
</span>
|
</span>
|
||||||
@@ -435,13 +435,13 @@ export function Sidebar() {
|
|||||||
{/* Section Label */}
|
{/* Section Label */}
|
||||||
{section.label && sidebarOpen && (
|
{section.label && sidebarOpen && (
|
||||||
<div className="hidden lg:block px-4 mb-2">
|
<div className="hidden lg:block px-4 mb-2">
|
||||||
<span className="text-[10px] font-semibold text-zinc-500 uppercase tracking-wider">
|
<span className="text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
|
||||||
{section.label}
|
{section.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{section.label && !sidebarOpen && (
|
{section.label && !sidebarOpen && (
|
||||||
<div className="h-px bg-zinc-800 mx-2 mb-2"></div>
|
<div className="h-px bg-sidebar-border mx-2 mb-2"></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Nav Items */}
|
{/* Nav Items */}
|
||||||
@@ -457,8 +457,8 @@ export function Sidebar() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center w-full px-2 lg:px-3 py-2.5 rounded-lg relative overflow-hidden transition-all titlebar-no-drag",
|
"group flex items-center w-full px-2 lg:px-3 py-2.5 rounded-lg relative overflow-hidden transition-all titlebar-no-drag",
|
||||||
isActive
|
isActive
|
||||||
? "bg-white/5 text-white border border-white/10"
|
? "bg-sidebar-accent/50 text-foreground border border-sidebar-border"
|
||||||
: "text-zinc-400 hover:text-white hover:bg-white/5",
|
: "text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50",
|
||||||
!sidebarOpen && "justify-center"
|
!sidebarOpen && "justify-center"
|
||||||
)}
|
)}
|
||||||
title={!sidebarOpen ? item.label : undefined}
|
title={!sidebarOpen ? item.label : undefined}
|
||||||
@@ -469,7 +469,7 @@ export function Sidebar() {
|
|||||||
)}
|
)}
|
||||||
<Icon
|
<Icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-4 h-4 flex-shrink-0 transition-colors",
|
"w-4 h-4 shrink-0 transition-colors",
|
||||||
isActive
|
isActive
|
||||||
? "text-brand-500"
|
? "text-brand-500"
|
||||||
: "group-hover:text-brand-400"
|
: "group-hover:text-brand-400"
|
||||||
@@ -515,7 +515,7 @@ export function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Section - User / Settings */}
|
{/* Bottom Section - User / Settings */}
|
||||||
<div className="border-t border-zinc-800 bg-zinc-900/50 flex-shrink-0">
|
<div className="border-t border-sidebar-border bg-sidebar-accent/10 shrink-0">
|
||||||
{/* Settings Link */}
|
{/* Settings Link */}
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<button
|
<button
|
||||||
@@ -523,8 +523,8 @@ export function Sidebar() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center w-full px-2 lg:px-3 py-2.5 rounded-lg relative overflow-hidden transition-all titlebar-no-drag",
|
"group flex items-center w-full px-2 lg:px-3 py-2.5 rounded-lg relative overflow-hidden transition-all titlebar-no-drag",
|
||||||
isActiveRoute("settings")
|
isActiveRoute("settings")
|
||||||
? "bg-white/5 text-white border border-white/10"
|
? "bg-sidebar-accent/50 text-foreground border border-sidebar-border"
|
||||||
: "text-zinc-400 hover:text-white hover:bg-white/5",
|
: "text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50",
|
||||||
sidebarOpen ? "justify-start" : "justify-center"
|
sidebarOpen ? "justify-start" : "justify-center"
|
||||||
)}
|
)}
|
||||||
title={!sidebarOpen ? "Settings" : undefined}
|
title={!sidebarOpen ? "Settings" : undefined}
|
||||||
@@ -535,7 +535,7 @@ export function Sidebar() {
|
|||||||
)}
|
)}
|
||||||
<Settings
|
<Settings
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-4 h-4 flex-shrink-0 transition-colors",
|
"w-4 h-4 shrink-0 transition-colors",
|
||||||
isActiveRoute("settings")
|
isActiveRoute("settings")
|
||||||
? "text-brand-500"
|
? "text-brand-500"
|
||||||
: "group-hover:text-brand-400"
|
: "group-hover:text-brand-400"
|
||||||
@@ -562,7 +562,7 @@ export function Sidebar() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!sidebarOpen && (
|
{!sidebarOpen && (
|
||||||
<span className="absolute left-full ml-2 px-2 py-1 bg-zinc-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-50 border border-zinc-700">
|
<span className="absolute left-full ml-2 px-2 py-1 bg-popover text-popover-foreground text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-50 border border-border">
|
||||||
Settings
|
Settings
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
@@ -19,6 +19,8 @@ const buttonVariants = cva(
|
|||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
"animated-outline":
|
||||||
|
"relative overflow-hidden rounded-xl hover:bg-transparent shadow-none",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
@@ -34,27 +36,60 @@ const buttonVariants = cva(
|
|||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
size,
|
size,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> &
|
}: React.ComponentProps<"button"> &
|
||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "button"
|
// Special handling for animated-outline variant
|
||||||
|
if (variant === "animated-outline" && !asChild) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant, size }),
|
||||||
|
"p-[1px]", // Force 1px padding for the gradient border
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-slot="button"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{/* Animated rotating gradient border */}
|
||||||
|
<span className="absolute inset-[-1000%] animate-[spin_2s_linear_infinite] animated-outline-gradient" />
|
||||||
|
|
||||||
|
{/* Inner content container */}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"animated-outline-inner inline-flex h-full w-full cursor-pointer items-center justify-center gap-2 rounded-[10px] px-4 py-1 text-sm font-medium backdrop-blur-3xl transition-all",
|
||||||
|
size === "sm" && "px-3 text-xs gap-1.5",
|
||||||
|
size === "lg" && "px-8",
|
||||||
|
size === "icon" && "p-0 gap-0"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
data-slot="button"
|
data-slot="button"
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
)
|
{children}
|
||||||
|
</Comp>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants };
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ const Slider = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-white/10">
|
<SliderPrimitive.Track className="slider-track relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted">
|
||||||
<SliderPrimitive.Range className="absolute h-full bg-gradient-to-r from-purple-600 to-blue-600" />
|
<SliderPrimitive.Range className="slider-range absolute h-full bg-primary" />
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-white/20 bg-zinc-800 shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-white/50 disabled:pointer-events-none disabled:opacity-50 hover:bg-zinc-700" />
|
<SliderPrimitive.Thumb className="slider-thumb block h-4 w-4 rounded-full border border-border bg-card shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent" />
|
||||||
</SliderPrimitive.Root>
|
</SliderPrimitive.Root>
|
||||||
));
|
));
|
||||||
Slider.displayName = SliderPrimitive.Root.displayName;
|
Slider.displayName = SliderPrimitive.Root.displayName;
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ export function AgentToolsView() {
|
|||||||
data-testid="agent-tools-view"
|
data-testid="agent-tools-view"
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-3 p-4 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="flex items-center gap-3 p-4 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<Wrench className="w-5 h-5 text-primary" />
|
<Wrench className="w-5 h-5 text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold">Agent Tools</h1>
|
<h1 className="text-xl font-bold">Agent Tools</h1>
|
||||||
|
|||||||
@@ -452,7 +452,7 @@ export function AgentView() {
|
|||||||
{/* Chat Area */}
|
{/* Chat Area */}
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="flex items-center justify-between p-4 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -503,7 +503,10 @@ export function AgentView() {
|
|||||||
|
|
||||||
{/* Messages */}
|
{/* Messages */}
|
||||||
{!currentSessionId ? (
|
{!currentSessionId ? (
|
||||||
<div className="flex-1 flex items-center justify-center" data-testid="no-session-placeholder">
|
<div
|
||||||
|
className="flex-1 flex items-center justify-center"
|
||||||
|
data-testid="no-session-placeholder"
|
||||||
|
>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Bot className="w-12 h-12 text-muted-foreground mx-auto mb-4 opacity-50" />
|
<Bot className="w-12 h-12 text-muted-foreground mx-auto mb-4 opacity-50" />
|
||||||
<h2 className="text-lg font-semibold mb-2">
|
<h2 className="text-lg font-semibold mb-2">
|
||||||
|
|||||||
1119
app/src/components/views/analysis-view.tsx
Normal file
1119
app/src/components/views/analysis-view.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -37,11 +37,26 @@ export function AutoModeLog({ onClose }: AutoModeLogProps) {
|
|||||||
case "error":
|
case "error":
|
||||||
return <AlertCircle className="w-4 h-4 text-red-500" />;
|
return <AlertCircle className="w-4 h-4 text-red-500" />;
|
||||||
case "planning":
|
case "planning":
|
||||||
return <ClipboardList className="w-4 h-4 text-cyan-500" data-testid="planning-phase-icon" />;
|
return (
|
||||||
|
<ClipboardList
|
||||||
|
className="w-4 h-4 text-cyan-500"
|
||||||
|
data-testid="planning-phase-icon"
|
||||||
|
/>
|
||||||
|
);
|
||||||
case "action":
|
case "action":
|
||||||
return <Zap className="w-4 h-4 text-orange-500" data-testid="action-phase-icon" />;
|
return (
|
||||||
|
<Zap
|
||||||
|
className="w-4 h-4 text-orange-500"
|
||||||
|
data-testid="action-phase-icon"
|
||||||
|
/>
|
||||||
|
);
|
||||||
case "verification":
|
case "verification":
|
||||||
return <ShieldCheck className="w-4 h-4 text-emerald-500" data-testid="verification-phase-icon" />;
|
return (
|
||||||
|
<ShieldCheck
|
||||||
|
className="w-4 h-4 text-emerald-500"
|
||||||
|
data-testid="verification-phase-icon"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,8 +95,8 @@ export function AutoModeLog({ onClose }: AutoModeLogProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="h-full flex flex-col border-white/10 bg-zinc-950/50 backdrop-blur-sm">
|
<Card className="h-full flex flex-col border-border bg-card backdrop-blur-sm">
|
||||||
<CardHeader className="p-4 border-b border-white/10 flex-shrink-0">
|
<CardHeader className="p-4 border-b border-border flex-shrink-0">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Loader2 className="w-5 h-5 text-purple-500 animate-spin" />
|
<Loader2 className="w-5 h-5 text-purple-500 animate-spin" />
|
||||||
@@ -127,12 +142,14 @@ export function AutoModeLog({ onClose }: AutoModeLogProps) {
|
|||||||
<div
|
<div
|
||||||
key={activity.id}
|
key={activity.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-3 rounded-lg bg-zinc-900/50 border-l-4 hover:bg-zinc-900/70 transition-colors",
|
"p-3 rounded-lg bg-secondary border-l-4 hover:bg-accent transition-colors",
|
||||||
getActivityColor(activity.type)
|
getActivityColor(activity.type)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="mt-0.5">{getActivityIcon(activity.type)}</div>
|
<div className="mt-0.5">
|
||||||
|
{getActivityIcon(activity.type)}
|
||||||
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-baseline gap-2 mb-1">
|
<div className="flex items-baseline gap-2 mb-1">
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
301
app/src/components/views/code-view.tsx
Normal file
301
app/src/components/views/code-view.tsx
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
import { useAppStore } from "@/store/app-store";
|
||||||
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
File,
|
||||||
|
Folder,
|
||||||
|
FolderOpen,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronDown,
|
||||||
|
RefreshCw,
|
||||||
|
Code,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface FileTreeNode {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
isDirectory: boolean;
|
||||||
|
children?: FileTreeNode[];
|
||||||
|
isExpanded?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IGNORE_PATTERNS = [
|
||||||
|
"node_modules",
|
||||||
|
".git",
|
||||||
|
".next",
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
".DS_Store",
|
||||||
|
"*.log",
|
||||||
|
];
|
||||||
|
|
||||||
|
const shouldIgnore = (name: string) => {
|
||||||
|
return IGNORE_PATTERNS.some((pattern) => {
|
||||||
|
if (pattern.startsWith("*")) {
|
||||||
|
return name.endsWith(pattern.slice(1));
|
||||||
|
}
|
||||||
|
return name === pattern;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CodeView() {
|
||||||
|
const { currentProject } = useAppStore();
|
||||||
|
const [fileTree, setFileTree] = useState<FileTreeNode[]>([]);
|
||||||
|
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
||||||
|
const [fileContent, setFileContent] = useState<string>("");
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(
|
||||||
|
new Set()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load directory tree
|
||||||
|
const loadTree = useCallback(async () => {
|
||||||
|
if (!currentProject) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.readdir(currentProject.path);
|
||||||
|
|
||||||
|
if (result.success && result.entries) {
|
||||||
|
const entries = result.entries
|
||||||
|
.filter((e) => !shouldIgnore(e.name))
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Directories first
|
||||||
|
if (a.isDirectory && !b.isDirectory) return -1;
|
||||||
|
if (!a.isDirectory && b.isDirectory) return 1;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
})
|
||||||
|
.map((e) => ({
|
||||||
|
name: e.name,
|
||||||
|
path: `${currentProject.path}/${e.name}`,
|
||||||
|
isDirectory: e.isDirectory,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setFileTree(entries);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load file tree:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [currentProject]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadTree();
|
||||||
|
}, [loadTree]);
|
||||||
|
|
||||||
|
// Load subdirectory
|
||||||
|
const loadSubdirectory = async (path: string): Promise<FileTreeNode[]> => {
|
||||||
|
try {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.readdir(path);
|
||||||
|
|
||||||
|
if (result.success && result.entries) {
|
||||||
|
return result.entries
|
||||||
|
.filter((e) => !shouldIgnore(e.name))
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.isDirectory && !b.isDirectory) return -1;
|
||||||
|
if (!a.isDirectory && b.isDirectory) return 1;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
})
|
||||||
|
.map((e) => ({
|
||||||
|
name: e.name,
|
||||||
|
path: `${path}/${e.name}`,
|
||||||
|
isDirectory: e.isDirectory,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load subdirectory:", error);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load file content
|
||||||
|
const loadFileContent = async (path: string) => {
|
||||||
|
try {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.readFile(path);
|
||||||
|
|
||||||
|
if (result.success && result.content) {
|
||||||
|
setFileContent(result.content);
|
||||||
|
setSelectedFile(path);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load file:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle folder expansion
|
||||||
|
const toggleFolder = async (node: FileTreeNode) => {
|
||||||
|
const newExpanded = new Set(expandedFolders);
|
||||||
|
|
||||||
|
if (expandedFolders.has(node.path)) {
|
||||||
|
newExpanded.delete(node.path);
|
||||||
|
} else {
|
||||||
|
newExpanded.add(node.path);
|
||||||
|
|
||||||
|
// Load children if not already loaded
|
||||||
|
if (!node.children) {
|
||||||
|
const children = await loadSubdirectory(node.path);
|
||||||
|
// Update the tree with children
|
||||||
|
const updateTree = (nodes: FileTreeNode[]): FileTreeNode[] => {
|
||||||
|
return nodes.map((n) => {
|
||||||
|
if (n.path === node.path) {
|
||||||
|
return { ...n, children };
|
||||||
|
}
|
||||||
|
if (n.children) {
|
||||||
|
return { ...n, children: updateTree(n.children) };
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
setFileTree(updateTree(fileTree));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setExpandedFolders(newExpanded);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render file tree node
|
||||||
|
const renderNode = (node: FileTreeNode, depth: number = 0) => {
|
||||||
|
const isExpanded = expandedFolders.has(node.path);
|
||||||
|
const isSelected = selectedFile === node.path;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={node.path}>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 py-1 px-2 rounded cursor-pointer hover:bg-muted/50",
|
||||||
|
isSelected && "bg-muted"
|
||||||
|
)}
|
||||||
|
style={{ paddingLeft: `${depth * 16 + 8}px` }}
|
||||||
|
onClick={() => {
|
||||||
|
if (node.isDirectory) {
|
||||||
|
toggleFolder(node);
|
||||||
|
} else {
|
||||||
|
loadFileContent(node.path);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-testid={`file-tree-item-${node.name}`}
|
||||||
|
>
|
||||||
|
{node.isDirectory ? (
|
||||||
|
<>
|
||||||
|
{isExpanded ? (
|
||||||
|
<ChevronDown className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||||
|
)}
|
||||||
|
{isExpanded ? (
|
||||||
|
<FolderOpen className="w-4 h-4 text-primary shrink-0" />
|
||||||
|
) : (
|
||||||
|
<Folder className="w-4 h-4 text-primary shrink-0" />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="w-4" />
|
||||||
|
<File className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<span className="text-sm truncate">{node.name}</span>
|
||||||
|
</div>
|
||||||
|
{node.isDirectory && isExpanded && node.children && (
|
||||||
|
<div>
|
||||||
|
{node.children.map((child) => renderNode(child, depth + 1))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!currentProject) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-1 flex items-center justify-center"
|
||||||
|
data-testid="code-view-no-project"
|
||||||
|
>
|
||||||
|
<p className="text-muted-foreground">No project selected</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-1 flex items-center justify-center"
|
||||||
|
data-testid="code-view-loading"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-1 flex flex-col overflow-hidden content-bg"
|
||||||
|
data-testid="code-view"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Code className="w-5 h-5 text-muted-foreground" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold">Code Explorer</h1>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{currentProject.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={loadTree}
|
||||||
|
data-testid="refresh-tree"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Split View */}
|
||||||
|
<div className="flex-1 flex overflow-hidden">
|
||||||
|
{/* File Tree */}
|
||||||
|
<div className="w-64 border-r overflow-y-auto" data-testid="file-tree">
|
||||||
|
<div className="p-2">{fileTree.map((node) => renderNode(node))}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Preview */}
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
{selectedFile ? (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="px-4 py-2 border-b bg-muted/30">
|
||||||
|
<p className="text-sm font-mono text-muted-foreground truncate">
|
||||||
|
{selectedFile.replace(currentProject.path, "")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Card className="flex-1 m-4 overflow-hidden">
|
||||||
|
<CardContent className="p-0 h-full">
|
||||||
|
<pre className="p-4 h-full overflow-auto text-sm font-mono whitespace-pre-wrap">
|
||||||
|
<code data-testid="code-content">{fileContent}</code>
|
||||||
|
</pre>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 flex items-center justify-center">
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Select a file to view its contents
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -53,7 +53,9 @@ export function ContextView() {
|
|||||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
const [newFileName, setNewFileName] = useState("");
|
const [newFileName, setNewFileName] = useState("");
|
||||||
const [newFileType, setNewFileType] = useState<"text" | "image">("text");
|
const [newFileType, setNewFileType] = useState<"text" | "image">("text");
|
||||||
const [uploadedImageData, setUploadedImageData] = useState<string | null>(null);
|
const [uploadedImageData, setUploadedImageData] = useState<string | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [newFileContent, setNewFileContent] = useState("");
|
const [newFileContent, setNewFileContent] = useState("");
|
||||||
const [isDropHovering, setIsDropHovering] = useState(false);
|
const [isDropHovering, setIsDropHovering] = useState(false);
|
||||||
|
|
||||||
@@ -78,7 +80,15 @@ export function ContextView() {
|
|||||||
|
|
||||||
// Determine if a file is an image based on extension
|
// Determine if a file is an image based on extension
|
||||||
const isImageFile = (filename: string): boolean => {
|
const isImageFile = (filename: string): boolean => {
|
||||||
const imageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"];
|
const imageExtensions = [
|
||||||
|
".png",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".gif",
|
||||||
|
".webp",
|
||||||
|
".svg",
|
||||||
|
".bmp",
|
||||||
|
];
|
||||||
const ext = filename.toLowerCase().substring(filename.lastIndexOf("."));
|
const ext = filename.toLowerCase().substring(filename.lastIndexOf("."));
|
||||||
return imageExtensions.includes(ext);
|
return imageExtensions.includes(ext);
|
||||||
};
|
};
|
||||||
@@ -270,7 +280,9 @@ export function ContextView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle drag and drop for .txt and .md files in the add context dialog textarea
|
// Handle drag and drop for .txt and .md files in the add context dialog textarea
|
||||||
const handleTextAreaDrop = async (e: React.DragEvent<HTMLTextAreaElement>) => {
|
const handleTextAreaDrop = async (
|
||||||
|
e: React.DragEvent<HTMLTextAreaElement>
|
||||||
|
) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsDropHovering(false);
|
setIsDropHovering(false);
|
||||||
@@ -282,8 +294,8 @@ export function ContextView() {
|
|||||||
const fileName = file.name.toLowerCase();
|
const fileName = file.name.toLowerCase();
|
||||||
|
|
||||||
// Only accept .txt and .md files
|
// Only accept .txt and .md files
|
||||||
if (!fileName.endsWith('.txt') && !fileName.endsWith('.md')) {
|
if (!fileName.endsWith(".txt") && !fileName.endsWith(".md")) {
|
||||||
console.warn('Only .txt and .md files are supported for drag and drop');
|
console.warn("Only .txt and .md files are supported for drag and drop");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +352,7 @@ export function ContextView() {
|
|||||||
data-testid="context-view"
|
data-testid="context-view"
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="flex items-center justify-between p-4 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<BookOpen className="w-5 h-5 text-muted-foreground" />
|
<BookOpen className="w-5 h-5 text-muted-foreground" />
|
||||||
<div>
|
<div>
|
||||||
@@ -381,7 +393,10 @@ export function ContextView() {
|
|||||||
Context Files ({contextFiles.length})
|
Context Files ({contextFiles.length})
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2" data-testid="context-file-list">
|
<div
|
||||||
|
className="flex-1 overflow-y-auto p-2"
|
||||||
|
data-testid="context-file-list"
|
||||||
|
>
|
||||||
{contextFiles.length === 0 ? (
|
{contextFiles.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full text-center p-4">
|
<div className="flex flex-col items-center justify-center h-full text-center p-4">
|
||||||
<Upload className="w-8 h-8 text-zinc-500 mb-2" />
|
<Upload className="w-8 h-8 text-zinc-500 mb-2" />
|
||||||
@@ -430,7 +445,9 @@ export function ContextView() {
|
|||||||
) : (
|
) : (
|
||||||
<FileText className="w-4 h-4 text-zinc-400" />
|
<FileText className="w-4 h-4 text-zinc-400" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm font-medium">{selectedFile.name}</span>
|
<span className="text-sm font-medium">
|
||||||
|
{selectedFile.name}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{selectedFile.type === "text" && (
|
{selectedFile.type === "text" && (
|
||||||
@@ -487,9 +504,7 @@ export function ContextView() {
|
|||||||
<div className="flex-1 flex items-center justify-center">
|
<div className="flex-1 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<File className="w-12 h-12 text-zinc-600 mx-auto mb-3" />
|
<File className="w-12 h-12 text-zinc-600 mx-auto mb-3" />
|
||||||
<p className="text-zinc-500">
|
<p className="text-zinc-500">Select a file to view or edit</p>
|
||||||
Select a file to view or edit
|
|
||||||
</p>
|
|
||||||
<p className="text-zinc-600 text-sm mt-1">
|
<p className="text-zinc-600 text-sm mt-1">
|
||||||
Or drop files here to add them
|
Or drop files here to add them
|
||||||
</p>
|
</p>
|
||||||
@@ -536,7 +551,9 @@ export function ContextView() {
|
|||||||
id="filename"
|
id="filename"
|
||||||
value={newFileName}
|
value={newFileName}
|
||||||
onChange={(e) => setNewFileName(e.target.value)}
|
onChange={(e) => setNewFileName(e.target.value)}
|
||||||
placeholder={newFileType === "text" ? "context.md" : "image.png"}
|
placeholder={
|
||||||
|
newFileType === "text" ? "context.md" : "image.png"
|
||||||
|
}
|
||||||
data-testid="new-file-name"
|
data-testid="new-file-name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -569,7 +586,9 @@ export function ContextView() {
|
|||||||
<div className="absolute inset-0 flex items-center justify-center bg-brand-500/20 rounded-lg pointer-events-none">
|
<div className="absolute inset-0 flex items-center justify-center bg-brand-500/20 rounded-lg pointer-events-none">
|
||||||
<div className="flex flex-col items-center text-brand-400">
|
<div className="flex flex-col items-center text-brand-400">
|
||||||
<Upload className="w-8 h-8 mb-2" />
|
<Upload className="w-8 h-8 mb-2" />
|
||||||
<span className="text-sm font-medium">Drop .txt or .md file here</span>
|
<span className="text-sm font-medium">
|
||||||
|
Drop .txt or .md file here
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -606,7 +625,9 @@ export function ContextView() {
|
|||||||
<Upload className="w-8 h-8 text-zinc-500 mb-2" />
|
<Upload className="w-8 h-8 text-zinc-500 mb-2" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm text-zinc-400">
|
<span className="text-sm text-zinc-400">
|
||||||
{uploadedImageData ? "Click to change" : "Click to upload"}
|
{uploadedImageData
|
||||||
|
? "Click to change"
|
||||||
|
: "Click to upload"}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -628,7 +649,10 @@ export function ContextView() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAddFile}
|
onClick={handleAddFile}
|
||||||
disabled={!newFileName.trim() || (newFileType === "image" && !uploadedImageData)}
|
disabled={
|
||||||
|
!newFileName.trim() ||
|
||||||
|
(newFileType === "image" && !uploadedImageData)
|
||||||
|
}
|
||||||
data-testid="confirm-add-file"
|
data-testid="confirm-add-file"
|
||||||
>
|
>
|
||||||
Add File
|
Add File
|
||||||
@@ -643,11 +667,15 @@ export function ContextView() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Delete Context File</DialogTitle>
|
<DialogTitle>Delete Context File</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Are you sure you want to delete "{selectedFile?.name}"? This action cannot be undone.
|
Are you sure you want to delete "{selectedFile?.name}"? This
|
||||||
|
action cannot be undone.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setIsDeleteDialogOpen(false)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsDeleteDialogOpen(false)}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ export function InterviewView() {
|
|||||||
data-testid="interview-view"
|
data-testid="interview-view"
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="flex items-center justify-between p-4 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -545,7 +545,7 @@ export function InterviewView() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleCreateProject}
|
onClick={handleCreateProject}
|
||||||
disabled={!projectName || !projectPath || isGenerating}
|
disabled={!projectName || !projectPath || isGenerating}
|
||||||
className="w-full bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
|
className="w-full bg-linear-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-primary-foreground border-0"
|
||||||
data-testid="interview-create-project"
|
data-testid="interview-create-project"
|
||||||
>
|
>
|
||||||
{isGenerating ? (
|
{isGenerating ? (
|
||||||
|
|||||||
@@ -97,9 +97,13 @@ export function KanbanCard({
|
|||||||
const { kanbanCardDetailLevel } = useAppStore();
|
const { kanbanCardDetailLevel } = useAppStore();
|
||||||
|
|
||||||
// Helper functions to check what should be shown based on detail level
|
// Helper functions to check what should be shown based on detail level
|
||||||
const showSteps = kanbanCardDetailLevel === "standard" || kanbanCardDetailLevel === "detailed";
|
const showSteps =
|
||||||
|
kanbanCardDetailLevel === "standard" ||
|
||||||
|
kanbanCardDetailLevel === "detailed";
|
||||||
const showAgentInfo = kanbanCardDetailLevel === "detailed";
|
const showAgentInfo = kanbanCardDetailLevel === "detailed";
|
||||||
const showProgressBar = kanbanCardDetailLevel === "standard" || kanbanCardDetailLevel === "detailed";
|
const showProgressBar =
|
||||||
|
kanbanCardDetailLevel === "standard" ||
|
||||||
|
kanbanCardDetailLevel === "detailed";
|
||||||
|
|
||||||
// Load context file for in_progress, waiting_approval, and verified features
|
// Load context file for in_progress, waiting_approval, and verified features
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -164,8 +168,7 @@ export function KanbanCard({
|
|||||||
// - skipTests items can be dragged even when in_progress or verified (unless currently running)
|
// - skipTests items can be dragged even when in_progress or verified (unless currently running)
|
||||||
// - Non-skipTests (TDD) items in progress or verified cannot be dragged
|
// - Non-skipTests (TDD) items in progress or verified cannot be dragged
|
||||||
const isDraggable =
|
const isDraggable =
|
||||||
feature.status === "backlog" ||
|
feature.status === "backlog" || (feature.skipTests && !isCurrentAutoTask);
|
||||||
(feature.skipTests && !isCurrentAutoTask);
|
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
listeners,
|
listeners,
|
||||||
@@ -188,7 +191,7 @@ export function KanbanCard({
|
|||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-grab active:cursor-grabbing transition-all backdrop-blur-sm border-white/10 relative",
|
"cursor-grab active:cursor-grabbing transition-all backdrop-blur-sm border-border relative",
|
||||||
isDragging && "opacity-50 scale-105 shadow-lg",
|
isDragging && "opacity-50 scale-105 shadow-lg",
|
||||||
isCurrentAutoTask &&
|
isCurrentAutoTask &&
|
||||||
"border-purple-500 border-2 shadow-purple-500/50 shadow-lg animate-pulse"
|
"border-purple-500 border-2 shadow-purple-500/50 shadow-lg animate-pulse"
|
||||||
@@ -199,7 +202,7 @@ export function KanbanCard({
|
|||||||
{/* Shortcut key badge for in-progress cards */}
|
{/* Shortcut key badge for in-progress cards */}
|
||||||
{shortcutKey && (
|
{shortcutKey && (
|
||||||
<div
|
<div
|
||||||
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-white/10 border border-white/20 text-zinc-300 z-10"
|
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-muted border border-border text-muted-foreground z-10"
|
||||||
data-testid={`shortcut-key-${feature.id}`}
|
data-testid={`shortcut-key-${feature.id}`}
|
||||||
>
|
>
|
||||||
{shortcutKey}
|
{shortcutKey}
|
||||||
@@ -293,19 +296,27 @@ export function KanbanCard({
|
|||||||
|
|
||||||
{/* Agent Info Panel - shows for in_progress, waiting_approval, verified */}
|
{/* Agent Info Panel - shows for in_progress, waiting_approval, verified */}
|
||||||
{/* Standard mode: Only show progress bar */}
|
{/* Standard mode: Only show progress bar */}
|
||||||
{showProgressBar && !showAgentInfo && feature.status !== "backlog" && agentInfo && (isCurrentAutoTask || feature.status === "in_progress") && (
|
{showProgressBar &&
|
||||||
<div className="mb-3 space-y-1">
|
!showAgentInfo &&
|
||||||
<div className="w-full h-1.5 bg-zinc-800 rounded-full overflow-hidden">
|
feature.status !== "backlog" &&
|
||||||
<div
|
agentInfo &&
|
||||||
className="w-full h-full bg-primary transition-transform duration-500 ease-out origin-left"
|
(isCurrentAutoTask || feature.status === "in_progress") && (
|
||||||
style={{ transform: `translateX(${agentInfo.progressPercentage - 100}%)` }}
|
<div className="mb-3 space-y-1">
|
||||||
/>
|
<div className="w-full h-1.5 bg-zinc-800 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="w-full h-full bg-primary transition-transform duration-500 ease-out origin-left"
|
||||||
|
style={{
|
||||||
|
transform: `translateX(${
|
||||||
|
agentInfo.progressPercentage - 100
|
||||||
|
}%)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
||||||
|
<span>{Math.round(agentInfo.progressPercentage)}%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
)}
|
||||||
<span>{Math.round(agentInfo.progressPercentage)}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Detailed mode: Show all agent info */}
|
{/* Detailed mode: Show all agent info */}
|
||||||
{showAgentInfo && feature.status !== "backlog" && agentInfo && (
|
{showAgentInfo && feature.status !== "backlog" && agentInfo && (
|
||||||
@@ -314,15 +325,22 @@ export function KanbanCard({
|
|||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<div className="flex items-center gap-1 text-cyan-400">
|
<div className="flex items-center gap-1 text-cyan-400">
|
||||||
<Cpu className="w-3 h-3" />
|
<Cpu className="w-3 h-3" />
|
||||||
<span className="font-medium">{formatModelName(DEFAULT_MODEL)}</span>
|
<span className="font-medium">
|
||||||
|
{formatModelName(DEFAULT_MODEL)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{agentInfo.currentPhase && (
|
{agentInfo.currentPhase && (
|
||||||
<div className={cn(
|
<div
|
||||||
"px-1.5 py-0.5 rounded text-[10px] font-medium",
|
className={cn(
|
||||||
agentInfo.currentPhase === "planning" && "bg-blue-500/20 text-blue-400",
|
"px-1.5 py-0.5 rounded text-[10px] font-medium",
|
||||||
agentInfo.currentPhase === "action" && "bg-amber-500/20 text-amber-400",
|
agentInfo.currentPhase === "planning" &&
|
||||||
agentInfo.currentPhase === "verification" && "bg-green-500/20 text-green-400"
|
"bg-blue-500/20 text-blue-400",
|
||||||
)}>
|
agentInfo.currentPhase === "action" &&
|
||||||
|
"bg-amber-500/20 text-amber-400",
|
||||||
|
agentInfo.currentPhase === "verification" &&
|
||||||
|
"bg-green-500/20 text-green-400"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{agentInfo.currentPhase}
|
{agentInfo.currentPhase}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -334,7 +352,11 @@ export function KanbanCard({
|
|||||||
<div className="w-full h-1.5 bg-zinc-800 rounded-full overflow-hidden">
|
<div className="w-full h-1.5 bg-zinc-800 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="w-full h-full bg-primary transition-transform duration-500 ease-out origin-left"
|
className="w-full h-full bg-primary transition-transform duration-500 ease-out origin-left"
|
||||||
style={{ transform: `translateX(${agentInfo.progressPercentage - 100}%)` }}
|
style={{
|
||||||
|
transform: `translateX(${
|
||||||
|
agentInfo.progressPercentage - 100
|
||||||
|
}%)`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
||||||
@@ -344,7 +366,10 @@ export function KanbanCard({
|
|||||||
{agentInfo.toolCallCount} tools
|
{agentInfo.toolCallCount} tools
|
||||||
</span>
|
</span>
|
||||||
{agentInfo.lastToolUsed && (
|
{agentInfo.lastToolUsed && (
|
||||||
<span className="text-zinc-500 truncate max-w-[80px]" title={agentInfo.lastToolUsed}>
|
<span
|
||||||
|
className="text-zinc-500 truncate max-w-[80px]"
|
||||||
|
title={agentInfo.lastToolUsed}
|
||||||
|
>
|
||||||
{agentInfo.lastToolUsed}
|
{agentInfo.lastToolUsed}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -360,7 +385,11 @@ export function KanbanCard({
|
|||||||
<div className="flex items-center gap-1 text-[10px] text-muted-foreground">
|
<div className="flex items-center gap-1 text-[10px] text-muted-foreground">
|
||||||
<ListTodo className="w-3 h-3" />
|
<ListTodo className="w-3 h-3" />
|
||||||
<span>
|
<span>
|
||||||
{agentInfo.todos.filter(t => t.status === "completed").length}/{agentInfo.todos.length} tasks
|
{
|
||||||
|
agentInfo.todos.filter((t) => t.status === "completed")
|
||||||
|
.length
|
||||||
|
}
|
||||||
|
/{agentInfo.todos.length} tasks
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-0.5 max-h-16 overflow-y-auto">
|
<div className="space-y-0.5 max-h-16 overflow-y-auto">
|
||||||
@@ -376,12 +405,15 @@ export function KanbanCard({
|
|||||||
) : (
|
) : (
|
||||||
<Circle className="w-2.5 h-2.5 text-zinc-500 shrink-0" />
|
<Circle className="w-2.5 h-2.5 text-zinc-500 shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span className={cn(
|
<span
|
||||||
"truncate",
|
className={cn(
|
||||||
todo.status === "completed" && "text-zinc-500 line-through",
|
"truncate",
|
||||||
todo.status === "in_progress" && "text-amber-400",
|
todo.status === "completed" &&
|
||||||
todo.status === "pending" && "text-zinc-400"
|
"text-zinc-500 line-through",
|
||||||
)}>
|
todo.status === "in_progress" && "text-amber-400",
|
||||||
|
todo.status === "pending" && "text-zinc-400"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{todo.content}
|
{todo.content}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -396,7 +428,8 @@ export function KanbanCard({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Summary for waiting_approval and verified - prioritize feature.summary from UpdateFeatureStatus */}
|
{/* Summary for waiting_approval and verified - prioritize feature.summary from UpdateFeatureStatus */}
|
||||||
{(feature.status === "waiting_approval" || feature.status === "verified") && (
|
{(feature.status === "waiting_approval" ||
|
||||||
|
feature.status === "verified") && (
|
||||||
<>
|
<>
|
||||||
{(feature.summary || summary || agentInfo.summary) && (
|
{(feature.summary || summary || agentInfo.summary) && (
|
||||||
<div className="space-y-1 pt-1 border-t border-white/5">
|
<div className="space-y-1 pt-1 border-t border-white/5">
|
||||||
@@ -423,20 +456,28 @@ export function KanbanCard({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Show tool count even without summary */}
|
{/* Show tool count even without summary */}
|
||||||
{!feature.summary && !summary && !agentInfo.summary && agentInfo.toolCallCount > 0 && (
|
{!feature.summary &&
|
||||||
<div className="flex items-center gap-2 text-[10px] text-muted-foreground pt-1 border-t border-white/5">
|
!summary &&
|
||||||
<span className="flex items-center gap-1">
|
!agentInfo.summary &&
|
||||||
<Wrench className="w-2.5 h-2.5" />
|
agentInfo.toolCallCount > 0 && (
|
||||||
{agentInfo.toolCallCount} tool calls
|
<div className="flex items-center gap-2 text-[10px] text-muted-foreground pt-1 border-t border-white/5">
|
||||||
</span>
|
|
||||||
{agentInfo.todos.length > 0 && (
|
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<CheckCircle2 className="w-2.5 h-2.5 text-green-500" />
|
<Wrench className="w-2.5 h-2.5" />
|
||||||
{agentInfo.todos.filter(t => t.status === "completed").length} tasks done
|
{agentInfo.toolCallCount} tool calls
|
||||||
</span>
|
</span>
|
||||||
)}
|
{agentInfo.todos.length > 0 && (
|
||||||
</div>
|
<span className="flex items-center gap-1">
|
||||||
)}
|
<CheckCircle2 className="w-2.5 h-2.5 text-green-500" />
|
||||||
|
{
|
||||||
|
agentInfo.todos.filter(
|
||||||
|
(t) => t.status === "completed"
|
||||||
|
).length
|
||||||
|
}{" "}
|
||||||
|
tasks done
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -672,7 +713,8 @@ export function KanbanCard({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Delete Feature</DialogTitle>
|
<DialogTitle>Delete Feature</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Are you sure you want to delete this feature? This action cannot be undone.
|
Are you sure you want to delete this feature? This action cannot
|
||||||
|
be undone.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
@@ -713,7 +755,10 @@ export function KanbanCard({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex-1 overflow-y-auto p-4 bg-zinc-900/50 rounded-lg border border-white/10">
|
<div className="flex-1 overflow-y-auto p-4 bg-zinc-900/50 rounded-lg border border-white/10">
|
||||||
<Markdown>
|
<Markdown>
|
||||||
{feature.summary || summary || agentInfo?.summary || "No summary available"}
|
{feature.summary ||
|
||||||
|
summary ||
|
||||||
|
agentInfo?.summary ||
|
||||||
|
"No summary available"}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ export function KanbanColumn({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col h-full rounded-lg bg-zinc-900/50 backdrop-blur-sm border border-white/5 transition-colors",
|
"flex flex-col h-full rounded-lg bg-card backdrop-blur-sm border border-border transition-colors",
|
||||||
isDoubleWidth ? "w-[37rem]" : "w-72",
|
isDoubleWidth ? "w-[37rem]" : "w-72",
|
||||||
isOver && "bg-zinc-800/50"
|
isOver && "bg-accent"
|
||||||
)}
|
)}
|
||||||
data-testid={`kanban-column-${id}`}
|
data-testid={`kanban-column-${id}`}
|
||||||
>
|
>
|
||||||
{/* Column Header */}
|
{/* Column Header */}
|
||||||
<div className="flex items-center gap-2 p-3 border-b border-white/5">
|
<div className="flex items-center gap-2 p-3 border-b border-border">
|
||||||
<div className={cn("w-3 h-3 rounded-full", color)} />
|
<div className={cn("w-3 h-3 rounded-full", color)} />
|
||||||
<h3 className="font-medium text-sm flex-1">{title}</h3>
|
<h3 className="font-medium text-sm flex-1">{title}</h3>
|
||||||
{headerAction}
|
{headerAction}
|
||||||
|
|||||||
@@ -5,21 +5,60 @@ import { useAppStore } from "@/store/app-store";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
import { Settings, Key, Eye, EyeOff, CheckCircle2, AlertCircle, Loader2, Zap, Sun, Moon, Palette, LayoutGrid, Minimize2, Square, Maximize2, Terminal } from "lucide-react";
|
Settings,
|
||||||
|
Key,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
CheckCircle2,
|
||||||
|
AlertCircle,
|
||||||
|
Loader2,
|
||||||
|
Zap,
|
||||||
|
Sun,
|
||||||
|
Moon,
|
||||||
|
Palette,
|
||||||
|
Terminal,
|
||||||
|
Ghost,
|
||||||
|
Snowflake,
|
||||||
|
Flame,
|
||||||
|
Sparkles,
|
||||||
|
Eclipse,
|
||||||
|
Trees,
|
||||||
|
Cat,
|
||||||
|
Atom,
|
||||||
|
Radio,
|
||||||
|
LayoutGrid,
|
||||||
|
Minimize2,
|
||||||
|
Square,
|
||||||
|
Maximize2,
|
||||||
|
} from "lucide-react";
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
|
|
||||||
export function SettingsView() {
|
export function SettingsView() {
|
||||||
const { apiKeys, setApiKeys, setCurrentView, theme, setTheme, kanbanCardDetailLevel, setKanbanCardDetailLevel } = useAppStore();
|
const {
|
||||||
|
apiKeys,
|
||||||
|
setApiKeys,
|
||||||
|
setCurrentView,
|
||||||
|
theme,
|
||||||
|
setTheme,
|
||||||
|
kanbanCardDetailLevel,
|
||||||
|
setKanbanCardDetailLevel,
|
||||||
|
} = useAppStore();
|
||||||
const [anthropicKey, setAnthropicKey] = useState(apiKeys.anthropic);
|
const [anthropicKey, setAnthropicKey] = useState(apiKeys.anthropic);
|
||||||
const [googleKey, setGoogleKey] = useState(apiKeys.google);
|
const [googleKey, setGoogleKey] = useState(apiKeys.google);
|
||||||
const [showAnthropicKey, setShowAnthropicKey] = useState(false);
|
const [showAnthropicKey, setShowAnthropicKey] = useState(false);
|
||||||
const [showGoogleKey, setShowGoogleKey] = useState(false);
|
const [showGoogleKey, setShowGoogleKey] = useState(false);
|
||||||
const [saved, setSaved] = useState(false);
|
const [saved, setSaved] = useState(false);
|
||||||
const [testingConnection, setTestingConnection] = useState(false);
|
const [testingConnection, setTestingConnection] = useState(false);
|
||||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [testResult, setTestResult] = useState<{
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
} | null>(null);
|
||||||
const [testingGeminiConnection, setTestingGeminiConnection] = useState(false);
|
const [testingGeminiConnection, setTestingGeminiConnection] = useState(false);
|
||||||
const [geminiTestResult, setGeminiTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [geminiTestResult, setGeminiTestResult] = useState<{
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
} | null>(null);
|
||||||
const [claudeCliStatus, setClaudeCliStatus] = useState<{
|
const [claudeCliStatus, setClaudeCliStatus] = useState<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
status?: string;
|
status?: string;
|
||||||
@@ -72,12 +111,21 @@ export function SettingsView() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok && data.success) {
|
if (response.ok && data.success) {
|
||||||
setTestResult({ success: true, message: data.message || "Connection successful! Claude responded." });
|
setTestResult({
|
||||||
|
success: true,
|
||||||
|
message: data.message || "Connection successful! Claude responded.",
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setTestResult({ success: false, message: data.error || "Failed to connect to Claude API." });
|
setTestResult({
|
||||||
|
success: false,
|
||||||
|
message: data.error || "Failed to connect to Claude API.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setTestResult({ success: false, message: "Network error. Please check your connection." });
|
setTestResult({
|
||||||
|
success: false,
|
||||||
|
message: "Network error. Please check your connection.",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setTestingConnection(false);
|
setTestingConnection(false);
|
||||||
}
|
}
|
||||||
@@ -99,12 +147,21 @@ export function SettingsView() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok && data.success) {
|
if (response.ok && data.success) {
|
||||||
setGeminiTestResult({ success: true, message: data.message || "Connection successful! Gemini responded." });
|
setGeminiTestResult({
|
||||||
|
success: true,
|
||||||
|
message: data.message || "Connection successful! Gemini responded.",
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setGeminiTestResult({ success: false, message: data.error || "Failed to connect to Gemini API." });
|
setGeminiTestResult({
|
||||||
|
success: false,
|
||||||
|
message: data.error || "Failed to connect to Gemini API.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setGeminiTestResult({ success: false, message: "Network error. Please check your connection." });
|
setGeminiTestResult({
|
||||||
|
success: false,
|
||||||
|
message: "Network error. Please check your connection.",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setTestingGeminiConnection(false);
|
setTestingGeminiConnection(false);
|
||||||
}
|
}
|
||||||
@@ -120,17 +177,22 @@ export function SettingsView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col overflow-hidden content-bg" data-testid="settings-view">
|
<div
|
||||||
|
className="flex-1 flex flex-col overflow-hidden content-bg"
|
||||||
|
data-testid="settings-view"
|
||||||
|
>
|
||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div className="flex-shrink-0 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="shrink-0 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<div className="px-8 py-6">
|
<div className="px-8 py-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-brand-500 to-purple-600 shadow-lg shadow-brand-500/20 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-xl bg-linear-to-br from-brand-500 to-brand-600 shadow-lg shadow-brand-500/20 flex items-center justify-center">
|
||||||
<Settings className="w-5 h-5 text-white" />
|
<Settings className="w-5 h-5 text-primary-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-white">Settings</h1>
|
<h1 className="text-2xl font-bold text-foreground">Settings</h1>
|
||||||
<p className="text-sm text-zinc-400">Configure your API keys and preferences</p>
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Configure your API keys and preferences
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,25 +202,28 @@ export function SettingsView() {
|
|||||||
<div className="flex-1 overflow-y-auto p-8">
|
<div className="flex-1 overflow-y-auto p-8">
|
||||||
<div className="max-w-4xl mx-auto space-y-6">
|
<div className="max-w-4xl mx-auto space-y-6">
|
||||||
{/* API Keys Section */}
|
{/* API Keys Section */}
|
||||||
<div className="rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md overflow-hidden">
|
<div className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden">
|
||||||
<div className="p-6 border-b border-white/10">
|
<div className="p-6 border-b border-border">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Key className="w-5 h-5 text-brand-500" />
|
<Key className="w-5 h-5 text-brand-500" />
|
||||||
<h2 className="text-lg font-semibold text-white">API Keys</h2>
|
<h2 className="text-lg font-semibold text-foreground">
|
||||||
|
API Keys
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
Configure your AI provider API keys. Keys are stored locally in your browser.
|
Configure your AI provider API keys. Keys are stored locally in
|
||||||
|
your browser.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{/* Claude/Anthropic API Key */}
|
{/* Claude/Anthropic API Key */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label htmlFor="anthropic-key" className="text-zinc-300">
|
<Label htmlFor="anthropic-key" className="text-foreground">
|
||||||
Anthropic API Key (Claude)
|
Anthropic API Key (Claude)
|
||||||
</Label>
|
</Label>
|
||||||
{apiKeys.anthropic && (
|
{apiKeys.anthropic && (
|
||||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
<CheckCircle2 className="w-4 h-4 text-brand-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -169,14 +234,14 @@ export function SettingsView() {
|
|||||||
value={anthropicKey}
|
value={anthropicKey}
|
||||||
onChange={(e) => setAnthropicKey(e.target.value)}
|
onChange={(e) => setAnthropicKey(e.target.value)}
|
||||||
placeholder="sk-ant-..."
|
placeholder="sk-ant-..."
|
||||||
className="pr-10 bg-zinc-950/50 border-white/10 text-white placeholder:text-zinc-500"
|
className="pr-10 bg-input border-border text-foreground placeholder:text-muted-foreground"
|
||||||
data-testid="anthropic-api-key-input"
|
data-testid="anthropic-api-key-input"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute right-0 top-0 h-full px-3 text-zinc-400 hover:text-white hover:bg-transparent"
|
className="absolute right-0 top-0 h-full px-3 text-muted-foreground hover:text-foreground hover:bg-transparent"
|
||||||
onClick={() => setShowAnthropicKey(!showAnthropicKey)}
|
onClick={() => setShowAnthropicKey(!showAnthropicKey)}
|
||||||
data-testid="toggle-anthropic-visibility"
|
data-testid="toggle-anthropic-visibility"
|
||||||
>
|
>
|
||||||
@@ -192,7 +257,7 @@ export function SettingsView() {
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={handleTestConnection}
|
onClick={handleTestConnection}
|
||||||
disabled={!anthropicKey || testingConnection}
|
disabled={!anthropicKey || testingConnection}
|
||||||
className="bg-white/5 hover:bg-white/10 text-white border border-white/10"
|
className="bg-secondary hover:bg-accent text-secondary-foreground border border-border"
|
||||||
data-testid="test-claude-connection"
|
data-testid="test-claude-connection"
|
||||||
>
|
>
|
||||||
{testingConnection ? (
|
{testingConnection ? (
|
||||||
@@ -218,14 +283,15 @@ export function SettingsView() {
|
|||||||
>
|
>
|
||||||
console.anthropic.com
|
console.anthropic.com
|
||||||
</a>
|
</a>
|
||||||
. Alternatively, the CLAUDE_CODE_OAUTH_TOKEN environment variable can be used.
|
. Alternatively, the CLAUDE_CODE_OAUTH_TOKEN environment
|
||||||
|
variable can be used.
|
||||||
</p>
|
</p>
|
||||||
{testResult && (
|
{testResult && (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 p-3 rounded-lg ${
|
className={`flex items-center gap-2 p-3 rounded-lg ${
|
||||||
testResult.success
|
testResult.success
|
||||||
? 'bg-green-500/10 border border-green-500/20 text-green-400'
|
? "bg-green-500/10 border border-green-500/20 text-green-400"
|
||||||
: 'bg-red-500/10 border border-red-500/20 text-red-400'
|
: "bg-red-500/10 border border-red-500/20 text-red-400"
|
||||||
}`}
|
}`}
|
||||||
data-testid="test-connection-result"
|
data-testid="test-connection-result"
|
||||||
>
|
>
|
||||||
@@ -234,7 +300,12 @@ export function SettingsView() {
|
|||||||
) : (
|
) : (
|
||||||
<AlertCircle className="w-4 h-4" />
|
<AlertCircle className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm" data-testid="test-connection-message">{testResult.message}</span>
|
<span
|
||||||
|
className="text-sm"
|
||||||
|
data-testid="test-connection-message"
|
||||||
|
>
|
||||||
|
{testResult.message}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -242,11 +313,11 @@ export function SettingsView() {
|
|||||||
{/* Google API Key */}
|
{/* Google API Key */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label htmlFor="google-key" className="text-zinc-300">
|
<Label htmlFor="google-key" className="text-foreground">
|
||||||
Google API Key (Gemini)
|
Google API Key (Gemini)
|
||||||
</Label>
|
</Label>
|
||||||
{apiKeys.google && (
|
{apiKeys.google && (
|
||||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
<CheckCircle2 className="w-4 h-4 text-brand-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -257,14 +328,14 @@ export function SettingsView() {
|
|||||||
value={googleKey}
|
value={googleKey}
|
||||||
onChange={(e) => setGoogleKey(e.target.value)}
|
onChange={(e) => setGoogleKey(e.target.value)}
|
||||||
placeholder="AIza..."
|
placeholder="AIza..."
|
||||||
className="pr-10 bg-zinc-950/50 border-white/10 text-white placeholder:text-zinc-500"
|
className="pr-10 bg-input border-border text-foreground placeholder:text-muted-foreground"
|
||||||
data-testid="google-api-key-input"
|
data-testid="google-api-key-input"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute right-0 top-0 h-full px-3 text-zinc-400 hover:text-white hover:bg-transparent"
|
className="absolute right-0 top-0 h-full px-3 text-muted-foreground hover:text-foreground hover:bg-transparent"
|
||||||
onClick={() => setShowGoogleKey(!showGoogleKey)}
|
onClick={() => setShowGoogleKey(!showGoogleKey)}
|
||||||
data-testid="toggle-google-visibility"
|
data-testid="toggle-google-visibility"
|
||||||
>
|
>
|
||||||
@@ -280,7 +351,7 @@ export function SettingsView() {
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={handleTestGeminiConnection}
|
onClick={handleTestGeminiConnection}
|
||||||
disabled={!googleKey || testingGeminiConnection}
|
disabled={!googleKey || testingGeminiConnection}
|
||||||
className="bg-white/5 hover:bg-white/10 text-white border border-white/10"
|
className="bg-secondary hover:bg-accent text-secondary-foreground border border-border"
|
||||||
data-testid="test-gemini-connection"
|
data-testid="test-gemini-connection"
|
||||||
>
|
>
|
||||||
{testingGeminiConnection ? (
|
{testingGeminiConnection ? (
|
||||||
@@ -297,7 +368,8 @@ export function SettingsView() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-zinc-500">
|
<p className="text-xs text-zinc-500">
|
||||||
Used for Gemini AI features (including image/design prompts). Get your key at{" "}
|
Used for Gemini AI features (including image/design prompts).
|
||||||
|
Get your key at{" "}
|
||||||
<a
|
<a
|
||||||
href="https://makersuite.google.com/app/apikey"
|
href="https://makersuite.google.com/app/apikey"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -311,8 +383,8 @@ export function SettingsView() {
|
|||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 p-3 rounded-lg ${
|
className={`flex items-center gap-2 p-3 rounded-lg ${
|
||||||
geminiTestResult.success
|
geminiTestResult.success
|
||||||
? 'bg-green-500/10 border border-green-500/20 text-green-400'
|
? "bg-green-500/10 border border-green-500/20 text-green-400"
|
||||||
: 'bg-red-500/10 border border-red-500/20 text-red-400'
|
: "bg-red-500/10 border border-red-500/20 text-red-400"
|
||||||
}`}
|
}`}
|
||||||
data-testid="gemini-test-connection-result"
|
data-testid="gemini-test-connection-result"
|
||||||
>
|
>
|
||||||
@@ -321,7 +393,12 @@ export function SettingsView() {
|
|||||||
) : (
|
) : (
|
||||||
<AlertCircle className="w-4 h-4" />
|
<AlertCircle className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm" data-testid="gemini-test-connection-message">{geminiTestResult.message}</span>
|
<span
|
||||||
|
className="text-sm"
|
||||||
|
data-testid="gemini-test-connection-message"
|
||||||
|
>
|
||||||
|
{geminiTestResult.message}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -332,8 +409,8 @@ export function SettingsView() {
|
|||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<p className="font-medium text-yellow-500">Security Notice</p>
|
<p className="font-medium text-yellow-500">Security Notice</p>
|
||||||
<p className="text-yellow-500/80 text-xs mt-1">
|
<p className="text-yellow-500/80 text-xs mt-1">
|
||||||
API keys are stored in your browser's local storage. Never share your API keys
|
API keys are stored in your browser's local storage. Never
|
||||||
or commit them to version control.
|
share your API keys or commit them to version control.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -421,43 +498,165 @@ export function SettingsView() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Appearance Section */}
|
{/* Appearance Section */}
|
||||||
<div className="rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md overflow-hidden">
|
<div className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden">
|
||||||
<div className="p-6 border-b border-white/10">
|
<div className="p-6 border-b border-border">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Palette className="w-5 h-5 text-brand-500" />
|
<Palette className="w-5 h-5 text-brand-500" />
|
||||||
<h2 className="text-lg font-semibold text-white">Appearance</h2>
|
<h2 className="text-lg font-semibold text-foreground">
|
||||||
|
Appearance
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
Customize the look and feel of your application.
|
Customize the look and feel of your application.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Label className="text-zinc-300">Theme</Label>
|
<Label className="text-foreground">Theme</Label>
|
||||||
<div className="flex gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setTheme("dark")}
|
onClick={() => setTheme("dark")}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-lg border transition-all ${
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
theme === "dark"
|
theme === "dark"
|
||||||
? "bg-white/5 border-brand-500 text-white"
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
: "bg-zinc-950/50 border-white/10 text-zinc-400 hover:text-white hover:bg-white/5"
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
data-testid="dark-mode-button"
|
data-testid="dark-mode-button"
|
||||||
>
|
>
|
||||||
<Moon className="w-4 h-4" />
|
<Moon className="w-4 h-4" />
|
||||||
<span className="font-medium text-sm">Dark Mode</span>
|
<span className="font-medium text-sm">Dark</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setTheme("light")}
|
onClick={() => setTheme("light")}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-lg border transition-all ${
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
theme === "light"
|
theme === "light"
|
||||||
? "bg-white/5 border-brand-500 text-white"
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
: "bg-zinc-950/50 border-white/10 text-zinc-400 hover:text-white hover:bg-white/5"
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
data-testid="light-mode-button"
|
data-testid="light-mode-button"
|
||||||
>
|
>
|
||||||
<Sun className="w-4 h-4" />
|
<Sun className="w-4 h-4" />
|
||||||
<span className="font-medium text-sm">Light Mode</span>
|
<span className="font-medium text-sm">Light</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("retro")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "retro"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="retro-mode-button"
|
||||||
|
>
|
||||||
|
<Terminal className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Retro</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("dracula")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "dracula"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="dracula-mode-button"
|
||||||
|
>
|
||||||
|
<Ghost className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Dracula</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("nord")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "nord"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="nord-mode-button"
|
||||||
|
>
|
||||||
|
<Snowflake className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Nord</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("monokai")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "monokai"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="monokai-mode-button"
|
||||||
|
>
|
||||||
|
<Flame className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Monokai</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("tokyonight")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "tokyonight"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="tokyonight-mode-button"
|
||||||
|
>
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Tokyo Night</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("solarized")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "solarized"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="solarized-mode-button"
|
||||||
|
>
|
||||||
|
<Eclipse className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Solarized</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("gruvbox")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "gruvbox"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="gruvbox-mode-button"
|
||||||
|
>
|
||||||
|
<Trees className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Gruvbox</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("catppuccin")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "catppuccin"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="catppuccin-mode-button"
|
||||||
|
>
|
||||||
|
<Cat className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Catppuccin</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("onedark")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "onedark"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="onedark-mode-button"
|
||||||
|
>
|
||||||
|
<Atom className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">One Dark</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme("synthwave")}
|
||||||
|
className={`flex items-center justify-center gap-2 px-3 py-3 rounded-lg border transition-all ${
|
||||||
|
theme === "synthwave"
|
||||||
|
? "bg-accent border-brand-500 text-foreground"
|
||||||
|
: "bg-input border-border text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
data-testid="synthwave-mode-button"
|
||||||
|
>
|
||||||
|
<Radio className="w-4 h-4" />
|
||||||
|
<span className="font-medium text-sm">Synthwave</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -469,7 +668,9 @@ export function SettingsView() {
|
|||||||
<div className="p-6 border-b border-white/10">
|
<div className="p-6 border-b border-white/10">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<LayoutGrid className="w-5 h-5 text-brand-500" />
|
<LayoutGrid className="w-5 h-5 text-brand-500" />
|
||||||
<h2 className="text-lg font-semibold text-white">Kanban Card Display</h2>
|
<h2 className="text-lg font-semibold text-white">
|
||||||
|
Kanban Card Display
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-zinc-400">
|
||||||
Control how much information is displayed on Kanban cards.
|
Control how much information is displayed on Kanban cards.
|
||||||
@@ -490,7 +691,9 @@ export function SettingsView() {
|
|||||||
>
|
>
|
||||||
<Minimize2 className="w-5 h-5" />
|
<Minimize2 className="w-5 h-5" />
|
||||||
<span className="font-medium text-sm">Minimal</span>
|
<span className="font-medium text-sm">Minimal</span>
|
||||||
<span className="text-xs text-zinc-500 text-center">Title & category only</span>
|
<span className="text-xs text-zinc-500 text-center">
|
||||||
|
Title & category only
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setKanbanCardDetailLevel("standard")}
|
onClick={() => setKanbanCardDetailLevel("standard")}
|
||||||
@@ -503,7 +706,9 @@ export function SettingsView() {
|
|||||||
>
|
>
|
||||||
<Square className="w-5 h-5" />
|
<Square className="w-5 h-5" />
|
||||||
<span className="font-medium text-sm">Standard</span>
|
<span className="font-medium text-sm">Standard</span>
|
||||||
<span className="text-xs text-zinc-500 text-center">Steps & progress</span>
|
<span className="text-xs text-zinc-500 text-center">
|
||||||
|
Steps & progress
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setKanbanCardDetailLevel("detailed")}
|
onClick={() => setKanbanCardDetailLevel("detailed")}
|
||||||
@@ -516,13 +721,18 @@ export function SettingsView() {
|
|||||||
>
|
>
|
||||||
<Maximize2 className="w-5 h-5" />
|
<Maximize2 className="w-5 h-5" />
|
||||||
<span className="font-medium text-sm">Detailed</span>
|
<span className="font-medium text-sm">Detailed</span>
|
||||||
<span className="text-xs text-zinc-500 text-center">Model, tools & tasks</span>
|
<span className="text-xs text-zinc-500 text-center">
|
||||||
|
Model, tools & tasks
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-zinc-500">
|
<p className="text-xs text-zinc-500">
|
||||||
<strong>Minimal:</strong> Shows only title and category<br />
|
<strong>Minimal:</strong> Shows only title and category
|
||||||
<strong>Standard:</strong> Adds steps preview and progress bar<br />
|
<br />
|
||||||
<strong>Detailed:</strong> Shows all info including model, tool calls, task list, and summaries
|
<strong>Standard:</strong> Adds steps preview and progress bar
|
||||||
|
<br />
|
||||||
|
<strong>Detailed:</strong> Shows all info including model,
|
||||||
|
tool calls, task list, and summaries
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -533,7 +743,7 @@ export function SettingsView() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
data-testid="save-settings"
|
data-testid="save-settings"
|
||||||
className="min-w-[120px] bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
|
className="min-w-[120px] bg-linear-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-primary-foreground border-0"
|
||||||
>
|
>
|
||||||
{saved ? (
|
{saved ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ export function SpecView() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.readFile(`${currentProject.path}/.automaker/app_spec.txt`);
|
const result = await api.readFile(
|
||||||
|
`${currentProject.path}/.automaker/app_spec.txt`
|
||||||
|
);
|
||||||
|
|
||||||
if (result.success && result.content) {
|
if (result.success && result.content) {
|
||||||
setAppSpec(result.content);
|
setAppSpec(result.content);
|
||||||
@@ -44,7 +46,10 @@ export function SpecView() {
|
|||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
await api.writeFile(`${currentProject.path}/.automaker/app_spec.txt`, appSpec);
|
await api.writeFile(
|
||||||
|
`${currentProject.path}/.automaker/app_spec.txt`,
|
||||||
|
appSpec
|
||||||
|
);
|
||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save spec:", error);
|
console.error("Failed to save spec:", error);
|
||||||
@@ -86,7 +91,7 @@ export function SpecView() {
|
|||||||
data-testid="spec-view"
|
data-testid="spec-view"
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="flex items-center justify-between p-4 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<FileText className="w-5 h-5 text-muted-foreground" />
|
<FileText className="w-5 h-5 text-muted-foreground" />
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -91,59 +91,65 @@ export function WelcomeView() {
|
|||||||
/**
|
/**
|
||||||
* Initialize project and optionally kick off project analysis agent
|
* Initialize project and optionally kick off project analysis agent
|
||||||
*/
|
*/
|
||||||
const initializeAndOpenProject = useCallback(async (path: string, name: string) => {
|
const initializeAndOpenProject = useCallback(
|
||||||
setIsOpening(true);
|
async (path: string, name: string) => {
|
||||||
try {
|
setIsOpening(true);
|
||||||
// Initialize the .automaker directory structure
|
try {
|
||||||
const initResult = await initializeProject(path);
|
// Initialize the .automaker directory structure
|
||||||
|
const initResult = await initializeProject(path);
|
||||||
|
|
||||||
if (!initResult.success) {
|
if (!initResult.success) {
|
||||||
toast.error("Failed to initialize project", {
|
toast.error("Failed to initialize project", {
|
||||||
description: initResult.error || "Unknown error occurred",
|
description: initResult.error || "Unknown error occurred",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = {
|
||||||
|
id: `project-${Date.now()}`,
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
lastOpened: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
addProject(project);
|
||||||
|
setCurrentProject(project);
|
||||||
|
|
||||||
|
// Show initialization dialog if files were created
|
||||||
|
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
|
||||||
|
setInitStatus({
|
||||||
|
isNewProject: initResult.isNewProject,
|
||||||
|
createdFiles: initResult.createdFiles,
|
||||||
|
projectName: name,
|
||||||
|
projectPath: path,
|
||||||
|
});
|
||||||
|
setShowInitDialog(true);
|
||||||
|
|
||||||
|
// Kick off agent to analyze the project and update app_spec.txt
|
||||||
|
console.log(
|
||||||
|
"[Welcome] Project initialized, created files:",
|
||||||
|
initResult.createdFiles
|
||||||
|
);
|
||||||
|
console.log("[Welcome] Kicking off project analysis agent...");
|
||||||
|
|
||||||
|
// Start analysis in background (don't await, let it run async)
|
||||||
|
analyzeProject(path);
|
||||||
|
} else {
|
||||||
|
toast.success("Project opened", {
|
||||||
|
description: `Opened ${name}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Welcome] Failed to open project:", error);
|
||||||
|
toast.error("Failed to open project", {
|
||||||
|
description: error instanceof Error ? error.message : "Unknown error",
|
||||||
});
|
});
|
||||||
return;
|
} finally {
|
||||||
|
setIsOpening(false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const project = {
|
[addProject, setCurrentProject, analyzeProject]
|
||||||
id: `project-${Date.now()}`,
|
);
|
||||||
name,
|
|
||||||
path,
|
|
||||||
lastOpened: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
addProject(project);
|
|
||||||
setCurrentProject(project);
|
|
||||||
|
|
||||||
// Show initialization dialog if files were created
|
|
||||||
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
|
|
||||||
setInitStatus({
|
|
||||||
isNewProject: initResult.isNewProject,
|
|
||||||
createdFiles: initResult.createdFiles,
|
|
||||||
projectName: name,
|
|
||||||
projectPath: path,
|
|
||||||
});
|
|
||||||
setShowInitDialog(true);
|
|
||||||
|
|
||||||
// Kick off agent to analyze the project and update app_spec.txt
|
|
||||||
console.log("[Welcome] Project initialized, created files:", initResult.createdFiles);
|
|
||||||
console.log("[Welcome] Kicking off project analysis agent...");
|
|
||||||
|
|
||||||
// Start analysis in background (don't await, let it run async)
|
|
||||||
analyzeProject(path);
|
|
||||||
} else {
|
|
||||||
toast.success("Project opened", {
|
|
||||||
description: `Opened ${name}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[Welcome] Failed to open project:", error);
|
|
||||||
toast.error("Failed to open project", {
|
|
||||||
description: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsOpening(false);
|
|
||||||
}
|
|
||||||
}, [addProject, setCurrentProject, analyzeProject]);
|
|
||||||
|
|
||||||
const handleOpenProject = useCallback(async () => {
|
const handleOpenProject = useCallback(async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
@@ -159,9 +165,12 @@ export function WelcomeView() {
|
|||||||
/**
|
/**
|
||||||
* Handle clicking on a recent project
|
* Handle clicking on a recent project
|
||||||
*/
|
*/
|
||||||
const handleRecentProjectClick = useCallback(async (project: { id: string; name: string; path: string }) => {
|
const handleRecentProjectClick = useCallback(
|
||||||
await initializeAndOpenProject(project.path, project.name);
|
async (project: { id: string; name: string; path: string }) => {
|
||||||
}, [initializeAndOpenProject]);
|
await initializeAndOpenProject(project.path, project.name);
|
||||||
|
},
|
||||||
|
[initializeAndOpenProject]
|
||||||
|
);
|
||||||
|
|
||||||
const handleNewProject = () => {
|
const handleNewProject = () => {
|
||||||
setNewProjectName("");
|
setNewProjectName("");
|
||||||
@@ -272,17 +281,17 @@ export function WelcomeView() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col content-bg" data-testid="welcome-view">
|
<div className="flex-1 flex flex-col content-bg" data-testid="welcome-view">
|
||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div className="flex-shrink-0 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
|
<div className="flex-shrink-0 border-b border-border bg-glass backdrop-blur-md">
|
||||||
<div className="px-8 py-6">
|
<div className="px-8 py-6">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-brand-500 to-purple-600 shadow-lg shadow-brand-500/20 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-xl bg-linear-to-br from-brand-500 to-brand-600 shadow-lg shadow-brand-500/20 flex items-center justify-center">
|
||||||
<Cpu className="w-5 h-5 text-white" />
|
<Cpu className="w-5 h-5 text-primary-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-white">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
Welcome to Automaker
|
Welcome to Automaker
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
Your autonomous AI development studio
|
Your autonomous AI development studio
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -296,20 +305,20 @@ export function WelcomeView() {
|
|||||||
{/* Quick Actions */}
|
{/* Quick Actions */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
|
||||||
<div
|
<div
|
||||||
className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md hover:bg-zinc-900/70 hover:border-white/20 transition-all duration-200"
|
className="group relative overflow-hidden rounded-xl border border-border bg-card backdrop-blur-md hover:bg-card/70 hover:border-border-glass transition-all duration-200"
|
||||||
data-testid="new-project-card"
|
data-testid="new-project-card"
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/5 to-purple-600/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/5 to-purple-600/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
<div className="relative p-6">
|
<div className="relative p-6">
|
||||||
<div className="flex items-start gap-4 mb-4">
|
<div className="flex items-start gap-4 mb-4">
|
||||||
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-brand-500 to-purple-600 shadow-lg shadow-brand-500/20 flex items-center justify-center group-hover:scale-110 transition-transform">
|
<div className="w-12 h-12 rounded-lg bg-linear-to-br from-brand-500 to-brand-600 shadow-lg shadow-brand-500/20 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||||
<Plus className="w-6 h-6 text-white" />
|
<Plus className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-white mb-1">
|
<h3 className="text-lg font-semibold text-foreground mb-1">
|
||||||
New Project
|
New Project
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
Create a new project from scratch with AI-powered
|
Create a new project from scratch with AI-powered
|
||||||
development
|
development
|
||||||
</p>
|
</p>
|
||||||
@@ -318,7 +327,7 @@ export function WelcomeView() {
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
|
className="w-full bg-linear-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-primary-foreground border-0"
|
||||||
data-testid="create-new-project"
|
data-testid="create-new-project"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
@@ -347,28 +356,28 @@ export function WelcomeView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md hover:bg-zinc-900/70 hover:border-white/20 transition-all duration-200 cursor-pointer"
|
className="group relative overflow-hidden rounded-xl border border-border bg-card backdrop-blur-md hover:bg-card/70 hover:border-border-glass transition-all duration-200 cursor-pointer"
|
||||||
onClick={handleOpenProject}
|
onClick={handleOpenProject}
|
||||||
data-testid="open-project-card"
|
data-testid="open-project-card"
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-cyan-600/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-cyan-600/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
<div className="relative p-6">
|
<div className="relative p-6">
|
||||||
<div className="flex items-start gap-4 mb-4">
|
<div className="flex items-start gap-4 mb-4">
|
||||||
<div className="w-12 h-12 rounded-lg bg-zinc-800 border border-white/10 flex items-center justify-center group-hover:scale-110 transition-transform">
|
<div className="w-12 h-12 rounded-lg bg-muted border border-border flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||||
<FolderOpen className="w-6 h-6 text-zinc-400 group-hover:text-white transition-colors" />
|
<FolderOpen className="w-6 h-6 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-white mb-1">
|
<h3 className="text-lg font-semibold text-foreground mb-1">
|
||||||
Open Project
|
Open Project
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
Open an existing project folder to continue working
|
Open an existing project folder to continue working
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="w-full bg-white/5 hover:bg-white/10 text-white border border-white/10 hover:border-white/20"
|
className="w-full bg-secondary hover:bg-secondary/80 text-foreground border border-border hover:border-border-glass"
|
||||||
data-testid="open-existing-project"
|
data-testid="open-existing-project"
|
||||||
>
|
>
|
||||||
<FolderOpen className="w-4 h-4 mr-2" />
|
<FolderOpen className="w-4 h-4 mr-2" />
|
||||||
@@ -382,8 +391,8 @@ export function WelcomeView() {
|
|||||||
{recentProjects.length > 0 && (
|
{recentProjects.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Clock className="w-5 h-5 text-zinc-400" />
|
<Clock className="w-5 h-5 text-muted-foreground" />
|
||||||
<h2 className="text-lg font-semibold text-white">
|
<h2 className="text-lg font-semibold text-foreground">
|
||||||
Recent Projects
|
Recent Projects
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -391,25 +400,25 @@ export function WelcomeView() {
|
|||||||
{recentProjects.map((project) => (
|
{recentProjects.map((project) => (
|
||||||
<div
|
<div
|
||||||
key={project.id}
|
key={project.id}
|
||||||
className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md hover:bg-zinc-900/70 hover:border-brand-500/50 transition-all duration-200 cursor-pointer"
|
className="group relative overflow-hidden rounded-xl border border-border bg-card backdrop-blur-md hover:bg-card/70 hover:border-brand-500/50 transition-all duration-200 cursor-pointer"
|
||||||
onClick={() => handleRecentProjectClick(project)}
|
onClick={() => handleRecentProjectClick(project)}
|
||||||
data-testid={`recent-project-${project.id}`}
|
data-testid={`recent-project-${project.id}`}
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-purple-600/0 group-hover:from-brand-500/5 group-hover:to-purple-600/5 transition-all"></div>
|
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-purple-600/0 group-hover:from-brand-500/5 group-hover:to-purple-600/5 transition-all"></div>
|
||||||
<div className="relative p-4">
|
<div className="relative p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="w-10 h-10 rounded-lg bg-zinc-800 border border-white/10 flex items-center justify-center group-hover:border-brand-500/50 transition-colors">
|
<div className="w-10 h-10 rounded-lg bg-muted border border-border flex items-center justify-center group-hover:border-brand-500/50 transition-colors">
|
||||||
<Folder className="w-5 h-5 text-zinc-400 group-hover:text-brand-500 transition-colors" />
|
<Folder className="w-5 h-5 text-muted-foreground group-hover:text-brand-500 transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="font-medium text-white truncate group-hover:text-brand-500 transition-colors">
|
<p className="font-medium text-foreground truncate group-hover:text-brand-500 transition-colors">
|
||||||
{project.name}
|
{project.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-zinc-500 truncate mt-0.5">
|
<p className="text-xs text-muted-foreground/70 truncate mt-0.5">
|
||||||
{project.path}
|
{project.path}
|
||||||
</p>
|
</p>
|
||||||
{project.lastOpened && (
|
{project.lastOpened && (
|
||||||
<p className="text-xs text-zinc-600 mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
{new Date(
|
{new Date(
|
||||||
project.lastOpened
|
project.lastOpened
|
||||||
).toLocaleDateString()}
|
).toLocaleDateString()}
|
||||||
@@ -427,10 +436,10 @@ export function WelcomeView() {
|
|||||||
{/* Empty State for No Projects */}
|
{/* Empty State for No Projects */}
|
||||||
{recentProjects.length === 0 && (
|
{recentProjects.length === 0 && (
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
<div className="w-16 h-16 rounded-2xl bg-zinc-900/50 border border-white/10 flex items-center justify-center mb-4">
|
<div className="w-16 h-16 rounded-2xl bg-muted border border-border flex items-center justify-center mb-4">
|
||||||
<Sparkles className="w-8 h-8 text-zinc-600" />
|
<Sparkles className="w-8 h-8 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-white mb-2">
|
<h3 className="text-lg font-semibold text-foreground mb-2">
|
||||||
No projects yet
|
No projects yet
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-zinc-400 max-w-md">
|
<p className="text-sm text-zinc-400 max-w-md">
|
||||||
@@ -447,18 +456,20 @@ export function WelcomeView() {
|
|||||||
onOpenChange={setShowNewProjectDialog}
|
onOpenChange={setShowNewProjectDialog}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="bg-zinc-900 border-white/10"
|
className="bg-card border-border"
|
||||||
data-testid="new-project-dialog"
|
data-testid="new-project-dialog"
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-white">Create New Project</DialogTitle>
|
<DialogTitle className="text-foreground">
|
||||||
<DialogDescription className="text-zinc-400">
|
Create New Project
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="text-muted-foreground">
|
||||||
Set up a new project directory with initial configuration files.
|
Set up a new project directory with initial configuration files.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="project-name" className="text-zinc-300">
|
<Label htmlFor="project-name" className="text-foreground">
|
||||||
Project Name
|
Project Name
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -466,12 +477,12 @@ export function WelcomeView() {
|
|||||||
placeholder="my-awesome-project"
|
placeholder="my-awesome-project"
|
||||||
value={newProjectName}
|
value={newProjectName}
|
||||||
onChange={(e) => setNewProjectName(e.target.value)}
|
onChange={(e) => setNewProjectName(e.target.value)}
|
||||||
className="bg-zinc-950/50 border-white/10 text-white placeholder:text-zinc-500"
|
className="bg-input border-border text-foreground placeholder:text-muted-foreground"
|
||||||
data-testid="project-name-input"
|
data-testid="project-name-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="project-path" className="text-zinc-300">
|
<Label htmlFor="project-path" className="text-foreground">
|
||||||
Parent Directory
|
Parent Directory
|
||||||
</Label>
|
</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -480,13 +491,13 @@ export function WelcomeView() {
|
|||||||
placeholder="/path/to/projects"
|
placeholder="/path/to/projects"
|
||||||
value={newProjectPath}
|
value={newProjectPath}
|
||||||
onChange={(e) => setNewProjectPath(e.target.value)}
|
onChange={(e) => setNewProjectPath(e.target.value)}
|
||||||
className="flex-1 bg-zinc-950/50 border-white/10 text-white placeholder:text-zinc-500"
|
className="flex-1 bg-input border-border text-foreground placeholder:text-muted-foreground"
|
||||||
data-testid="project-path-input"
|
data-testid="project-path-input"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={handleSelectDirectory}
|
onClick={handleSelectDirectory}
|
||||||
className="bg-white/5 hover:bg-white/10 text-white border border-white/10"
|
className="bg-secondary hover:bg-secondary/80 text-foreground border border-border"
|
||||||
data-testid="browse-directory"
|
data-testid="browse-directory"
|
||||||
>
|
>
|
||||||
Browse
|
Browse
|
||||||
@@ -498,14 +509,14 @@ export function WelcomeView() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setShowNewProjectDialog(false)}
|
onClick={() => setShowNewProjectDialog(false)}
|
||||||
className="text-zinc-400 hover:text-white hover:bg-white/5"
|
className="text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateProject}
|
onClick={handleCreateProject}
|
||||||
disabled={!newProjectName || !newProjectPath || isCreating}
|
disabled={!newProjectName || !newProjectPath || isCreating}
|
||||||
className="bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
|
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
|
||||||
data-testid="confirm-create-project"
|
data-testid="confirm-create-project"
|
||||||
>
|
>
|
||||||
{isCreating ? "Creating..." : "Create Project"}
|
{isCreating ? "Creating..." : "Create Project"}
|
||||||
@@ -517,15 +528,17 @@ export function WelcomeView() {
|
|||||||
{/* Project Initialization Dialog */}
|
{/* Project Initialization Dialog */}
|
||||||
<Dialog open={showInitDialog} onOpenChange={setShowInitDialog}>
|
<Dialog open={showInitDialog} onOpenChange={setShowInitDialog}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="bg-zinc-900 border-white/10"
|
className="bg-card border-border"
|
||||||
data-testid="project-init-dialog"
|
data-testid="project-init-dialog"
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-white flex items-center gap-2">
|
<DialogTitle className="text-foreground flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-brand-500" />
|
<Sparkles className="w-5 h-5 text-brand-500" />
|
||||||
{initStatus?.isNewProject ? "Project Initialized" : "Project Updated"}
|
{initStatus?.isNewProject
|
||||||
|
? "Project Initialized"
|
||||||
|
: "Project Updated"}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-zinc-400">
|
<DialogDescription className="text-muted-foreground">
|
||||||
{initStatus?.isNewProject
|
{initStatus?.isNewProject
|
||||||
? `Created .automaker directory structure for ${initStatus?.projectName}`
|
? `Created .automaker directory structure for ${initStatus?.projectName}`
|
||||||
: `Updated missing files in .automaker for ${initStatus?.projectName}`}
|
: `Updated missing files in .automaker for ${initStatus?.projectName}`}
|
||||||
@@ -533,15 +546,17 @@ export function WelcomeView() {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="text-sm text-zinc-300 font-medium">Created files:</p>
|
<p className="text-sm text-foreground font-medium">
|
||||||
|
Created files:
|
||||||
|
</p>
|
||||||
<ul className="space-y-1.5">
|
<ul className="space-y-1.5">
|
||||||
{initStatus?.createdFiles.map((file) => (
|
{initStatus?.createdFiles.map((file) => (
|
||||||
<li
|
<li
|
||||||
key={file}
|
key={file}
|
||||||
className="flex items-center gap-2 text-sm text-zinc-400"
|
className="flex items-center gap-2 text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
||||||
<code className="text-xs bg-zinc-800 px-2 py-0.5 rounded">
|
<code className="text-xs bg-muted px-2 py-0.5 rounded">
|
||||||
{file}
|
{file}
|
||||||
</code>
|
</code>
|
||||||
</li>
|
</li>
|
||||||
@@ -550,7 +565,7 @@ export function WelcomeView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{initStatus?.isNewProject && (
|
{initStatus?.isNewProject && (
|
||||||
<div className="mt-4 p-3 rounded-lg bg-zinc-800/50 border border-white/5">
|
<div className="mt-4 p-3 rounded-lg bg-muted/50 border border-border-glass">
|
||||||
{isAnalyzing ? (
|
{isAnalyzing ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Loader2 className="w-4 h-4 text-brand-500 animate-spin" />
|
<Loader2 className="w-4 h-4 text-brand-500 animate-spin" />
|
||||||
@@ -559,9 +574,9 @@ export function WelcomeView() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-muted-foreground">
|
||||||
<span className="text-brand-400">Tip:</span> Edit the{" "}
|
<span className="text-brand-400">Tip:</span> Edit the{" "}
|
||||||
<code className="text-xs bg-zinc-800 px-1.5 py-0.5 rounded">
|
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
|
||||||
app_spec.txt
|
app_spec.txt
|
||||||
</code>{" "}
|
</code>{" "}
|
||||||
file to describe your project. The AI agent will use this to
|
file to describe your project. The AI agent will use this to
|
||||||
@@ -574,7 +589,7 @@ export function WelcomeView() {
|
|||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowInitDialog(false)}
|
onClick={() => setShowInitDialog(false)}
|
||||||
className="bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
|
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
|
||||||
data-testid="close-init-dialog"
|
data-testid="close-init-dialog"
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
@@ -586,12 +601,14 @@ export function WelcomeView() {
|
|||||||
{/* Loading overlay when opening project */}
|
{/* Loading overlay when opening project */}
|
||||||
{isOpening && (
|
{isOpening && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm"
|
||||||
data-testid="project-opening-overlay"
|
data-testid="project-opening-overlay"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center gap-3 p-6 rounded-xl bg-zinc-900 border border-white/10">
|
<div className="flex flex-col items-center gap-3 p-6 rounded-xl bg-card border border-border">
|
||||||
<Loader2 className="w-8 h-8 text-brand-500 animate-spin" />
|
<Loader2 className="w-8 h-8 text-brand-500 animate-spin" />
|
||||||
<p className="text-white font-medium">Initializing project...</p>
|
<p className="text-foreground font-medium">
|
||||||
|
Initializing project...
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ export function useAutoMode() {
|
|||||||
|
|
||||||
case "auto_mode_phase":
|
case "auto_mode_phase":
|
||||||
// Log phase transitions (Planning, Action, Verification)
|
// Log phase transitions (Planning, Action, Verification)
|
||||||
console.log(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
|
console.log(
|
||||||
|
`[AutoMode] Phase: ${event.phase} for ${event.featureId}`
|
||||||
|
);
|
||||||
addAutoModeActivity({
|
addAutoModeActivity({
|
||||||
featureId: event.featureId,
|
featureId: event.featureId,
|
||||||
type: event.phase,
|
type: event.phase,
|
||||||
@@ -125,7 +127,13 @@ export function useAutoMode() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [addRunningTask, removeRunningTask, clearRunningTasks, setAutoModeRunning, addAutoModeActivity]);
|
}, [
|
||||||
|
addRunningTask,
|
||||||
|
removeRunningTask,
|
||||||
|
clearRunningTasks,
|
||||||
|
setAutoModeRunning,
|
||||||
|
addAutoModeActivity,
|
||||||
|
]);
|
||||||
|
|
||||||
// Start auto mode
|
// Start auto mode
|
||||||
const start = useCallback(async () => {
|
const start = useCallback(async () => {
|
||||||
@@ -181,33 +189,36 @@ export function useAutoMode() {
|
|||||||
}, [setAutoModeRunning, clearRunningTasks]);
|
}, [setAutoModeRunning, clearRunningTasks]);
|
||||||
|
|
||||||
// Stop a specific feature
|
// Stop a specific feature
|
||||||
const stopFeature = useCallback(async (featureId: string) => {
|
const stopFeature = useCallback(
|
||||||
try {
|
async (featureId: string) => {
|
||||||
const api = getElectronAPI();
|
try {
|
||||||
if (!api?.autoMode?.stopFeature) {
|
const api = getElectronAPI();
|
||||||
throw new Error("Stop feature API not available");
|
if (!api?.autoMode?.stopFeature) {
|
||||||
}
|
throw new Error("Stop feature API not available");
|
||||||
|
}
|
||||||
|
|
||||||
const result = await api.autoMode.stopFeature(featureId);
|
const result = await api.autoMode.stopFeature(featureId);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
removeRunningTask(featureId);
|
removeRunningTask(featureId);
|
||||||
console.log("[AutoMode] Feature stopped successfully:", featureId);
|
console.log("[AutoMode] Feature stopped successfully:", featureId);
|
||||||
addAutoModeActivity({
|
addAutoModeActivity({
|
||||||
featureId,
|
featureId,
|
||||||
type: "complete",
|
type: "complete",
|
||||||
message: "Feature stopped by user",
|
message: "Feature stopped by user",
|
||||||
passes: false,
|
passes: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("[AutoMode] Failed to stop feature:", result.error);
|
console.error("[AutoMode] Failed to stop feature:", result.error);
|
||||||
throw new Error(result.error || "Failed to stop feature");
|
throw new Error(result.error || "Failed to stop feature");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[AutoMode] Error stopping feature:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.error("[AutoMode] Error stopping feature:", error);
|
[removeRunningTask, addAutoModeActivity]
|
||||||
throw error;
|
);
|
||||||
}
|
|
||||||
}, [removeRunningTask, addAutoModeActivity]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isRunning: isAutoModeRunning,
|
isRunning: isAutoModeRunning,
|
||||||
|
|||||||
@@ -2,8 +2,31 @@ import { create } from "zustand";
|
|||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import type { Project } from "@/lib/electron";
|
import type { Project } from "@/lib/electron";
|
||||||
|
|
||||||
export type ViewMode = "welcome" | "spec" | "board" | "agent" | "settings" | "tools" | "interview" | "context";
|
export type ViewMode =
|
||||||
export type ThemeMode = "light" | "dark" | "system";
|
| "welcome"
|
||||||
|
| "spec"
|
||||||
|
| "board"
|
||||||
|
| "agent"
|
||||||
|
| "settings"
|
||||||
|
| "tools"
|
||||||
|
| "interview"
|
||||||
|
| "context";
|
||||||
|
|
||||||
|
export type ThemeMode =
|
||||||
|
| "light"
|
||||||
|
| "dark"
|
||||||
|
| "system"
|
||||||
|
| "retro"
|
||||||
|
| "dracula"
|
||||||
|
| "nord"
|
||||||
|
| "monokai"
|
||||||
|
| "tokyonight"
|
||||||
|
| "solarized"
|
||||||
|
| "gruvbox"
|
||||||
|
| "catppuccin"
|
||||||
|
| "onedark"
|
||||||
|
| "synthwave";
|
||||||
|
|
||||||
export type KanbanCardDetailLevel = "minimal" | "standard" | "detailed";
|
export type KanbanCardDetailLevel = "minimal" | "standard" | "detailed";
|
||||||
|
|
||||||
export interface ApiKeys {
|
export interface ApiKeys {
|
||||||
@@ -116,7 +139,15 @@ export interface AutoModeActivity {
|
|||||||
id: string;
|
id: string;
|
||||||
featureId: string;
|
featureId: string;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
type: "start" | "progress" | "tool" | "complete" | "error" | "planning" | "action" | "verification";
|
type:
|
||||||
|
| "start"
|
||||||
|
| "progress"
|
||||||
|
| "tool"
|
||||||
|
| "complete"
|
||||||
|
| "error"
|
||||||
|
| "planning"
|
||||||
|
| "action"
|
||||||
|
| "verification";
|
||||||
message: string;
|
message: string;
|
||||||
tool?: string;
|
tool?: string;
|
||||||
passes?: boolean;
|
passes?: boolean;
|
||||||
@@ -170,7 +201,9 @@ export interface AppActions {
|
|||||||
addRunningTask: (taskId: string) => void;
|
addRunningTask: (taskId: string) => void;
|
||||||
removeRunningTask: (taskId: string) => void;
|
removeRunningTask: (taskId: string) => void;
|
||||||
clearRunningTasks: () => void;
|
clearRunningTasks: () => void;
|
||||||
addAutoModeActivity: (activity: Omit<AutoModeActivity, "id" | "timestamp">) => void;
|
addAutoModeActivity: (
|
||||||
|
activity: Omit<AutoModeActivity, "id" | "timestamp">
|
||||||
|
) => void;
|
||||||
clearAutoModeActivity: () => void;
|
clearAutoModeActivity: () => void;
|
||||||
setMaxConcurrency: (max: number) => void;
|
setMaxConcurrency: (max: number) => void;
|
||||||
|
|
||||||
@@ -217,11 +250,17 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const existing = projects.findIndex((p) => p.path === project.path);
|
const existing = projects.findIndex((p) => p.path === project.path);
|
||||||
if (existing >= 0) {
|
if (existing >= 0) {
|
||||||
const updated = [...projects];
|
const updated = [...projects];
|
||||||
updated[existing] = { ...project, lastOpened: new Date().toISOString() };
|
updated[existing] = {
|
||||||
|
...project,
|
||||||
|
lastOpened: new Date().toISOString(),
|
||||||
|
};
|
||||||
set({ projects: updated });
|
set({ projects: updated });
|
||||||
} else {
|
} else {
|
||||||
set({
|
set({
|
||||||
projects: [...projects, { ...project, lastOpened: new Date().toISOString() }],
|
projects: [
|
||||||
|
...projects,
|
||||||
|
{ ...project, lastOpened: new Date().toISOString() },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -259,7 +298,9 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
addFeature: (feature) => {
|
addFeature: (feature) => {
|
||||||
const id = `feature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
const id = `feature-${Date.now()}-${Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2, 9)}`;
|
||||||
set({ features: [...get().features, { ...feature, id }] });
|
set({ features: [...get().features, { ...feature, id }] });
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -294,14 +335,19 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
const session: ChatSession = {
|
const session: ChatSession = {
|
||||||
id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
title: title || `Chat ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
|
title:
|
||||||
|
title ||
|
||||||
|
`Chat ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
|
||||||
projectId: currentProject.id,
|
projectId: currentProject.id,
|
||||||
messages: [{
|
messages: [
|
||||||
id: "welcome",
|
{
|
||||||
role: "assistant",
|
id: "welcome",
|
||||||
content: "Hello! I'm the Automaker Agent. I can help you build software autonomously. What would you like to create today?",
|
role: "assistant",
|
||||||
timestamp: now,
|
content:
|
||||||
}],
|
"Hello! I'm the Automaker Agent. I can help you build software autonomously. What would you like to create today?",
|
||||||
|
timestamp: now,
|
||||||
|
},
|
||||||
|
],
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
archived: false,
|
archived: false,
|
||||||
@@ -328,14 +374,18 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const currentSession = get().currentChatSession;
|
const currentSession = get().currentChatSession;
|
||||||
if (currentSession && currentSession.id === sessionId) {
|
if (currentSession && currentSession.id === sessionId) {
|
||||||
set({
|
set({
|
||||||
currentChatSession: { ...currentSession, ...updates, updatedAt: new Date() }
|
currentChatSession: {
|
||||||
|
...currentSession,
|
||||||
|
...updates,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addMessageToSession: (sessionId, message) => {
|
addMessageToSession: (sessionId, message) => {
|
||||||
const sessions = get().chatSessions;
|
const sessions = get().chatSessions;
|
||||||
const sessionIndex = sessions.findIndex(s => s.id === sessionId);
|
const sessionIndex = sessions.findIndex((s) => s.id === sessionId);
|
||||||
|
|
||||||
if (sessionIndex >= 0) {
|
if (sessionIndex >= 0) {
|
||||||
const updatedSessions = [...sessions];
|
const updatedSessions = [...sessions];
|
||||||
@@ -351,7 +401,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const currentSession = get().currentChatSession;
|
const currentSession = get().currentChatSession;
|
||||||
if (currentSession && currentSession.id === sessionId) {
|
if (currentSession && currentSession.id === sessionId) {
|
||||||
set({
|
set({
|
||||||
currentChatSession: updatedSessions[sessionIndex]
|
currentChatSession: updatedSessions[sessionIndex],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,7 +423,8 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const currentSession = get().currentChatSession;
|
const currentSession = get().currentChatSession;
|
||||||
set({
|
set({
|
||||||
chatSessions: get().chatSessions.filter((s) => s.id !== sessionId),
|
chatSessions: get().chatSessions.filter((s) => s.id !== sessionId),
|
||||||
currentChatSession: currentSession?.id === sessionId ? null : currentSession,
|
currentChatSession:
|
||||||
|
currentSession?.id === sessionId ? null : currentSession,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -392,13 +443,19 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
removeRunningTask: (taskId) => {
|
removeRunningTask: (taskId) => {
|
||||||
set({ runningAutoTasks: get().runningAutoTasks.filter(id => id !== taskId) });
|
set({
|
||||||
|
runningAutoTasks: get().runningAutoTasks.filter(
|
||||||
|
(id) => id !== taskId
|
||||||
|
),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
clearRunningTasks: () => set({ runningAutoTasks: [] }),
|
clearRunningTasks: () => set({ runningAutoTasks: [] }),
|
||||||
|
|
||||||
addAutoModeActivity: (activity) => {
|
addAutoModeActivity: (activity) => {
|
||||||
const id = `activity-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
const id = `activity-${Date.now()}-${Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2, 9)}`;
|
||||||
const newActivity: AutoModeActivity = {
|
const newActivity: AutoModeActivity = {
|
||||||
...activity,
|
...activity,
|
||||||
id,
|
id,
|
||||||
@@ -417,7 +474,8 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
setMaxConcurrency: (max) => set({ maxConcurrency: max }),
|
setMaxConcurrency: (max) => set({ maxConcurrency: max }),
|
||||||
|
|
||||||
// Kanban Card Settings actions
|
// Kanban Card Settings actions
|
||||||
setKanbanCardDetailLevel: (level) => set({ kanbanCardDetailLevel: level }),
|
setKanbanCardDetailLevel: (level) =>
|
||||||
|
set({ kanbanCardDetailLevel: level }),
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
reset: () => set(initialState),
|
reset: () => set(initialState),
|
||||||
|
|||||||
Reference in New Issue
Block a user