feat: add i18n internationalization system (#248)
* Add multilingual support and i18n integration - Implemented i18n framework using `react-i18next` for translation management. - Added support for English and French languages with translation files. - Integrated language selector into settings. - Updated all text strings in UI components to use translation keys. - Ensured smooth language switching with live updates. * Migrate remaining hard-coded strings to i18n system - TaskCard: status labels, review reasons, badges, action buttons - PhaseProgressIndicator: execution phases, progress labels - KanbanBoard: drop zone, show archived, tooltips - CustomModelModal: dialog title, description, labels - ProactiveSwapListener: account switch notifications - AgentProfileSelector: phase labels, custom configuration - GeneralSettings: agent framework option Added translation keys for en/fr locales in tasks.json, common.json, and settings.json for complete i18n coverage. * Add i18n support to dialogs and settings components - AddFeatureDialog: form labels, validation messages, buttons - AddProjectModal: dialog steps, form fields, actions - RateLimitIndicator: rate limit notifications - RateLimitModal: account switching, upgrade prompts - AdvancedSettings: updates and notifications sections - ThemeSettings: theme selection labels - Updated dialogs.json locales (en/fr) * Fix truncated 'ready' message in dialogs locales * Fix backlog terminology in i18n locales Change "Planning"/"Planification" to standard PM term "Backlog" * Migrate settings navigation and integration labels to i18n - AppSettings: nav items, section titles, buttons - IntegrationSettings: Claude accounts, auto-switch, API keys labels - Added settings nav/projectSections/integrations translation keys - Added buttons.saving to common translations * Migrate AgentProfileSettings and Sidebar init dialog to i18n - AgentProfileSettings: migrate phase config labels, section title, description, and all hardcoded strings to settings namespace - Sidebar: migrate init dialog strings to dialogs namespace with common buttons from common namespace - Add new translation keys for agent profile settings and update dialog * Migrate AppSettings navigation labels to i18n - Add useTranslation hook to AppSettings.tsx - Replace hardcoded section labels with dynamic translations - Add projectSections translations for project settings nav - Add rerunWizardDescription translation key * Add explicit typing to notificationItems array Import NotificationSettings type and use keyof to properly type the notification item keys, removing manual type assertion.
This commit is contained in:
Generated
+118
-29
@@ -40,10 +40,12 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"i18next": "^25.7.3",
|
||||
"lucide-react": "^0.560.0",
|
||||
"motion": "^12.23.26",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-i18next": "^16.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^3.0.6",
|
||||
"remark-gfm": "^4.0.1",
|
||||
@@ -152,6 +154,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -395,7 +398,6 @@
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -537,6 +539,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -560,6 +563,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -599,6 +603,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -993,7 +998,6 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cross-dirname": "^0.1.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1015,7 +1019,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
@@ -4016,8 +4019,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4204,6 +4206,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -4214,6 +4217,7 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4305,6 +4309,7 @@
|
||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.49.0",
|
||||
"@typescript-eslint/types": "8.49.0",
|
||||
@@ -4704,7 +4709,8 @@
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/7zip-bin": {
|
||||
"version": "5.2.0",
|
||||
@@ -4726,6 +4732,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4786,6 +4793,7 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -4958,7 +4966,6 @@
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
@@ -5343,6 +5350,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -6013,8 +6021,7 @@
|
||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
@@ -6349,6 +6356,7 @@
|
||||
"integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.0.12",
|
||||
"builder-util": "26.0.11",
|
||||
@@ -6406,8 +6414,7 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
@@ -6483,6 +6490,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^22.7.7",
|
||||
@@ -6611,7 +6619,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.1",
|
||||
"debug": "^4.1.1",
|
||||
@@ -6632,7 +6639,6 @@
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -6648,7 +6654,6 @@
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
@@ -6659,7 +6664,6 @@
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
@@ -7029,6 +7033,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8161,6 +8166,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
@@ -8246,6 +8260,38 @@
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.7.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.3.tgz",
|
||||
"integrity": "sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-corefoundation": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
|
||||
@@ -9024,6 +9070,7 @@
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -9955,7 +10002,6 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -11779,6 +11825,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -11876,6 +11923,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -11912,7 +11960,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"commander": "^9.4.0"
|
||||
},
|
||||
@@ -11930,7 +11977,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
@@ -11951,7 +11997,6 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -11967,7 +12012,6 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -11980,8 +12024,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proc-log": {
|
||||
"version": "2.0.1",
|
||||
@@ -12085,6 +12128,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12094,6 +12138,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -12101,6 +12146,33 @@
|
||||
"react": "^19.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.0.tgz",
|
||||
"integrity": "sha512-IMpPTyCTKxEj8klCrLKUTIUa8uYTd851+jcu2fJuUB9Agkk9Qq8asw4omyeHVnOXHrLgQJGTm5zTvn8HpaPiqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 25.6.2",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -13383,7 +13455,8 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -13440,7 +13513,6 @@
|
||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "~2.6.2"
|
||||
@@ -13467,7 +13539,6 @@
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -13489,7 +13560,6 @@
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -13503,7 +13573,6 @@
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
@@ -13518,7 +13587,6 @@
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
@@ -13833,8 +13901,9 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14099,6 +14168,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||
@@ -14175,6 +14253,7 @@
|
||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -14821,6 +14900,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
@@ -15208,6 +15296,7 @@
|
||||
"integrity": "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -73,10 +73,12 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"i18next": "^25.7.3",
|
||||
"lucide-react": "^0.560.0",
|
||||
"motion": "^12.23.26",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-i18next": "^16.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^3.0.6",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Settings2, Download, RefreshCw, AlertCircle } from 'lucide-react';
|
||||
import {
|
||||
DndContext,
|
||||
@@ -185,6 +186,14 @@ export function App() {
|
||||
}
|
||||
}, [settingsHaveLoaded, settings.onboardingCompleted]);
|
||||
|
||||
// Sync i18n language with settings
|
||||
const { t, i18n } = useTranslation('dialogs');
|
||||
useEffect(() => {
|
||||
if (settings.language && settings.language !== i18n.language) {
|
||||
i18n.changeLanguage(settings.language);
|
||||
}
|
||||
}, [settings.language, i18n]);
|
||||
|
||||
// Listen for open-app-settings events (e.g., from project settings)
|
||||
useEffect(() => {
|
||||
const handleOpenAppSettings = (event: Event) => {
|
||||
@@ -746,19 +755,19 @@ export function App() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5" />
|
||||
Initialize Auto Claude
|
||||
{t('initialize.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
This project doesn't have Auto Claude initialized. Would you like to set it up now?
|
||||
{t('initialize.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<div className="rounded-lg bg-muted p-4 text-sm">
|
||||
<p className="font-medium mb-2">This will:</p>
|
||||
<p className="font-medium mb-2">{t('initialize.willDo')}</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
|
||||
<li>Create a <code className="text-xs bg-background px-1 py-0.5 rounded">.auto-claude</code> folder in your project</li>
|
||||
<li>Copy the Auto Claude framework files</li>
|
||||
<li>Set up the specs directory for your tasks</li>
|
||||
<li>{t('initialize.createFolder')}</li>
|
||||
<li>{t('initialize.copyFramework')}</li>
|
||||
<li>{t('initialize.setupSpecs')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{!settings.autoBuildPath && (
|
||||
@@ -766,9 +775,9 @@ export function App() {
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-warning mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-warning">Source path not configured</p>
|
||||
<p className="font-medium text-warning">{t('initialize.sourcePathNotConfigured')}</p>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Please set the Auto Claude source path in App Settings before initializing.
|
||||
{t('initialize.sourcePathNotConfiguredDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -779,7 +788,7 @@ export function App() {
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-destructive">Initialization Failed</p>
|
||||
<p className="font-medium text-destructive">{t('initialize.initFailed')}</p>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
{initError}
|
||||
</p>
|
||||
@@ -790,7 +799,7 @@ export function App() {
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleSkipInit} disabled={isInitializing}>
|
||||
Skip
|
||||
{t('common:buttons.skip', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleInitialize}
|
||||
@@ -799,12 +808,12 @@ export function App() {
|
||||
{isInitializing ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
Initializing...
|
||||
{t('common:labels.initializing', { ns: 'common' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Initialize
|
||||
{t('common:buttons.initialize', { ns: 'common' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
* ```
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader2, X } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -68,18 +69,18 @@ interface AddFeatureDialogProps {
|
||||
defaultPhaseId?: string;
|
||||
}
|
||||
|
||||
// Complexity options
|
||||
// Complexity options (keys for translation)
|
||||
const COMPLEXITY_OPTIONS = [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' }
|
||||
{ value: 'low', labelKey: 'addFeature.lowComplexity' },
|
||||
{ value: 'medium', labelKey: 'addFeature.mediumComplexity' },
|
||||
{ value: 'high', labelKey: 'addFeature.highComplexity' }
|
||||
] as const;
|
||||
|
||||
// Impact options
|
||||
// Impact options (keys for translation)
|
||||
const IMPACT_OPTIONS = [
|
||||
{ value: 'low', label: 'Low Impact' },
|
||||
{ value: 'medium', label: 'Medium Impact' },
|
||||
{ value: 'high', label: 'High Impact' }
|
||||
{ value: 'low', labelKey: 'addFeature.lowImpact' },
|
||||
{ value: 'medium', labelKey: 'addFeature.mediumImpact' },
|
||||
{ value: 'high', labelKey: 'addFeature.highImpact' }
|
||||
] as const;
|
||||
|
||||
export function AddFeatureDialog({
|
||||
@@ -89,6 +90,8 @@ export function AddFeatureDialog({
|
||||
onFeatureAdded,
|
||||
defaultPhaseId
|
||||
}: AddFeatureDialogProps) {
|
||||
const { t } = useTranslation('dialogs');
|
||||
|
||||
// Form state
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
@@ -122,15 +125,15 @@ export function AddFeatureDialog({
|
||||
const handleSave = async () => {
|
||||
// Validate required fields
|
||||
if (!title.trim()) {
|
||||
setError('Title is required');
|
||||
setError(t('addFeature.titleRequired'));
|
||||
return;
|
||||
}
|
||||
if (!description.trim()) {
|
||||
setError('Description is required');
|
||||
setError(t('addFeature.descriptionRequired'));
|
||||
return;
|
||||
}
|
||||
if (!phaseId) {
|
||||
setError('Please select a phase');
|
||||
setError(t('addFeature.phaseRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,7 +171,7 @@ export function AddFeatureDialog({
|
||||
onOpenChange(false);
|
||||
onFeatureAdded?.(newFeatureId);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to add feature. Please try again.');
|
||||
setError(err instanceof Error ? err.message : t('addFeature.failedToAdd'));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -187,10 +190,9 @@ export function AddFeatureDialog({
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[550px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-foreground">Add Feature</DialogTitle>
|
||||
<DialogTitle className="text-foreground">{t('addFeature.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Add a new feature to your roadmap. Provide details about what you want to build
|
||||
and how it fits into your product strategy.
|
||||
{t('addFeature.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -198,11 +200,11 @@ export function AddFeatureDialog({
|
||||
{/* Title (Required) */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-title" className="text-sm font-medium text-foreground">
|
||||
Feature Title <span className="text-destructive">*</span>
|
||||
{t('addFeature.featureTitle')} <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="add-feature-title"
|
||||
placeholder="e.g., User Authentication, Dark Mode Support"
|
||||
placeholder={t('addFeature.featureTitlePlaceholder')}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
disabled={isSaving}
|
||||
@@ -212,11 +214,11 @@ export function AddFeatureDialog({
|
||||
{/* Description (Required) */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-description" className="text-sm font-medium text-foreground">
|
||||
Description <span className="text-destructive">*</span>
|
||||
{t('addFeature.featureDescription')} <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
id="add-feature-description"
|
||||
placeholder="Describe what this feature does and why it's valuable to users."
|
||||
placeholder={t('addFeature.featureDescriptionPlaceholder')}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
rows={3}
|
||||
@@ -227,11 +229,11 @@ export function AddFeatureDialog({
|
||||
{/* Rationale (Optional) */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-rationale" className="text-sm font-medium text-foreground">
|
||||
Rationale <span className="text-muted-foreground font-normal">(optional)</span>
|
||||
{t('addFeature.rationale')} <span className="text-muted-foreground font-normal">({t('addFeature.optional')})</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
id="add-feature-rationale"
|
||||
placeholder="Explain why this feature should be built and how it fits the product vision."
|
||||
placeholder={t('addFeature.rationalePlaceholder')}
|
||||
value={rationale}
|
||||
onChange={(e) => setRationale(e.target.value)}
|
||||
rows={2}
|
||||
@@ -244,7 +246,7 @@ export function AddFeatureDialog({
|
||||
{/* Phase */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-phase" className="text-sm font-medium text-foreground">
|
||||
Phase <span className="text-destructive">*</span>
|
||||
{t('addFeature.phase')} <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={phaseId}
|
||||
@@ -252,7 +254,7 @@ export function AddFeatureDialog({
|
||||
disabled={isSaving}
|
||||
>
|
||||
<SelectTrigger id="add-feature-phase">
|
||||
<SelectValue placeholder="Select phase" />
|
||||
<SelectValue placeholder={t('addFeature.selectPhase')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{phases.map((phase) => (
|
||||
@@ -267,7 +269,7 @@ export function AddFeatureDialog({
|
||||
{/* Priority */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-priority" className="text-sm font-medium text-foreground">
|
||||
Priority
|
||||
{t('addFeature.priority')}
|
||||
</Label>
|
||||
<Select
|
||||
value={priority}
|
||||
@@ -275,7 +277,7 @@ export function AddFeatureDialog({
|
||||
disabled={isSaving}
|
||||
>
|
||||
<SelectTrigger id="add-feature-priority">
|
||||
<SelectValue placeholder="Select priority" />
|
||||
<SelectValue placeholder={t('addFeature.selectPriority')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(ROADMAP_PRIORITY_LABELS).map(([value, label]) => (
|
||||
@@ -290,7 +292,7 @@ export function AddFeatureDialog({
|
||||
{/* Complexity */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-complexity" className="text-sm font-medium text-foreground">
|
||||
Complexity
|
||||
{t('addFeature.complexity')}
|
||||
</Label>
|
||||
<Select
|
||||
value={complexity}
|
||||
@@ -298,12 +300,12 @@ export function AddFeatureDialog({
|
||||
disabled={isSaving}
|
||||
>
|
||||
<SelectTrigger id="add-feature-complexity">
|
||||
<SelectValue placeholder="Select complexity" />
|
||||
<SelectValue placeholder={t('addFeature.selectComplexity')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{COMPLEXITY_OPTIONS.map(({ value, label }) => (
|
||||
{COMPLEXITY_OPTIONS.map(({ value, labelKey }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
{t(labelKey)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -313,7 +315,7 @@ export function AddFeatureDialog({
|
||||
{/* Impact */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="add-feature-impact" className="text-sm font-medium text-foreground">
|
||||
Impact
|
||||
{t('addFeature.impact')}
|
||||
</Label>
|
||||
<Select
|
||||
value={impact}
|
||||
@@ -321,12 +323,12 @@ export function AddFeatureDialog({
|
||||
disabled={isSaving}
|
||||
>
|
||||
<SelectTrigger id="add-feature-impact">
|
||||
<SelectValue placeholder="Select impact" />
|
||||
<SelectValue placeholder={t('addFeature.selectImpact')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{IMPACT_OPTIONS.map(({ value, label }) => (
|
||||
{IMPACT_OPTIONS.map(({ value, labelKey }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
{t(labelKey)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -345,7 +347,7 @@ export function AddFeatureDialog({
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose} disabled={isSaving}>
|
||||
Cancel
|
||||
{t('addFeature.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
@@ -354,10 +356,10 @@ export function AddFeatureDialog({
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Adding...
|
||||
{t('addFeature.adding')}
|
||||
</>
|
||||
) : (
|
||||
'Add Feature'
|
||||
t('addFeature.addFeature')
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FolderOpen, FolderPlus, ChevronRight } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
@@ -24,6 +25,7 @@ interface AddProjectModalProps {
|
||||
}
|
||||
|
||||
export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProjectModalProps) {
|
||||
const { t } = useTranslation('dialogs');
|
||||
const [step, setStep] = useState<ModalStep>('choose');
|
||||
const [projectName, setProjectName] = useState('');
|
||||
const [projectLocation, setProjectLocation] = useState('');
|
||||
@@ -79,7 +81,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to open project');
|
||||
setError(err instanceof Error ? err.message : t('addProject.failedToOpen'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,11 +98,11 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
|
||||
const handleCreateProject = async () => {
|
||||
if (!projectName.trim()) {
|
||||
setError('Please enter a project name');
|
||||
setError(t('addProject.nameRequired'));
|
||||
return;
|
||||
}
|
||||
if (!projectLocation.trim()) {
|
||||
setError('Please select a location');
|
||||
setError(t('addProject.locationRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -141,7 +143,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
onOpenChange(false);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to create project');
|
||||
setError(err instanceof Error ? err.message : t('addProject.failedToCreate'));
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
@@ -150,9 +152,9 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
const renderChooseStep = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Project</DialogTitle>
|
||||
<DialogTitle>{t('addProject.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Choose how you'd like to add a project
|
||||
{t('addProject.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -170,9 +172,9 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
<FolderOpen className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground">Open Existing Folder</h3>
|
||||
<h3 className="font-medium text-foreground">{t('addProject.openExisting')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">
|
||||
Browse to an existing project on your computer
|
||||
{t('addProject.openExistingDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRight className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||
@@ -191,9 +193,9 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
<FolderPlus className="h-6 w-6 text-success" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground">Create New Project</h3>
|
||||
<h3 className="font-medium text-foreground">{t('addProject.createNew')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">
|
||||
Start fresh with a new project folder
|
||||
{t('addProject.createNewDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRight className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||
@@ -211,46 +213,46 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
const renderCreateForm = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Project</DialogTitle>
|
||||
<DialogTitle>{t('addProject.createNewTitle')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Set up a new project folder
|
||||
{t('addProject.createNewSubtitle')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4 space-y-4">
|
||||
{/* Project Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="project-name">Project Name</Label>
|
||||
<Label htmlFor="project-name">{t('addProject.projectName')}</Label>
|
||||
<Input
|
||||
id="project-name"
|
||||
placeholder="my-awesome-project"
|
||||
placeholder={t('addProject.projectNamePlaceholder')}
|
||||
value={projectName}
|
||||
onChange={(e) => setProjectName(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This will be the folder name. Use lowercase with hyphens.
|
||||
{t('addProject.projectNameHelp')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="project-location">Location</Label>
|
||||
<Label htmlFor="project-location">{t('addProject.location')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="project-location"
|
||||
placeholder="Select a folder..."
|
||||
placeholder={t('addProject.locationPlaceholder')}
|
||||
value={projectLocation}
|
||||
onChange={(e) => setProjectLocation(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button variant="outline" onClick={handleSelectLocation}>
|
||||
Browse
|
||||
{t('addProject.browse')}
|
||||
</Button>
|
||||
</div>
|
||||
{projectLocation && projectName && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Will create: <code className="bg-muted px-1 py-0.5 rounded">{projectLocation}/{projectName}</code>
|
||||
{t('addProject.willCreate')} <code className="bg-muted px-1 py-0.5 rounded">{projectLocation}/{projectName}</code>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -265,7 +267,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
className="h-4 w-4 rounded border-border bg-background"
|
||||
/>
|
||||
<Label htmlFor="init-git" className="text-sm font-normal cursor-pointer">
|
||||
Initialize git repository
|
||||
{t('addProject.initGit')}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
@@ -278,10 +280,10 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setStep('choose')} disabled={isCreating}>
|
||||
Back
|
||||
{t('addProject.back')}
|
||||
</Button>
|
||||
<Button onClick={handleCreateProject} disabled={isCreating}>
|
||||
{isCreating ? 'Creating...' : 'Create Project'}
|
||||
{isCreating ? t('addProject.creating') : t('addProject.createProject')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* Used in TaskCreationWizard and TaskEditDialog.
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Brain, Scale, Zap, Sliders, Sparkles, ChevronDown, ChevronUp, Pencil } from 'lucide-react';
|
||||
import { Label } from './ui/label';
|
||||
import {
|
||||
@@ -60,11 +61,12 @@ const iconMap: Record<string, React.ElementType> = {
|
||||
Sparkles
|
||||
};
|
||||
|
||||
const PHASE_LABELS: Record<keyof PhaseModelConfig, { label: string; description: string }> = {
|
||||
spec: { label: 'Spec Creation', description: 'Discovery, requirements, context gathering' },
|
||||
planning: { label: 'Planning', description: 'Implementation planning and architecture' },
|
||||
coding: { label: 'Coding', description: 'Actual code implementation' },
|
||||
qa: { label: 'QA Review', description: 'Quality assurance and validation' }
|
||||
// Phase label translation keys
|
||||
const PHASE_LABEL_KEYS: Record<keyof PhaseModelConfig, { label: string; description: string }> = {
|
||||
spec: { label: 'agentProfile.phases.spec.label', description: 'agentProfile.phases.spec.description' },
|
||||
planning: { label: 'agentProfile.phases.planning.label', description: 'agentProfile.phases.planning.description' },
|
||||
coding: { label: 'agentProfile.phases.coding.label', description: 'agentProfile.phases.coding.description' },
|
||||
qa: { label: 'agentProfile.phases.qa.label', description: 'agentProfile.phases.qa.description' }
|
||||
};
|
||||
|
||||
export function AgentProfileSelector({
|
||||
@@ -80,6 +82,7 @@ export function AgentProfileSelector({
|
||||
onPhaseThinkingChange,
|
||||
disabled
|
||||
}: AgentProfileSelectorProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
const [showPhaseDetails, setShowPhaseDetails] = useState(false);
|
||||
|
||||
const isCustom = profileId === 'custom';
|
||||
@@ -137,8 +140,8 @@ export function AgentProfileSelector({
|
||||
if (isCustom) {
|
||||
return {
|
||||
icon: Sliders,
|
||||
label: 'Custom Configuration',
|
||||
description: 'Choose model & thinking level'
|
||||
label: t('agentProfile.customConfiguration'),
|
||||
description: t('agentProfile.customDescription')
|
||||
};
|
||||
}
|
||||
const profile = DEFAULT_AGENT_PROFILES.find(p => p.id === profileId);
|
||||
@@ -164,7 +167,7 @@ export function AgentProfileSelector({
|
||||
{/* Agent Profile Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-profile" className="text-sm font-medium text-foreground">
|
||||
Agent Profile
|
||||
{t('agentProfile.label')}
|
||||
</Label>
|
||||
<Select
|
||||
value={profileId}
|
||||
@@ -204,9 +207,9 @@ export function AgentProfileSelector({
|
||||
<div className="flex items-center gap-2">
|
||||
<Sliders className="h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
<span className="font-medium">Custom</span>
|
||||
<span className="font-medium">{t('agentProfile.custom')}</span>
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(Choose model & thinking level)
|
||||
({t('agentProfile.customDescription')})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,11 +236,11 @@ export function AgentProfileSelector({
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm text-foreground">Phase Configuration</span>
|
||||
<span className="font-medium text-sm text-foreground">{t('agentProfile.phaseConfiguration')}</span>
|
||||
{!showPhaseDetails && (
|
||||
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Pencil className="h-3 w-3" />
|
||||
<span>Click to customize</span>
|
||||
<span>{t('agentProfile.clickToCustomize')}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -252,11 +255,11 @@ export function AgentProfileSelector({
|
||||
{!showPhaseDetails && (
|
||||
<div className="px-4 pb-4 -mt-1">
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
{(Object.keys(PHASE_LABELS) as Array<keyof PhaseModelConfig>).map((phase) => {
|
||||
{(Object.keys(PHASE_LABEL_KEYS) as Array<keyof PhaseModelConfig>).map((phase) => {
|
||||
const modelLabel = AVAILABLE_MODELS.find(m => m.value === currentPhaseModels[phase])?.label?.replace('Claude ', '') || currentPhaseModels[phase];
|
||||
return (
|
||||
<div key={phase} className="flex items-center justify-between rounded bg-background/50 px-2 py-1">
|
||||
<span className="text-muted-foreground">{PHASE_LABELS[phase].label}:</span>
|
||||
<span className="text-muted-foreground">{t(PHASE_LABEL_KEYS[phase].label)}:</span>
|
||||
<span className="font-medium">{modelLabel}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -268,19 +271,19 @@ export function AgentProfileSelector({
|
||||
{/* Detailed Phase Configuration */}
|
||||
{showPhaseDetails && (
|
||||
<div className="px-4 pb-4 space-y-4 border-t border-border pt-4">
|
||||
{(Object.keys(PHASE_LABELS) as Array<keyof PhaseModelConfig>).map((phase) => (
|
||||
{(Object.keys(PHASE_LABEL_KEYS) as Array<keyof PhaseModelConfig>).map((phase) => (
|
||||
<div key={phase} className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium text-foreground">
|
||||
{PHASE_LABELS[phase].label}
|
||||
{t(PHASE_LABEL_KEYS[phase].label)}
|
||||
</Label>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{PHASE_LABELS[phase].description}
|
||||
{t(PHASE_LABEL_KEYS[phase].description)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] text-muted-foreground">Model</Label>
|
||||
<Label className="text-[10px] text-muted-foreground">{t('agentProfile.model')}</Label>
|
||||
<Select
|
||||
value={currentPhaseModels[phase]}
|
||||
onValueChange={(value) => handlePhaseModelChange(phase, value as ModelType)}
|
||||
@@ -299,7 +302,7 @@ export function AgentProfileSelector({
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] text-muted-foreground">Thinking</Label>
|
||||
<Label className="text-[10px] text-muted-foreground">{t('agentProfile.thinking')}</Label>
|
||||
<Select
|
||||
value={currentPhaseThinking[phase]}
|
||||
onValueChange={(value) => handlePhaseThinkingChange(phase, value as ThinkingLevel)}
|
||||
@@ -331,7 +334,7 @@ export function AgentProfileSelector({
|
||||
{/* Model Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="custom-model" className="text-xs font-medium text-muted-foreground">
|
||||
Model
|
||||
{t('agentProfile.model')}
|
||||
</Label>
|
||||
<Select
|
||||
value={model}
|
||||
@@ -339,7 +342,7 @@ export function AgentProfileSelector({
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger id="custom-model" className="h-9">
|
||||
<SelectValue placeholder="Select model" />
|
||||
<SelectValue placeholder={t('agentProfile.selectModel')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{AVAILABLE_MODELS.map((m) => (
|
||||
@@ -354,7 +357,7 @@ export function AgentProfileSelector({
|
||||
{/* Thinking Level Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="custom-thinking" className="text-xs font-medium text-muted-foreground">
|
||||
Thinking Level
|
||||
{t('agentProfile.thinking')}
|
||||
</Label>
|
||||
<Select
|
||||
value={thinkingLevel}
|
||||
@@ -362,7 +365,7 @@ export function AgentProfileSelector({
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger id="custom-thinking" className="h-9">
|
||||
<SelectValue placeholder="Select thinking level" />
|
||||
<SelectValue placeholder={t('agentProfile.selectThinkingLevel')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{THINKING_LEVELS.map((level) => (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -28,6 +29,7 @@ interface CustomModelModalProps {
|
||||
}
|
||||
|
||||
export function CustomModelModal({ currentConfig, onSave, onClose, open = true }: CustomModelModalProps) {
|
||||
const { t } = useTranslation('dialogs');
|
||||
const [model, setModel] = useState<ModelType>(
|
||||
currentConfig?.model || 'sonnet'
|
||||
);
|
||||
@@ -55,15 +57,15 @@ export function CustomModelModal({ currentConfig, onSave, onClose, open = true }
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Custom Model Configuration</DialogTitle>
|
||||
<DialogTitle>{t('customModel.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Configure the model and thinking level for this chat session.
|
||||
{t('customModel.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="model-select">Model</Label>
|
||||
<Label htmlFor="model-select">{t('customModel.model')}</Label>
|
||||
<Select value={model} onValueChange={(v) => setModel(v as ModelType)}>
|
||||
<SelectTrigger id="model-select">
|
||||
<SelectValue />
|
||||
@@ -79,7 +81,7 @@ export function CustomModelModal({ currentConfig, onSave, onClose, open = true }
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="thinking-select">Thinking Level</Label>
|
||||
<Label htmlFor="thinking-select">{t('customModel.thinkingLevel')}</Label>
|
||||
<Select value={thinkingLevel} onValueChange={(v) => setThinkingLevel(v as ThinkingLevel)}>
|
||||
<SelectTrigger id="thinking-select">
|
||||
<SelectValue />
|
||||
@@ -102,10 +104,10 @@ export function CustomModelModal({ currentConfig, onSave, onClose, open = true }
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
{t('customModel.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
Apply
|
||||
{t('customModel.apply')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Github,
|
||||
GitBranch,
|
||||
@@ -63,6 +64,7 @@ export function GitHubSetupModal({
|
||||
onComplete,
|
||||
onSkip
|
||||
}: GitHubSetupModalProps) {
|
||||
const { t } = useTranslation('dialogs');
|
||||
const [step, setStep] = useState<SetupStep>('github-auth');
|
||||
const [githubToken, setGithubToken] = useState<string | null>(null);
|
||||
const [githubRepo, setGithubRepo] = useState<string | null>(null);
|
||||
@@ -379,10 +381,10 @@ export function GitHubSetupModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Github className="h-5 w-5" />
|
||||
Connect to GitHub
|
||||
{t('githubSetup.connectTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Auto Claude requires GitHub to manage your code branches and keep tasks up to date.
|
||||
{t('githubSetup.connectDescription')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -401,10 +403,10 @@ export function GitHubSetupModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5" />
|
||||
Connect to Claude AI
|
||||
{t('githubSetup.claudeTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Auto Claude uses Claude AI for intelligent features like Roadmap generation, Task automation, and Ideation.
|
||||
{t('githubSetup.claudeDescription')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -444,7 +446,7 @@ export function GitHubSetupModal({
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Auto Claude will use this repository for managing task branches and keeping your code up to date.
|
||||
{t('githubSetup.repoDescription')}
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GitBranch, Terminal, CheckCircle2, AlertCircle, Loader2, FolderGit2 } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import {
|
||||
@@ -28,6 +29,7 @@ export function GitSetupModal({
|
||||
onGitInitialized,
|
||||
onSkip
|
||||
}: GitSetupModalProps) {
|
||||
const { t } = useTranslation('dialogs');
|
||||
const [isInitializing, setIsInitializing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [step, setStep] = useState<'info' | 'initializing' | 'success'>('info');
|
||||
@@ -76,10 +78,10 @@ export function GitSetupModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FolderGit2 className="h-5 w-5 text-primary" />
|
||||
Git Repository Required
|
||||
{t('gitSetup.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Auto Claude uses git to safely build features in isolated workspaces
|
||||
{t('gitSetup.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -91,13 +93,13 @@ export function GitSetupModal({
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-sm">
|
||||
{needsGitInit
|
||||
? 'This folder is not a git repository'
|
||||
: 'Git repository has no commits'}
|
||||
? t('gitSetup.notGitRepo')
|
||||
: t('gitSetup.noCommits')}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{needsGitInit
|
||||
? 'Git needs to be initialized before Auto Claude can manage your code.'
|
||||
: 'At least one commit is required for Auto Claude to create worktrees.'}
|
||||
? t('gitSetup.needsInit')
|
||||
: t('gitSetup.needsCommit')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,17 +107,17 @@ export function GitSetupModal({
|
||||
|
||||
{/* What will happen */}
|
||||
<div className="rounded-lg border border-border p-4">
|
||||
<p className="font-medium text-sm mb-3">We'll set up git for you:</p>
|
||||
<p className="font-medium text-sm mb-3">{t('gitSetup.willSetup')}</p>
|
||||
<ul className="space-y-2">
|
||||
{needsGitInit && (
|
||||
<li className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<GitBranch className="h-4 w-4 text-primary" />
|
||||
Initialize a new git repository
|
||||
{t('gitSetup.initRepo')}
|
||||
</li>
|
||||
)}
|
||||
<li className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<CheckCircle2 className="h-4 w-4 text-primary" />
|
||||
Create an initial commit with your current files
|
||||
{t('gitSetup.createCommit')}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -123,7 +125,7 @@ export function GitSetupModal({
|
||||
{/* Manual instructions for advanced users */}
|
||||
<details className="text-sm">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
Prefer to do it manually?
|
||||
{t('gitSetup.manual')}
|
||||
</summary>
|
||||
<div className="mt-3 rounded-lg bg-muted/50 p-3 font-mono text-xs space-y-1">
|
||||
<p className="text-muted-foreground">Open a terminal in your project folder and run:</p>
|
||||
@@ -157,7 +159,7 @@ export function GitSetupModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-primary" />
|
||||
Setting up Git
|
||||
{t('gitSetup.settingUp')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -165,7 +167,7 @@ export function GitSetupModal({
|
||||
<div className="space-y-3 text-center">
|
||||
<Terminal className="h-12 w-12 text-muted-foreground mx-auto" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Initializing git repository and creating initial commit...
|
||||
{t('gitSetup.initializingRepo')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,7 +179,7 @@ export function GitSetupModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-5 w-5 text-success" />
|
||||
Git Initialized
|
||||
{t('gitSetup.success')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -187,7 +189,7 @@ export function GitSetupModal({
|
||||
<CheckCircle2 className="h-8 w-8 text-success" />
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your project is now ready to use with Auto Claude!
|
||||
{t('gitSetup.readyToUse')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
DndContext,
|
||||
DragOverlay,
|
||||
@@ -45,47 +46,48 @@ interface DroppableColumnProps {
|
||||
}
|
||||
|
||||
// Empty state content for each column
|
||||
const getEmptyStateContent = (status: TaskStatus): { icon: React.ReactNode; message: string; subtext?: string } => {
|
||||
const getEmptyStateContent = (status: TaskStatus, t: (key: string) => string): { icon: React.ReactNode; message: string; subtext?: string } => {
|
||||
switch (status) {
|
||||
case 'backlog':
|
||||
return {
|
||||
icon: <Inbox className="h-6 w-6 text-muted-foreground/50" />,
|
||||
message: 'No tasks planned',
|
||||
subtext: 'Add a task to get started'
|
||||
message: t('kanban.emptyBacklog'),
|
||||
subtext: t('kanban.emptyBacklogHint')
|
||||
};
|
||||
case 'in_progress':
|
||||
return {
|
||||
icon: <Loader2 className="h-6 w-6 text-muted-foreground/50" />,
|
||||
message: 'Nothing running',
|
||||
subtext: 'Start a task from Planning'
|
||||
message: t('kanban.emptyInProgress'),
|
||||
subtext: t('kanban.emptyInProgressHint')
|
||||
};
|
||||
case 'ai_review':
|
||||
return {
|
||||
icon: <Eye className="h-6 w-6 text-muted-foreground/50" />,
|
||||
message: 'No tasks in review',
|
||||
subtext: 'AI will review completed tasks'
|
||||
message: t('kanban.emptyAiReview'),
|
||||
subtext: t('kanban.emptyAiReviewHint')
|
||||
};
|
||||
case 'human_review':
|
||||
return {
|
||||
icon: <Eye className="h-6 w-6 text-muted-foreground/50" />,
|
||||
message: 'Nothing to review',
|
||||
subtext: 'Tasks await your approval here'
|
||||
message: t('kanban.emptyHumanReview'),
|
||||
subtext: t('kanban.emptyHumanReviewHint')
|
||||
};
|
||||
case 'done':
|
||||
return {
|
||||
icon: <CheckCircle2 className="h-6 w-6 text-muted-foreground/50" />,
|
||||
message: 'No completed tasks',
|
||||
subtext: 'Approved tasks appear here'
|
||||
message: t('kanban.emptyDone'),
|
||||
subtext: t('kanban.emptyDoneHint')
|
||||
};
|
||||
default:
|
||||
return {
|
||||
icon: <Inbox className="h-6 w-6 text-muted-foreground/50" />,
|
||||
message: 'No tasks'
|
||||
message: t('kanban.emptyDefault')
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArchiveAll }: DroppableColumnProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const { setNodeRef } = useDroppable({
|
||||
id: status
|
||||
});
|
||||
@@ -109,7 +111,7 @@ function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArc
|
||||
}
|
||||
};
|
||||
|
||||
const emptyState = getEmptyStateContent(status);
|
||||
const emptyState = getEmptyStateContent(status, t);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -148,7 +150,7 @@ function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArc
|
||||
size="icon"
|
||||
className="h-7 w-7 hover:bg-muted-foreground/10 hover:text-muted-foreground transition-colors"
|
||||
onClick={onArchiveAll}
|
||||
title="Archive all done tasks"
|
||||
title={t('tooltips.archiveAllDone')}
|
||||
>
|
||||
<Archive className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -176,7 +178,7 @@ function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArc
|
||||
<div className="h-8 w-8 rounded-full bg-primary/20 flex items-center justify-center mb-2">
|
||||
<Plus className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-primary">Drop here</span>
|
||||
<span className="text-sm font-medium text-primary">{t('kanban.dropHere')}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -210,6 +212,7 @@ function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArc
|
||||
}
|
||||
|
||||
export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick }: KanbanBoardProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||
const [overColumnId, setOverColumnId] = useState<string | null>(null);
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
@@ -349,7 +352,7 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick }: KanbanBoardP
|
||||
className="flex items-center gap-1.5 text-sm text-muted-foreground cursor-pointer"
|
||||
>
|
||||
<Archive className="h-3.5 w-3.5" />
|
||||
Show archived
|
||||
{t('kanban.showArchived')}
|
||||
{archivedCount > 0 && (
|
||||
<span className="ml-1 text-xs px-1.5 py-0.5 rounded-full bg-muted">
|
||||
{archivedCount}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { ExecutionPhase, TaskLogs, Subtask } from '../../shared/types';
|
||||
|
||||
@@ -11,15 +12,26 @@ interface PhaseProgressIndicatorProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Phase display configuration
|
||||
const PHASE_CONFIG: Record<ExecutionPhase, { label: string; color: string; bgColor: string }> = {
|
||||
idle: { label: 'Idle', color: 'bg-muted-foreground', bgColor: 'bg-muted' },
|
||||
planning: { label: 'Planning', color: 'bg-amber-500', bgColor: 'bg-amber-500/20' },
|
||||
coding: { label: 'Coding', color: 'bg-info', bgColor: 'bg-info/20' },
|
||||
qa_review: { label: 'Reviewing', color: 'bg-purple-500', bgColor: 'bg-purple-500/20' },
|
||||
qa_fixing: { label: 'Fixing', color: 'bg-orange-500', bgColor: 'bg-orange-500/20' },
|
||||
complete: { label: 'Complete', color: 'bg-success', bgColor: 'bg-success/20' },
|
||||
failed: { label: 'Failed', color: 'bg-destructive', bgColor: 'bg-destructive/20' },
|
||||
// Phase display configuration (colors only - labels are translated)
|
||||
const PHASE_COLORS: Record<ExecutionPhase, { color: string; bgColor: string }> = {
|
||||
idle: { color: 'bg-muted-foreground', bgColor: 'bg-muted' },
|
||||
planning: { color: 'bg-amber-500', bgColor: 'bg-amber-500/20' },
|
||||
coding: { color: 'bg-info', bgColor: 'bg-info/20' },
|
||||
qa_review: { color: 'bg-purple-500', bgColor: 'bg-purple-500/20' },
|
||||
qa_fixing: { color: 'bg-orange-500', bgColor: 'bg-orange-500/20' },
|
||||
complete: { color: 'bg-success', bgColor: 'bg-success/20' },
|
||||
failed: { color: 'bg-destructive', bgColor: 'bg-destructive/20' },
|
||||
};
|
||||
|
||||
// Phase label translation keys
|
||||
const PHASE_LABEL_KEYS: Record<ExecutionPhase, string> = {
|
||||
idle: 'execution.phases.idle',
|
||||
planning: 'execution.phases.planning',
|
||||
coding: 'execution.phases.coding',
|
||||
qa_review: 'execution.phases.reviewing',
|
||||
qa_fixing: 'execution.phases.fixing',
|
||||
complete: 'execution.phases.complete',
|
||||
failed: 'execution.phases.failed',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,6 +48,8 @@ export function PhaseProgressIndicator({
|
||||
isRunning = false,
|
||||
className,
|
||||
}: PhaseProgressIndicatorProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
|
||||
// Calculate subtask-based progress (for coding phase)
|
||||
const completedSubtasks = subtasks.filter((c) => c.status === 'completed').length;
|
||||
const totalSubtasks = subtasks.length;
|
||||
@@ -57,7 +71,8 @@ export function PhaseProgressIndicator({
|
||||
const isIndeterminatePhase = phase === 'planning' || phase === 'qa_review' || phase === 'qa_fixing';
|
||||
const showSubtaskProgress = phase === 'coding' || (totalSubtasks > 0 && !isIndeterminatePhase);
|
||||
|
||||
const config = PHASE_CONFIG[phase] || PHASE_CONFIG.idle;
|
||||
const colors = PHASE_COLORS[phase] || PHASE_COLORS.idle;
|
||||
const phaseLabel = t(PHASE_LABEL_KEYS[phase] || PHASE_LABEL_KEYS.idle);
|
||||
const activeEntries = getActivePhaseEntries();
|
||||
|
||||
return (
|
||||
@@ -66,12 +81,12 @@ export function PhaseProgressIndicator({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isStuck ? 'Interrupted' : showSubtaskProgress ? 'Progress' : config.label}
|
||||
{isStuck ? t('execution.labels.interrupted') : showSubtaskProgress ? t('execution.labels.progress') : phaseLabel}
|
||||
</span>
|
||||
{/* Activity indicator dot for non-coding phases */}
|
||||
{isRunning && !isStuck && isIndeterminatePhase && (
|
||||
<motion.div
|
||||
className={cn('h-1.5 w-1.5 rounded-full', config.color)}
|
||||
className={cn('h-1.5 w-1.5 rounded-full', colors.color)}
|
||||
animate={{
|
||||
scale: [1, 1.5, 1],
|
||||
opacity: [1, 0.5, 1],
|
||||
@@ -89,7 +104,7 @@ export function PhaseProgressIndicator({
|
||||
`${subtaskProgress}%`
|
||||
) : activeEntries > 0 ? (
|
||||
<span className="text-muted-foreground">
|
||||
{activeEntries} {activeEntries === 1 ? 'entry' : 'entries'}
|
||||
{activeEntries} {activeEntries === 1 ? t('execution.labels.entry') : t('execution.labels.entries')}
|
||||
</span>
|
||||
) : (
|
||||
'—'
|
||||
@@ -118,7 +133,7 @@ export function PhaseProgressIndicator({
|
||||
// Determinate progress for coding phase
|
||||
<motion.div
|
||||
key="determinate"
|
||||
className={cn('h-full rounded-full', config.color)}
|
||||
className={cn('h-full rounded-full', colors.color)}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${subtaskProgress}%` }}
|
||||
transition={{ duration: 0.5, ease: 'easeOut' }}
|
||||
@@ -127,7 +142,7 @@ export function PhaseProgressIndicator({
|
||||
// Indeterminate animated progress for planning/validation
|
||||
<motion.div
|
||||
key="indeterminate"
|
||||
className={cn('absolute h-full w-1/3 rounded-full', config.color)}
|
||||
className={cn('absolute h-full w-1/3 rounded-full', colors.color)}
|
||||
animate={{
|
||||
x: ['-100%', '400%'],
|
||||
}}
|
||||
@@ -141,7 +156,7 @@ export function PhaseProgressIndicator({
|
||||
// Static progress based on subtasks (when not running)
|
||||
<motion.div
|
||||
key="static"
|
||||
className={cn('h-full rounded-full', config.color)}
|
||||
className={cn('h-full rounded-full', colors.color)}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${subtaskProgress}%` }}
|
||||
transition={{ duration: 0.5, ease: 'easeOut' }}
|
||||
@@ -210,10 +225,12 @@ function PhaseStepsIndicator({
|
||||
currentPhase: ExecutionPhase;
|
||||
isStuck: boolean;
|
||||
}) {
|
||||
const phases: { key: ExecutionPhase; label: string }[] = [
|
||||
{ key: 'planning', label: 'Plan' },
|
||||
{ key: 'coding', label: 'Code' },
|
||||
{ key: 'qa_review', label: 'QA' },
|
||||
const { t } = useTranslation('tasks');
|
||||
|
||||
const phases: { key: ExecutionPhase; labelKey: string }[] = [
|
||||
{ key: 'planning', labelKey: 'execution.shortPhases.plan' },
|
||||
{ key: 'coding', labelKey: 'execution.shortPhases.code' },
|
||||
{ key: 'qa_review', labelKey: 'execution.shortPhases.qa' },
|
||||
];
|
||||
|
||||
const getPhaseState = (phaseKey: ExecutionPhase) => {
|
||||
@@ -261,7 +278,7 @@ function PhaseStepsIndicator({
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
{phase.label}
|
||||
{t(phase.labelKey)}
|
||||
</motion.div>
|
||||
{index < phases.length - 1 && (
|
||||
<div
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RefreshCw, X } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
@@ -17,6 +18,7 @@ interface SwapNotification {
|
||||
}
|
||||
|
||||
export function ProactiveSwapListener() {
|
||||
const { t } = useTranslation('common');
|
||||
const [notification, setNotification] = useState<SwapNotification | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
@@ -61,13 +63,13 @@ export function ProactiveSwapListener() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-semibold text-foreground">Account Switched</p>
|
||||
<p className="text-sm font-semibold text-foreground">{t('notification.accountSwitched')}</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Switched from <strong>{notification.fromProfile}</strong> to{' '}
|
||||
{t('notification.swapFrom')} <strong>{notification.fromProfile}</strong> {t('notification.swapTo')}{' '}
|
||||
<strong>{notification.toProfile}</strong>
|
||||
<br />
|
||||
<span className="text-[10px]">
|
||||
({notification.reason} swap)
|
||||
{t('notification.swapReason', { reason: notification.reason })}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTriangle, X } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { useRateLimitStore } from '../stores/rate-limit-store';
|
||||
@@ -7,6 +8,7 @@ import { useRateLimitStore } from '../stores/rate-limit-store';
|
||||
* Clicking on it reopens the rate limit modal.
|
||||
*/
|
||||
export function RateLimitIndicator() {
|
||||
const { t } = useTranslation('common');
|
||||
const {
|
||||
hasPendingRateLimit,
|
||||
pendingRateLimitType,
|
||||
@@ -27,7 +29,7 @@ export function RateLimitIndicator() {
|
||||
|
||||
// Get source info for SDK rate limits
|
||||
const source = pendingRateLimitType === 'sdk' ? sdkRateLimitInfo?.source : null;
|
||||
const sourceLabel = source ? getSourceLabel(source) : 'Claude';
|
||||
const sourceLabel = source ? getSourceLabel(source, t) : t('rateLimit.sources.claude');
|
||||
|
||||
return (
|
||||
<div className="mx-3 mb-3">
|
||||
@@ -46,17 +48,17 @@ export function RateLimitIndicator() {
|
||||
<AlertTriangle className="h-4 w-4 text-warning mt-0.5 shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-medium text-warning">
|
||||
Rate Limited
|
||||
{t('rateLimit.title')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 truncate">
|
||||
{resetTime ? (
|
||||
<>Resets {resetTime}</>
|
||||
t('rateLimit.resetsAt', { time: resetTime })
|
||||
) : (
|
||||
<>{sourceLabel} hit usage limit</>
|
||||
t('rateLimit.hitLimit', { source: sourceLabel })
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-primary mt-1">
|
||||
Click to manage →
|
||||
{t('rateLimit.clickToManage')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -69,20 +71,20 @@ export function RateLimitIndicator() {
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<span className="sr-only">{t('labels.dismiss')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getSourceLabel(source: string): string {
|
||||
function getSourceLabel(source: string, t: (key: string) => string): string {
|
||||
switch (source) {
|
||||
case 'changelog': return 'Changelog';
|
||||
case 'task': return 'Task';
|
||||
case 'roadmap': return 'Roadmap';
|
||||
case 'ideation': return 'Ideation';
|
||||
case 'title-generator': return 'Title Generator';
|
||||
default: return 'Claude';
|
||||
case 'changelog': return t('rateLimit.sources.changelog');
|
||||
case 'task': return t('rateLimit.sources.task');
|
||||
case 'roadmap': return t('rateLimit.sources.roadmap');
|
||||
case 'ideation': return t('rateLimit.sources.ideation');
|
||||
case 'title-generator': return t('rateLimit.sources.titleGenerator');
|
||||
default: return t('rateLimit.sources.claude');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertCircle, ExternalLink, Clock, RefreshCw, User, ChevronDown, Check, Zap, Star, Plus } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -25,6 +26,7 @@ import { useClaudeProfileStore, loadClaudeProfiles, switchTerminalToProfile } fr
|
||||
const CLAUDE_UPGRADE_URL = 'https://claude.ai/upgrade';
|
||||
|
||||
export function RateLimitModal() {
|
||||
const { t } = useTranslation('common');
|
||||
const { isModalOpen, rateLimitInfo, hideRateLimitModal, clearPendingRateLimit } = useRateLimitStore();
|
||||
const { profiles, activeProfileId, isSwitching } = useClaudeProfileStore();
|
||||
const [selectedProfileId, setSelectedProfileId] = useState<string | null>(null);
|
||||
@@ -168,12 +170,12 @@ export function RateLimitModal() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-warning">
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
Claude Code Usage Limit Reached
|
||||
{t('rateLimit.modalTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
You've reached your Claude Code usage limit for this period.
|
||||
{t('rateLimit.modalDescription')}
|
||||
{currentProfile && !currentProfile.isDefault && (
|
||||
<span className="text-muted-foreground"> (Profile: {currentProfile.name})</span>
|
||||
<span className="text-muted-foreground"> ({t('rateLimit.profile', { name: currentProfile.name })})</span>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -185,10 +187,10 @@ export function RateLimitModal() {
|
||||
<Zap className="h-5 w-5 text-green-500 shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Auto-switching to {suggestedProfile?.name}
|
||||
{t('rateLimit.autoSwitching', { name: suggestedProfile?.name })}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
Claude will restart with your other account automatically
|
||||
{t('rateLimit.autoSwitchingDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -200,10 +202,10 @@ export function RateLimitModal() {
|
||||
<Clock className="h-5 w-5 text-muted-foreground shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Resets {rateLimitInfo.resetTime}
|
||||
{t('rateLimit.resetsTime', { time: rateLimitInfo.resetTime })}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
Your usage will be restored at this time
|
||||
{t('rateLimit.usageRestored')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,16 +216,16 @@ export function RateLimitModal() {
|
||||
<div className="rounded-lg border border-accent/50 bg-accent/10 p-4">
|
||||
<h4 className="text-sm font-medium text-foreground mb-2 flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
{hasMultipleProfiles ? 'Switch Claude Account' : 'Use Another Account'}
|
||||
{hasMultipleProfiles ? t('rateLimit.switchAccount') : t('rateLimit.useAnotherAccount')}
|
||||
</h4>
|
||||
|
||||
{hasMultipleProfiles ? (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
{suggestedProfile ? (
|
||||
<>Recommended: <strong>{suggestedProfile.name}</strong> has more capacity available.</>
|
||||
t('rateLimit.recommended', { name: suggestedProfile.name })
|
||||
) : (
|
||||
'You have other Claude subscriptions configured. Switch to continue working:'
|
||||
t('rateLimit.otherSubscriptions')
|
||||
)}
|
||||
</p>
|
||||
|
||||
@@ -232,7 +234,7 @@ export function RateLimitModal() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="flex-1 justify-between">
|
||||
<span className="truncate flex items-center gap-2">
|
||||
{selectedProfile?.name || 'Select account...'}
|
||||
{selectedProfile?.name || t('rateLimit.selectAccount')}
|
||||
{selectedProfileId === rateLimitInfo?.suggestedProfileId && (
|
||||
<Star className="h-3 w-3 text-yellow-500" />
|
||||
)}
|
||||
@@ -268,7 +270,7 @@ export function RateLimitModal() {
|
||||
className="flex items-center gap-2 text-muted-foreground"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add new account...
|
||||
{t('rateLimit.addNewAccount')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -283,12 +285,12 @@ export function RateLimitModal() {
|
||||
{isSwitching ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
Switching...
|
||||
{t('rateLimit.switching')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
Switch
|
||||
{t('buttons.switch')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -304,7 +306,7 @@ export function RateLimitModal() {
|
||||
{availableProfiles.length > 0 && (
|
||||
<div className="flex items-center justify-between mt-4 pt-3 border-t border-border/50">
|
||||
<Label htmlFor="auto-switch" className="text-xs text-muted-foreground cursor-pointer">
|
||||
Auto-switch on rate limit
|
||||
{t('rateLimit.autoSwitchOnRateLimit')}
|
||||
</Label>
|
||||
<Switch
|
||||
id="auto-switch"
|
||||
@@ -317,18 +319,18 @@ export function RateLimitModal() {
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Add another Claude subscription to automatically switch when you hit rate limits.
|
||||
{t('rateLimit.addAnotherSubscription')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Add new account section */}
|
||||
<div className={hasMultipleProfiles ? "mt-4 pt-3 border-t border-border/50" : ""}>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
{hasMultipleProfiles ? 'Add another account:' : 'Connect a Claude account:'}
|
||||
{hasMultipleProfiles ? t('rateLimit.addAnotherAccount') : t('rateLimit.connectAccount')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="Account name (e.g., Work, Personal)"
|
||||
placeholder={t('rateLimit.accountNamePlaceholder')}
|
||||
value={newProfileName}
|
||||
onChange={(e) => setNewProfileName(e.target.value)}
|
||||
className="flex-1 h-8 text-sm"
|
||||
@@ -350,11 +352,11 @@ export function RateLimitModal() {
|
||||
) : (
|
||||
<Plus className="h-3 w-3" />
|
||||
)}
|
||||
Add
|
||||
{t('buttons.add')}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
This will open Claude login to authenticate the new account.
|
||||
{t('rateLimit.willOpenLogin')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,10 +365,10 @@ export function RateLimitModal() {
|
||||
{/* Upgrade prompt */}
|
||||
<div className="rounded-lg border border-primary/30 bg-primary/5 p-4">
|
||||
<h4 className="text-sm font-medium text-foreground mb-2">
|
||||
Upgrade for more usage
|
||||
{t('rateLimit.upgradeTitle')}
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Upgrade your Claude subscription for higher usage limits.
|
||||
{t('rateLimit.upgradeDescription')}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -375,14 +377,14 @@ export function RateLimitModal() {
|
||||
onClick={handleUpgrade}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
Upgrade Subscription
|
||||
{t('rateLimit.upgradeSubscription')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={hideRateLimitModal}>
|
||||
{autoSwitchHappened ? 'Continue' : hasMultipleProfiles ? 'Close' : 'Got it'}
|
||||
{autoSwitchHappened ? t('buttons.continue') : hasMultipleProfiles ? t('buttons.close') : t('buttons.gotIt')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Plus,
|
||||
Settings,
|
||||
@@ -59,24 +60,24 @@ interface SidebarProps {
|
||||
|
||||
interface NavItem {
|
||||
id: SidebarView;
|
||||
label: string;
|
||||
labelKey: string;
|
||||
icon: React.ElementType;
|
||||
shortcut?: string;
|
||||
}
|
||||
|
||||
const projectNavItems: NavItem[] = [
|
||||
{ id: 'kanban', label: 'Kanban Board', icon: LayoutGrid, shortcut: 'K' },
|
||||
{ id: 'terminals', label: 'Agent Terminals', icon: Terminal, shortcut: 'A' },
|
||||
{ id: 'insights', label: 'Insights', icon: Sparkles, shortcut: 'N' },
|
||||
{ id: 'roadmap', label: 'Roadmap', icon: Map, shortcut: 'D' },
|
||||
{ id: 'ideation', label: 'Ideation', icon: Lightbulb, shortcut: 'I' },
|
||||
{ id: 'changelog', label: 'Changelog', icon: FileText, shortcut: 'L' },
|
||||
{ id: 'context', label: 'Context', icon: BookOpen, shortcut: 'C' }
|
||||
{ id: 'kanban', labelKey: 'navigation:items.kanban', icon: LayoutGrid, shortcut: 'K' },
|
||||
{ id: 'terminals', labelKey: 'navigation:items.terminals', icon: Terminal, shortcut: 'A' },
|
||||
{ id: 'insights', labelKey: 'navigation:items.insights', icon: Sparkles, shortcut: 'N' },
|
||||
{ id: 'roadmap', labelKey: 'navigation:items.roadmap', icon: Map, shortcut: 'D' },
|
||||
{ id: 'ideation', labelKey: 'navigation:items.ideation', icon: Lightbulb, shortcut: 'I' },
|
||||
{ id: 'changelog', labelKey: 'navigation:items.changelog', icon: FileText, shortcut: 'L' },
|
||||
{ id: 'context', labelKey: 'navigation:items.context', icon: BookOpen, shortcut: 'C' }
|
||||
];
|
||||
|
||||
const toolsNavItems: NavItem[] = [
|
||||
{ id: 'github-issues', label: 'GitHub Issues', icon: Github, shortcut: 'G' },
|
||||
{ id: 'worktrees', label: 'Worktrees', icon: GitBranch, shortcut: 'W' }
|
||||
{ id: 'github-issues', labelKey: 'navigation:items.githubIssues', icon: Github, shortcut: 'G' },
|
||||
{ id: 'worktrees', labelKey: 'navigation:items.worktrees', icon: GitBranch, shortcut: 'W' }
|
||||
];
|
||||
|
||||
export function Sidebar({
|
||||
@@ -85,6 +86,7 @@ export function Sidebar({
|
||||
activeView = 'kanban',
|
||||
onViewChange
|
||||
}: SidebarProps) {
|
||||
const { t } = useTranslation(['navigation', 'dialogs', 'common']);
|
||||
const projects = useProjectStore((state) => state.projects);
|
||||
const selectedProjectId = useProjectStore((state) => state.selectedProjectId);
|
||||
const selectProject = useProjectStore((state) => state.selectProject);
|
||||
@@ -269,7 +271,7 @@ export function Sidebar({
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4 shrink-0" />
|
||||
<span className="flex-1 text-left">{item.label}</span>
|
||||
<span className="flex-1 text-left">{t(item.labelKey)}</span>
|
||||
{item.shortcut && (
|
||||
<kbd className="pointer-events-none hidden h-5 select-none items-center gap-1 rounded-md border border-border bg-secondary px-1.5 font-mono text-[10px] font-medium text-muted-foreground sm:flex">
|
||||
{item.shortcut}
|
||||
@@ -298,7 +300,7 @@ export function Sidebar({
|
||||
{/* Project Section */}
|
||||
<div className="mb-6">
|
||||
<h3 className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Project
|
||||
{t('sections.project')}
|
||||
</h3>
|
||||
<nav className="space-y-1">
|
||||
{projectNavItems.map(renderNavItem)}
|
||||
@@ -308,7 +310,7 @@ export function Sidebar({
|
||||
{/* Tools Section */}
|
||||
<div>
|
||||
<h3 className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Tools
|
||||
{t('sections.tools')}
|
||||
</h3>
|
||||
<nav className="space-y-1">
|
||||
{toolsNavItems.map(renderNavItem)}
|
||||
@@ -335,10 +337,10 @@ export function Sidebar({
|
||||
onClick={onSettingsClick}
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Settings
|
||||
{t('actions.settings')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Application Settings</TooltipContent>
|
||||
<TooltipContent side="top">{t('tooltips.settings')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -350,7 +352,7 @@ export function Sidebar({
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Help & Feedback</TooltipContent>
|
||||
<TooltipContent side="top">{t('tooltips.help')}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -361,11 +363,11 @@ export function Sidebar({
|
||||
disabled={!selectedProjectId || !selectedProject?.autoBuildPath}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Task
|
||||
{t('actions.newTask')}
|
||||
</Button>
|
||||
{selectedProject && !selectedProject.autoBuildPath && (
|
||||
<p className="mt-2 text-xs text-muted-foreground text-center">
|
||||
Initialize Auto Claude to create tasks
|
||||
{t('messages.initializeToCreateTasks')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -382,19 +384,19 @@ export function Sidebar({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5" />
|
||||
Initialize Auto Claude
|
||||
{t('dialogs:initialize.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
This project doesn't have Auto Claude initialized. Would you like to set it up now?
|
||||
{t('dialogs:initialize.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<div className="rounded-lg bg-muted p-4 text-sm">
|
||||
<p className="font-medium mb-2">This will:</p>
|
||||
<p className="font-medium mb-2">{t('dialogs:initialize.willDo')}</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
|
||||
<li>Create a <code className="text-xs bg-background px-1 py-0.5 rounded">.auto-claude</code> folder in your project</li>
|
||||
<li>Copy the Auto Claude framework files</li>
|
||||
<li>Set up the specs directory for your tasks</li>
|
||||
<li>{t('dialogs:initialize.createFolder')}</li>
|
||||
<li>{t('dialogs:initialize.copyFramework')}</li>
|
||||
<li>{t('dialogs:initialize.setupSpecs')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{!settings.autoBuildPath && (
|
||||
@@ -402,9 +404,9 @@ export function Sidebar({
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-warning mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-warning">Source path not configured</p>
|
||||
<p className="font-medium text-warning">{t('dialogs:initialize.sourcePathNotConfigured')}</p>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Please set the Auto Claude source path in App Settings before initializing.
|
||||
{t('dialogs:initialize.sourcePathNotConfiguredDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -413,7 +415,7 @@ export function Sidebar({
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleSkipInit} disabled={isInitializing}>
|
||||
Skip
|
||||
{t('common:buttons.skip')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleInitialize}
|
||||
@@ -422,12 +424,12 @@ export function Sidebar({
|
||||
{isInitializing ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
Initializing...
|
||||
{t('common:labels.initializing')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Initialize
|
||||
{t('common:buttons.initialize')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -441,15 +443,15 @@ export function Sidebar({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RefreshCw className="h-5 w-5" />
|
||||
Auto Claude
|
||||
{t('dialogs:update.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Project is initialized.
|
||||
{t('dialogs:update.projectInitialized')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowUpdateDialog(false)}>
|
||||
Close
|
||||
{t('common:buttons.close')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive } from 'lucide-react';
|
||||
import { Card, CardContent } from './ui/card';
|
||||
import { Badge } from './ui/badge';
|
||||
@@ -39,6 +40,7 @@ interface TaskCardProps {
|
||||
}
|
||||
|
||||
export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const [isStuck, setIsStuck] = useState(false);
|
||||
const [isRecovering, setIsRecovering] = useState(false);
|
||||
|
||||
@@ -136,15 +138,15 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
const getStatusLabel = (status: string) => {
|
||||
switch (status) {
|
||||
case 'in_progress':
|
||||
return 'Running';
|
||||
return t('labels.running');
|
||||
case 'ai_review':
|
||||
return 'AI Review';
|
||||
return t('labels.aiReview');
|
||||
case 'human_review':
|
||||
return 'Needs Review';
|
||||
return t('labels.needsReview');
|
||||
case 'done':
|
||||
return 'Complete';
|
||||
return t('status.complete');
|
||||
default:
|
||||
return 'Pending';
|
||||
return t('labels.pending');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -152,13 +154,13 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
if (!reason) return null;
|
||||
switch (reason) {
|
||||
case 'completed':
|
||||
return { label: 'Completed', variant: 'success' };
|
||||
return { label: t('reviewReason.completed'), variant: 'success' };
|
||||
case 'errors':
|
||||
return { label: 'Has Errors', variant: 'destructive' };
|
||||
return { label: t('reviewReason.hasErrors'), variant: 'destructive' };
|
||||
case 'qa_rejected':
|
||||
return { label: 'QA Issues', variant: 'warning' };
|
||||
return { label: t('reviewReason.qaIssues'), variant: 'warning' };
|
||||
case 'plan_review':
|
||||
return { label: 'Approve Plan', variant: 'warning' };
|
||||
return { label: t('reviewReason.approvePlan'), variant: 'warning' };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -195,7 +197,7 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
className="text-[10px] px-1.5 py-0.5 flex items-center gap-1 bg-warning/10 text-warning border-warning/30 badge-priority-urgent"
|
||||
>
|
||||
<AlertTriangle className="h-2.5 w-2.5" />
|
||||
Stuck
|
||||
{t('labels.stuck')}
|
||||
</Badge>
|
||||
)}
|
||||
{/* Incomplete indicator - task in human_review but no subtasks completed */}
|
||||
@@ -205,7 +207,7 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
className="text-[10px] px-1.5 py-0.5 flex items-center gap-1 bg-orange-500/10 text-orange-400 border-orange-500/30"
|
||||
>
|
||||
<AlertTriangle className="h-2.5 w-2.5" />
|
||||
Incomplete
|
||||
{t('labels.incomplete')}
|
||||
</Badge>
|
||||
)}
|
||||
{/* Archived indicator - task has been released */}
|
||||
@@ -215,7 +217,7 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
className="text-[10px] px-1.5 py-0.5 flex items-center gap-1 bg-muted text-muted-foreground border-border"
|
||||
>
|
||||
<Archive className="h-2.5 w-2.5" />
|
||||
Archived
|
||||
{t('status.archived')}
|
||||
</Badge>
|
||||
)}
|
||||
{/* Execution phase badge - shown when actively running */}
|
||||
@@ -237,7 +239,7 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
variant={isStuck ? 'warning' : isIncomplete ? 'warning' : getStatusBadgeVariant(task.status)}
|
||||
className="text-[10px] px-1.5 py-0.5"
|
||||
>
|
||||
{isStuck ? 'Needs Recovery' : isIncomplete ? 'Needs Resume' : getStatusLabel(task.status)}
|
||||
{isStuck ? t('labels.needsRecovery') : isIncomplete ? t('labels.needsResume') : getStatusLabel(task.status)}
|
||||
</Badge>
|
||||
)}
|
||||
{/* Review reason badge - explains why task needs human review */}
|
||||
@@ -347,12 +349,12 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
{isRecovering ? (
|
||||
<>
|
||||
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" />
|
||||
Recovering...
|
||||
{t('labels.recovering')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RotateCcw className="mr-1.5 h-3 w-3" />
|
||||
Recover
|
||||
{t('actions.recover')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -364,7 +366,7 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
onClick={handleStartStop}
|
||||
>
|
||||
<Play className="mr-1.5 h-3 w-3" />
|
||||
Resume
|
||||
{t('actions.resume')}
|
||||
</Button>
|
||||
) : task.status === 'done' && !task.metadata?.archivedAt ? (
|
||||
<Button
|
||||
@@ -372,10 +374,10 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
size="sm"
|
||||
className="h-7 px-2.5 hover:bg-muted-foreground/10"
|
||||
onClick={handleArchive}
|
||||
title="Archive task"
|
||||
title={t('tooltips.archiveTask')}
|
||||
>
|
||||
<Archive className="mr-1.5 h-3 w-3" />
|
||||
Archive
|
||||
{t('actions.archive')}
|
||||
</Button>
|
||||
) : (task.status === 'backlog' || task.status === 'in_progress') && (
|
||||
<Button
|
||||
@@ -387,12 +389,12 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
|
||||
{isRunning ? (
|
||||
<>
|
||||
<Square className="mr-1.5 h-3 w-3" />
|
||||
Stop
|
||||
{t('actions.stop')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="mr-1.5 h-3 w-3" />
|
||||
Start
|
||||
{t('actions.start')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FolderOpen, FolderPlus, Clock, ChevronRight, Folder } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from './ui/button';
|
||||
import { Card } from './ui/card';
|
||||
import { ScrollArea } from './ui/scroll-area';
|
||||
@@ -18,6 +19,8 @@ export function WelcomeScreen({
|
||||
onOpenProject,
|
||||
onSelectProject
|
||||
}: WelcomeScreenProps) {
|
||||
const { t } = useTranslation(['welcome', 'common']);
|
||||
|
||||
// Sort projects by updatedAt (most recent first)
|
||||
const recentProjects = [...projects]
|
||||
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
|
||||
@@ -30,10 +33,10 @@ export function WelcomeScreen({
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffMins < 1) return 'Just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
if (diffMins < 1) return t('common:time.justNow');
|
||||
if (diffMins < 60) return t('common:time.minutesAgo', { count: diffMins });
|
||||
if (diffHours < 24) return t('common:time.hoursAgo', { count: diffHours });
|
||||
if (diffDays < 7) return t('common:time.daysAgo', { count: diffDays });
|
||||
return new Date(date).toLocaleDateString();
|
||||
};
|
||||
|
||||
@@ -43,10 +46,10 @@ export function WelcomeScreen({
|
||||
{/* Hero Section */}
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-3xl font-bold text-foreground tracking-tight">
|
||||
Welcome to Auto Claude
|
||||
{t('welcome:hero.title')}
|
||||
</h1>
|
||||
<p className="mt-3 text-muted-foreground">
|
||||
Build software autonomously with AI-powered agents
|
||||
{t('welcome:hero.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +61,7 @@ export function WelcomeScreen({
|
||||
className="gap-2 px-6"
|
||||
>
|
||||
<FolderPlus className="h-5 w-5" />
|
||||
New Project
|
||||
{t('welcome:actions.newProject')}
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
@@ -67,7 +70,7 @@ export function WelcomeScreen({
|
||||
className="gap-2 px-6"
|
||||
>
|
||||
<FolderOpen className="h-5 w-5" />
|
||||
Open Project
|
||||
{t('welcome:actions.openProject')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +80,7 @@ export function WelcomeScreen({
|
||||
<div className="p-4 pb-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||
<Clock className="h-4 w-4" />
|
||||
Recent Projects
|
||||
{t('welcome:recentProjects.title')}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
@@ -126,9 +129,9 @@ export function WelcomeScreen({
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-accent/20 mx-auto mb-4">
|
||||
<Folder className="h-6 w-6 text-accent-foreground" />
|
||||
</div>
|
||||
<h3 className="font-medium text-foreground mb-1">No projects yet</h3>
|
||||
<h3 className="font-medium text-foreground mb-1">{t('welcome:recentProjects.empty')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Create a new project or open an existing one to get started
|
||||
{t('welcome:recentProjects.emptyDescription')}
|
||||
</p>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
BookOpen,
|
||||
ArrowRight
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent } from '../ui/card';
|
||||
|
||||
@@ -62,25 +63,27 @@ export function CompletionStep({
|
||||
onOpenTaskCreator,
|
||||
onOpenSettings
|
||||
}: CompletionStepProps) {
|
||||
const { t } = useTranslation('onboarding');
|
||||
|
||||
const nextSteps = [
|
||||
{
|
||||
icon: <FileText className="h-5 w-5" />,
|
||||
title: 'Create a Task',
|
||||
description: 'Start by creating your first task to see Auto Claude in action.',
|
||||
title: t('completion.createTask.title'),
|
||||
description: t('completion.createTask.description'),
|
||||
action: onOpenTaskCreator,
|
||||
actionLabel: 'Open Task Creator'
|
||||
actionLabel: t('completion.createTask.action')
|
||||
},
|
||||
{
|
||||
icon: <Settings className="h-5 w-5" />,
|
||||
title: 'Customize Settings',
|
||||
description: 'Fine-tune your preferences, configure integrations, or re-run this wizard.',
|
||||
title: t('completion.customizeSettings.title'),
|
||||
description: t('completion.customizeSettings.description'),
|
||||
action: onOpenSettings,
|
||||
actionLabel: 'Open Settings'
|
||||
actionLabel: t('completion.customizeSettings.action')
|
||||
},
|
||||
{
|
||||
icon: <BookOpen className="h-5 w-5" />,
|
||||
title: 'Explore Documentation',
|
||||
description: 'Learn more about advanced features, best practices, and troubleshooting.'
|
||||
title: t('completion.exploreDocs.title'),
|
||||
description: t('completion.exploreDocs.description')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -100,10 +103,10 @@ export function CompletionStep({
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-foreground tracking-tight">
|
||||
You're All Set!
|
||||
{t('completion.title')}
|
||||
</h1>
|
||||
<p className="mt-3 text-muted-foreground text-lg">
|
||||
Auto Claude is ready to help you build amazing software
|
||||
{t('completion.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -114,11 +117,10 @@ export function CompletionStep({
|
||||
<CheckCircle2 className="h-6 w-6 text-success shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-medium text-success">
|
||||
Setup Complete
|
||||
{t('completion.setupComplete')}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-success/80">
|
||||
Your environment is configured and ready. You can start creating tasks
|
||||
immediately or explore the application at your own pace.
|
||||
{t('completion.setupCompleteDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,7 +131,7 @@ export function CompletionStep({
|
||||
<div className="space-y-4 mb-10">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||
<Rocket className="h-4 w-4" />
|
||||
What's Next?
|
||||
{t('completion.whatsNext')}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{nextSteps.map((step, index) => (
|
||||
@@ -153,10 +155,10 @@ export function CompletionStep({
|
||||
className="gap-2 px-10"
|
||||
>
|
||||
<Rocket className="h-5 w-5" />
|
||||
Finish & Start Building
|
||||
{t('completion.finish')}
|
||||
</Button>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
You can always re-run this wizard from Settings → Application
|
||||
{t('completion.rerunHint')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Wand2 } from 'lucide-react';
|
||||
import {
|
||||
FullScreenDialog,
|
||||
@@ -26,12 +27,12 @@ interface OnboardingWizardProps {
|
||||
// Wizard step identifiers
|
||||
type WizardStepId = 'welcome' | 'oauth' | 'memory' | 'completion';
|
||||
|
||||
// Step configuration
|
||||
const WIZARD_STEPS: { id: WizardStepId; label: string }[] = [
|
||||
{ id: 'welcome', label: 'Welcome' },
|
||||
{ id: 'oauth', label: 'Auth' },
|
||||
{ id: 'memory', label: 'Memory' },
|
||||
{ id: 'completion', label: 'Done' }
|
||||
// Step configuration with translation keys
|
||||
const WIZARD_STEPS: { id: WizardStepId; labelKey: string }[] = [
|
||||
{ id: 'welcome', labelKey: 'steps.welcome' },
|
||||
{ id: 'oauth', labelKey: 'steps.auth' },
|
||||
{ id: 'memory', labelKey: 'steps.memory' },
|
||||
{ id: 'completion', labelKey: 'steps.done' }
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,7 @@ export function OnboardingWizard({
|
||||
onOpenTaskCreator,
|
||||
onOpenSettings
|
||||
}: OnboardingWizardProps) {
|
||||
const { t } = useTranslation('onboarding');
|
||||
const { updateSettings } = useSettingsStore();
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
||||
const [completedSteps, setCompletedSteps] = useState<Set<WizardStepId>>(new Set());
|
||||
@@ -61,7 +63,7 @@ export function OnboardingWizard({
|
||||
// Build step data for progress indicator
|
||||
const steps: WizardStep[] = WIZARD_STEPS.map((step, index) => ({
|
||||
id: step.id,
|
||||
label: step.label,
|
||||
label: t(step.labelKey),
|
||||
completed: completedSteps.has(step.id) || index < currentStepIndex
|
||||
}));
|
||||
|
||||
@@ -189,10 +191,10 @@ export function OnboardingWizard({
|
||||
<FullScreenDialogHeader>
|
||||
<FullScreenDialogTitle className="flex items-center gap-3">
|
||||
<Wand2 className="h-6 w-6" />
|
||||
Setup Wizard
|
||||
{t('wizard.title')}
|
||||
</FullScreenDialogTitle>
|
||||
<FullScreenDialogDescription>
|
||||
Configure your Auto Claude environment in a few simple steps
|
||||
{t('wizard.description')}
|
||||
</FullScreenDialogDescription>
|
||||
|
||||
{/* Progress indicator - show for all steps except welcome and completion */}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Sparkles, Zap, Brain, FileCode } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent } from '../ui/card';
|
||||
|
||||
@@ -36,26 +37,28 @@ function FeatureCard({ icon, title, description }: FeatureCardProps) {
|
||||
* Displays a welcome message with a feature overview and actions to get started or skip.
|
||||
*/
|
||||
export function WelcomeStep({ onGetStarted, onSkip }: WelcomeStepProps) {
|
||||
const { t } = useTranslation('onboarding');
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <Sparkles className="h-5 w-5" />,
|
||||
title: 'AI-Powered Development',
|
||||
description: 'Generate code and build features using Claude Code agents'
|
||||
title: t('welcome.features.aiPowered.title'),
|
||||
description: t('welcome.features.aiPowered.description')
|
||||
},
|
||||
{
|
||||
icon: <FileCode className="h-5 w-5" />,
|
||||
title: 'Spec-Driven Workflow',
|
||||
description: 'Define tasks with clear specifications and let Auto Claude handle the implementation'
|
||||
title: t('welcome.features.specDriven.title'),
|
||||
description: t('welcome.features.specDriven.description')
|
||||
},
|
||||
{
|
||||
icon: <Brain className="h-5 w-5" />,
|
||||
title: 'Memory & Context',
|
||||
description: 'Persistent memory across sessions with Graphiti'
|
||||
title: t('welcome.features.memory.title'),
|
||||
description: t('welcome.features.memory.description')
|
||||
},
|
||||
{
|
||||
icon: <Zap className="h-5 w-5" />,
|
||||
title: 'Parallel Execution',
|
||||
description: 'Run multiple agents in parallel for faster development cycles'
|
||||
title: t('welcome.features.parallel.title'),
|
||||
description: t('welcome.features.parallel.description')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -65,10 +68,10 @@ export function WelcomeStep({ onGetStarted, onSkip }: WelcomeStepProps) {
|
||||
{/* Hero Section */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-foreground tracking-tight">
|
||||
Welcome to Auto Claude
|
||||
{t('welcome.title')}
|
||||
</h1>
|
||||
<p className="mt-3 text-muted-foreground text-lg">
|
||||
Build software autonomously with AI-powered agents
|
||||
{t('welcome.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -87,9 +90,7 @@ export function WelcomeStep({ onGetStarted, onSkip }: WelcomeStepProps) {
|
||||
{/* Description */}
|
||||
<div className="text-center mb-8">
|
||||
<p className="text-muted-foreground">
|
||||
This wizard will help you set up your environment in just a few steps.
|
||||
You can configure your Claude OAuth token, set up memory features,
|
||||
and create your first task.
|
||||
{t('wizard.helpText')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +102,7 @@ export function WelcomeStep({ onGetStarted, onSkip }: WelcomeStepProps) {
|
||||
className="gap-2 px-8"
|
||||
>
|
||||
<Sparkles className="h-5 w-5" />
|
||||
Get Started
|
||||
{t('welcome.getStarted')}
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
@@ -109,7 +110,7 @@ export function WelcomeStep({ onGetStarted, onSkip }: WelcomeStepProps) {
|
||||
onClick={onSkip}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Skip Setup
|
||||
{t('welcome.skip')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
RefreshCw,
|
||||
CheckCircle2,
|
||||
@@ -20,7 +21,8 @@ import type {
|
||||
AutoBuildSourceUpdateCheck,
|
||||
AutoBuildSourceUpdateProgress,
|
||||
AppUpdateAvailableEvent,
|
||||
AppUpdateProgress
|
||||
AppUpdateProgress,
|
||||
NotificationSettings
|
||||
} from '../../../shared/types';
|
||||
|
||||
/**
|
||||
@@ -71,6 +73,8 @@ interface AdvancedSettingsProps {
|
||||
* Advanced settings for updates and notifications
|
||||
*/
|
||||
export function AdvancedSettings({ settings, onSettingsChange, section, version }: AdvancedSettingsProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
|
||||
// Auto Claude source update state
|
||||
const [sourceUpdateCheck, setSourceUpdateCheck] = useState<AutoBuildSourceUpdateCheck | null>(null);
|
||||
const [isCheckingSourceUpdate, setIsCheckingSourceUpdate] = useState(false);
|
||||
@@ -203,8 +207,8 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
if (section === 'updates') {
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Updates"
|
||||
description="Manage Auto Claude updates"
|
||||
title={t('updates.title')}
|
||||
description={t('updates.description')}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Electron App Update Section */}
|
||||
@@ -212,20 +216,20 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
<div className="rounded-lg border-2 border-info/50 bg-info/5 p-5 space-y-4">
|
||||
<div className="flex items-center gap-2 text-info">
|
||||
<Sparkles className="h-5 w-5" />
|
||||
<h3 className="font-semibold">App Update Ready</h3>
|
||||
<h3 className="font-semibold">{t('updates.appUpdateReady')}</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||
New Version
|
||||
{t('updates.newVersion')}
|
||||
</p>
|
||||
<p className="text-base font-medium text-foreground">
|
||||
{appUpdateInfo?.version || 'Unknown'}
|
||||
</p>
|
||||
{appUpdateInfo?.releaseDate && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Released {new Date(appUpdateInfo.releaseDate).toLocaleDateString()}
|
||||
{t('updates.released')} {new Date(appUpdateInfo.releaseDate).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -249,7 +253,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{isDownloadingAppUpdate && appDownloadProgress && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Downloading...</span>
|
||||
<span className="text-muted-foreground">{t('updates.downloading')}</span>
|
||||
<span className="text-foreground font-medium">
|
||||
{Math.round(appDownloadProgress.percent)}%
|
||||
</span>
|
||||
@@ -265,7 +269,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{isAppUpdateDownloaded && (
|
||||
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||
<span>Update downloaded! Click Install to restart and apply the update.</span>
|
||||
<span>{t('updates.updateDownloaded')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -274,7 +278,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{isAppUpdateDownloaded ? (
|
||||
<Button onClick={handleInstallAppUpdate}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Install and Restart
|
||||
{t('updates.installAndRestart')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -284,12 +288,12 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{isDownloadingAppUpdate ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
Downloading...
|
||||
{t('updates.downloading')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download Update
|
||||
{t('updates.downloadUpdate')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -302,9 +306,9 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
<div className="rounded-lg border border-border bg-muted/50 p-5 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">Version</p>
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">{t('updates.version')}</p>
|
||||
<p className="text-base font-medium text-foreground">
|
||||
{displayVersion || 'Loading...'}
|
||||
{displayVersion || t('updates.loading')}
|
||||
</p>
|
||||
</div>
|
||||
{isCheckingSourceUpdate ? (
|
||||
@@ -319,13 +323,13 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{/* Update status */}
|
||||
{isCheckingSourceUpdate ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Checking for updates...
|
||||
{t('updates.checkingForUpdates')}
|
||||
</p>
|
||||
) : sourceUpdateCheck ? (
|
||||
<>
|
||||
{sourceUpdateCheck.latestVersion && sourceUpdateCheck.updateAvailable && (
|
||||
<p className="text-sm text-info">
|
||||
New version available: {sourceUpdateCheck.latestVersion}
|
||||
{t('updates.newVersionAvailable')} {sourceUpdateCheck.latestVersion}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -335,7 +339,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
|
||||
{!sourceUpdateCheck.updateAvailable && !sourceUpdateCheck.error && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You're running the latest version.
|
||||
{t('updates.latestVersion')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -353,7 +357,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
className="inline-flex items-center gap-1.5 text-sm text-info hover:text-info/80 hover:underline transition-colors"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
View full release on GitHub
|
||||
{t('updates.viewRelease')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -380,7 +384,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
) : (
|
||||
<Button onClick={handleDownloadSourceUpdate}>
|
||||
<CloudDownload className="mr-2 h-4 w-4" />
|
||||
Download Update
|
||||
{t('updates.downloadUpdate')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -388,7 +392,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Unable to check for updates
|
||||
{t('updates.unableToCheck')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -400,16 +404,16 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
disabled={isCheckingSourceUpdate}
|
||||
>
|
||||
<RefreshCw className={cn('mr-2 h-4 w-4', isCheckingSourceUpdate && 'animate-spin')} />
|
||||
Check for Updates
|
||||
{t('updates.checkForUpdates')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-4 rounded-lg border border-border">
|
||||
<div className="space-y-1">
|
||||
<Label className="font-medium text-foreground">Auto-Update Projects</Label>
|
||||
<Label className="font-medium text-foreground">{t('updates.autoUpdateProjects')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically update Auto Claude in projects when a new version is available
|
||||
{t('updates.autoUpdateProjectsDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -422,9 +426,9 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
|
||||
<div className="flex items-center justify-between p-4 rounded-lg border border-border">
|
||||
<div className="space-y-1">
|
||||
<Label className="font-medium text-foreground">Beta Updates</Label>
|
||||
<Label className="font-medium text-foreground">{t('updates.betaUpdates')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Receive pre-release beta versions with new features (may be less stable)
|
||||
{t('updates.betaUpdatesDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -440,25 +444,31 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
}
|
||||
|
||||
// notifications section
|
||||
const notificationItems: Array<{
|
||||
key: keyof NotificationSettings;
|
||||
labelKey: string;
|
||||
descriptionKey: string;
|
||||
}> = [
|
||||
{ key: 'onTaskComplete', labelKey: 'notifications.onTaskComplete', descriptionKey: 'notifications.onTaskCompleteDescription' },
|
||||
{ key: 'onTaskFailed', labelKey: 'notifications.onTaskFailed', descriptionKey: 'notifications.onTaskFailedDescription' },
|
||||
{ key: 'onReviewNeeded', labelKey: 'notifications.onReviewNeeded', descriptionKey: 'notifications.onReviewNeededDescription' },
|
||||
{ key: 'sound', labelKey: 'notifications.sound', descriptionKey: 'notifications.soundDescription' }
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Notifications"
|
||||
description="Configure default notification preferences"
|
||||
title={t('notifications.title')}
|
||||
description={t('notifications.description')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ key: 'onTaskComplete', label: 'On Task Complete', description: 'Notify when a task finishes successfully' },
|
||||
{ key: 'onTaskFailed', label: 'On Task Failed', description: 'Notify when a task encounters an error' },
|
||||
{ key: 'onReviewNeeded', label: 'On Review Needed', description: 'Notify when QA requires your review' },
|
||||
{ key: 'sound', label: 'Sound', description: 'Play sound with notifications' }
|
||||
].map((item) => (
|
||||
{notificationItems.map((item) => (
|
||||
<div key={item.key} className="flex items-center justify-between p-4 rounded-lg border border-border">
|
||||
<div className="space-y-1">
|
||||
<Label className="font-medium text-foreground">{item.label}</Label>
|
||||
<p className="text-sm text-muted-foreground">{item.description}</p>
|
||||
<Label className="font-medium text-foreground">{t(item.labelKey)}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t(item.descriptionKey)}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.notifications[item.key as keyof typeof settings.notifications]}
|
||||
checked={settings.notifications[item.key]}
|
||||
onCheckedChange={(checked) =>
|
||||
onSettingsChange({
|
||||
...settings,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Brain, Scale, Zap, Check, Sparkles, ChevronDown, ChevronUp, RotateCcw } from 'lucide-react';
|
||||
import { cn } from '../../lib/utils';
|
||||
import {
|
||||
@@ -31,12 +32,7 @@ const iconMap: Record<string, React.ElementType> = {
|
||||
Sparkles
|
||||
};
|
||||
|
||||
const PHASE_LABELS: Record<keyof PhaseModelConfig, { label: string; description: string }> = {
|
||||
spec: { label: 'Spec Creation', description: 'Discovery, requirements, context gathering' },
|
||||
planning: { label: 'Planning', description: 'Implementation planning and architecture' },
|
||||
coding: { label: 'Coding', description: 'Actual code implementation' },
|
||||
qa: { label: 'QA Review', description: 'Quality assurance and validation' }
|
||||
};
|
||||
const PHASE_KEYS: Array<keyof PhaseModelConfig> = ['spec', 'planning', 'coding', 'qa'];
|
||||
|
||||
/**
|
||||
* Agent Profile Settings component
|
||||
@@ -44,6 +40,7 @@ const PHASE_LABELS: Record<keyof PhaseModelConfig, { label: string; description:
|
||||
* Used in the Settings page under Agent Settings
|
||||
*/
|
||||
export function AgentProfileSettings() {
|
||||
const { t } = useTranslation('settings');
|
||||
const settings = useSettingsStore((state) => state.settings);
|
||||
const selectedProfileId = settings.selectedAgentProfile || 'auto';
|
||||
const [showPhaseConfig, setShowPhaseConfig] = useState(selectedProfileId === 'auto');
|
||||
@@ -102,8 +99,7 @@ export function AgentProfileSettings() {
|
||||
* Check if current phase config differs from defaults
|
||||
*/
|
||||
const hasCustomConfig = (): boolean => {
|
||||
const phases: Array<keyof PhaseModelConfig> = ['spec', 'planning', 'coding', 'qa'];
|
||||
return phases.some(
|
||||
return PHASE_KEYS.some(
|
||||
phase =>
|
||||
currentPhaseModels[phase] !== DEFAULT_PHASE_MODELS[phase] ||
|
||||
currentPhaseThinking[phase] !== DEFAULT_PHASE_THINKING[phase]
|
||||
@@ -164,7 +160,7 @@ export function AgentProfileSettings() {
|
||||
{getModelLabel(profile.model)}
|
||||
</span>
|
||||
<span className="inline-flex items-center rounded bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
|
||||
{getThinkingLabel(profile.thinkingLevel)} Thinking
|
||||
{getThinkingLabel(profile.thinkingLevel)} {t('agentProfile.thinking')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,16 +171,14 @@ export function AgentProfileSettings() {
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Default Agent Profile"
|
||||
description="Select a preset configuration for model and thinking level"
|
||||
title={t('agentProfile.title')}
|
||||
description={t('agentProfile.sectionDescription')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* Description */}
|
||||
<div className="rounded-lg bg-muted/50 p-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Agent profiles provide preset configurations for Claude model and thinking level.
|
||||
When you create a new task, these settings will be used as defaults. You can always
|
||||
override them in the task creation wizard.
|
||||
{t('agentProfile.profilesInfo')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -203,9 +197,9 @@ export function AgentProfileSettings() {
|
||||
className="flex w-full items-center justify-between p-4 text-left hover:bg-muted/50 transition-colors rounded-t-lg"
|
||||
>
|
||||
<div>
|
||||
<h4 className="font-medium text-sm text-foreground">Phase Configuration</h4>
|
||||
<h4 className="font-medium text-sm text-foreground">{t('agentProfile.phaseConfiguration')}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
Customize model and thinking level for each phase
|
||||
{t('agentProfile.phaseConfigurationDescription')}
|
||||
</p>
|
||||
</div>
|
||||
{showPhaseConfig ? (
|
||||
@@ -228,27 +222,27 @@ export function AgentProfileSettings() {
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<RotateCcw className="h-3 w-3 mr-1.5" />
|
||||
Reset to defaults
|
||||
{t('agentProfile.resetToDefaults')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Phase Configuration Grid */}
|
||||
<div className="space-y-4">
|
||||
{(Object.keys(PHASE_LABELS) as Array<keyof PhaseModelConfig>).map((phase) => (
|
||||
{PHASE_KEYS.map((phase) => (
|
||||
<div key={phase} className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-foreground">
|
||||
{PHASE_LABELS[phase].label}
|
||||
{t(`agentProfile.phases.${phase}.label`)}
|
||||
</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{PHASE_LABELS[phase].description}
|
||||
{t(`agentProfile.phases.${phase}.description`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* Model Select */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">Model</Label>
|
||||
<Label className="text-xs text-muted-foreground">{t('agentProfile.model')}</Label>
|
||||
<Select
|
||||
value={currentPhaseModels[phase]}
|
||||
onValueChange={(value) => handlePhaseModelChange(phase, value as ModelTypeShort)}
|
||||
@@ -267,7 +261,7 @@ export function AgentProfileSettings() {
|
||||
</div>
|
||||
{/* Thinking Level Select */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">Thinking Level</Label>
|
||||
<Label className="text-xs text-muted-foreground">{t('agentProfile.thinkingLevel')}</Label>
|
||||
<Select
|
||||
value={currentPhaseThinking[phase]}
|
||||
onValueChange={(value) => handlePhaseThinkingChange(phase, value as ThinkingLevel)}
|
||||
@@ -291,8 +285,7 @@ export function AgentProfileSettings() {
|
||||
|
||||
{/* Info note */}
|
||||
<p className="text-[10px] text-muted-foreground mt-4 pt-3 border-t border-border">
|
||||
These settings will be used as defaults when creating new tasks with the Auto profile.
|
||||
You can override them per-task in the task creation wizard.
|
||||
{t('agentProfile.phaseConfigNote')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Settings,
|
||||
Save,
|
||||
@@ -14,7 +15,8 @@ import {
|
||||
Github,
|
||||
Database,
|
||||
Sparkles,
|
||||
Monitor
|
||||
Monitor,
|
||||
Globe
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
FullScreenDialog,
|
||||
@@ -31,6 +33,7 @@ import { cn } from '../../lib/utils';
|
||||
import { useSettings } from './hooks/useSettings';
|
||||
import { ThemeSettings } from './ThemeSettings';
|
||||
import { DisplaySettings } from './DisplaySettings';
|
||||
import { LanguageSettings } from './LanguageSettings';
|
||||
import { GeneralSettings } from './GeneralSettings';
|
||||
import { IntegrationSettings } from './IntegrationSettings';
|
||||
import { AdvancedSettings } from './AdvancedSettings';
|
||||
@@ -48,31 +51,30 @@ interface AppSettingsDialogProps {
|
||||
}
|
||||
|
||||
// App-level settings sections
|
||||
export type AppSection = 'appearance' | 'display' | 'agent' | 'paths' | 'integrations' | 'updates' | 'notifications';
|
||||
export type AppSection = 'appearance' | 'display' | 'language' | 'agent' | 'paths' | 'integrations' | 'updates' | 'notifications';
|
||||
|
||||
interface NavItem<T extends string> {
|
||||
interface NavItemConfig<T extends string> {
|
||||
id: T;
|
||||
label: string;
|
||||
icon: React.ElementType;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const appNavItems: NavItem<AppSection>[] = [
|
||||
{ id: 'appearance', label: 'Appearance', icon: Palette, description: 'Theme and visual preferences' },
|
||||
{ id: 'display', label: 'Display', icon: Monitor, description: 'UI scale and zoom' },
|
||||
{ id: 'agent', label: 'Agent Settings', icon: Bot, description: 'Default model and framework' },
|
||||
{ id: 'paths', label: 'Paths', icon: FolderOpen, description: 'Python and framework paths' },
|
||||
{ id: 'integrations', label: 'Integrations', icon: Key, description: 'API keys & Claude accounts' },
|
||||
{ id: 'updates', label: 'Updates', icon: Package, description: 'Auto Claude updates' },
|
||||
{ id: 'notifications', label: 'Notifications', icon: Bell, description: 'Alert preferences' }
|
||||
const appNavItemsConfig: NavItemConfig<AppSection>[] = [
|
||||
{ id: 'appearance', icon: Palette },
|
||||
{ id: 'display', icon: Monitor },
|
||||
{ id: 'language', icon: Globe },
|
||||
{ id: 'agent', icon: Bot },
|
||||
{ id: 'paths', icon: FolderOpen },
|
||||
{ id: 'integrations', icon: Key },
|
||||
{ id: 'updates', icon: Package },
|
||||
{ id: 'notifications', icon: Bell }
|
||||
];
|
||||
|
||||
const projectNavItems: NavItem<ProjectSettingsSection>[] = [
|
||||
{ id: 'general', label: 'General', icon: Settings2, description: 'Auto-Build and agent config' },
|
||||
{ id: 'claude', label: 'Claude Auth', icon: Key, description: 'Claude authentication' },
|
||||
{ id: 'linear', label: 'Linear', icon: Zap, description: 'Linear integration' },
|
||||
{ id: 'github', label: 'GitHub', icon: Github, description: 'GitHub issues sync' },
|
||||
{ id: 'memory', label: 'Memory', icon: Database, description: 'Graphiti memory backend' }
|
||||
const projectNavItemsConfig: NavItemConfig<ProjectSettingsSection>[] = [
|
||||
{ id: 'general', icon: Settings2 },
|
||||
{ id: 'claude', icon: Key },
|
||||
{ id: 'linear', icon: Zap },
|
||||
{ id: 'github', icon: Github },
|
||||
{ id: 'memory', icon: Database }
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -80,6 +82,7 @@ const projectNavItems: NavItem<ProjectSettingsSection>[] = [
|
||||
* Coordinates app and project settings sections
|
||||
*/
|
||||
export function AppSettingsDialog({ open, onOpenChange, initialSection, initialProjectSection, onRerunWizard }: AppSettingsDialogProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
const { settings, setSettings, isSaving, error, saveSettings, revertTheme, commitTheme } = useSettings();
|
||||
const [version, setVersion] = useState<string>('');
|
||||
|
||||
@@ -162,6 +165,8 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
return <ThemeSettings settings={settings} onSettingsChange={setSettings} />;
|
||||
case 'display':
|
||||
return <DisplaySettings settings={settings} onSettingsChange={setSettings} />;
|
||||
case 'language':
|
||||
return <LanguageSettings settings={settings} onSettingsChange={setSettings} />;
|
||||
case 'agent':
|
||||
return <GeneralSettings settings={settings} onSettingsChange={setSettings} section="agent" />;
|
||||
case 'paths':
|
||||
@@ -207,10 +212,10 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
<FullScreenDialogHeader>
|
||||
<FullScreenDialogTitle className="flex items-center gap-3">
|
||||
<Settings className="h-6 w-6" />
|
||||
Settings
|
||||
{t('title')}
|
||||
</FullScreenDialogTitle>
|
||||
<FullScreenDialogDescription>
|
||||
Configure application and project settings
|
||||
{t('tabs.app')} & {t('tabs.project')}
|
||||
</FullScreenDialogDescription>
|
||||
</FullScreenDialogHeader>
|
||||
|
||||
@@ -223,10 +228,10 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
{/* APPLICATION Section */}
|
||||
<div>
|
||||
<h3 className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Application
|
||||
{t('tabs.app')}
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{appNavItems.map((item) => {
|
||||
{appNavItemsConfig.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = activeTopLevel === 'app' && appSection === item.id;
|
||||
return (
|
||||
@@ -245,8 +250,8 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
>
|
||||
<Icon className="h-5 w-5 mt-0.5 shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium text-sm">{item.label}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{item.description}</div>
|
||||
<div className="font-medium text-sm">{t(`sections.${item.id}.title`)}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t(`sections.${item.id}.description`)}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -267,8 +272,8 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
>
|
||||
<Sparkles className="h-5 w-5 mt-0.5 shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium text-sm">Re-run Wizard</div>
|
||||
<div className="text-xs text-muted-foreground truncate">Start the setup wizard again</div>
|
||||
<div className="font-medium text-sm">{t('actions.rerunWizard')}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('actions.rerunWizardDescription')}</div>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
@@ -278,7 +283,7 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
{/* PROJECT Section */}
|
||||
<div>
|
||||
<h3 className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Project
|
||||
{t('tabs.project')}
|
||||
</h3>
|
||||
|
||||
{/* Project Selector */}
|
||||
@@ -291,7 +296,7 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
|
||||
{/* Project Nav Items */}
|
||||
<div className="space-y-1">
|
||||
{projectNavItems.map((item) => {
|
||||
{projectNavItemsConfig.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = activeTopLevel === 'project' && projectSection === item.id;
|
||||
return (
|
||||
@@ -313,8 +318,8 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
>
|
||||
<Icon className="h-5 w-5 mt-0.5 shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium text-sm">{item.label}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{item.description}</div>
|
||||
<div className="font-medium text-sm">{t(`projectSections.${item.id}.title`)}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t(`projectSections.${item.id}.description`)}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -327,7 +332,7 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
{version && (
|
||||
<div className="mt-8 pt-4 border-t border-border">
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
Version {version}
|
||||
{t('updates.version')} {version}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -352,7 +357,7 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
</div>
|
||||
)}
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
{t('common:buttons.cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
@@ -361,12 +366,12 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
|
||||
{(isSaving || (activeTopLevel === 'project' && projectSettingsHook?.isSaving)) ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
{t('common:buttons.saving', 'Saving...')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
Save Settings
|
||||
{t('actions.save')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Monitor, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Label } from '../ui/label';
|
||||
import { SettingsSection } from './SettingsSection';
|
||||
@@ -11,11 +12,11 @@ interface DisplaySettingsProps {
|
||||
onSettingsChange: (settings: AppSettings) => void;
|
||||
}
|
||||
|
||||
// Preset scale values (100%, 125%, 150%)
|
||||
// Preset scale values with translation keys
|
||||
const SCALE_PRESETS = [
|
||||
{ value: UI_SCALE_DEFAULT, label: '100%', description: 'Default' },
|
||||
{ value: 125, label: '125%', description: 'Comfortable' },
|
||||
{ value: 150, label: '150%', description: 'Large' }
|
||||
{ value: UI_SCALE_DEFAULT, label: '100%', descriptionKey: 'scale.default' },
|
||||
{ value: 125, label: '125%', descriptionKey: 'scale.comfortable' },
|
||||
{ value: 150, label: '150%', descriptionKey: 'scale.large' }
|
||||
] as const;
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ const SCALE_PRESETS = [
|
||||
* Changes apply immediately for live preview (like theme), saved on "Save Settings"
|
||||
*/
|
||||
export function DisplaySettings({ settings, onSettingsChange }: DisplaySettingsProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
const updateStoreSettings = useSettingsStore((state) => state.updateSettings);
|
||||
|
||||
const currentScale = settings.uiScale ?? UI_SCALE_DEFAULT;
|
||||
@@ -45,15 +47,15 @@ export function DisplaySettings({ settings, onSettingsChange }: DisplaySettingsP
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Display"
|
||||
description="Adjust the size of UI elements"
|
||||
title={t('sections.display.title')}
|
||||
description={t('sections.display.description')}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Preset Buttons */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium text-foreground">Scale Presets</Label>
|
||||
<Label className="text-sm font-medium text-foreground">{t('scale.presets')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Quick scale options for common preferences
|
||||
{t('scale.presetsDescription')}
|
||||
</p>
|
||||
<div className="grid grid-cols-3 gap-3 max-w-md pt-1">
|
||||
{SCALE_PRESETS.map((preset) => {
|
||||
@@ -73,7 +75,7 @@ export function DisplaySettings({ settings, onSettingsChange }: DisplaySettingsP
|
||||
<Monitor className="h-4 w-4" />
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-medium">{preset.label}</div>
|
||||
<div className="text-xs text-muted-foreground">{preset.description}</div>
|
||||
<div className="text-xs text-muted-foreground">{t(preset.descriptionKey)}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -84,7 +86,7 @@ export function DisplaySettings({ settings, onSettingsChange }: DisplaySettingsP
|
||||
{/* Fine-tune Slider */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-foreground">Fine-tune Scale</Label>
|
||||
<Label className="text-sm font-medium text-foreground">{t('scale.fineTune')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-mono text-muted-foreground">
|
||||
{currentScale}%
|
||||
@@ -105,7 +107,7 @@ export function DisplaySettings({ settings, onSettingsChange }: DisplaySettingsP
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Adjust from {UI_SCALE_MIN}% to {UI_SCALE_MAX}% in {UI_SCALE_STEP}% increments
|
||||
{t('scale.fineTuneDescription')}
|
||||
</p>
|
||||
|
||||
{/* Slider with icons */}
|
||||
@@ -154,7 +156,7 @@ export function DisplaySettings({ settings, onSettingsChange }: DisplaySettingsP
|
||||
{/* Preview hint */}
|
||||
<div className="rounded-lg bg-muted/50 border border-border p-4 text-sm">
|
||||
<p className="text-muted-foreground">
|
||||
Changes preview immediately. Click <strong className="text-foreground">Save Settings</strong> to persist your preferences.
|
||||
{t('scale.preview')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Label } from '../ui/label';
|
||||
import { Input } from '../ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||
@@ -23,6 +24,8 @@ interface GeneralSettingsProps {
|
||||
* General settings component for agent configuration and paths
|
||||
*/
|
||||
export function GeneralSettings({ settings, onSettingsChange, section }: GeneralSettingsProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
|
||||
if (section === 'agent') {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -31,13 +34,13 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
|
||||
{/* Other Agent Settings */}
|
||||
<SettingsSection
|
||||
title="Other Agent Settings"
|
||||
description="Additional agent configuration options"
|
||||
title={t('general.otherAgentSettings')}
|
||||
description={t('general.otherAgentSettingsDescription')}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="agentFramework" className="text-sm font-medium text-foreground">Agent Framework</Label>
|
||||
<p className="text-sm text-muted-foreground">The coding framework used for autonomous tasks</p>
|
||||
<Label htmlFor="agentFramework" className="text-sm font-medium text-foreground">{t('general.agentFramework')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('general.agentFrameworkDescription')}</p>
|
||||
<Select
|
||||
value={settings.agentFramework}
|
||||
onValueChange={(value) => onSettingsChange({ ...settings, agentFramework: value })}
|
||||
@@ -46,7 +49,7 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto-claude">Auto Claude</SelectItem>
|
||||
<SelectItem value="auto-claude">{t('general.agentFrameworkAutoClaude')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -54,10 +57,10 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
<div className="flex items-center justify-between max-w-md">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="autoNameTerminals" className="text-sm font-medium text-foreground">
|
||||
AI Terminal Naming
|
||||
{t('general.aiTerminalNaming')}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically name terminals based on commands (uses Haiku)
|
||||
{t('general.aiTerminalNamingDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -71,9 +74,9 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
{/* Feature Model Configuration */}
|
||||
<div className="space-y-4 pt-4 border-t border-border">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-sm font-medium text-foreground">Feature Model Settings</Label>
|
||||
<Label className="text-sm font-medium text-foreground">{t('general.featureModelSettings')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Model and thinking level for Insights, Ideation, and Roadmap
|
||||
{t('general.featureModelSettingsDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -94,7 +97,7 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
<div className="grid grid-cols-2 gap-3 max-w-md">
|
||||
{/* Model Select */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">Model</Label>
|
||||
<Label className="text-xs text-muted-foreground">{t('general.model')}</Label>
|
||||
<Select
|
||||
value={featureModels[feature]}
|
||||
onValueChange={(value) => {
|
||||
@@ -116,7 +119,7 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
</div>
|
||||
{/* Thinking Level Select */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">Thinking Level</Label>
|
||||
<Label className="text-xs text-muted-foreground">{t('general.thinkingLevel')}</Label>
|
||||
<Select
|
||||
value={featureThinking[feature]}
|
||||
onValueChange={(value) => {
|
||||
@@ -150,27 +153,27 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General
|
||||
// paths section
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Paths"
|
||||
description="Configure executable and framework paths"
|
||||
title={t('general.paths')}
|
||||
description={t('general.pathsDescription')}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="pythonPath" className="text-sm font-medium text-foreground">Python Path</Label>
|
||||
<p className="text-sm text-muted-foreground">Path to Python executable (leave empty for default)</p>
|
||||
<Label htmlFor="pythonPath" className="text-sm font-medium text-foreground">{t('general.pythonPath')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('general.pythonPathDescription')}</p>
|
||||
<Input
|
||||
id="pythonPath"
|
||||
placeholder="python3 (default)"
|
||||
placeholder={t('general.pythonPathPlaceholder')}
|
||||
className="w-full max-w-lg"
|
||||
value={settings.pythonPath || ''}
|
||||
onChange={(e) => onSettingsChange({ ...settings, pythonPath: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="autoBuildPath" className="text-sm font-medium text-foreground">Auto Claude Path</Label>
|
||||
<p className="text-sm text-muted-foreground">Relative path to auto-claude directory in projects</p>
|
||||
<Label htmlFor="autoBuildPath" className="text-sm font-medium text-foreground">{t('general.autoClaudePath')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('general.autoClaudePathDescription')}</p>
|
||||
<Input
|
||||
id="autoBuildPath"
|
||||
placeholder="auto-claude (default)"
|
||||
placeholder={t('general.autoClaudePathPlaceholder')}
|
||||
className="w-full max-w-lg"
|
||||
value={settings.autoBuildPath || ''}
|
||||
onChange={(e) => onSettingsChange({ ...settings, autoBuildPath: e.target.value })}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Key,
|
||||
Eye,
|
||||
@@ -38,6 +39,8 @@ interface IntegrationSettingsProps {
|
||||
* Integration settings for Claude accounts and API keys
|
||||
*/
|
||||
export function IntegrationSettings({ settings, onSettingsChange, isOpen }: IntegrationSettingsProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
const { t: tCommon } = useTranslation('common');
|
||||
// Password visibility toggle for global API keys
|
||||
const [showGlobalOpenAIKey, setShowGlobalOpenAIKey] = useState(false);
|
||||
|
||||
@@ -291,20 +294,20 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Integrations"
|
||||
description="Manage Claude accounts and API keys"
|
||||
title={t('integrations.title')}
|
||||
description={t('integrations.description')}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Claude Accounts Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
<h4 className="text-sm font-semibold text-foreground">Claude Accounts</h4>
|
||||
<h4 className="text-sm font-semibold text-foreground">{t('integrations.claudeAccounts')}</h4>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-muted/30 border border-border p-4">
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Add multiple Claude subscriptions to automatically switch between them when you hit rate limits.
|
||||
{t('integrations.claudeAccountsDescription')}
|
||||
</p>
|
||||
|
||||
{/* Accounts list */}
|
||||
@@ -314,7 +317,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
</div>
|
||||
) : claudeProfiles.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border p-4 text-center mb-4">
|
||||
<p className="text-sm text-muted-foreground">No accounts configured yet</p>
|
||||
<p className="text-sm text-muted-foreground">{t('integrations.noAccountsYet')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2 mb-4">
|
||||
@@ -376,22 +379,22 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-sm font-medium text-foreground">{profile.name}</span>
|
||||
{profile.isDefault && (
|
||||
<span className="text-xs bg-muted px-1.5 py-0.5 rounded">Default</span>
|
||||
<span className="text-xs bg-muted px-1.5 py-0.5 rounded">{t('integrations.default')}</span>
|
||||
)}
|
||||
{profile.id === activeProfileId && (
|
||||
<span className="text-xs bg-primary/20 text-primary px-1.5 py-0.5 rounded flex items-center gap-1">
|
||||
<Star className="h-3 w-3" />
|
||||
Active
|
||||
{t('integrations.active')}
|
||||
</span>
|
||||
)}
|
||||
{(profile.oauthToken || (profile.isDefault && profile.configDir)) ? (
|
||||
<span className="text-xs bg-success/20 text-success px-1.5 py-0.5 rounded flex items-center gap-1">
|
||||
<Check className="h-3 w-3" />
|
||||
Authenticated
|
||||
{t('integrations.authenticated')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs bg-warning/20 text-warning px-1.5 py-0.5 rounded">
|
||||
Needs Auth
|
||||
{t('integrations.needsAuth')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -419,7 +422,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
) : (
|
||||
<LogIn className="h-3 w-3" />
|
||||
)}
|
||||
Authenticate
|
||||
{t('integrations.authenticate')}
|
||||
</Button>
|
||||
) : (
|
||||
/* Re-authenticate button for already authenticated profiles */
|
||||
@@ -446,7 +449,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
className="gap-1 h-7 text-xs"
|
||||
>
|
||||
<Check className="h-3 w-3" />
|
||||
Set Active
|
||||
{t('integrations.setActive')}
|
||||
</Button>
|
||||
)}
|
||||
{/* Toggle token entry button */}
|
||||
@@ -498,10 +501,10 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div className="bg-muted/30 rounded-lg p-3 mt-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium text-muted-foreground">
|
||||
Manual Token Entry
|
||||
{t('integrations.manualTokenEntry')}
|
||||
</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Run <code className="px-1 py-0.5 bg-muted rounded font-mono text-xs">claude setup-token</code> to get your token
|
||||
{t('integrations.runSetupToken')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -509,7 +512,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showManualToken ? 'text' : 'password'}
|
||||
placeholder="sk-ant-oat01-..."
|
||||
placeholder={t('integrations.tokenPlaceholder')}
|
||||
value={manualToken}
|
||||
onChange={(e) => setManualToken(e.target.value)}
|
||||
className="pr-10 font-mono text-xs h-8"
|
||||
@@ -525,7 +528,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email (optional, for display)"
|
||||
placeholder={t('integrations.emailPlaceholder')}
|
||||
value={manualTokenEmail}
|
||||
onChange={(e) => setManualTokenEmail(e.target.value)}
|
||||
className="text-xs h-8"
|
||||
@@ -539,7 +542,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
onClick={() => toggleTokenEntry(profile.id)}
|
||||
className="h-7 text-xs"
|
||||
>
|
||||
Cancel
|
||||
{tCommon('buttons.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -552,7 +555,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
) : (
|
||||
<Check className="h-3 w-3" />
|
||||
)}
|
||||
Save Token
|
||||
{t('integrations.saveToken')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -566,7 +569,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
{/* Add new account */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="Account name (e.g., Work, Personal)"
|
||||
placeholder={t('integrations.accountNamePlaceholder')}
|
||||
value={newProfileName}
|
||||
onChange={(e) => setNewProfileName(e.target.value)}
|
||||
className="flex-1 h-8 text-sm"
|
||||
@@ -587,7 +590,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
) : (
|
||||
<Plus className="h-3 w-3" />
|
||||
)}
|
||||
Add
|
||||
{tCommon('buttons.add')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -598,21 +601,20 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div className="space-y-4 pt-6 border-t border-border">
|
||||
<div className="flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4 text-muted-foreground" />
|
||||
<h4 className="text-sm font-semibold text-foreground">Automatic Account Switching</h4>
|
||||
<h4 className="text-sm font-semibold text-foreground">{t('integrations.autoSwitching')}</h4>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-muted/30 border border-border p-4 space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically switch between Claude accounts to avoid interruptions.
|
||||
Configure proactive monitoring to switch before hitting limits.
|
||||
{t('integrations.autoSwitchingDescription')}
|
||||
</p>
|
||||
|
||||
{/* Master toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-sm font-medium">Enable automatic switching</Label>
|
||||
<Label className="text-sm font-medium">{t('integrations.enableAutoSwitching')}</Label>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Master switch for all auto-swap features
|
||||
{t('integrations.masterSwitch')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -630,10 +632,10 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div>
|
||||
<Label className="text-sm font-medium flex items-center gap-2">
|
||||
<Activity className="h-3.5 w-3.5" />
|
||||
Proactive Monitoring
|
||||
{t('integrations.proactiveMonitoring')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Check usage regularly and swap before hitting limits
|
||||
{t('integrations.proactiveDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -647,24 +649,24 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<>
|
||||
{/* Check interval */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm">Check usage every</Label>
|
||||
<Label className="text-sm">{t('integrations.checkUsageEvery')}</Label>
|
||||
<select
|
||||
className="w-full px-3 py-2 bg-background border border-input rounded-md text-sm"
|
||||
value={autoSwitchSettings?.usageCheckInterval ?? 30000}
|
||||
onChange={(e) => handleUpdateAutoSwitch({ usageCheckInterval: parseInt(e.target.value) })}
|
||||
disabled={isLoadingAutoSwitch}
|
||||
>
|
||||
<option value={15000}>15 seconds</option>
|
||||
<option value={30000}>30 seconds (recommended)</option>
|
||||
<option value={60000}>1 minute</option>
|
||||
<option value={0}>Disabled</option>
|
||||
<option value={15000}>{t('integrations.seconds15')}</option>
|
||||
<option value={30000}>{t('integrations.seconds30')}</option>
|
||||
<option value={60000}>{t('integrations.minute1')}</option>
|
||||
<option value={0}>{t('integrations.disabled')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Session threshold */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm">Session usage threshold</Label>
|
||||
<Label className="text-sm">{t('integrations.sessionThreshold')}</Label>
|
||||
<span className="text-sm font-mono">{autoSwitchSettings?.sessionThreshold ?? 95}%</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -678,14 +680,14 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Switch when session usage reaches this level (recommended: 95%)
|
||||
{t('integrations.sessionThresholdDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Weekly threshold */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm">Weekly usage threshold</Label>
|
||||
<Label className="text-sm">{t('integrations.weeklyThreshold')}</Label>
|
||||
<span className="text-sm font-mono">{autoSwitchSettings?.weeklyThreshold ?? 99}%</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -699,7 +701,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Switch when weekly usage reaches this level (recommended: 99%)
|
||||
{t('integrations.weeklyThresholdDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
@@ -712,10 +714,10 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div>
|
||||
<Label className="text-sm font-medium flex items-center gap-2">
|
||||
<AlertCircle className="h-3.5 w-3.5" />
|
||||
Reactive Recovery
|
||||
{t('integrations.reactiveRecovery')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Auto-swap when unexpected rate limit is hit
|
||||
{t('integrations.reactiveDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -735,14 +737,14 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div className="space-y-4 pt-4 border-t border-border">
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-4 w-4 text-muted-foreground" />
|
||||
<h4 className="text-sm font-semibold text-foreground">API Keys</h4>
|
||||
<h4 className="text-sm font-semibold text-foreground">{t('integrations.apiKeys')}</h4>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-info/10 border border-info/30 p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="h-4 w-4 text-info shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Keys set here are used as defaults. Individual projects can override these in their settings.
|
||||
{t('integrations.apiKeysInfo')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -750,10 +752,10 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="globalOpenAIKey" className="text-sm font-medium text-foreground">
|
||||
OpenAI API Key
|
||||
{t('integrations.openaiKey')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Required for Graphiti memory backend (embeddings)
|
||||
{t('integrations.openaiKeyDescription')}
|
||||
</p>
|
||||
<div className="relative max-w-lg">
|
||||
<Input
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Globe } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Label } from '../ui/label';
|
||||
import { SettingsSection } from './SettingsSection';
|
||||
import { useSettingsStore } from '../../stores/settings-store';
|
||||
import { AVAILABLE_LANGUAGES, type SupportedLanguage } from '../../../shared/constants/i18n';
|
||||
import type { AppSettings } from '../../../shared/types';
|
||||
|
||||
interface LanguageSettingsProps {
|
||||
settings: AppSettings;
|
||||
onSettingsChange: (settings: AppSettings) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Language settings section for interface language selection
|
||||
* Changes apply immediately for live preview, saved on "Save Settings"
|
||||
*/
|
||||
export function LanguageSettings({ settings, onSettingsChange }: LanguageSettingsProps) {
|
||||
const { t, i18n } = useTranslation('settings');
|
||||
const updateStoreSettings = useSettingsStore((state) => state.updateSettings);
|
||||
|
||||
const currentLanguage = settings.language ?? 'en';
|
||||
|
||||
const handleLanguageChange = (newLanguage: SupportedLanguage) => {
|
||||
// Update local draft state
|
||||
onSettingsChange({ ...settings, language: newLanguage });
|
||||
|
||||
// Apply immediately to store for live preview
|
||||
updateStoreSettings({ language: newLanguage });
|
||||
|
||||
// Change i18n language immediately for live preview
|
||||
i18n.changeLanguage(newLanguage);
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('sections.language.title')}
|
||||
description={t('sections.language.description')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium text-foreground">
|
||||
{t('language.label')}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('language.description')}
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3 max-w-md pt-1">
|
||||
{AVAILABLE_LANGUAGES.map((lang) => {
|
||||
const isSelected = currentLanguage === lang.value;
|
||||
return (
|
||||
<button
|
||||
key={lang.value}
|
||||
onClick={() => handleLanguageChange(lang.value)}
|
||||
className={cn(
|
||||
'flex items-center gap-3 p-4 rounded-lg border-2 transition-all',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
isSelected
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/50 hover:bg-accent/50'
|
||||
)}
|
||||
>
|
||||
<Globe className="h-5 w-5 shrink-0" />
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-medium">{lang.nativeLabel}</div>
|
||||
<div className="text-xs text-muted-foreground">{lang.label}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SettingsSection } from './SettingsSection';
|
||||
import { ThemeSelector } from './ThemeSelector';
|
||||
import type { AppSettings } from '../../../shared/types';
|
||||
@@ -12,10 +13,12 @@ interface ThemeSettingsProps {
|
||||
* Wraps the ThemeSelector component with a consistent settings section layout
|
||||
*/
|
||||
export function ThemeSettings({ settings, onSettingsChange }: ThemeSettingsProps) {
|
||||
const { t } = useTranslation('settings');
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title="Appearance"
|
||||
description="Customize how Auto Claude looks"
|
||||
title={t('theme.title')}
|
||||
description={t('theme.description')}
|
||||
>
|
||||
<ThemeSelector settings={settings} onSettingsChange={onSettingsChange} />
|
||||
</SettingsSection>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Initialize browser mock before anything else (no-op in Electron)
|
||||
import './lib/browser-mock';
|
||||
|
||||
// Initialize i18n before React
|
||||
import '../shared/i18n';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { App } from './App';
|
||||
|
||||
@@ -44,7 +44,9 @@ export const DEFAULT_APP_SETTINGS = {
|
||||
// UI Scale (default 100% - standard size)
|
||||
uiScale: UI_SCALE_DEFAULT,
|
||||
// Beta updates opt-in (receive pre-release versions)
|
||||
betaUpdates: false
|
||||
betaUpdates: false,
|
||||
// Language preference (default to English)
|
||||
language: 'en' as const
|
||||
};
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Internationalization constants
|
||||
* Available languages and display labels
|
||||
*/
|
||||
|
||||
export type SupportedLanguage = 'en' | 'fr';
|
||||
|
||||
export const AVAILABLE_LANGUAGES = [
|
||||
{ value: 'en' as const, label: 'English', nativeLabel: 'English' },
|
||||
{ value: 'fr' as const, label: 'French', nativeLabel: 'Français' }
|
||||
] as const;
|
||||
|
||||
export const DEFAULT_LANGUAGE: SupportedLanguage = 'en';
|
||||
@@ -0,0 +1,61 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// Import English translation resources
|
||||
import enCommon from './locales/en/common.json';
|
||||
import enNavigation from './locales/en/navigation.json';
|
||||
import enSettings from './locales/en/settings.json';
|
||||
import enTasks from './locales/en/tasks.json';
|
||||
import enWelcome from './locales/en/welcome.json';
|
||||
import enOnboarding from './locales/en/onboarding.json';
|
||||
import enDialogs from './locales/en/dialogs.json';
|
||||
|
||||
// Import French translation resources
|
||||
import frCommon from './locales/fr/common.json';
|
||||
import frNavigation from './locales/fr/navigation.json';
|
||||
import frSettings from './locales/fr/settings.json';
|
||||
import frTasks from './locales/fr/tasks.json';
|
||||
import frWelcome from './locales/fr/welcome.json';
|
||||
import frOnboarding from './locales/fr/onboarding.json';
|
||||
import frDialogs from './locales/fr/dialogs.json';
|
||||
|
||||
export const defaultNS = 'common';
|
||||
|
||||
export const resources = {
|
||||
en: {
|
||||
common: enCommon,
|
||||
navigation: enNavigation,
|
||||
settings: enSettings,
|
||||
tasks: enTasks,
|
||||
welcome: enWelcome,
|
||||
onboarding: enOnboarding,
|
||||
dialogs: enDialogs
|
||||
},
|
||||
fr: {
|
||||
common: frCommon,
|
||||
navigation: frNavigation,
|
||||
settings: frSettings,
|
||||
tasks: frTasks,
|
||||
welcome: frWelcome,
|
||||
onboarding: frOnboarding,
|
||||
dialogs: frDialogs
|
||||
}
|
||||
} as const;
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
lng: 'en', // Default language (will be overridden by settings)
|
||||
fallbackLng: 'en',
|
||||
defaultNS,
|
||||
ns: ['common', 'navigation', 'settings', 'tasks', 'welcome', 'onboarding', 'dialogs'],
|
||||
interpolation: {
|
||||
escapeValue: false // React already escapes values
|
||||
},
|
||||
react: {
|
||||
useSuspense: false // Disable suspense for Electron compatibility
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"buttons": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"skip": "Skip",
|
||||
"next": "Next",
|
||||
"back": "Back",
|
||||
"close": "Close",
|
||||
"initialize": "Initialize",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm",
|
||||
"retry": "Retry",
|
||||
"create": "Create",
|
||||
"open": "Open",
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"refresh": "Refresh",
|
||||
"merge": "Merge",
|
||||
"discard": "Discard",
|
||||
"switch": "Switch",
|
||||
"add": "Add",
|
||||
"gotIt": "Got it",
|
||||
"continue": "Continue",
|
||||
"saving": "Saving..."
|
||||
},
|
||||
"labels": {
|
||||
"loading": "Loading...",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"initializing": "Initializing...",
|
||||
"saving": "Saving...",
|
||||
"noData": "No data",
|
||||
"optional": "Optional",
|
||||
"required": "Required",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"time": {
|
||||
"justNow": "Just now",
|
||||
"minutesAgo": "{{count}}m ago",
|
||||
"hoursAgo": "{{count}}h ago",
|
||||
"daysAgo": "{{count}}d ago"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "An error occurred",
|
||||
"networkError": "Network error",
|
||||
"notFound": "Not found",
|
||||
"unauthorized": "Unauthorized"
|
||||
},
|
||||
"notification": {
|
||||
"accountSwitched": "Account Switched",
|
||||
"swapFrom": "Switched from",
|
||||
"swapTo": "to",
|
||||
"swapReason": "({{reason}} swap)"
|
||||
},
|
||||
"rateLimit": {
|
||||
"title": "Rate Limited",
|
||||
"resetsAt": "Resets {{time}}",
|
||||
"hitLimit": "{{source}} hit usage limit",
|
||||
"clickToManage": "Click to manage →",
|
||||
"modalTitle": "Claude Code Usage Limit Reached",
|
||||
"modalDescription": "You've reached your Claude Code usage limit for this period.",
|
||||
"profile": "Profile: {{name}}",
|
||||
"autoSwitching": "Auto-switching to {{name}}",
|
||||
"autoSwitchingDescription": "Claude will restart with your other account automatically",
|
||||
"resetsTime": "Resets {{time}}",
|
||||
"usageRestored": "Your usage will be restored at this time",
|
||||
"switchAccount": "Switch Claude Account",
|
||||
"useAnotherAccount": "Use Another Account",
|
||||
"recommended": "Recommended: {{name}} has more capacity available.",
|
||||
"otherSubscriptions": "You have other Claude subscriptions configured. Switch to continue working:",
|
||||
"selectAccount": "Select account...",
|
||||
"switching": "Switching...",
|
||||
"addNewAccount": "Add new account...",
|
||||
"addAnotherSubscription": "Add another Claude subscription to automatically switch when you hit rate limits.",
|
||||
"addAnotherAccount": "Add another account:",
|
||||
"connectAccount": "Connect a Claude account:",
|
||||
"accountNamePlaceholder": "Account name (e.g., Work, Personal)",
|
||||
"willOpenLogin": "This will open Claude login to authenticate the new account.",
|
||||
"autoSwitchOnRateLimit": "Auto-switch on rate limit",
|
||||
"upgradeTitle": "Upgrade for more usage",
|
||||
"upgradeDescription": "Upgrade your Claude subscription for higher usage limits.",
|
||||
"upgradeSubscription": "Upgrade Subscription",
|
||||
"sources": {
|
||||
"changelog": "Changelog",
|
||||
"task": "Task",
|
||||
"roadmap": "Roadmap",
|
||||
"ideation": "Ideation",
|
||||
"titleGenerator": "Title Generator",
|
||||
"claude": "Claude"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"initialize": {
|
||||
"title": "Initialize Auto Claude",
|
||||
"description": "This project doesn't have Auto Claude initialized. Would you like to set it up now?",
|
||||
"willDo": "This will:",
|
||||
"createFolder": "Create a .auto-claude folder in your project",
|
||||
"copyFramework": "Copy the Auto Claude framework files",
|
||||
"setupSpecs": "Set up the specs directory for your tasks",
|
||||
"sourcePathNotConfigured": "Source path not configured",
|
||||
"sourcePathNotConfiguredDescription": "Please set the Auto Claude source path in App Settings before initializing.",
|
||||
"initFailed": "Initialization Failed",
|
||||
"initFailedDescription": "Failed to initialize Auto Claude. Please try again."
|
||||
},
|
||||
"gitSetup": {
|
||||
"title": "Git Repository Required",
|
||||
"description": "Auto Claude uses git to safely build features in isolated workspaces",
|
||||
"notGitRepo": "This folder is not a git repository",
|
||||
"noCommits": "Git repository has no commits",
|
||||
"needsInit": "Git needs to be initialized before Auto Claude can manage your code.",
|
||||
"needsCommit": "At least one commit is required for Auto Claude to create worktrees.",
|
||||
"willSetup": "We'll set up git for you:",
|
||||
"initRepo": "Initialize a new git repository",
|
||||
"createCommit": "Create an initial commit with your current files",
|
||||
"manual": "Prefer to do it manually?",
|
||||
"settingUp": "Setting up Git",
|
||||
"initializingRepo": "Initializing git repository and creating initial commit...",
|
||||
"success": "Git Initialized",
|
||||
"readyToUse": "Your project is now ready to use with Auto Claude!"
|
||||
},
|
||||
"githubSetup": {
|
||||
"connectTitle": "Connect to GitHub",
|
||||
"connectDescription": "Auto Claude requires GitHub to manage your code branches and keep tasks up to date.",
|
||||
"claudeTitle": "Connect to Claude AI",
|
||||
"claudeDescription": "Auto Claude uses Claude AI for intelligent features like Roadmap generation, Task automation, and Ideation.",
|
||||
"selectRepo": "Select Repository",
|
||||
"repoDescription": "Auto Claude will use this repository for managing task branches and keeping your code up to date.",
|
||||
"selectBranch": "Select Base Branch",
|
||||
"branchDescription": "Choose which branch Auto Claude should use as the base for creating task branches.",
|
||||
"whyBranch": "Why select a branch?",
|
||||
"branchExplanation": "Auto Claude creates isolated workspaces for each task. Selecting the right base branch ensures your tasks start with the latest code from your main development line.",
|
||||
"ready": "Auto Claude is ready to use! You can now create tasks that will be automatically based on the {{branchName}} branch."
|
||||
},
|
||||
"worktrees": {
|
||||
"title": "Worktrees",
|
||||
"description": "Manage isolated workspaces for your Auto Claude tasks",
|
||||
"empty": "No Worktrees",
|
||||
"emptyDescription": "Worktrees are created automatically when Auto Claude builds features. They provide isolated workspaces for each task.",
|
||||
"merge": "Merge Worktree",
|
||||
"mergeDescription": "Merge changes from this worktree into the base branch.",
|
||||
"delete": "Delete Worktree?",
|
||||
"deleteDescription": "This will permanently delete the worktree and all uncommitted changes. This action cannot be undone."
|
||||
},
|
||||
"update": {
|
||||
"title": "Auto Claude",
|
||||
"projectInitialized": "Project is initialized."
|
||||
},
|
||||
"addFeature": {
|
||||
"title": "Add Feature",
|
||||
"description": "Add a new feature to your roadmap. Provide details about what you want to build and how it fits into your product strategy.",
|
||||
"featureTitle": "Feature Title",
|
||||
"featureTitlePlaceholder": "e.g., User Authentication, Dark Mode Support",
|
||||
"featureDescription": "Description",
|
||||
"featureDescriptionPlaceholder": "Describe what this feature does and why it's valuable to users.",
|
||||
"rationale": "Rationale",
|
||||
"optional": "optional",
|
||||
"rationalePlaceholder": "Explain why this feature should be built and how it fits the product vision.",
|
||||
"phase": "Phase",
|
||||
"selectPhase": "Select phase",
|
||||
"priority": "Priority",
|
||||
"selectPriority": "Select priority",
|
||||
"complexity": "Complexity",
|
||||
"selectComplexity": "Select complexity",
|
||||
"impact": "Impact",
|
||||
"selectImpact": "Select impact",
|
||||
"lowComplexity": "Low",
|
||||
"mediumComplexity": "Medium",
|
||||
"highComplexity": "High",
|
||||
"lowImpact": "Low Impact",
|
||||
"mediumImpact": "Medium Impact",
|
||||
"highImpact": "High Impact",
|
||||
"titleRequired": "Title is required",
|
||||
"descriptionRequired": "Description is required",
|
||||
"phaseRequired": "Please select a phase",
|
||||
"cancel": "Cancel",
|
||||
"adding": "Adding...",
|
||||
"addFeature": "Add Feature",
|
||||
"failedToAdd": "Failed to add feature. Please try again."
|
||||
},
|
||||
"addProject": {
|
||||
"title": "Add Project",
|
||||
"description": "Choose how you'd like to add a project",
|
||||
"openExisting": "Open Existing Folder",
|
||||
"openExistingDescription": "Browse to an existing project on your computer",
|
||||
"createNew": "Create New Project",
|
||||
"createNewDescription": "Start fresh with a new project folder",
|
||||
"createNewTitle": "Create New Project",
|
||||
"createNewSubtitle": "Set up a new project folder",
|
||||
"projectName": "Project Name",
|
||||
"projectNamePlaceholder": "my-awesome-project",
|
||||
"projectNameHelp": "This will be the folder name. Use lowercase with hyphens.",
|
||||
"location": "Location",
|
||||
"locationPlaceholder": "Select a folder...",
|
||||
"willCreate": "Will create:",
|
||||
"browse": "Browse",
|
||||
"initGit": "Initialize git repository",
|
||||
"back": "Back",
|
||||
"creating": "Creating...",
|
||||
"createProject": "Create Project",
|
||||
"nameRequired": "Please enter a project name",
|
||||
"locationRequired": "Please select a location",
|
||||
"failedToOpen": "Failed to open project",
|
||||
"failedToCreate": "Failed to create project"
|
||||
},
|
||||
"customModel": {
|
||||
"title": "Custom Model Configuration",
|
||||
"description": "Configure the model and thinking level for this chat session.",
|
||||
"model": "Model",
|
||||
"thinkingLevel": "Thinking Level",
|
||||
"cancel": "Cancel",
|
||||
"apply": "Apply"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"sections": {
|
||||
"project": "Project",
|
||||
"tools": "Tools"
|
||||
},
|
||||
"items": {
|
||||
"kanban": "Kanban Board",
|
||||
"terminals": "Agent Terminals",
|
||||
"insights": "Insights",
|
||||
"roadmap": "Roadmap",
|
||||
"ideation": "Ideation",
|
||||
"changelog": "Changelog",
|
||||
"context": "Context",
|
||||
"githubIssues": "GitHub Issues",
|
||||
"gitlabIssues": "GitLab Issues",
|
||||
"worktrees": "Worktrees"
|
||||
},
|
||||
"actions": {
|
||||
"settings": "Settings",
|
||||
"help": "Help & Feedback",
|
||||
"newTask": "New Task"
|
||||
},
|
||||
"tooltips": {
|
||||
"settings": "Application Settings",
|
||||
"help": "Help & Feedback"
|
||||
},
|
||||
"messages": {
|
||||
"initializeToCreateTasks": "Initialize Auto Claude to create tasks"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"wizard": {
|
||||
"title": "Setup Wizard",
|
||||
"description": "Configure your Auto Claude environment in a few simple steps",
|
||||
"helpText": "This wizard will help you set up your environment in just a few steps. You can configure your Claude OAuth token, set up memory features, and create your first task."
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Welcome to Auto Claude",
|
||||
"subtitle": "Build software autonomously with AI-powered agents",
|
||||
"getStarted": "Get Started",
|
||||
"skip": "Skip Setup",
|
||||
"features": {
|
||||
"aiPowered": {
|
||||
"title": "AI-Powered Development",
|
||||
"description": "Generate code and build features using Claude Code agents"
|
||||
},
|
||||
"specDriven": {
|
||||
"title": "Spec-Driven Workflow",
|
||||
"description": "Define tasks with clear specifications and let Auto Claude handle the implementation"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Memory & Context",
|
||||
"description": "Persistent memory across sessions with Graphiti"
|
||||
},
|
||||
"parallel": {
|
||||
"title": "Parallel Execution",
|
||||
"description": "Run multiple agents in parallel for faster development cycles"
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth": {
|
||||
"title": "Claude Authentication",
|
||||
"description": "Connect your Claude account to enable AI features"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Memory",
|
||||
"description": "Auto Claude Memory helps remember context across your coding sessions"
|
||||
},
|
||||
"completion": {
|
||||
"title": "You're All Set!",
|
||||
"subtitle": "Auto Claude is ready to help you build amazing software",
|
||||
"setupComplete": "Setup Complete",
|
||||
"setupCompleteDescription": "Your environment is configured and ready. You can start creating tasks immediately or explore the application at your own pace.",
|
||||
"whatsNext": "What's Next?",
|
||||
"createTask": {
|
||||
"title": "Create a Task",
|
||||
"description": "Start by creating your first task to see Auto Claude in action.",
|
||||
"action": "Open Task Creator"
|
||||
},
|
||||
"customizeSettings": {
|
||||
"title": "Customize Settings",
|
||||
"description": "Fine-tune your preferences, configure integrations, or re-run this wizard.",
|
||||
"action": "Open Settings"
|
||||
},
|
||||
"exploreDocs": {
|
||||
"title": "Explore Documentation",
|
||||
"description": "Learn more about advanced features, best practices, and troubleshooting."
|
||||
},
|
||||
"finish": "Finish & Start Building",
|
||||
"rerunHint": "You can always re-run this wizard from Settings → Application"
|
||||
},
|
||||
"steps": {
|
||||
"welcome": "Welcome",
|
||||
"auth": "Auth",
|
||||
"memory": "Memory",
|
||||
"done": "Done"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"title": "Settings",
|
||||
"tabs": {
|
||||
"app": "App Settings",
|
||||
"project": "Project Settings"
|
||||
},
|
||||
"sections": {
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"description": "Customize how Auto Claude looks"
|
||||
},
|
||||
"display": {
|
||||
"title": "Display",
|
||||
"description": "Adjust the size of UI elements"
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Choose your preferred language"
|
||||
},
|
||||
"agent": {
|
||||
"title": "Agent Settings",
|
||||
"description": "Default model and framework"
|
||||
},
|
||||
"paths": {
|
||||
"title": "Paths",
|
||||
"description": "Python and framework paths"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrations",
|
||||
"description": "API keys & Claude accounts"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Updates",
|
||||
"description": "Auto Claude updates"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "Alert preferences"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"label": "Interface Language",
|
||||
"description": "Select the language for the application interface"
|
||||
},
|
||||
"scale": {
|
||||
"presets": "Scale Presets",
|
||||
"presetsDescription": "Quick scale options for common preferences",
|
||||
"fineTune": "Fine-tune Scale",
|
||||
"fineTuneDescription": "Adjust from 75% to 200% in 5% increments",
|
||||
"default": "Default",
|
||||
"comfortable": "Comfortable",
|
||||
"large": "Large",
|
||||
"preview": "Changes preview immediately. Click Save Settings to persist."
|
||||
},
|
||||
"general": {
|
||||
"otherAgentSettings": "Other Agent Settings",
|
||||
"otherAgentSettingsDescription": "Additional agent configuration options",
|
||||
"agentFramework": "Agent Framework",
|
||||
"agentFrameworkDescription": "The coding framework used for autonomous tasks",
|
||||
"agentFrameworkAutoClaude": "Auto Claude",
|
||||
"aiTerminalNaming": "AI Terminal Naming",
|
||||
"aiTerminalNamingDescription": "Automatically name terminals based on commands (uses Haiku)",
|
||||
"featureModelSettings": "Feature Model Settings",
|
||||
"featureModelSettingsDescription": "Model and thinking level for Insights, Ideation, and Roadmap",
|
||||
"model": "Model",
|
||||
"thinkingLevel": "Thinking Level",
|
||||
"paths": "Paths",
|
||||
"pathsDescription": "Configure executable and framework paths",
|
||||
"pythonPath": "Python Path",
|
||||
"pythonPathDescription": "Path to Python executable (leave empty for default)",
|
||||
"pythonPathPlaceholder": "python3 (default)",
|
||||
"autoClaudePath": "Auto Claude Path",
|
||||
"autoClaudePathDescription": "Relative path to auto-claude directory in projects",
|
||||
"autoClaudePathPlaceholder": "auto-claude (default)",
|
||||
"autoNameTerminals": "Automatically name terminals",
|
||||
"autoNameTerminalsDescription": "Use AI to generate descriptive names for terminal tabs based on their activity"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Appearance",
|
||||
"description": "Customize how Auto Claude looks",
|
||||
"mode": "Mode",
|
||||
"modeDescription": "Choose between light and dark themes",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System",
|
||||
"colorTheme": "Color Theme",
|
||||
"colorThemeDescription": "Choose your preferred color palette"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Updates",
|
||||
"description": "Manage Auto Claude updates",
|
||||
"appUpdateReady": "App Update Ready",
|
||||
"newVersion": "New Version",
|
||||
"released": "Released",
|
||||
"downloading": "Downloading...",
|
||||
"updateDownloaded": "Update downloaded! Click Install to restart and apply the update.",
|
||||
"installAndRestart": "Install and Restart",
|
||||
"downloadUpdate": "Download Update",
|
||||
"version": "Version",
|
||||
"loading": "Loading...",
|
||||
"checkingForUpdates": "Checking for updates...",
|
||||
"newVersionAvailable": "New version available:",
|
||||
"latestVersion": "You're running the latest version.",
|
||||
"viewRelease": "View full release on GitHub",
|
||||
"unableToCheck": "Unable to check for updates",
|
||||
"checkForUpdates": "Check for Updates",
|
||||
"autoUpdateProjects": "Auto-Update Projects",
|
||||
"autoUpdateProjectsDescription": "Automatically update Auto Claude in projects when a new version is available",
|
||||
"betaUpdates": "Beta Updates",
|
||||
"betaUpdatesDescription": "Receive pre-release beta versions with new features (may be less stable)"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "Configure default notification preferences",
|
||||
"onTaskComplete": "On Task Complete",
|
||||
"onTaskCompleteDescription": "Notify when a task finishes successfully",
|
||||
"onTaskFailed": "On Task Failed",
|
||||
"onTaskFailedDescription": "Notify when a task encounters an error",
|
||||
"onReviewNeeded": "On Review Needed",
|
||||
"onReviewNeededDescription": "Notify when QA requires your review",
|
||||
"sound": "Sound",
|
||||
"soundDescription": "Play sound with notifications"
|
||||
},
|
||||
"actions": {
|
||||
"save": "Save Settings",
|
||||
"rerunWizard": "Re-run Wizard",
|
||||
"rerunWizardDescription": "Start the setup wizard again"
|
||||
},
|
||||
"projectSections": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"description": "Auto-Build and agent config"
|
||||
},
|
||||
"claude": {
|
||||
"title": "Claude Auth",
|
||||
"description": "Claude authentication"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Linear",
|
||||
"description": "Linear integration"
|
||||
},
|
||||
"github": {
|
||||
"title": "GitHub",
|
||||
"description": "GitHub issues sync"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Memory",
|
||||
"description": "Graphiti memory backend"
|
||||
}
|
||||
},
|
||||
"agentProfile": {
|
||||
"label": "Agent Profile",
|
||||
"title": "Default Agent Profile",
|
||||
"sectionDescription": "Select a preset configuration for model and thinking level",
|
||||
"profilesInfo": "Agent profiles provide preset configurations for Claude model and thinking level. When you create a new task, these settings will be used as defaults. You can always override them in the task creation wizard.",
|
||||
"custom": "Custom",
|
||||
"customConfiguration": "Custom Configuration",
|
||||
"customDescription": "Choose model & thinking level",
|
||||
"phaseConfiguration": "Phase Configuration",
|
||||
"phaseConfigurationDescription": "Customize model and thinking level for each phase",
|
||||
"clickToCustomize": "Click to customize",
|
||||
"model": "Model",
|
||||
"thinking": "Thinking",
|
||||
"thinkingLevel": "Thinking Level",
|
||||
"selectModel": "Select model",
|
||||
"selectThinkingLevel": "Select thinking level",
|
||||
"perPhaseOptimization": "(per-phase optimization)",
|
||||
"resetToDefaults": "Reset to defaults",
|
||||
"phaseConfigNote": "These settings will be used as defaults when creating new tasks with the Auto profile. You can override them per-task in the task creation wizard.",
|
||||
"phases": {
|
||||
"spec": {
|
||||
"label": "Spec Creation",
|
||||
"description": "Discovery, requirements, context gathering"
|
||||
},
|
||||
"planning": {
|
||||
"label": "Planning",
|
||||
"description": "Implementation planning and architecture"
|
||||
},
|
||||
"coding": {
|
||||
"label": "Coding",
|
||||
"description": "Actual code implementation"
|
||||
},
|
||||
"qa": {
|
||||
"label": "QA Review",
|
||||
"description": "Quality assurance and validation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"roles": {
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"mobile": "Mobile",
|
||||
"shared": "Shared",
|
||||
"apiGateway": "API Gateway",
|
||||
"worker": "Worker",
|
||||
"other": "Other"
|
||||
}
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrations",
|
||||
"description": "Manage Claude accounts and API keys",
|
||||
"claudeAccounts": "Claude Accounts",
|
||||
"claudeAccountsDescription": "Add multiple Claude subscriptions to automatically switch between them when you hit rate limits.",
|
||||
"noAccountsYet": "No accounts configured yet",
|
||||
"default": "Default",
|
||||
"active": "Active",
|
||||
"authenticated": "Authenticated",
|
||||
"needsAuth": "Needs Auth",
|
||||
"authenticate": "Authenticate",
|
||||
"setActive": "Set Active",
|
||||
"manualTokenEntry": "Manual Token Entry",
|
||||
"runSetupToken": "Run claude setup-token to get your token",
|
||||
"tokenPlaceholder": "sk-ant-oat01-...",
|
||||
"emailPlaceholder": "Email (optional, for display)",
|
||||
"saveToken": "Save Token",
|
||||
"accountNamePlaceholder": "Account name (e.g., Work, Personal)",
|
||||
"autoSwitching": "Automatic Account Switching",
|
||||
"autoSwitchingDescription": "Automatically switch between Claude accounts to avoid interruptions. Configure proactive monitoring to switch before hitting limits.",
|
||||
"enableAutoSwitching": "Enable automatic switching",
|
||||
"masterSwitch": "Master switch for all auto-swap features",
|
||||
"proactiveMonitoring": "Proactive Monitoring",
|
||||
"proactiveDescription": "Check usage regularly and swap before hitting limits",
|
||||
"checkUsageEvery": "Check usage every",
|
||||
"seconds15": "15 seconds",
|
||||
"seconds30": "30 seconds (recommended)",
|
||||
"minute1": "1 minute",
|
||||
"disabled": "Disabled",
|
||||
"sessionThreshold": "Session usage threshold",
|
||||
"sessionThresholdDescription": "Switch when session usage reaches this level (recommended: 95%)",
|
||||
"weeklyThreshold": "Weekly usage threshold",
|
||||
"weeklyThresholdDescription": "Switch when weekly usage reaches this level (recommended: 99%)",
|
||||
"reactiveRecovery": "Reactive Recovery",
|
||||
"reactiveDescription": "Auto-swap when unexpected rate limit is hit",
|
||||
"apiKeys": "API Keys",
|
||||
"apiKeysInfo": "Keys set here are used as defaults. Individual projects can override these in their settings.",
|
||||
"openaiKey": "OpenAI API Key",
|
||||
"openaiKeyDescription": "Required for Graphiti memory backend (embeddings)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"status": {
|
||||
"backlog": "Backlog",
|
||||
"todo": "To Do",
|
||||
"in_progress": "In Progress",
|
||||
"review": "Review",
|
||||
"complete": "Complete",
|
||||
"archived": "Archived"
|
||||
},
|
||||
"actions": {
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"recover": "Recover",
|
||||
"resume": "Resume",
|
||||
"archive": "Archive",
|
||||
"delete": "Delete",
|
||||
"view": "View Details"
|
||||
},
|
||||
"labels": {
|
||||
"running": "Running",
|
||||
"aiReview": "AI Review",
|
||||
"needsReview": "Needs Review",
|
||||
"pending": "Pending",
|
||||
"stuck": "Stuck",
|
||||
"incomplete": "Incomplete",
|
||||
"recovering": "Recovering...",
|
||||
"needsRecovery": "Needs Recovery",
|
||||
"needsResume": "Needs Resume"
|
||||
},
|
||||
"reviewReason": {
|
||||
"completed": "Completed",
|
||||
"hasErrors": "Has Errors",
|
||||
"qaIssues": "QA Issues",
|
||||
"approvePlan": "Approve Plan"
|
||||
},
|
||||
"tooltips": {
|
||||
"archiveTask": "Archive task",
|
||||
"archiveAllDone": "Archive all done tasks"
|
||||
},
|
||||
"creation": {
|
||||
"title": "Create New Task",
|
||||
"description": "Describe what you want to build",
|
||||
"placeholder": "Describe your task..."
|
||||
},
|
||||
"empty": {
|
||||
"title": "No tasks yet",
|
||||
"description": "Create your first task to get started"
|
||||
},
|
||||
"kanban": {
|
||||
"emptyBacklog": "No tasks planned",
|
||||
"emptyBacklogHint": "Add a task to get started",
|
||||
"emptyInProgress": "Nothing running",
|
||||
"emptyInProgressHint": "Start a task from Backlog",
|
||||
"emptyAiReview": "No tasks in review",
|
||||
"emptyAiReviewHint": "AI will review completed tasks",
|
||||
"emptyHumanReview": "Nothing to review",
|
||||
"emptyHumanReviewHint": "Tasks await your approval here",
|
||||
"emptyDone": "No completed tasks",
|
||||
"emptyDoneHint": "Approved tasks appear here",
|
||||
"emptyDefault": "No tasks",
|
||||
"dropHere": "Drop here",
|
||||
"showArchived": "Show archived"
|
||||
},
|
||||
"execution": {
|
||||
"phases": {
|
||||
"idle": "Idle",
|
||||
"planning": "Planning",
|
||||
"coding": "Coding",
|
||||
"reviewing": "Reviewing",
|
||||
"fixing": "Fixing",
|
||||
"complete": "Complete",
|
||||
"failed": "Failed"
|
||||
},
|
||||
"labels": {
|
||||
"interrupted": "Interrupted",
|
||||
"progress": "Progress",
|
||||
"entry": "entry",
|
||||
"entries": "entries"
|
||||
},
|
||||
"shortPhases": {
|
||||
"plan": "Plan",
|
||||
"code": "Code",
|
||||
"qa": "QA"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hero": {
|
||||
"title": "Welcome to Auto Claude",
|
||||
"subtitle": "Build software autonomously with AI-powered agents"
|
||||
},
|
||||
"actions": {
|
||||
"newProject": "New Project",
|
||||
"openProject": "Open Project"
|
||||
},
|
||||
"recentProjects": {
|
||||
"title": "Recent Projects",
|
||||
"empty": "No projects yet",
|
||||
"emptyDescription": "Create a new project or open an existing one to get started",
|
||||
"openFolder": "Open Folder"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"buttons": {
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"skip": "Passer",
|
||||
"next": "Suivant",
|
||||
"back": "Retour",
|
||||
"close": "Fermer",
|
||||
"initialize": "Initialiser",
|
||||
"delete": "Supprimer",
|
||||
"confirm": "Confirmer",
|
||||
"retry": "Réessayer",
|
||||
"create": "Créer",
|
||||
"open": "Ouvrir",
|
||||
"start": "Démarrer",
|
||||
"stop": "Arrêter",
|
||||
"refresh": "Actualiser",
|
||||
"merge": "Fusionner",
|
||||
"discard": "Abandonner",
|
||||
"switch": "Changer",
|
||||
"add": "Ajouter",
|
||||
"gotIt": "Compris",
|
||||
"continue": "Continuer",
|
||||
"saving": "Enregistrement..."
|
||||
},
|
||||
"labels": {
|
||||
"loading": "Chargement...",
|
||||
"error": "Erreur",
|
||||
"success": "Succès",
|
||||
"initializing": "Initialisation...",
|
||||
"saving": "Enregistrement...",
|
||||
"noData": "Aucune donnée",
|
||||
"optional": "Optionnel",
|
||||
"required": "Requis",
|
||||
"dismiss": "Ignorer"
|
||||
},
|
||||
"time": {
|
||||
"justNow": "À l'instant",
|
||||
"minutesAgo": "Il y a {{count}} min",
|
||||
"hoursAgo": "Il y a {{count}}h",
|
||||
"daysAgo": "Il y a {{count}}j"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "Une erreur s'est produite",
|
||||
"networkError": "Erreur réseau",
|
||||
"notFound": "Non trouvé",
|
||||
"unauthorized": "Non autorisé"
|
||||
},
|
||||
"notification": {
|
||||
"accountSwitched": "Compte changé",
|
||||
"swapFrom": "Passage de",
|
||||
"swapTo": "à",
|
||||
"swapReason": "(changement {{reason}})"
|
||||
},
|
||||
"rateLimit": {
|
||||
"title": "Limite atteinte",
|
||||
"resetsAt": "Réinitialisation {{time}}",
|
||||
"hitLimit": "{{source}} a atteint la limite d'utilisation",
|
||||
"clickToManage": "Cliquez pour gérer →",
|
||||
"modalTitle": "Limite d'utilisation Claude Code atteinte",
|
||||
"modalDescription": "Vous avez atteint votre limite d'utilisation Claude Code pour cette période.",
|
||||
"profile": "Profil : {{name}}",
|
||||
"autoSwitching": "Changement automatique vers {{name}}",
|
||||
"autoSwitchingDescription": "Claude va redémarrer avec votre autre compte automatiquement",
|
||||
"resetsTime": "Réinitialisation {{time}}",
|
||||
"usageRestored": "Votre utilisation sera restaurée à ce moment",
|
||||
"switchAccount": "Changer de compte Claude",
|
||||
"useAnotherAccount": "Utiliser un autre compte",
|
||||
"recommended": "Recommandé : {{name}} a plus de capacité disponible.",
|
||||
"otherSubscriptions": "Vous avez d'autres abonnements Claude configurés. Changez pour continuer à travailler :",
|
||||
"selectAccount": "Sélectionner un compte...",
|
||||
"switching": "Changement...",
|
||||
"addNewAccount": "Ajouter un nouveau compte...",
|
||||
"addAnotherSubscription": "Ajoutez un autre abonnement Claude pour basculer automatiquement quand vous atteignez les limites.",
|
||||
"addAnotherAccount": "Ajouter un autre compte :",
|
||||
"connectAccount": "Connecter un compte Claude :",
|
||||
"accountNamePlaceholder": "Nom du compte (ex. Travail, Personnel)",
|
||||
"willOpenLogin": "Cela ouvrira la connexion Claude pour authentifier le nouveau compte.",
|
||||
"autoSwitchOnRateLimit": "Changement auto en cas de limite",
|
||||
"upgradeTitle": "Passez à la version supérieure pour plus d'utilisation",
|
||||
"upgradeDescription": "Mettez à niveau votre abonnement Claude pour des limites d'utilisation plus élevées.",
|
||||
"upgradeSubscription": "Mettre à niveau l'abonnement",
|
||||
"sources": {
|
||||
"changelog": "Changelog",
|
||||
"task": "Tâche",
|
||||
"roadmap": "Feuille de route",
|
||||
"ideation": "Idéation",
|
||||
"titleGenerator": "Générateur de titre",
|
||||
"claude": "Claude"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"initialize": {
|
||||
"title": "Initialiser Auto Claude",
|
||||
"description": "Ce projet n'a pas Auto Claude initialisé. Voulez-vous le configurer maintenant ?",
|
||||
"willDo": "Ceci va :",
|
||||
"createFolder": "Créer un dossier .auto-claude dans votre projet",
|
||||
"copyFramework": "Copier les fichiers du framework Auto Claude",
|
||||
"setupSpecs": "Configurer le répertoire des spécifications pour vos tâches",
|
||||
"sourcePathNotConfigured": "Chemin source non configuré",
|
||||
"sourcePathNotConfiguredDescription": "Veuillez définir le chemin source Auto Claude dans les paramètres de l'application avant d'initialiser.",
|
||||
"initFailed": "Échec de l'initialisation",
|
||||
"initFailedDescription": "Échec de l'initialisation de Auto Claude. Veuillez réessayer."
|
||||
},
|
||||
"gitSetup": {
|
||||
"title": "Dépôt Git requis",
|
||||
"description": "Auto Claude utilise git pour construire des fonctionnalités en toute sécurité dans des espaces de travail isolés",
|
||||
"notGitRepo": "Ce dossier n'est pas un dépôt git",
|
||||
"noCommits": "Le dépôt git n'a pas de commits",
|
||||
"needsInit": "Git doit être initialisé avant que Auto Claude puisse gérer votre code.",
|
||||
"needsCommit": "Au moins un commit est requis pour que Auto Claude puisse créer des worktrees.",
|
||||
"willSetup": "Nous allons configurer git pour vous :",
|
||||
"initRepo": "Initialiser un nouveau dépôt git",
|
||||
"createCommit": "Créer un commit initial avec vos fichiers actuels",
|
||||
"manual": "Préférez-vous le faire manuellement ?",
|
||||
"settingUp": "Configuration de Git",
|
||||
"initializingRepo": "Initialisation du dépôt git et création du commit initial...",
|
||||
"success": "Git initialisé",
|
||||
"readyToUse": "Votre projet est maintenant prêt à être utilisé avec Auto Claude !"
|
||||
},
|
||||
"githubSetup": {
|
||||
"connectTitle": "Connecter à GitHub",
|
||||
"connectDescription": "Auto Claude nécessite GitHub pour gérer vos branches de code et maintenir les tâches à jour.",
|
||||
"claudeTitle": "Connecter à Claude AI",
|
||||
"claudeDescription": "Auto Claude utilise Claude AI pour des fonctionnalités intelligentes comme la génération de feuille de route, l'automatisation des tâches et l'idéation.",
|
||||
"selectRepo": "Sélectionner le dépôt",
|
||||
"repoDescription": "Auto Claude utilisera ce dépôt pour gérer les branches de tâches et maintenir votre code à jour.",
|
||||
"selectBranch": "Sélectionner la branche de base",
|
||||
"branchDescription": "Choisissez quelle branche Auto Claude doit utiliser comme base pour créer les branches de tâches.",
|
||||
"whyBranch": "Pourquoi sélectionner une branche ?",
|
||||
"branchExplanation": "Auto Claude crée des espaces de travail isolés pour chaque tâche. Sélectionner la bonne branche de base garantit que vos tâches démarrent avec le code le plus récent de votre ligne de développement principale.",
|
||||
"ready": "Auto Claude est prêt à l'emploi ! Vous pouvez maintenant créer des tâches qui seront automatiquement basées sur la branche {{branchName}}."
|
||||
},
|
||||
"worktrees": {
|
||||
"title": "Worktrees",
|
||||
"description": "Gérez les espaces de travail isolés pour vos tâches Auto Claude",
|
||||
"empty": "Aucun worktree",
|
||||
"emptyDescription": "Les worktrees sont créés automatiquement quand Auto Claude construit des fonctionnalités. Ils fournissent des espaces de travail isolés pour chaque tâche.",
|
||||
"merge": "Fusionner le worktree",
|
||||
"mergeDescription": "Fusionner les modifications de ce worktree dans la branche de base.",
|
||||
"delete": "Supprimer le worktree ?",
|
||||
"deleteDescription": "Ceci supprimera définitivement le worktree et toutes les modifications non committées. Cette action est irréversible."
|
||||
},
|
||||
"update": {
|
||||
"title": "Auto Claude",
|
||||
"projectInitialized": "Le projet est initialisé."
|
||||
},
|
||||
"addFeature": {
|
||||
"title": "Ajouter une fonctionnalité",
|
||||
"description": "Ajoutez une nouvelle fonctionnalité à votre feuille de route. Fournissez des détails sur ce que vous voulez construire et comment cela s'intègre dans votre stratégie produit.",
|
||||
"featureTitle": "Titre de la fonctionnalité",
|
||||
"featureTitlePlaceholder": "ex. Authentification utilisateur, Mode sombre",
|
||||
"featureDescription": "Description",
|
||||
"featureDescriptionPlaceholder": "Décrivez ce que fait cette fonctionnalité et pourquoi elle est utile aux utilisateurs.",
|
||||
"rationale": "Justification",
|
||||
"optional": "optionnel",
|
||||
"rationalePlaceholder": "Expliquez pourquoi cette fonctionnalité devrait être construite et comment elle s'intègre dans la vision produit.",
|
||||
"phase": "Phase",
|
||||
"selectPhase": "Sélectionner une phase",
|
||||
"priority": "Priorité",
|
||||
"selectPriority": "Sélectionner une priorité",
|
||||
"complexity": "Complexité",
|
||||
"selectComplexity": "Sélectionner la complexité",
|
||||
"impact": "Impact",
|
||||
"selectImpact": "Sélectionner l'impact",
|
||||
"lowComplexity": "Faible",
|
||||
"mediumComplexity": "Moyen",
|
||||
"highComplexity": "Élevé",
|
||||
"lowImpact": "Impact faible",
|
||||
"mediumImpact": "Impact moyen",
|
||||
"highImpact": "Impact élevé",
|
||||
"titleRequired": "Le titre est requis",
|
||||
"descriptionRequired": "La description est requise",
|
||||
"phaseRequired": "Veuillez sélectionner une phase",
|
||||
"cancel": "Annuler",
|
||||
"adding": "Ajout en cours...",
|
||||
"addFeature": "Ajouter la fonctionnalité",
|
||||
"failedToAdd": "Échec de l'ajout de la fonctionnalité. Veuillez réessayer."
|
||||
},
|
||||
"addProject": {
|
||||
"title": "Ajouter un projet",
|
||||
"description": "Choisissez comment vous souhaitez ajouter un projet",
|
||||
"openExisting": "Ouvrir un dossier existant",
|
||||
"openExistingDescription": "Parcourir vers un projet existant sur votre ordinateur",
|
||||
"createNew": "Créer un nouveau projet",
|
||||
"createNewDescription": "Commencer avec un nouveau dossier de projet",
|
||||
"createNewTitle": "Créer un nouveau projet",
|
||||
"createNewSubtitle": "Configurer un nouveau dossier de projet",
|
||||
"projectName": "Nom du projet",
|
||||
"projectNamePlaceholder": "mon-super-projet",
|
||||
"projectNameHelp": "Ce sera le nom du dossier. Utilisez des minuscules avec des tirets.",
|
||||
"location": "Emplacement",
|
||||
"locationPlaceholder": "Sélectionner un dossier...",
|
||||
"willCreate": "Va créer :",
|
||||
"browse": "Parcourir",
|
||||
"initGit": "Initialiser un dépôt git",
|
||||
"back": "Retour",
|
||||
"creating": "Création en cours...",
|
||||
"createProject": "Créer le projet",
|
||||
"nameRequired": "Veuillez entrer un nom de projet",
|
||||
"locationRequired": "Veuillez sélectionner un emplacement",
|
||||
"failedToOpen": "Échec de l'ouverture du projet",
|
||||
"failedToCreate": "Échec de la création du projet"
|
||||
},
|
||||
"customModel": {
|
||||
"title": "Configuration du modèle personnalisé",
|
||||
"description": "Configurez le modèle et le niveau de réflexion pour cette session de chat.",
|
||||
"model": "Modèle",
|
||||
"thinkingLevel": "Niveau de réflexion",
|
||||
"cancel": "Annuler",
|
||||
"apply": "Appliquer"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"sections": {
|
||||
"project": "Projet",
|
||||
"tools": "Outils"
|
||||
},
|
||||
"items": {
|
||||
"kanban": "Tableau Kanban",
|
||||
"terminals": "Terminaux Agent",
|
||||
"insights": "Insights",
|
||||
"roadmap": "Feuille de route",
|
||||
"ideation": "Idéation",
|
||||
"changelog": "Journal des modifications",
|
||||
"context": "Contexte",
|
||||
"githubIssues": "Issues GitHub",
|
||||
"gitlabIssues": "Issues GitLab",
|
||||
"worktrees": "Worktrees"
|
||||
},
|
||||
"actions": {
|
||||
"settings": "Paramètres",
|
||||
"help": "Aide & Feedback",
|
||||
"newTask": "Nouvelle tâche"
|
||||
},
|
||||
"tooltips": {
|
||||
"settings": "Paramètres de l'application",
|
||||
"help": "Aide & Feedback"
|
||||
},
|
||||
"messages": {
|
||||
"initializeToCreateTasks": "Initialisez Auto Claude pour créer des tâches"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"wizard": {
|
||||
"title": "Assistant de configuration",
|
||||
"description": "Configurez votre environnement Auto Claude en quelques étapes simples",
|
||||
"helpText": "Cet assistant vous aidera à configurer votre environnement en quelques étapes. Vous pouvez configurer votre token OAuth Claude, activer les fonctionnalités de mémoire et créer votre première tâche."
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Bienvenue sur Auto Claude",
|
||||
"subtitle": "Construisez des logiciels de manière autonome avec des agents IA",
|
||||
"getStarted": "Commencer",
|
||||
"skip": "Passer la configuration",
|
||||
"features": {
|
||||
"aiPowered": {
|
||||
"title": "Développement assisté par IA",
|
||||
"description": "Générez du code et construisez des fonctionnalités avec les agents Claude Code"
|
||||
},
|
||||
"specDriven": {
|
||||
"title": "Workflow basé sur les specs",
|
||||
"description": "Définissez des tâches avec des spécifications claires et laissez Auto Claude gérer l'implémentation"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Mémoire & Contexte",
|
||||
"description": "Mémoire persistante entre les sessions avec Graphiti"
|
||||
},
|
||||
"parallel": {
|
||||
"title": "Exécution parallèle",
|
||||
"description": "Exécutez plusieurs agents en parallèle pour des cycles de développement plus rapides"
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth": {
|
||||
"title": "Authentification Claude",
|
||||
"description": "Connectez votre compte Claude pour activer les fonctionnalités IA"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Mémoire",
|
||||
"description": "La mémoire Auto Claude aide à retenir le contexte entre vos sessions de code"
|
||||
},
|
||||
"completion": {
|
||||
"title": "Vous êtes prêt !",
|
||||
"subtitle": "Auto Claude est prêt à vous aider à construire des logiciels incroyables",
|
||||
"setupComplete": "Configuration terminée",
|
||||
"setupCompleteDescription": "Votre environnement est configuré et prêt. Vous pouvez commencer à créer des tâches immédiatement ou explorer l'application à votre rythme.",
|
||||
"whatsNext": "Et maintenant ?",
|
||||
"createTask": {
|
||||
"title": "Créer une tâche",
|
||||
"description": "Commencez par créer votre première tâche pour voir Auto Claude en action.",
|
||||
"action": "Ouvrir le créateur de tâches"
|
||||
},
|
||||
"customizeSettings": {
|
||||
"title": "Personnaliser les paramètres",
|
||||
"description": "Affinez vos préférences, configurez les intégrations ou relancez cet assistant.",
|
||||
"action": "Ouvrir les paramètres"
|
||||
},
|
||||
"exploreDocs": {
|
||||
"title": "Explorer la documentation",
|
||||
"description": "En savoir plus sur les fonctionnalités avancées, les bonnes pratiques et le dépannage."
|
||||
},
|
||||
"finish": "Terminer et commencer à construire",
|
||||
"rerunHint": "Vous pouvez toujours relancer cet assistant depuis Paramètres → Application"
|
||||
},
|
||||
"steps": {
|
||||
"welcome": "Bienvenue",
|
||||
"auth": "Auth",
|
||||
"memory": "Mémoire",
|
||||
"done": "Terminé"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"title": "Paramètres",
|
||||
"tabs": {
|
||||
"app": "Paramètres de l'app",
|
||||
"project": "Paramètres du projet"
|
||||
},
|
||||
"sections": {
|
||||
"appearance": {
|
||||
"title": "Apparence",
|
||||
"description": "Personnalisez l'apparence de Auto Claude"
|
||||
},
|
||||
"display": {
|
||||
"title": "Affichage",
|
||||
"description": "Ajustez la taille des éléments de l'interface"
|
||||
},
|
||||
"language": {
|
||||
"title": "Langue",
|
||||
"description": "Choisissez votre langue préférée"
|
||||
},
|
||||
"agent": {
|
||||
"title": "Paramètres de l'agent",
|
||||
"description": "Modèle par défaut et framework"
|
||||
},
|
||||
"paths": {
|
||||
"title": "Chemins",
|
||||
"description": "Chemins Python et framework"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Intégrations",
|
||||
"description": "Clés API & comptes Claude"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Mises à jour",
|
||||
"description": "Mises à jour Auto Claude"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "Préférences d'alertes"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"label": "Langue de l'interface",
|
||||
"description": "Sélectionnez la langue de l'interface de l'application"
|
||||
},
|
||||
"scale": {
|
||||
"presets": "Préréglages d'échelle",
|
||||
"presetsDescription": "Options d'échelle rapides pour les préférences courantes",
|
||||
"fineTune": "Ajustement fin",
|
||||
"fineTuneDescription": "Ajustez de 75% à 200% par incréments de 5%",
|
||||
"default": "Par défaut",
|
||||
"comfortable": "Confortable",
|
||||
"large": "Grand",
|
||||
"preview": "Les changements sont prévisualisés immédiatement. Cliquez sur Enregistrer pour les conserver."
|
||||
},
|
||||
"general": {
|
||||
"otherAgentSettings": "Autres paramètres de l'agent",
|
||||
"otherAgentSettingsDescription": "Options de configuration supplémentaires de l'agent",
|
||||
"agentFramework": "Framework de l'agent",
|
||||
"agentFrameworkDescription": "Le framework de codage utilisé pour les tâches autonomes",
|
||||
"agentFrameworkAutoClaude": "Auto Claude",
|
||||
"aiTerminalNaming": "Nommage IA des terminaux",
|
||||
"aiTerminalNamingDescription": "Nommer automatiquement les terminaux en fonction des commandes (utilise Haiku)",
|
||||
"featureModelSettings": "Paramètres du modèle de fonctionnalité",
|
||||
"featureModelSettingsDescription": "Modèle et niveau de réflexion pour Insights, Idéation et Roadmap",
|
||||
"model": "Modèle",
|
||||
"thinkingLevel": "Niveau de réflexion",
|
||||
"paths": "Chemins",
|
||||
"pathsDescription": "Configurer les chemins des exécutables et du framework",
|
||||
"pythonPath": "Chemin Python",
|
||||
"pythonPathDescription": "Chemin vers l'exécutable Python (laisser vide pour la valeur par défaut)",
|
||||
"pythonPathPlaceholder": "python3 (par défaut)",
|
||||
"autoClaudePath": "Chemin Auto Claude",
|
||||
"autoClaudePathDescription": "Chemin relatif vers le répertoire auto-claude dans les projets",
|
||||
"autoClaudePathPlaceholder": "auto-claude (par défaut)",
|
||||
"autoNameTerminals": "Nommer automatiquement les terminaux",
|
||||
"autoNameTerminalsDescription": "Utiliser l'IA pour générer des noms descriptifs pour les onglets de terminal en fonction de leur activité"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Apparence",
|
||||
"description": "Personnalisez l'apparence de Auto Claude",
|
||||
"mode": "Mode",
|
||||
"modeDescription": "Choisir entre les thèmes clair et sombre",
|
||||
"light": "Clair",
|
||||
"dark": "Sombre",
|
||||
"system": "Système",
|
||||
"colorTheme": "Thème de couleur",
|
||||
"colorThemeDescription": "Choisissez votre palette de couleurs préférée"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Mises à jour",
|
||||
"description": "Gérer les mises à jour de Auto Claude",
|
||||
"appUpdateReady": "Mise à jour de l'app prête",
|
||||
"newVersion": "Nouvelle version",
|
||||
"released": "Publiée le",
|
||||
"downloading": "Téléchargement...",
|
||||
"updateDownloaded": "Mise à jour téléchargée ! Cliquez sur Installer pour redémarrer et appliquer la mise à jour.",
|
||||
"installAndRestart": "Installer et redémarrer",
|
||||
"downloadUpdate": "Télécharger la mise à jour",
|
||||
"version": "Version",
|
||||
"loading": "Chargement...",
|
||||
"checkingForUpdates": "Vérification des mises à jour...",
|
||||
"newVersionAvailable": "Nouvelle version disponible :",
|
||||
"latestVersion": "Vous utilisez la dernière version.",
|
||||
"viewRelease": "Voir la version complète sur GitHub",
|
||||
"unableToCheck": "Impossible de vérifier les mises à jour",
|
||||
"checkForUpdates": "Vérifier les mises à jour",
|
||||
"autoUpdateProjects": "Mise à jour automatique des projets",
|
||||
"autoUpdateProjectsDescription": "Mettre à jour automatiquement Auto Claude dans les projets quand une nouvelle version est disponible",
|
||||
"betaUpdates": "Mises à jour bêta",
|
||||
"betaUpdatesDescription": "Recevoir les versions bêta pré-release avec de nouvelles fonctionnalités (peut être moins stable)"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "Configurer les préférences de notification par défaut",
|
||||
"onTaskComplete": "À la fin d'une tâche",
|
||||
"onTaskCompleteDescription": "Notifier quand une tâche se termine avec succès",
|
||||
"onTaskFailed": "En cas d'échec",
|
||||
"onTaskFailedDescription": "Notifier quand une tâche rencontre une erreur",
|
||||
"onReviewNeeded": "Révision requise",
|
||||
"onReviewNeededDescription": "Notifier quand le QA nécessite votre révision",
|
||||
"sound": "Son",
|
||||
"soundDescription": "Jouer un son avec les notifications"
|
||||
},
|
||||
"actions": {
|
||||
"save": "Enregistrer les paramètres",
|
||||
"rerunWizard": "Relancer l'assistant",
|
||||
"rerunWizardDescription": "Redémarrer l'assistant de configuration"
|
||||
},
|
||||
"projectSections": {
|
||||
"general": {
|
||||
"title": "Général",
|
||||
"description": "Auto-Build et configuration de l'agent"
|
||||
},
|
||||
"claude": {
|
||||
"title": "Auth Claude",
|
||||
"description": "Authentification Claude"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Linear",
|
||||
"description": "Intégration Linear"
|
||||
},
|
||||
"github": {
|
||||
"title": "GitHub",
|
||||
"description": "Synchronisation issues GitHub"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Mémoire",
|
||||
"description": "Backend mémoire Graphiti"
|
||||
}
|
||||
},
|
||||
"agentProfile": {
|
||||
"label": "Profil d'agent",
|
||||
"title": "Profil d'agent par défaut",
|
||||
"sectionDescription": "Sélectionnez une configuration prédéfinie pour le modèle et le niveau de réflexion",
|
||||
"profilesInfo": "Les profils d'agent fournissent des configurations prédéfinies pour le modèle Claude et le niveau de réflexion. Quand vous créez une nouvelle tâche, ces paramètres seront utilisés par défaut. Vous pouvez toujours les modifier dans l'assistant de création de tâche.",
|
||||
"custom": "Personnalisé",
|
||||
"customConfiguration": "Configuration personnalisée",
|
||||
"customDescription": "Choisir le modèle et le niveau de réflexion",
|
||||
"phaseConfiguration": "Configuration par phase",
|
||||
"phaseConfigurationDescription": "Personnaliser le modèle et le niveau de réflexion pour chaque phase",
|
||||
"clickToCustomize": "Cliquer pour personnaliser",
|
||||
"model": "Modèle",
|
||||
"thinking": "Réflexion",
|
||||
"thinkingLevel": "Niveau de réflexion",
|
||||
"selectModel": "Sélectionner un modèle",
|
||||
"selectThinkingLevel": "Sélectionner un niveau de réflexion",
|
||||
"perPhaseOptimization": "(optimisation par phase)",
|
||||
"resetToDefaults": "Réinitialiser par défaut",
|
||||
"phaseConfigNote": "Ces paramètres seront utilisés par défaut lors de la création de nouvelles tâches avec le profil Auto. Vous pouvez les modifier par tâche dans l'assistant de création.",
|
||||
"phases": {
|
||||
"spec": {
|
||||
"label": "Création de spec",
|
||||
"description": "Découverte, exigences, collecte de contexte"
|
||||
},
|
||||
"planning": {
|
||||
"label": "Planification",
|
||||
"description": "Planification de l'implémentation et architecture"
|
||||
},
|
||||
"coding": {
|
||||
"label": "Codage",
|
||||
"description": "Implémentation du code"
|
||||
},
|
||||
"qa": {
|
||||
"label": "Révision QA",
|
||||
"description": "Assurance qualité et validation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"roles": {
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"mobile": "Mobile",
|
||||
"shared": "Partagé",
|
||||
"apiGateway": "Passerelle API",
|
||||
"worker": "Worker",
|
||||
"other": "Autre"
|
||||
}
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Intégrations",
|
||||
"description": "Gérer les comptes Claude et les clés API",
|
||||
"claudeAccounts": "Comptes Claude",
|
||||
"claudeAccountsDescription": "Ajoutez plusieurs abonnements Claude pour basculer automatiquement entre eux quand vous atteignez les limites.",
|
||||
"noAccountsYet": "Aucun compte configuré",
|
||||
"default": "Par défaut",
|
||||
"active": "Actif",
|
||||
"authenticated": "Authentifié",
|
||||
"needsAuth": "Auth requise",
|
||||
"authenticate": "Authentifier",
|
||||
"setActive": "Définir actif",
|
||||
"manualTokenEntry": "Saisie manuelle du token",
|
||||
"runSetupToken": "Exécutez claude setup-token pour obtenir votre token",
|
||||
"tokenPlaceholder": "sk-ant-oat01-...",
|
||||
"emailPlaceholder": "Email (optionnel, pour l'affichage)",
|
||||
"saveToken": "Enregistrer le token",
|
||||
"accountNamePlaceholder": "Nom du compte (ex: Travail, Personnel)",
|
||||
"autoSwitching": "Basculement automatique de compte",
|
||||
"autoSwitchingDescription": "Basculer automatiquement entre les comptes Claude pour éviter les interruptions. Configurez la surveillance proactive pour changer avant d'atteindre les limites.",
|
||||
"enableAutoSwitching": "Activer le basculement automatique",
|
||||
"masterSwitch": "Interrupteur principal pour toutes les fonctionnalités auto-swap",
|
||||
"proactiveMonitoring": "Surveillance proactive",
|
||||
"proactiveDescription": "Vérifier l'utilisation régulièrement et changer avant d'atteindre les limites",
|
||||
"checkUsageEvery": "Vérifier l'utilisation toutes les",
|
||||
"seconds15": "15 secondes",
|
||||
"seconds30": "30 secondes (recommandé)",
|
||||
"minute1": "1 minute",
|
||||
"disabled": "Désactivé",
|
||||
"sessionThreshold": "Seuil d'utilisation de session",
|
||||
"sessionThresholdDescription": "Changer quand l'utilisation de session atteint ce niveau (recommandé: 95%)",
|
||||
"weeklyThreshold": "Seuil d'utilisation hebdomadaire",
|
||||
"weeklyThresholdDescription": "Changer quand l'utilisation hebdomadaire atteint ce niveau (recommandé: 99%)",
|
||||
"reactiveRecovery": "Récupération réactive",
|
||||
"reactiveDescription": "Basculer automatiquement quand une limite inattendue est atteinte",
|
||||
"apiKeys": "Clés API",
|
||||
"apiKeysInfo": "Les clés définies ici sont utilisées par défaut. Les projets individuels peuvent les remplacer dans leurs paramètres.",
|
||||
"openaiKey": "Clé API OpenAI",
|
||||
"openaiKeyDescription": "Requise pour le backend mémoire Graphiti (embeddings)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"status": {
|
||||
"backlog": "Backlog",
|
||||
"todo": "À faire",
|
||||
"in_progress": "En cours",
|
||||
"review": "Révision",
|
||||
"complete": "Terminé",
|
||||
"archived": "Archivé"
|
||||
},
|
||||
"actions": {
|
||||
"start": "Démarrer",
|
||||
"stop": "Arrêter",
|
||||
"recover": "Récupérer",
|
||||
"resume": "Reprendre",
|
||||
"archive": "Archiver",
|
||||
"delete": "Supprimer",
|
||||
"view": "Voir les détails"
|
||||
},
|
||||
"labels": {
|
||||
"running": "En cours",
|
||||
"aiReview": "Révision IA",
|
||||
"needsReview": "À réviser",
|
||||
"pending": "En attente",
|
||||
"stuck": "Bloqué",
|
||||
"incomplete": "Incomplet",
|
||||
"recovering": "Récupération...",
|
||||
"needsRecovery": "Récupération requise",
|
||||
"needsResume": "Reprise requise"
|
||||
},
|
||||
"reviewReason": {
|
||||
"completed": "Terminé",
|
||||
"hasErrors": "Contient des erreurs",
|
||||
"qaIssues": "Problèmes QA",
|
||||
"approvePlan": "Approuver le plan"
|
||||
},
|
||||
"tooltips": {
|
||||
"archiveTask": "Archiver la tâche",
|
||||
"archiveAllDone": "Archiver toutes les tâches terminées"
|
||||
},
|
||||
"creation": {
|
||||
"title": "Créer une nouvelle tâche",
|
||||
"description": "Décrivez ce que vous voulez construire",
|
||||
"placeholder": "Décrivez votre tâche..."
|
||||
},
|
||||
"empty": {
|
||||
"title": "Aucune tâche",
|
||||
"description": "Créez votre première tâche pour commencer"
|
||||
},
|
||||
"kanban": {
|
||||
"emptyBacklog": "Aucune tâche planifiée",
|
||||
"emptyBacklogHint": "Ajoutez une tâche pour commencer",
|
||||
"emptyInProgress": "Rien en cours",
|
||||
"emptyInProgressHint": "Démarrez une tâche depuis le Backlog",
|
||||
"emptyAiReview": "Aucune tâche en révision",
|
||||
"emptyAiReviewHint": "L'IA révisera les tâches terminées",
|
||||
"emptyHumanReview": "Rien à réviser",
|
||||
"emptyHumanReviewHint": "Les tâches attendent votre approbation ici",
|
||||
"emptyDone": "Aucune tâche terminée",
|
||||
"emptyDoneHint": "Les tâches approuvées apparaissent ici",
|
||||
"emptyDefault": "Aucune tâche",
|
||||
"dropHere": "Déposer ici",
|
||||
"showArchived": "Afficher les archivées"
|
||||
},
|
||||
"execution": {
|
||||
"phases": {
|
||||
"idle": "Inactif",
|
||||
"planning": "Planification",
|
||||
"coding": "Codage",
|
||||
"reviewing": "Révision",
|
||||
"fixing": "Correction",
|
||||
"complete": "Terminé",
|
||||
"failed": "Échoué"
|
||||
},
|
||||
"labels": {
|
||||
"interrupted": "Interrompu",
|
||||
"progress": "Progression",
|
||||
"entry": "entrée",
|
||||
"entries": "entrées"
|
||||
},
|
||||
"shortPhases": {
|
||||
"plan": "Plan",
|
||||
"code": "Code",
|
||||
"qa": "QA"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hero": {
|
||||
"title": "Bienvenue sur Auto Claude",
|
||||
"subtitle": "Construisez des logiciels de manière autonome avec des agents IA"
|
||||
},
|
||||
"actions": {
|
||||
"newProject": "Nouveau projet",
|
||||
"openProject": "Ouvrir un projet"
|
||||
},
|
||||
"recentProjects": {
|
||||
"title": "Projets récents",
|
||||
"empty": "Aucun projet",
|
||||
"emptyDescription": "Créez un nouveau projet ou ouvrez-en un existant pour commencer",
|
||||
"openFolder": "Ouvrir le dossier"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import type { NotificationSettings } from './project';
|
||||
import type { ChangelogFormat, ChangelogAudience, ChangelogEmojiLevel } from './changelog';
|
||||
import type { SupportedLanguage } from '../constants/i18n';
|
||||
|
||||
// Color theme types for multi-theme support
|
||||
export type ColorTheme = 'default' | 'dusk' | 'lime' | 'ocean' | 'retro' | 'neo' | 'forest';
|
||||
@@ -113,6 +114,8 @@ export interface AppSettings {
|
||||
betaUpdates?: boolean;
|
||||
// Migration flags (internal use)
|
||||
_migratedAgentProfileToAuto?: boolean;
|
||||
// Language preference for UI (i18n)
|
||||
language?: SupportedLanguage;
|
||||
}
|
||||
|
||||
// Auto-Claude Source Environment Configuration (for auto-claude repo .env)
|
||||
|
||||
Reference in New Issue
Block a user