Spaces:
Sleeping
Sleeping
Commit
·
e7addf3
1
Parent(s):
16eff4c
Final
Browse files- .gitignore +2 -1
- Dockerfile +1 -1
- crew_result.txt +0 -0
- entrypoint.sh +1 -1
- frontend/app/.gitignore +24 -0
- frontend/app/README.md +16 -0
- frontend/app/components.json +22 -0
- frontend/app/eslint.config.js +29 -0
- frontend/app/index.html +13 -0
- frontend/app/jsconfig.json +8 -0
- frontend/app/package-lock.json +0 -0
- frontend/app/package.json +40 -0
- frontend/app/public/vite.svg +1 -0
- frontend/app/src/App.css +42 -0
- frontend/app/src/App.jsx +634 -0
- frontend/app/src/assets/react.svg +1 -0
- frontend/app/src/components/ui/button.jsx +54 -0
- frontend/app/src/components/ui/card.jsx +101 -0
- frontend/app/src/index.css +186 -0
- frontend/app/src/lib/utils.js +6 -0
- frontend/app/src/main.jsx +10 -0
- frontend/app/vite.config.js +14 -0
- poem.txt +1 -0
- requirements.txt +0 -0
- sql_files/app.db +0 -0
- sql_files/schema.sql +17 -0
- src/__pycache__/app.cpython-313.pyc +0 -0
- src/app.py +202 -8
- src/codeagent/.gitignore +4 -0
- src/codeagent/README.md +56 -0
- src/codeagent/pyproject.toml +21 -0
- src/codeagent/src/codeagent/__init__.py +0 -0
- src/codeagent/src/codeagent/crews/SqlCrew/__init__.py +1 -0
- src/codeagent/src/codeagent/crews/SqlCrew/config/agents.yaml +56 -0
- src/codeagent/src/codeagent/crews/SqlCrew/config/tasks.yaml +33 -0
- src/codeagent/src/codeagent/crews/SqlCrew/sqlcrew.py +160 -0
- src/codeagent/src/codeagent/main.py +86 -0
- src/codeagent/src/codeagent/tools/__init__.py +0 -0
- src/codeagent/src/codeagent/tools/custom_tools.py +64 -0
.gitignore
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
-
/python
|
|
|
|
|
|
| 1 |
+
/python
|
| 2 |
+
/frontend/node_modules
|
Dockerfile
CHANGED
|
@@ -41,6 +41,6 @@ ENV PORT=7860
|
|
| 41 |
|
| 42 |
EXPOSE 11434 7860 5000
|
| 43 |
|
| 44 |
-
HEALTHCHECK CMD curl --fail http://localhost:
|
| 45 |
|
| 46 |
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
|
|
| 41 |
|
| 42 |
EXPOSE 11434 7860 5000
|
| 43 |
|
| 44 |
+
HEALTHCHECK CMD curl --fail http://localhost:7860/_stcore/health || exit 1
|
| 45 |
|
| 46 |
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
crew_result.txt
ADDED
|
File without changes
|
entrypoint.sh
CHANGED
|
@@ -9,6 +9,6 @@ done
|
|
| 9 |
|
| 10 |
ollama pull nomic-embed-text:latest
|
| 11 |
|
| 12 |
-
ollama pull
|
| 13 |
|
| 14 |
exec flask --app src/app run --host=0.0.0.0 --port=$PORT
|
|
|
|
| 9 |
|
| 10 |
ollama pull nomic-embed-text:latest
|
| 11 |
|
| 12 |
+
ollama pull llama3.1:8b-instruct-q4_0 || true
|
| 13 |
|
| 14 |
exec flask --app src/app run --host=0.0.0.0 --port=$PORT
|
frontend/app/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/app/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
| 9 |
+
|
| 10 |
+
## React Compiler
|
| 11 |
+
|
| 12 |
+
The React Compiler is not enabled on this template. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the ESLint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
frontend/app/components.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
+
"style": "new-york",
|
| 4 |
+
"rsc": false,
|
| 5 |
+
"tsx": false,
|
| 6 |
+
"tailwind": {
|
| 7 |
+
"config": "",
|
| 8 |
+
"css": "src/index.css",
|
| 9 |
+
"baseColor": "neutral",
|
| 10 |
+
"cssVariables": true,
|
| 11 |
+
"prefix": ""
|
| 12 |
+
},
|
| 13 |
+
"iconLibrary": "lucide",
|
| 14 |
+
"aliases": {
|
| 15 |
+
"components": "@/components",
|
| 16 |
+
"utils": "@/lib/utils",
|
| 17 |
+
"ui": "@/components/ui",
|
| 18 |
+
"lib": "@/lib",
|
| 19 |
+
"hooks": "@/hooks"
|
| 20 |
+
},
|
| 21 |
+
"registries": {}
|
| 22 |
+
}
|
frontend/app/eslint.config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
| 6 |
+
|
| 7 |
+
export default defineConfig([
|
| 8 |
+
globalIgnores(['dist']),
|
| 9 |
+
{
|
| 10 |
+
files: ['**/*.{js,jsx}'],
|
| 11 |
+
extends: [
|
| 12 |
+
js.configs.recommended,
|
| 13 |
+
reactHooks.configs['recommended-latest'],
|
| 14 |
+
reactRefresh.configs.vite,
|
| 15 |
+
],
|
| 16 |
+
languageOptions: {
|
| 17 |
+
ecmaVersion: 2020,
|
| 18 |
+
globals: globals.browser,
|
| 19 |
+
parserOptions: {
|
| 20 |
+
ecmaVersion: 'latest',
|
| 21 |
+
ecmaFeatures: { jsx: true },
|
| 22 |
+
sourceType: 'module',
|
| 23 |
+
},
|
| 24 |
+
},
|
| 25 |
+
rules: {
|
| 26 |
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
| 27 |
+
},
|
| 28 |
+
},
|
| 29 |
+
])
|
frontend/app/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>app</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
frontend/app/jsconfig.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"baseUrl": ".",
|
| 4 |
+
"paths": {
|
| 5 |
+
"@/*": ["./src/*"]
|
| 6 |
+
}
|
| 7 |
+
}
|
| 8 |
+
}
|
frontend/app/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/app/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "app",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@monaco-editor/react": "^4.7.0",
|
| 14 |
+
"@radix-ui/react-slot": "^1.2.3",
|
| 15 |
+
"@tailwindcss/vite": "^4.1.13",
|
| 16 |
+
"class-variance-authority": "^0.7.1",
|
| 17 |
+
"clsx": "^2.1.1",
|
| 18 |
+
"lucide-react": "^0.544.0",
|
| 19 |
+
"monaco-editor": "^0.53.0",
|
| 20 |
+
"react": "^19.1.1",
|
| 21 |
+
"react-dom": "^19.1.1",
|
| 22 |
+
"socket.io-client": "^4.8.1",
|
| 23 |
+
"tailwind-merge": "^3.3.1",
|
| 24 |
+
"tailwind-variants": "^3.1.1",
|
| 25 |
+
"tailwindcss": "^4.1.13",
|
| 26 |
+
"zustand": "^5.0.8"
|
| 27 |
+
},
|
| 28 |
+
"devDependencies": {
|
| 29 |
+
"@eslint/js": "^9.36.0",
|
| 30 |
+
"@types/react": "^19.1.13",
|
| 31 |
+
"@types/react-dom": "^19.1.9",
|
| 32 |
+
"@vitejs/plugin-react": "^5.0.3",
|
| 33 |
+
"eslint": "^9.36.0",
|
| 34 |
+
"eslint-plugin-react-hooks": "^5.2.0",
|
| 35 |
+
"eslint-plugin-react-refresh": "^0.4.20",
|
| 36 |
+
"globals": "^16.4.0",
|
| 37 |
+
"tw-animate-css": "^1.3.8",
|
| 38 |
+
"vite": "^7.1.7"
|
| 39 |
+
}
|
| 40 |
+
}
|
frontend/app/public/vite.svg
ADDED
|
|
frontend/app/src/App.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#root {
|
| 2 |
+
max-width: 1280px;
|
| 3 |
+
margin: 0 auto;
|
| 4 |
+
padding: 2rem;
|
| 5 |
+
text-align: center;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.logo {
|
| 9 |
+
height: 6em;
|
| 10 |
+
padding: 1.5em;
|
| 11 |
+
will-change: filter;
|
| 12 |
+
transition: filter 300ms;
|
| 13 |
+
}
|
| 14 |
+
.logo:hover {
|
| 15 |
+
filter: drop-shadow(0 0 2em #646cffaa);
|
| 16 |
+
}
|
| 17 |
+
.logo.react:hover {
|
| 18 |
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@keyframes logo-spin {
|
| 22 |
+
from {
|
| 23 |
+
transform: rotate(0deg);
|
| 24 |
+
}
|
| 25 |
+
to {
|
| 26 |
+
transform: rotate(360deg);
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
@media (prefers-reduced-motion: no-preference) {
|
| 31 |
+
a:nth-of-type(2) .logo {
|
| 32 |
+
animation: logo-spin infinite 20s linear;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.card {
|
| 37 |
+
padding: 2em;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.read-the-docs {
|
| 41 |
+
color: #888;
|
| 42 |
+
}
|
frontend/app/src/App.jsx
ADDED
|
@@ -0,0 +1,634 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useState, useRef } from "react";
|
| 2 |
+
import { Editor, DiffEditor } from "@monaco-editor/react";
|
| 3 |
+
import { create } from "zustand";
|
| 4 |
+
import { io } from "socket.io-client";
|
| 5 |
+
|
| 6 |
+
const BASE_URL = "http://localhost:7860";
|
| 7 |
+
|
| 8 |
+
const useStore = create((set, get) => ({
|
| 9 |
+
files: [],
|
| 10 |
+
selectedFile: null,
|
| 11 |
+
editorContent: "",
|
| 12 |
+
dbSchema: { tables: [] },
|
| 13 |
+
chat: [],
|
| 14 |
+
consoleOutput: "",
|
| 15 |
+
loading: false,
|
| 16 |
+
fileProposals: {},
|
| 17 |
+
showDiffForFile: null,
|
| 18 |
+
fileContents: {},
|
| 19 |
+
setFiles: (files) => set({ files }),
|
| 20 |
+
setSelectedFile: (name) => set({ selectedFile: { name } }),
|
| 21 |
+
setEditorContent: (editorContent) => set({ editorContent }),
|
| 22 |
+
setDbSchema: (dbSchema) => set({ dbSchema }),
|
| 23 |
+
pushChat: (msg) => set((s) => ({ chat: [...s.chat, msg] })),
|
| 24 |
+
setConsoleOutput: (consoleOutput) => set({ consoleOutput }),
|
| 25 |
+
setLoading: (loading) => set({ loading }),
|
| 26 |
+
setFileProposal: (name, details) => set((s) => ({ fileProposals: { ...s.fileProposals, [name]: { ...details, active: true } } })),
|
| 27 |
+
setShowDiffForFile: (fileName) => set({ showDiffForFile: fileName }),
|
| 28 |
+
openFile: async (name) => {
|
| 29 |
+
const currentState = get();
|
| 30 |
+
if (currentState.selectedFile?.name && currentState.editorContent) {
|
| 31 |
+
set({ fileContents: { ...currentState.fileContents, [currentState.selectedFile.name]: currentState.editorContent } });
|
| 32 |
+
}
|
| 33 |
+
set({ loading: true });
|
| 34 |
+
let content = "";
|
| 35 |
+
const state = get();
|
| 36 |
+
const proposal = state.fileProposals[name];
|
| 37 |
+
if (proposal?.isNew) {
|
| 38 |
+
content = "";
|
| 39 |
+
} else {
|
| 40 |
+
content = state.fileContents[name];
|
| 41 |
+
if (!content) {
|
| 42 |
+
try {
|
| 43 |
+
const res = await fetch(`${BASE_URL}/files/${encodeURIComponent(name)}`);
|
| 44 |
+
if (!res.ok) throw new Error("Failed to load file");
|
| 45 |
+
const data = await res.json();
|
| 46 |
+
content = data.content;
|
| 47 |
+
set({ fileContents: { ...get().fileContents, [name]: content } });
|
| 48 |
+
} catch (e) {
|
| 49 |
+
console.warn(e);
|
| 50 |
+
content = `-- demo content for ${name}\nSELECT 1;`;
|
| 51 |
+
set({ fileContents: { ...get().fileContents, [name]: content } });
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
set({ selectedFile: { name }, editorContent: content, loading: false });
|
| 56 |
+
const finalState = get();
|
| 57 |
+
const prop = finalState.fileProposals[name];
|
| 58 |
+
if (prop?.active) {
|
| 59 |
+
set({ showDiffForFile: name });
|
| 60 |
+
} else {
|
| 61 |
+
set({ showDiffForFile: null });
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
acceptProposal: () => {
|
| 65 |
+
const state = get();
|
| 66 |
+
const file = state.selectedFile?.name;
|
| 67 |
+
if (state.showDiffForFile !== file) return;
|
| 68 |
+
const prop = state.fileProposals[file];
|
| 69 |
+
if (!prop) return;
|
| 70 |
+
if (prop.isNew) {
|
| 71 |
+
const confirmedName = prompt("Save new file as (.sql required):", prop.suggestedName);
|
| 72 |
+
if (!confirmedName?.trim() || !confirmedName.endsWith(".sql")) {
|
| 73 |
+
return;
|
| 74 |
+
}
|
| 75 |
+
const finalName = confirmedName.trim();
|
| 76 |
+
fetch(`${BASE_URL}/files`, {
|
| 77 |
+
method: "POST",
|
| 78 |
+
headers: { "Content-Type": "application/json" },
|
| 79 |
+
body: JSON.stringify({ filename: finalName, content: prop.code }),
|
| 80 |
+
})
|
| 81 |
+
.then((res) => {
|
| 82 |
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
| 83 |
+
return res.json();
|
| 84 |
+
})
|
| 85 |
+
.then(() => {
|
| 86 |
+
const newProps = { ...state.fileProposals };
|
| 87 |
+
delete newProps[file];
|
| 88 |
+
const currentFiles = get().files;
|
| 89 |
+
set({
|
| 90 |
+
files: [...currentFiles, { name: finalName }],
|
| 91 |
+
editorContent: prop.code,
|
| 92 |
+
fileContents: { ...get().fileContents, [finalName]: prop.code },
|
| 93 |
+
showDiffForFile: null,
|
| 94 |
+
fileProposals: newProps,
|
| 95 |
+
});
|
| 96 |
+
if (finalName !== file) {
|
| 97 |
+
set({ selectedFile: { name: finalName } });
|
| 98 |
+
}
|
| 99 |
+
})
|
| 100 |
+
.catch((e) => console.error("Failed to create file:", e));
|
| 101 |
+
} else {
|
| 102 |
+
fetch(`${BASE_URL}/files/${encodeURIComponent(file)}`, {
|
| 103 |
+
method: "PUT",
|
| 104 |
+
headers: { "Content-Type": "application/json" },
|
| 105 |
+
body: JSON.stringify({ content: prop.code }),
|
| 106 |
+
})
|
| 107 |
+
.then((res) => {
|
| 108 |
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
| 109 |
+
const newProps = { ...state.fileProposals };
|
| 110 |
+
delete newProps[file];
|
| 111 |
+
set({
|
| 112 |
+
editorContent: prop.code,
|
| 113 |
+
fileContents: { ...state.fileContents, [file]: prop.code },
|
| 114 |
+
showDiffForFile: null,
|
| 115 |
+
fileProposals: newProps,
|
| 116 |
+
});
|
| 117 |
+
})
|
| 118 |
+
.catch((e) => console.error("Failed to save after accept:", e));
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
rejectProposal: () => {
|
| 122 |
+
const state = get();
|
| 123 |
+
const file = state.selectedFile?.name;
|
| 124 |
+
if (state.showDiffForFile !== file) return;
|
| 125 |
+
const newProps = { ...state.fileProposals };
|
| 126 |
+
delete newProps[file];
|
| 127 |
+
set({ showDiffForFile: null, fileProposals: newProps });
|
| 128 |
+
},
|
| 129 |
+
refreshFiles: async () => {
|
| 130 |
+
try {
|
| 131 |
+
const res = await fetch(`${BASE_URL}/files`);
|
| 132 |
+
if (!res.ok) throw new Error("Failed to load files");
|
| 133 |
+
let data = await res.json();
|
| 134 |
+
data = data.filter((f) => f.name.endsWith(".sql"));
|
| 135 |
+
set({ files: data || [] });
|
| 136 |
+
} catch (e) {
|
| 137 |
+
console.warn(e);
|
| 138 |
+
}
|
| 139 |
+
},
|
| 140 |
+
}));
|
| 141 |
+
|
| 142 |
+
const Card = ({ children, className = "" }) => (
|
| 143 |
+
<div className={`rounded-xl border bg-white shadow-sm ${className}`}>{children}</div>
|
| 144 |
+
);
|
| 145 |
+
const CardContent = ({ children, className = "" }) => (
|
| 146 |
+
<div className={`p-3 ${className}`}>{children}</div>
|
| 147 |
+
);
|
| 148 |
+
const IconButton = ({ children, onClick, className = "" }) => (
|
| 149 |
+
<button
|
| 150 |
+
onClick={onClick}
|
| 151 |
+
className={`px-3 py-1 rounded-md border hover:shadow-sm text-sm bg-blue-500 text-white ${className}`}
|
| 152 |
+
>
|
| 153 |
+
{children}
|
| 154 |
+
</button>
|
| 155 |
+
);
|
| 156 |
+
|
| 157 |
+
function FileExplorer() {
|
| 158 |
+
const { files, selectedFile, loading, fileProposals, fileContents, editorContent } = useStore();
|
| 159 |
+
const openFile = useStore((s) => s.openFile);
|
| 160 |
+
|
| 161 |
+
const loadFiles = async () => {
|
| 162 |
+
useStore.setState({ loading: true });
|
| 163 |
+
try {
|
| 164 |
+
const res = await fetch(`${BASE_URL}/files`);
|
| 165 |
+
if (!res.ok) throw new Error("Failed to load files");
|
| 166 |
+
let data = await res.json();
|
| 167 |
+
data = data.filter((f) => f.name.endsWith(".sql"));
|
| 168 |
+
useStore.setState({ files: data || [] });
|
| 169 |
+
} catch (e) {
|
| 170 |
+
console.warn(e);
|
| 171 |
+
} finally {
|
| 172 |
+
useStore.setState({ loading: false });
|
| 173 |
+
}
|
| 174 |
+
};
|
| 175 |
+
|
| 176 |
+
useEffect(() => {
|
| 177 |
+
loadFiles();
|
| 178 |
+
}, []);
|
| 179 |
+
|
| 180 |
+
useEffect(() => {
|
| 181 |
+
if (files.length > 0 && !selectedFile?.name) {
|
| 182 |
+
openFile(files[0].name);
|
| 183 |
+
}
|
| 184 |
+
}, [files, selectedFile?.name, openFile]);
|
| 185 |
+
|
| 186 |
+
async function createNewFile() {
|
| 187 |
+
const name = prompt("New file name (must end with .sql):");
|
| 188 |
+
if (!name || !name.endsWith(".sql")) return;
|
| 189 |
+
try {
|
| 190 |
+
await fetch(`${BASE_URL}/files`, {
|
| 191 |
+
method: "POST",
|
| 192 |
+
headers: { "Content-Type": "application/json" },
|
| 193 |
+
body: JSON.stringify({ filename: name, content: "" }),
|
| 194 |
+
});
|
| 195 |
+
loadFiles();
|
| 196 |
+
} catch (e) {
|
| 197 |
+
console.warn(e);
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
const proposedNews = Object.entries(fileProposals).filter(([n, p]) => p.isNew);
|
| 202 |
+
|
| 203 |
+
return (
|
| 204 |
+
<Card className="h-full flex flex-col">
|
| 205 |
+
<CardContent className="flex items-center justify-between">
|
| 206 |
+
<h3 className="text-sm font-semibold">Files</h3>
|
| 207 |
+
<div className="flex gap-2">
|
| 208 |
+
<IconButton onClick={createNewFile}>New</IconButton>
|
| 209 |
+
</div>
|
| 210 |
+
</CardContent>
|
| 211 |
+
|
| 212 |
+
<div className="px-3 pb-3 overflow-auto">
|
| 213 |
+
<ul className="space-y-1">
|
| 214 |
+
{files.map((f) => {
|
| 215 |
+
const hasProposal = fileProposals[f.name]?.active;
|
| 216 |
+
const isDirty = f.name === selectedFile?.name && (!fileContents[f.name] || editorContent !== fileContents[f.name]);
|
| 217 |
+
return (
|
| 218 |
+
<li key={f.name}>
|
| 219 |
+
<button
|
| 220 |
+
onClick={() => openFile(f.name)}
|
| 221 |
+
className={`w-full text-left px-2 py-1 rounded-md text-white hover:bg-slate-50 ${selectedFile?.name === f.name ? "bg-slate-100 font-medium" : ""}`}
|
| 222 |
+
>
|
| 223 |
+
<div className="flex items-center justify-between">
|
| 224 |
+
<span>{f.name}{isDirty ? " *" : ""}</span>
|
| 225 |
+
{hasProposal && <div className="w-2 h-2 bg-green-500 rounded-full"></div>}
|
| 226 |
+
</div>
|
| 227 |
+
</button>
|
| 228 |
+
</li>
|
| 229 |
+
);
|
| 230 |
+
})}
|
| 231 |
+
{proposedNews.map(([name]) => {
|
| 232 |
+
const prop = fileProposals[name];
|
| 233 |
+
const isDirty = name === selectedFile?.name && (!fileContents[name] || editorContent !== fileContents[name]);
|
| 234 |
+
return (
|
| 235 |
+
<li key={`p-${name}`}>
|
| 236 |
+
<button
|
| 237 |
+
onClick={() => openFile(name)}
|
| 238 |
+
className={`w-full text-left px-2 py-1 rounded-md text-white hover:bg-slate-50 ${selectedFile?.name === name ? "bg-slate-100 font-medium" : ""}`}
|
| 239 |
+
>
|
| 240 |
+
<div className="flex items-center justify-between">
|
| 241 |
+
<span className="italic text-blue-600">{name} (proposed){isDirty ? " *" : ""}</span>
|
| 242 |
+
<div className="w-2 h-2 bg-yellow-500 rounded-full"></div>
|
| 243 |
+
</div>
|
| 244 |
+
</button>
|
| 245 |
+
</li>
|
| 246 |
+
);
|
| 247 |
+
})}
|
| 248 |
+
</ul>
|
| 249 |
+
</div>
|
| 250 |
+
|
| 251 |
+
<div className="mt-auto p-3 border-t">
|
| 252 |
+
<div className="text-xs text-slate-500">App Status: {loading ? "loading" : "ready"}</div>
|
| 253 |
+
</div>
|
| 254 |
+
</Card>
|
| 255 |
+
);
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
function DBViewer() {
|
| 259 |
+
const { dbSchema, setDbSchema, setConsoleOutput, setFileProposal, setShowDiffForFile} = useStore();
|
| 260 |
+
|
| 261 |
+
useEffect(() => {
|
| 262 |
+
async function loadSchema() {
|
| 263 |
+
try {
|
| 264 |
+
const tres = await fetch(`${BASE_URL}/tables`);
|
| 265 |
+
if (!tres.ok) throw new Error("no tables");
|
| 266 |
+
const tables = await tres.json();
|
| 267 |
+
const tablesWithCols = await Promise.all(
|
| 268 |
+
tables.map(async (t) => {
|
| 269 |
+
const qres = await fetch(`${BASE_URL}/query`, {
|
| 270 |
+
method: "POST",
|
| 271 |
+
headers: { "Content-Type": "application/json" },
|
| 272 |
+
body: JSON.stringify({ query: `PRAGMA table_info(${t})` }),
|
| 273 |
+
});
|
| 274 |
+
const colsData = await qres.json();
|
| 275 |
+
console.log(colsData)
|
| 276 |
+
const columns = colsData.map((c) => `${c.name} ${c.type}`);
|
| 277 |
+
return { name: t, columns };
|
| 278 |
+
})
|
| 279 |
+
);
|
| 280 |
+
setDbSchema({ tables: tables.map((t) => ({ name: t, columns: [] })) });
|
| 281 |
+
} catch (e) {
|
| 282 |
+
}
|
| 283 |
+
}
|
| 284 |
+
loadSchema();
|
| 285 |
+
}, [setDbSchema]);
|
| 286 |
+
|
| 287 |
+
async function inspectTable(table) {
|
| 288 |
+
try {
|
| 289 |
+
const res = await fetch(`${BASE_URL}/query`, {
|
| 290 |
+
method: "POST",
|
| 291 |
+
headers: { "Content-Type": "application/json" },
|
| 292 |
+
body: JSON.stringify({ query: `SELECT * FROM ${table} LIMIT 50;` }),
|
| 293 |
+
});
|
| 294 |
+
const data = await res.json();
|
| 295 |
+
setConsoleOutput(JSON.stringify(data, null, 2));
|
| 296 |
+
} catch (e) {
|
| 297 |
+
setConsoleOutput(`Failed to query table ${table}: ${e.message}`);
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
return (
|
| 302 |
+
<Card className="h-full">
|
| 303 |
+
<CardContent>
|
| 304 |
+
<div className="flex items-center justify-between">
|
| 305 |
+
<h3 className="text-sm font-semibold">DB Viewer</h3>
|
| 306 |
+
<div className="text-xs text-slate-500">SQLite</div>
|
| 307 |
+
</div>
|
| 308 |
+
|
| 309 |
+
<div className="mt-3 space-y-3 overflow-auto" style={{ maxHeight: "40vh" }}>
|
| 310 |
+
{dbSchema.tables.map((t) => (
|
| 311 |
+
<div key={t.name} className="p-2 rounded border hover:bg-slate-50">
|
| 312 |
+
<div className="flex justify-between items-center">
|
| 313 |
+
<div>
|
| 314 |
+
<div className="font-medium">{t.name}</div>
|
| 315 |
+
<div className="text-xs text-slate-500">{t.columns.length} columns</div>
|
| 316 |
+
</div>
|
| 317 |
+
<div className="flex gap-2">
|
| 318 |
+
<IconButton onClick={() => inspectTable(t.name)}>Preview</IconButton>
|
| 319 |
+
</div>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
))}
|
| 323 |
+
</div>
|
| 324 |
+
</CardContent>
|
| 325 |
+
</Card>
|
| 326 |
+
);
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
function WSListener() {
|
| 330 |
+
const { setFileProposal, setShowDiffForFile, selectedFile, pushChat } = useStore();
|
| 331 |
+
const [isWaiting, setIsWaiting] = useState(true);
|
| 332 |
+
|
| 333 |
+
useEffect(() => {
|
| 334 |
+
const socket = io("http://localhost:7860");
|
| 335 |
+
|
| 336 |
+
socket.on("connect", () => {
|
| 337 |
+
console.log("Connected to WebSocket server");
|
| 338 |
+
setIsWaiting(false);
|
| 339 |
+
});
|
| 340 |
+
|
| 341 |
+
socket.on("event", (data) => {
|
| 342 |
+
console.log("WS message:", data);
|
| 343 |
+
if (data.kind === "code_change") {
|
| 344 |
+
setFileProposal(data.filename || selectedFile?.name, {
|
| 345 |
+
code: data.proposedCode,
|
| 346 |
+
isNew: data.isNew,
|
| 347 |
+
suggestedName: data.newFileName,
|
| 348 |
+
});
|
| 349 |
+
setShowDiffForFile(data.filename || selectedFile?.name);
|
| 350 |
+
}
|
| 351 |
+
console.log(data)
|
| 352 |
+
if (data.kind === "agent_message") {
|
| 353 |
+
pushChat({ role: "agent", text: `${data.output}`});
|
| 354 |
+
}
|
| 355 |
+
});
|
| 356 |
+
|
| 357 |
+
socket.on("disconnect", () => {
|
| 358 |
+
console.log("Disconnected from WebSocket server");
|
| 359 |
+
setIsWaiting(true);
|
| 360 |
+
});
|
| 361 |
+
|
| 362 |
+
return () => {
|
| 363 |
+
socket.disconnect();
|
| 364 |
+
};
|
| 365 |
+
}, []);
|
| 366 |
+
|
| 367 |
+
return isWaiting ? <div>Waiting for agent response...</div> : null;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
function AgentChat() {
|
| 371 |
+
const { chat, pushChat, setFileProposal, setShowDiffForFile, selectedFile, openFile } = useStore();
|
| 372 |
+
const [input, setInput] = useState("");
|
| 373 |
+
const [sending, setSending] = useState(false);
|
| 374 |
+
|
| 375 |
+
async function sendMessage() {
|
| 376 |
+
if (!input.trim() || sending) return;
|
| 377 |
+
pushChat({ role: "user", text: input });
|
| 378 |
+
setInput("");
|
| 379 |
+
setSending(true);
|
| 380 |
+
|
| 381 |
+
try {
|
| 382 |
+
const res = await fetch(`${BASE_URL}/run_crew`, {
|
| 383 |
+
method: "POST",
|
| 384 |
+
headers: { "Content-Type": "application/json" },
|
| 385 |
+
body: JSON.stringify({ instructions: input, file: selectedFile?.name }),
|
| 386 |
+
});
|
| 387 |
+
const data = await res.json();
|
| 388 |
+
pushChat({ role: "agent", text: data.status || "Crew is Working..." });
|
| 389 |
+
} catch (e) {
|
| 390 |
+
pushChat({ role: "agent", text: `Error: ${e.message}` });
|
| 391 |
+
} finally {
|
| 392 |
+
setSending(false);
|
| 393 |
+
}
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
return (
|
| 397 |
+
<Card className="flex flex-col h-full">
|
| 398 |
+
<CardContent className="flex-1 flex flex-col">
|
| 399 |
+
<div className="flex items-center justify-between mb-2">
|
| 400 |
+
<h3 className="text-sm font-semibold">Agent</h3>
|
| 401 |
+
</div>
|
| 402 |
+
|
| 403 |
+
<div className="flex-1 overflow-auto border rounded p-2 bg-slate-50">
|
| 404 |
+
{chat.map((m, i) => (
|
| 405 |
+
<div key={i} className={`mb-2 ${m.role === "user" ? "text-right" : ""}`}>
|
| 406 |
+
<div
|
| 407 |
+
className={`${
|
| 408 |
+
m.role === "user" ? "inline-block bg-blue-600 text-white" : "inline-block bg-white text-slate-800"
|
| 409 |
+
} px-3 py-1 rounded-lg`}
|
| 410 |
+
>
|
| 411 |
+
{m.text}
|
| 412 |
+
{m.proposedCode && (
|
| 413 |
+
<div className="mt-2 p-2 bg-gray-100 rounded">
|
| 414 |
+
<pre className="text-xs overflow-auto">{m.proposedCode}</pre>
|
| 415 |
+
</div>
|
| 416 |
+
)}
|
| 417 |
+
</div>
|
| 418 |
+
</div>
|
| 419 |
+
))}
|
| 420 |
+
</div>
|
| 421 |
+
|
| 422 |
+
<div className="mt-2 flex gap-2">
|
| 423 |
+
<input
|
| 424 |
+
className="flex-1 border rounded px-3 py-2"
|
| 425 |
+
value={input}
|
| 426 |
+
onChange={(e) => setInput(e.target.value)}
|
| 427 |
+
placeholder="Instruct SQL agent"
|
| 428 |
+
onKeyDown={(e) => {
|
| 429 |
+
if (e.key === "Enter") sendMessage();
|
| 430 |
+
}}
|
| 431 |
+
/>
|
| 432 |
+
<IconButton onClick={sendMessage} className="bg-blue-600 text-white">
|
| 433 |
+
{sending ? "..." : "Send"}
|
| 434 |
+
</IconButton>
|
| 435 |
+
</div>
|
| 436 |
+
</CardContent>
|
| 437 |
+
</Card>
|
| 438 |
+
);
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
function OutputConsole() {
|
| 442 |
+
const { consoleOutput } = useStore();
|
| 443 |
+
return (
|
| 444 |
+
<Card>
|
| 445 |
+
<CardContent className="p-0">
|
| 446 |
+
<div className="bg-black text-green-300 font-mono text-sm p-3 h-40 overflow-auto">
|
| 447 |
+
{consoleOutput || "Console output will appear here."}
|
| 448 |
+
</div>
|
| 449 |
+
</CardContent>
|
| 450 |
+
</Card>
|
| 451 |
+
);
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
function SQLMonacoEditor() {
|
| 455 |
+
const {
|
| 456 |
+
editorContent,
|
| 457 |
+
setEditorContent,
|
| 458 |
+
selectedFile,
|
| 459 |
+
showDiffForFile,
|
| 460 |
+
fileProposals,
|
| 461 |
+
fileContents,
|
| 462 |
+
acceptProposal,
|
| 463 |
+
rejectProposal,
|
| 464 |
+
} = useStore();
|
| 465 |
+
const proposal = fileProposals[selectedFile?.name];
|
| 466 |
+
const showDiff = !!proposal?.active && showDiffForFile === selectedFile?.name;
|
| 467 |
+
const proposedContent = proposal?.code || "";
|
| 468 |
+
const isDirty = selectedFile?.name && (!fileContents[selectedFile.name] || editorContent !== fileContents[selectedFile.name]);
|
| 469 |
+
|
| 470 |
+
const saveFile = async () => {
|
| 471 |
+
let fileName = selectedFile?.name;
|
| 472 |
+
if (!fileName) {
|
| 473 |
+
const name = prompt("Save as (must end with .sql):");
|
| 474 |
+
if (!name || !name.endsWith(".sql")) {
|
| 475 |
+
alert("Invalid filename");
|
| 476 |
+
return;
|
| 477 |
+
}
|
| 478 |
+
fileName = name;
|
| 479 |
+
try {
|
| 480 |
+
const res = await fetch(`${BASE_URL}/files`, {
|
| 481 |
+
method: "POST",
|
| 482 |
+
headers: { "Content-Type": "application/json" },
|
| 483 |
+
body: JSON.stringify({ filename: name, content: editorContent }),
|
| 484 |
+
});
|
| 485 |
+
if (!res.ok) throw new Error("Create failed");
|
| 486 |
+
const currentFiles = useStore.getState().files;
|
| 487 |
+
useStore.setState({ files: [...currentFiles, { name }] });
|
| 488 |
+
useStore.setState({ selectedFile: { name }, editorContent, fileContents: { ...useStore.getState().fileContents, [name]: editorContent } });
|
| 489 |
+
alert("File created and saved");
|
| 490 |
+
return;
|
| 491 |
+
} catch (e) {
|
| 492 |
+
alert("Failed to create file: " + e.message);
|
| 493 |
+
return;
|
| 494 |
+
}
|
| 495 |
+
}
|
| 496 |
+
try {
|
| 497 |
+
await fetch(`${BASE_URL}/files/${encodeURIComponent(fileName)}`, {
|
| 498 |
+
method: "PUT",
|
| 499 |
+
headers: { "Content-Type": "application/json" },
|
| 500 |
+
body: JSON.stringify({ content: editorContent }),
|
| 501 |
+
});
|
| 502 |
+
useStore.setState({ fileContents: { ...useStore.getState().fileContents, [fileName]: editorContent } });
|
| 503 |
+
alert("Saved");
|
| 504 |
+
} catch (e) {
|
| 505 |
+
alert("Save failed: " + e.message);
|
| 506 |
+
}
|
| 507 |
+
};
|
| 508 |
+
|
| 509 |
+
const runQuery = async () => {
|
| 510 |
+
let fileName = selectedFile?.name;
|
| 511 |
+
if (!fileName) {
|
| 512 |
+
const name = prompt("Save query as (must end with .sql):");
|
| 513 |
+
if (!name || !name.endsWith(".sql")) {
|
| 514 |
+
alert("Invalid filename");
|
| 515 |
+
return;
|
| 516 |
+
}
|
| 517 |
+
fileName = name;
|
| 518 |
+
try {
|
| 519 |
+
const res = await fetch(`${BASE_URL}/files`, {
|
| 520 |
+
method: "POST",
|
| 521 |
+
headers: { "Content-Type": "application/json" },
|
| 522 |
+
body: JSON.stringify({ filename: name, content: editorContent }),
|
| 523 |
+
});
|
| 524 |
+
if (!res.ok) throw new Error("Create failed");
|
| 525 |
+
const currentFiles = useStore.getState().files;
|
| 526 |
+
useStore.setState({ files: [...currentFiles, { name }], selectedFile: { name }, editorContent, fileContents: { ...useStore.getState().fileContents, [name]: editorContent } });
|
| 527 |
+
} catch (e) {
|
| 528 |
+
alert("Failed to create file: " + e.message);
|
| 529 |
+
return;
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
try {
|
| 533 |
+
const res = await fetch(`${BASE_URL}/query`, {
|
| 534 |
+
method: "POST",
|
| 535 |
+
headers: { "Content-Type": "application/json" },
|
| 536 |
+
body: JSON.stringify({ query: editorContent }),
|
| 537 |
+
});
|
| 538 |
+
if (!res.ok) {
|
| 539 |
+
let err;
|
| 540 |
+
try {
|
| 541 |
+
err = await res.json();
|
| 542 |
+
} catch {
|
| 543 |
+
err = "Query failed";
|
| 544 |
+
}
|
| 545 |
+
throw new Error(err.error || JSON.stringify(err) || err);
|
| 546 |
+
}
|
| 547 |
+
const data = await res.json();
|
| 548 |
+
useStore.setState({ consoleOutput: JSON.stringify(data, null, 2) });
|
| 549 |
+
} catch (e) {
|
| 550 |
+
useStore.setState({ consoleOutput: "Run failed: " + e.message });
|
| 551 |
+
}
|
| 552 |
+
};
|
| 553 |
+
|
| 554 |
+
const commonOptions = { minimap: { enabled: false }, fontSize: 13, wordWrap: "on" };
|
| 555 |
+
const editorKey = `${selectedFile?.name}-${showDiff ? "diff" : "normal"}`;
|
| 556 |
+
|
| 557 |
+
return (
|
| 558 |
+
<Card className="h-full flex flex-col">
|
| 559 |
+
<CardContent className="flex-1 flex flex-col p-0">
|
| 560 |
+
<div className="p-3 border-b flex items-center justify-between">
|
| 561 |
+
<div>
|
| 562 |
+
<div className="text-sm font-semibold">{selectedFile?.name || "No file selected"}{isDirty ? " *" : ""}</div>
|
| 563 |
+
<div className="text-xs text-slate-500">SQL Editor {showDiff ? "(Diff View)" : ""}</div>
|
| 564 |
+
</div>
|
| 565 |
+
<div className="flex gap-2">
|
| 566 |
+
{showDiff ? (
|
| 567 |
+
<>
|
| 568 |
+
<IconButton onClick={acceptProposal}>Accept</IconButton>
|
| 569 |
+
<IconButton onClick={rejectProposal} className="bg-red-500">
|
| 570 |
+
Reject
|
| 571 |
+
</IconButton>
|
| 572 |
+
</>
|
| 573 |
+
) : (
|
| 574 |
+
<>
|
| 575 |
+
<IconButton onClick={saveFile}>Save</IconButton>
|
| 576 |
+
<IconButton onClick={runQuery}>Run</IconButton>
|
| 577 |
+
</>
|
| 578 |
+
)}
|
| 579 |
+
</div>
|
| 580 |
+
</div>
|
| 581 |
+
|
| 582 |
+
<div key={editorKey} className="flex-1">
|
| 583 |
+
{showDiff ? (
|
| 584 |
+
<DiffEditor
|
| 585 |
+
height="100%"
|
| 586 |
+
language="sql"
|
| 587 |
+
original={editorContent}
|
| 588 |
+
modified={proposedContent}
|
| 589 |
+
options={{ ...commonOptions, renderSideBySide: false, diffCodeLens: false }}
|
| 590 |
+
/>
|
| 591 |
+
) : (
|
| 592 |
+
<Editor
|
| 593 |
+
height="100%"
|
| 594 |
+
defaultLanguage="sql"
|
| 595 |
+
value={editorContent}
|
| 596 |
+
onChange={(v) => setEditorContent(v || "")}
|
| 597 |
+
options={commonOptions}
|
| 598 |
+
/>
|
| 599 |
+
)}
|
| 600 |
+
</div>
|
| 601 |
+
</CardContent>
|
| 602 |
+
</Card>
|
| 603 |
+
);
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
export default function App() {
|
| 607 |
+
return (
|
| 608 |
+
<div className="h-screen w-screen bg-slate-50 p-4 box-border">
|
| 609 |
+
<div className="grid grid-cols-12 gap-4 h-full">
|
| 610 |
+
<div className="col-span-2 flex flex-col gap-4 h-full">
|
| 611 |
+
<FileExplorer />
|
| 612 |
+
<div className="h-1/3"></div>
|
| 613 |
+
</div>
|
| 614 |
+
|
| 615 |
+
<div className="col-span-7 flex flex-col gap-4 h-full">
|
| 616 |
+
<SQLMonacoEditor />
|
| 617 |
+
</div>
|
| 618 |
+
|
| 619 |
+
<div className="col-span-3 flex flex-col gap-4 h-full">
|
| 620 |
+
<div className="flex-1">
|
| 621 |
+
<AgentChat />
|
| 622 |
+
</div>
|
| 623 |
+
<div className="h-1/3">
|
| 624 |
+
<DBViewer />
|
| 625 |
+
</div>
|
| 626 |
+
<div className="h-1/3">
|
| 627 |
+
<OutputConsole />
|
| 628 |
+
</div>
|
| 629 |
+
</div>
|
| 630 |
+
</div>
|
| 631 |
+
<WSListener />
|
| 632 |
+
</div>
|
| 633 |
+
);
|
| 634 |
+
}
|
frontend/app/src/assets/react.svg
ADDED
|
|
frontend/app/src/components/ui/button.jsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import { Slot } from "@radix-ui/react-slot"
|
| 3 |
+
import { cva } from "class-variance-authority";
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const buttonVariants = cva(
|
| 8 |
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
| 13 |
+
destructive:
|
| 14 |
+
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
| 15 |
+
outline:
|
| 16 |
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
| 17 |
+
secondary:
|
| 18 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
| 19 |
+
ghost:
|
| 20 |
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
| 21 |
+
link: "text-primary underline-offset-4 hover:underline",
|
| 22 |
+
},
|
| 23 |
+
size: {
|
| 24 |
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
| 25 |
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
| 26 |
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
| 27 |
+
icon: "size-9",
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
defaultVariants: {
|
| 31 |
+
variant: "default",
|
| 32 |
+
size: "default",
|
| 33 |
+
},
|
| 34 |
+
}
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
function Button({
|
| 38 |
+
className,
|
| 39 |
+
variant,
|
| 40 |
+
size,
|
| 41 |
+
asChild = false,
|
| 42 |
+
...props
|
| 43 |
+
}) {
|
| 44 |
+
const Comp = asChild ? Slot : "button"
|
| 45 |
+
|
| 46 |
+
return (
|
| 47 |
+
<Comp
|
| 48 |
+
data-slot="button"
|
| 49 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
| 50 |
+
{...props} />
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export { Button, buttonVariants }
|
frontend/app/src/components/ui/card.jsx
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
function Card({
|
| 6 |
+
className,
|
| 7 |
+
...props
|
| 8 |
+
}) {
|
| 9 |
+
return (
|
| 10 |
+
<div
|
| 11 |
+
data-slot="card"
|
| 12 |
+
className={cn(
|
| 13 |
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
| 14 |
+
className
|
| 15 |
+
)}
|
| 16 |
+
{...props} />
|
| 17 |
+
);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
function CardHeader({
|
| 21 |
+
className,
|
| 22 |
+
...props
|
| 23 |
+
}) {
|
| 24 |
+
return (
|
| 25 |
+
<div
|
| 26 |
+
data-slot="card-header"
|
| 27 |
+
className={cn(
|
| 28 |
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
| 29 |
+
className
|
| 30 |
+
)}
|
| 31 |
+
{...props} />
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
function CardTitle({
|
| 36 |
+
className,
|
| 37 |
+
...props
|
| 38 |
+
}) {
|
| 39 |
+
return (
|
| 40 |
+
<div
|
| 41 |
+
data-slot="card-title"
|
| 42 |
+
className={cn("leading-none font-semibold", className)}
|
| 43 |
+
{...props} />
|
| 44 |
+
);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
function CardDescription({
|
| 48 |
+
className,
|
| 49 |
+
...props
|
| 50 |
+
}) {
|
| 51 |
+
return (
|
| 52 |
+
<div
|
| 53 |
+
data-slot="card-description"
|
| 54 |
+
className={cn("text-muted-foreground text-sm", className)}
|
| 55 |
+
{...props} />
|
| 56 |
+
);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
function CardAction({
|
| 60 |
+
className,
|
| 61 |
+
...props
|
| 62 |
+
}) {
|
| 63 |
+
return (
|
| 64 |
+
<div
|
| 65 |
+
data-slot="card-action"
|
| 66 |
+
className={cn(
|
| 67 |
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
| 68 |
+
className
|
| 69 |
+
)}
|
| 70 |
+
{...props} />
|
| 71 |
+
);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
function CardContent({
|
| 75 |
+
className,
|
| 76 |
+
...props
|
| 77 |
+
}) {
|
| 78 |
+
return (<div data-slot="card-content" className={cn("px-6", className)} {...props} />);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
function CardFooter({
|
| 82 |
+
className,
|
| 83 |
+
...props
|
| 84 |
+
}) {
|
| 85 |
+
return (
|
| 86 |
+
<div
|
| 87 |
+
data-slot="card-footer"
|
| 88 |
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
| 89 |
+
{...props} />
|
| 90 |
+
);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
export {
|
| 94 |
+
Card,
|
| 95 |
+
CardHeader,
|
| 96 |
+
CardFooter,
|
| 97 |
+
CardTitle,
|
| 98 |
+
CardAction,
|
| 99 |
+
CardDescription,
|
| 100 |
+
CardContent,
|
| 101 |
+
}
|
frontend/app/src/index.css
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import "tailwindcss";
|
| 2 |
+
@import "tw-animate-css";
|
| 3 |
+
|
| 4 |
+
@custom-variant dark (&:is(.dark *));
|
| 5 |
+
|
| 6 |
+
:root {
|
| 7 |
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
| 8 |
+
line-height: 1.5;
|
| 9 |
+
font-weight: 400;
|
| 10 |
+
|
| 11 |
+
color-scheme: light dark;
|
| 12 |
+
color: rgba(255, 255, 255, 0.87);
|
| 13 |
+
background-color: #242424;
|
| 14 |
+
|
| 15 |
+
font-synthesis: none;
|
| 16 |
+
text-rendering: optimizeLegibility;
|
| 17 |
+
-webkit-font-smoothing: antialiased;
|
| 18 |
+
-moz-osx-font-smoothing: grayscale;
|
| 19 |
+
--radius: 0.625rem;
|
| 20 |
+
--background: oklch(1 0 0);
|
| 21 |
+
--foreground: oklch(0.145 0 0);
|
| 22 |
+
--card: oklch(1 0 0);
|
| 23 |
+
--card-foreground: oklch(0.145 0 0);
|
| 24 |
+
--popover: oklch(1 0 0);
|
| 25 |
+
--popover-foreground: oklch(0.145 0 0);
|
| 26 |
+
--primary: oklch(0.205 0 0);
|
| 27 |
+
--primary-foreground: oklch(0.985 0 0);
|
| 28 |
+
--secondary: oklch(0.97 0 0);
|
| 29 |
+
--secondary-foreground: oklch(0.205 0 0);
|
| 30 |
+
--muted: oklch(0.97 0 0);
|
| 31 |
+
--muted-foreground: oklch(0.556 0 0);
|
| 32 |
+
--accent: oklch(0.97 0 0);
|
| 33 |
+
--accent-foreground: oklch(0.205 0 0);
|
| 34 |
+
--destructive: oklch(0.577 0.245 27.325);
|
| 35 |
+
--border: oklch(0.922 0 0);
|
| 36 |
+
--input: oklch(0.922 0 0);
|
| 37 |
+
--ring: oklch(0.708 0 0);
|
| 38 |
+
--chart-1: oklch(0.646 0.222 41.116);
|
| 39 |
+
--chart-2: oklch(0.6 0.118 184.704);
|
| 40 |
+
--chart-3: oklch(0.398 0.07 227.392);
|
| 41 |
+
--chart-4: oklch(0.828 0.189 84.429);
|
| 42 |
+
--chart-5: oklch(0.769 0.188 70.08);
|
| 43 |
+
--sidebar: oklch(0.985 0 0);
|
| 44 |
+
--sidebar-foreground: oklch(0.145 0 0);
|
| 45 |
+
--sidebar-primary: oklch(0.205 0 0);
|
| 46 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 47 |
+
--sidebar-accent: oklch(0.97 0 0);
|
| 48 |
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
| 49 |
+
--sidebar-border: oklch(0.922 0 0);
|
| 50 |
+
--sidebar-ring: oklch(0.708 0 0);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
a {
|
| 54 |
+
font-weight: 500;
|
| 55 |
+
color: #646cff;
|
| 56 |
+
text-decoration: inherit;
|
| 57 |
+
}
|
| 58 |
+
a:hover {
|
| 59 |
+
color: #535bf2;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
body {
|
| 63 |
+
margin: 0;
|
| 64 |
+
display: flex;
|
| 65 |
+
place-items: center;
|
| 66 |
+
min-width: 320px;
|
| 67 |
+
min-height: 100vh;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
h1 {
|
| 71 |
+
font-size: 3.2em;
|
| 72 |
+
line-height: 1.1;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
button {
|
| 76 |
+
border-radius: 8px;
|
| 77 |
+
border: 1px solid transparent;
|
| 78 |
+
padding: 0.6em 1.2em;
|
| 79 |
+
font-size: 1em;
|
| 80 |
+
font-weight: 500;
|
| 81 |
+
font-family: inherit;
|
| 82 |
+
background-color: #1a1a1a;
|
| 83 |
+
cursor: pointer;
|
| 84 |
+
transition: border-color 0.25s;
|
| 85 |
+
}
|
| 86 |
+
button:hover {
|
| 87 |
+
border-color: #646cff;
|
| 88 |
+
}
|
| 89 |
+
button:focus,
|
| 90 |
+
button:focus-visible {
|
| 91 |
+
outline: 4px auto -webkit-focus-ring-color;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
@media (prefers-color-scheme: light) {
|
| 95 |
+
:root {
|
| 96 |
+
color: #213547;
|
| 97 |
+
background-color: #ffffff;
|
| 98 |
+
}
|
| 99 |
+
a:hover {
|
| 100 |
+
color: #747bff;
|
| 101 |
+
}
|
| 102 |
+
button {
|
| 103 |
+
background-color: #f9f9f9;
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
@theme inline {
|
| 108 |
+
--radius-sm: calc(var(--radius) - 4px);
|
| 109 |
+
--radius-md: calc(var(--radius) - 2px);
|
| 110 |
+
--radius-lg: var(--radius);
|
| 111 |
+
--radius-xl: calc(var(--radius) + 4px);
|
| 112 |
+
--color-background: var(--background);
|
| 113 |
+
--color-foreground: var(--foreground);
|
| 114 |
+
--color-card: var(--card);
|
| 115 |
+
--color-card-foreground: var(--card-foreground);
|
| 116 |
+
--color-popover: var(--popover);
|
| 117 |
+
--color-popover-foreground: var(--popover-foreground);
|
| 118 |
+
--color-primary: var(--primary);
|
| 119 |
+
--color-primary-foreground: var(--primary-foreground);
|
| 120 |
+
--color-secondary: var(--secondary);
|
| 121 |
+
--color-secondary-foreground: var(--secondary-foreground);
|
| 122 |
+
--color-muted: var(--muted);
|
| 123 |
+
--color-muted-foreground: var(--muted-foreground);
|
| 124 |
+
--color-accent: var(--accent);
|
| 125 |
+
--color-accent-foreground: var(--accent-foreground);
|
| 126 |
+
--color-destructive: var(--destructive);
|
| 127 |
+
--color-border: var(--border);
|
| 128 |
+
--color-input: var(--input);
|
| 129 |
+
--color-ring: var(--ring);
|
| 130 |
+
--color-chart-1: var(--chart-1);
|
| 131 |
+
--color-chart-2: var(--chart-2);
|
| 132 |
+
--color-chart-3: var(--chart-3);
|
| 133 |
+
--color-chart-4: var(--chart-4);
|
| 134 |
+
--color-chart-5: var(--chart-5);
|
| 135 |
+
--color-sidebar: var(--sidebar);
|
| 136 |
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
| 137 |
+
--color-sidebar-primary: var(--sidebar-primary);
|
| 138 |
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
| 139 |
+
--color-sidebar-accent: var(--sidebar-accent);
|
| 140 |
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
| 141 |
+
--color-sidebar-border: var(--sidebar-border);
|
| 142 |
+
--color-sidebar-ring: var(--sidebar-ring);
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.dark {
|
| 146 |
+
--background: oklch(0.145 0 0);
|
| 147 |
+
--foreground: oklch(0.985 0 0);
|
| 148 |
+
--card: oklch(0.205 0 0);
|
| 149 |
+
--card-foreground: oklch(0.985 0 0);
|
| 150 |
+
--popover: oklch(0.205 0 0);
|
| 151 |
+
--popover-foreground: oklch(0.985 0 0);
|
| 152 |
+
--primary: oklch(0.922 0 0);
|
| 153 |
+
--primary-foreground: oklch(0.205 0 0);
|
| 154 |
+
--secondary: oklch(0.269 0 0);
|
| 155 |
+
--secondary-foreground: oklch(0.985 0 0);
|
| 156 |
+
--muted: oklch(0.269 0 0);
|
| 157 |
+
--muted-foreground: oklch(0.708 0 0);
|
| 158 |
+
--accent: oklch(0.269 0 0);
|
| 159 |
+
--accent-foreground: oklch(0.985 0 0);
|
| 160 |
+
--destructive: oklch(0.704 0.191 22.216);
|
| 161 |
+
--border: oklch(1 0 0 / 10%);
|
| 162 |
+
--input: oklch(1 0 0 / 15%);
|
| 163 |
+
--ring: oklch(0.556 0 0);
|
| 164 |
+
--chart-1: oklch(0.488 0.243 264.376);
|
| 165 |
+
--chart-2: oklch(0.696 0.17 162.48);
|
| 166 |
+
--chart-3: oklch(0.769 0.188 70.08);
|
| 167 |
+
--chart-4: oklch(0.627 0.265 303.9);
|
| 168 |
+
--chart-5: oklch(0.645 0.246 16.439);
|
| 169 |
+
--sidebar: oklch(0.205 0 0);
|
| 170 |
+
--sidebar-foreground: oklch(0.985 0 0);
|
| 171 |
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
| 172 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 173 |
+
--sidebar-accent: oklch(0.269 0 0);
|
| 174 |
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
| 175 |
+
--sidebar-border: oklch(1 0 0 / 10%);
|
| 176 |
+
--sidebar-ring: oklch(0.556 0 0);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
@layer base {
|
| 180 |
+
* {
|
| 181 |
+
@apply border-border outline-ring/50;
|
| 182 |
+
}
|
| 183 |
+
body {
|
| 184 |
+
@apply bg-background text-foreground;
|
| 185 |
+
}
|
| 186 |
+
}
|
frontend/app/src/lib/utils.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { clsx } from "clsx";
|
| 2 |
+
import { twMerge } from "tailwind-merge"
|
| 3 |
+
|
| 4 |
+
export function cn(...inputs) {
|
| 5 |
+
return twMerge(clsx(inputs));
|
| 6 |
+
}
|
frontend/app/src/main.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.jsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/app/vite.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from "path"
|
| 2 |
+
import tailwindcss from "@tailwindcss/vite"
|
| 3 |
+
import react from "@vitejs/plugin-react"
|
| 4 |
+
import { defineConfig } from "vite"
|
| 5 |
+
|
| 6 |
+
// https://vite.dev/config/
|
| 7 |
+
export default defineConfig({
|
| 8 |
+
plugins: [react(), tailwindcss()],
|
| 9 |
+
resolve: {
|
| 10 |
+
alias: {
|
| 11 |
+
"@": path.resolve(__dirname, "./src"),
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
+
})
|
poem.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
CrewAI is the ace of poetic pace, crafting verse with AI in its perfect place.
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
sql_files/app.db
ADDED
|
Binary file (24.6 kB). View file
|
|
|
sql_files/schema.sql
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CREATE TABLE Fruits (
|
| 2 |
+
fruit_id INT PRIMARY KEY,
|
| 3 |
+
name VARCHAR(255),
|
| 4 |
+
type VARCHAR(100)
|
| 5 |
+
);
|
| 6 |
+
|
| 7 |
+
CREATE TABLE Tastes (
|
| 8 |
+
taste_id INT PRIMARY KEY,
|
| 9 |
+
description VARCHAR(255)
|
| 10 |
+
);
|
| 11 |
+
|
| 12 |
+
CREATE TABLE Fruit_Tastes (
|
| 13 |
+
fruit_id INT NOT NULL,
|
| 14 |
+
taste_id INT NOT NULL,
|
| 15 |
+
CONSTRAINT fk_fruit FOREIGN KEY (fruit_id) REFERENCES Fruits(fruit_id),
|
| 16 |
+
CONSTRAINT fk_taste FOREIGN KEY (taste_id) REFERENCES Tastes(taste_id)
|
| 17 |
+
);
|
src/__pycache__/app.cpython-313.pyc
ADDED
|
Binary file (11.8 kB). View file
|
|
|
src/app.py
CHANGED
|
@@ -1,11 +1,205 @@
|
|
| 1 |
-
from flask import Flask
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
app = Flask(__name__)
|
| 4 |
|
| 5 |
-
|
| 6 |
-
def home():
|
| 7 |
-
return "Flask + Ollama on Hugging Face Spaces!"
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify, send_from_directory, render_template
|
| 2 |
+
import os
|
| 3 |
+
import sqlite3
|
| 4 |
+
import subprocess
|
| 5 |
+
import threading
|
| 6 |
+
import queue
|
| 7 |
+
from flask import Response
|
| 8 |
+
from flask_cors import CORS
|
| 9 |
+
import json
|
| 10 |
+
import re
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from flask_socketio import SocketIO, emit
|
| 13 |
|
|
|
|
| 14 |
|
| 15 |
+
event_queue = queue.Queue()
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
app = Flask(__name__, static_folder='../frontend/app/dist', static_url_path='/')
|
| 18 |
+
socketio = SocketIO(app, cors_allowed_origins="*")
|
| 19 |
+
|
| 20 |
+
__all__ = ["app", "socketio"]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
from codeagent.src.codeagent.main import CodeAgentFlow
|
| 24 |
+
|
| 25 |
+
CORS(app)
|
| 26 |
+
|
| 27 |
+
SQL_FILES_DIR = 'sql_files'
|
| 28 |
+
os.makedirs(SQL_FILES_DIR, exist_ok=True)
|
| 29 |
+
DB_FILE = 'app.db'
|
| 30 |
+
|
| 31 |
+
@app.route('/')
|
| 32 |
+
def index():
|
| 33 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 34 |
+
|
| 35 |
+
@app.route('/files', methods=['GET'])
|
| 36 |
+
def get_files():
|
| 37 |
+
files = os.listdir(SQL_FILES_DIR)
|
| 38 |
+
return jsonify([{'name': f} for f in files])
|
| 39 |
+
|
| 40 |
+
@app.route('/files', methods=['POST'])
|
| 41 |
+
def create_file():
|
| 42 |
+
data = request.json
|
| 43 |
+
filename = data['filename']
|
| 44 |
+
content = data.get('content', '')
|
| 45 |
+
filepath = os.path.join(SQL_FILES_DIR, filename)
|
| 46 |
+
with open(filepath, 'w') as f:
|
| 47 |
+
f.write(content)
|
| 48 |
+
return jsonify({"status": "created", "file": filename})
|
| 49 |
+
|
| 50 |
+
@app.route('/files/<filename>', methods=['PUT'])
|
| 51 |
+
def update_file(filename):
|
| 52 |
+
content = request.json.get('content', '')
|
| 53 |
+
filepath = os.path.join(SQL_FILES_DIR, filename)
|
| 54 |
+
if not os.path.exists(filepath):
|
| 55 |
+
return jsonify({"error": "file not found"}), 404
|
| 56 |
+
with open(filepath, 'w') as f:
|
| 57 |
+
f.write(content)
|
| 58 |
+
return jsonify({"status": "updated", "file": filename})
|
| 59 |
+
|
| 60 |
+
@app.route('/files/<filename>', methods=['GET'])
|
| 61 |
+
def get_file_content(filename):
|
| 62 |
+
filepath = os.path.join(SQL_FILES_DIR, filename)
|
| 63 |
+
if os.path.exists(filepath):
|
| 64 |
+
with open(filepath, 'r') as f:
|
| 65 |
+
content = f.read()
|
| 66 |
+
return jsonify({"filename": filename, "content": content})
|
| 67 |
+
return jsonify({"error": "file not found"}), 404
|
| 68 |
+
|
| 69 |
+
@app.route('/files/<filename>', methods=['DELETE'])
|
| 70 |
+
def delete_file(filename):
|
| 71 |
+
filepath = os.path.join(SQL_FILES_DIR, filename)
|
| 72 |
+
if os.path.exists(filepath):
|
| 73 |
+
os.remove(filepath)
|
| 74 |
+
return jsonify({"status": "deleted", "file": filename})
|
| 75 |
+
return jsonify({"error": "file not found"}), 404
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def get_db_connection():
|
| 79 |
+
conn = sqlite3.connect(os.path.join(SQL_FILES_DIR,DB_FILE))
|
| 80 |
+
conn.row_factory = sqlite3.Row
|
| 81 |
+
return conn
|
| 82 |
+
|
| 83 |
+
@app.route('/tables', methods=['GET'])
|
| 84 |
+
def get_tables():
|
| 85 |
+
conn = get_db_connection()
|
| 86 |
+
cursor = conn.cursor()
|
| 87 |
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
| 88 |
+
tables = [row['name'] for row in cursor.fetchall()]
|
| 89 |
+
conn.close()
|
| 90 |
+
return jsonify(tables)
|
| 91 |
+
|
| 92 |
+
recent_result = None
|
| 93 |
+
@app.route('/query', methods=['POST'])
|
| 94 |
+
def run_query():
|
| 95 |
+
query = request.json.get('query', '')
|
| 96 |
+
print(query)
|
| 97 |
+
conn = get_db_connection()
|
| 98 |
+
cursor = conn.cursor()
|
| 99 |
+
try:
|
| 100 |
+
query_lc = str(query).strip().lower()
|
| 101 |
+
|
| 102 |
+
if ";" in query.strip() and not query_lc.startswith("select") and not query_lc.startswith("pragma"):
|
| 103 |
+
# Multiple statements → executescript
|
| 104 |
+
cursor.executescript(query)
|
| 105 |
+
conn.commit()
|
| 106 |
+
result = {"rows_affected": cursor.rowcount}
|
| 107 |
+
else:
|
| 108 |
+
cursor.execute(query)
|
| 109 |
+
|
| 110 |
+
if query_lc.startswith("pragma"):
|
| 111 |
+
rows = cursor.fetchall()
|
| 112 |
+
result = [dict(row) for row in rows] if rows else []
|
| 113 |
+
elif query_lc.startswith("select"):
|
| 114 |
+
result = [dict(row) for row in cursor.fetchall()]
|
| 115 |
+
else:
|
| 116 |
+
conn.commit()
|
| 117 |
+
result = {"rows_affected": cursor.rowcount}
|
| 118 |
+
|
| 119 |
+
conn.close()
|
| 120 |
+
global recent_result
|
| 121 |
+
recent_result = {"query": query, "result": result}
|
| 122 |
+
|
| 123 |
+
return jsonify(result)
|
| 124 |
+
except Exception as e:
|
| 125 |
+
conn.close()
|
| 126 |
+
return jsonify({"error": str(e)}), 400
|
| 127 |
+
|
| 128 |
+
@app.route('/recent_query', methods=['GET'])
|
| 129 |
+
def recent_query():
|
| 130 |
+
return jsonify(recent_result)
|
| 131 |
+
|
| 132 |
+
@app.route('/execute_file', methods=['POST'])
|
| 133 |
+
def execute_file():
|
| 134 |
+
"""
|
| 135 |
+
Execute a .sql file located in SQL_FILES_DIR using sqlite3.executescript.
|
| 136 |
+
Accepts: { "filename": "schema.sql" }
|
| 137 |
+
Returns: { "status": "success", "file": "...", "started_at": "...", "finished_at": "..."}
|
| 138 |
+
or { "error": "message" }
|
| 139 |
+
"""
|
| 140 |
+
data = request.json or {}
|
| 141 |
+
filename = data.get('filename')
|
| 142 |
+
if not filename:
|
| 143 |
+
return jsonify({"error": "filename is required"}), 400
|
| 144 |
+
|
| 145 |
+
filepath = os.path.join(SQL_FILES_DIR, filename)
|
| 146 |
+
if not os.path.exists(filepath):
|
| 147 |
+
return jsonify({"error": f"file not found: {filename}"}), 404
|
| 148 |
+
|
| 149 |
+
started_at = datetime.utcnow().isoformat()
|
| 150 |
+
conn = get_db_connection()
|
| 151 |
+
try:
|
| 152 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 153 |
+
sql_text = f.read()
|
| 154 |
+
|
| 155 |
+
conn.executescript(sql_text)
|
| 156 |
+
conn.commit()
|
| 157 |
+
finished_at = datetime.utcnow().isoformat()
|
| 158 |
+
return jsonify({
|
| 159 |
+
"status": "success",
|
| 160 |
+
"file": filename,
|
| 161 |
+
"started_at": started_at,
|
| 162 |
+
"finished_at": finished_at
|
| 163 |
+
})
|
| 164 |
+
except Exception as e:
|
| 165 |
+
conn.rollback()
|
| 166 |
+
return jsonify({"error": str(e), "file": filename}), 400
|
| 167 |
+
finally:
|
| 168 |
+
conn.close()
|
| 169 |
+
|
| 170 |
+
def run_crew_task(user_input):
|
| 171 |
+
codeAgent = CodeAgentFlow(user_input=user_input)
|
| 172 |
+
codeAgent.kickoff()
|
| 173 |
+
|
| 174 |
+
@app.route('/run_crew', methods=['POST'])
|
| 175 |
+
def run_crew():
|
| 176 |
+
instructions = request.json.get('instructions', '')
|
| 177 |
+
threading.Thread(target=run_crew_task, args=(instructions,)).start()
|
| 178 |
+
return jsonify({"status": "Crew is Working..."})
|
| 179 |
+
|
| 180 |
+
def send_event(msg):
|
| 181 |
+
socketio.emit("event", msg)
|
| 182 |
+
|
| 183 |
+
@app.route('/send')
|
| 184 |
+
def sendSSE():
|
| 185 |
+
msg = {
|
| 186 |
+
"filename": "Users_query.sql",
|
| 187 |
+
"proposedCode": "SELECT * FROM Users;",
|
| 188 |
+
"isNew": False,
|
| 189 |
+
"newFileName": "Users_query.sql",
|
| 190 |
+
"kind": "code_change"
|
| 191 |
+
}
|
| 192 |
+
send_event(msg)
|
| 193 |
+
return "sent"
|
| 194 |
+
|
| 195 |
+
@socketio.on('connect')
|
| 196 |
+
def handle_connect():
|
| 197 |
+
print("Client connected")
|
| 198 |
+
emit("event", {"status": "connected"})
|
| 199 |
+
|
| 200 |
+
@socketio.on('disconnect')
|
| 201 |
+
def handle_disconnect():
|
| 202 |
+
print("Client disconnected")
|
| 203 |
+
|
| 204 |
+
if __name__ == '__main__':
|
| 205 |
+
socketio.run(app, host="0.0.0.0", port=7860, debug=False)
|
src/codeagent/.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
__pycache__/
|
| 3 |
+
lib/
|
| 4 |
+
.DS_Store
|
src/codeagent/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# {{crew_name}} Crew
|
| 2 |
+
|
| 3 |
+
Welcome to the {{crew_name}} Crew project, powered by [crewAI](https://crewai.com). This template is designed to help you set up a multi-agent AI system with ease, leveraging the powerful and flexible framework provided by crewAI. Our goal is to enable your agents to collaborate effectively on complex tasks, maximizing their collective intelligence and capabilities.
|
| 4 |
+
|
| 5 |
+
## Installation
|
| 6 |
+
|
| 7 |
+
Ensure you have Python >=3.10 <3.14 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience.
|
| 8 |
+
|
| 9 |
+
First, if you haven't already, install uv:
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
pip install uv
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
Next, navigate to your project directory and install the dependencies:
|
| 16 |
+
|
| 17 |
+
(Optional) Lock the dependencies and install them by using the CLI command:
|
| 18 |
+
```bash
|
| 19 |
+
crewai install
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
### Customizing
|
| 23 |
+
|
| 24 |
+
**Add your `OPENAI_API_KEY` into the `.env` file**
|
| 25 |
+
|
| 26 |
+
- Modify `src/codeagent/config/agents.yaml` to define your agents
|
| 27 |
+
- Modify `src/codeagent/config/tasks.yaml` to define your tasks
|
| 28 |
+
- Modify `src/codeagent/crew.py` to add your own logic, tools and specific args
|
| 29 |
+
- Modify `src/codeagent/main.py` to add custom inputs for your agents and tasks
|
| 30 |
+
|
| 31 |
+
## Running the Project
|
| 32 |
+
|
| 33 |
+
To kickstart your flow and begin execution, run this from the root folder of your project:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
crewai run
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
This command initializes the codeagent Flow as defined in your configuration.
|
| 40 |
+
|
| 41 |
+
This example, unmodified, will run the create a `report.md` file with the output of a research on LLMs in the root folder.
|
| 42 |
+
|
| 43 |
+
## Understanding Your Crew
|
| 44 |
+
|
| 45 |
+
The codeagent Crew is composed of multiple AI agents, each with unique roles, goals, and tools. These agents collaborate on a series of tasks, defined in `config/tasks.yaml`, leveraging their collective skills to achieve complex objectives. The `config/agents.yaml` file outlines the capabilities and configurations of each agent in your crew.
|
| 46 |
+
|
| 47 |
+
## Support
|
| 48 |
+
|
| 49 |
+
For support, questions, or feedback regarding the {{crew_name}} Crew or crewAI.
|
| 50 |
+
|
| 51 |
+
- Visit our [documentation](https://docs.crewai.com)
|
| 52 |
+
- Reach out to us through our [GitHub repository](https://github.com/joaomdmoura/crewai)
|
| 53 |
+
- [Join our Discord](https://discord.com/invite/X4JWnZnxPb)
|
| 54 |
+
- [Chat with our docs](https://chatg.pt/DWjSBZn)
|
| 55 |
+
|
| 56 |
+
Let's create wonders together with the power and simplicity of crewAI.
|
src/codeagent/pyproject.toml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "codeagent"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "codeagent using crewAI"
|
| 5 |
+
authors = [{ name = "Your Name", email = "you@example.com" }]
|
| 6 |
+
requires-python = ">=3.10,<3.14"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"crewai[tools]>=0.177.0,<1.0.0",
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
[project.scripts]
|
| 12 |
+
kickoff = "codeagent.main:kickoff"
|
| 13 |
+
run_crew = "codeagent.main:kickoff"
|
| 14 |
+
plot = "codeagent.main:plot"
|
| 15 |
+
|
| 16 |
+
[build-system]
|
| 17 |
+
requires = ["hatchling"]
|
| 18 |
+
build-backend = "hatchling.build"
|
| 19 |
+
|
| 20 |
+
[tool.crewai]
|
| 21 |
+
type = "flow"
|
src/codeagent/src/codeagent/__init__.py
ADDED
|
File without changes
|
src/codeagent/src/codeagent/crews/SqlCrew/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Poem crew template."""
|
src/codeagent/src/codeagent/crews/SqlCrew/config/agents.yaml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
manager:
|
| 2 |
+
role: "Crew Manager"
|
| 3 |
+
goal: >
|
| 4 |
+
Understand the user's intent and choose the minimal set of tasks and the best-suited agents
|
| 5 |
+
to fulfill it safely. Avoid destructive ops unless explicitly requested.
|
| 6 |
+
If user only needs a SELECT, do not run DDL or DML. Do not allow any agent except
|
| 7 |
+
the SQL Writing Agent to author SQL queries or write .sql files.
|
| 8 |
+
backstory: >
|
| 9 |
+
A seasoned release lead who routes work precisely.
|
| 10 |
+
Always follows the principle of least privilege and avoids unnecessary steps.
|
| 11 |
+
allow_delegation: true
|
| 12 |
+
verbose: true
|
| 13 |
+
|
| 14 |
+
schema_agent:
|
| 15 |
+
role: "Schema Agent"
|
| 16 |
+
goal: >
|
| 17 |
+
Design robust, normalized database schemas and validate correctness
|
| 18 |
+
(tables, columns, types, primary keys, foreign keys).
|
| 19 |
+
Do NOT write SQL queries or save .sql files.
|
| 20 |
+
backstory: >
|
| 21 |
+
A meticulous database engineer focused on relational integrity,
|
| 22 |
+
evolution-friendly design, and practical constraints.
|
| 23 |
+
verbose: true
|
| 24 |
+
|
| 25 |
+
sql_exec_agent:
|
| 26 |
+
role: "SQL Execution Agent"
|
| 27 |
+
goal: >
|
| 28 |
+
Execute .sql files in the correct order (setup → schema → data),
|
| 29 |
+
and report results clearly with errors if any.
|
| 30 |
+
backstory: >
|
| 31 |
+
A release engineer for database changes, automating the sequence of DDL and DML executions.
|
| 32 |
+
verbose: true
|
| 33 |
+
|
| 34 |
+
code_quality_agent:
|
| 35 |
+
role: "Code Quality Agent"
|
| 36 |
+
goal: >
|
| 37 |
+
Review SQL for best practices, detect anti-patterns, and suggest improvements.
|
| 38 |
+
Do NOT author new SQL unless explicitly asked to propose a corrected snippet.
|
| 39 |
+
Never write files.
|
| 40 |
+
backstory: >
|
| 41 |
+
A vigilant reviewer who enforces consistency, performance awareness,
|
| 42 |
+
and safe data modification patterns.
|
| 43 |
+
verbose: true
|
| 44 |
+
|
| 45 |
+
sql_writing_agent:
|
| 46 |
+
role: "SQL Writing Agent"
|
| 47 |
+
goal: >
|
| 48 |
+
Generate safe, performant SQL queries based on the user's intent and the schema context,
|
| 49 |
+
lint them for common issues, and propose the final SQL via propose_code_changes tool for UI review and save.
|
| 50 |
+
Only produce SELECT/READ queries unless the user explicitly requests DML/DDL.
|
| 51 |
+
Avoid SELECT *; prefer explicit columns; document filters at the top of the file.
|
| 52 |
+
backstory: >
|
| 53 |
+
An analyst who writes clear, maintainable SQL. Prioritizes readability, safety,
|
| 54 |
+
and reproducibility. Collaborates with Schema Agent for schema awareness
|
| 55 |
+
but alone is authorized to write .sql files.
|
| 56 |
+
verbose: true
|
src/codeagent/src/codeagent/crews/SqlCrew/config/tasks.yaml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tasks:
|
| 2 |
+
analyze_intent:
|
| 3 |
+
description: >
|
| 4 |
+
Analyze the user's request to determine required actions: schema design, query writing, review, or execution.
|
| 5 |
+
Delegate to appropriate agents only if necessary. Prioritize safety and minimal steps.
|
| 6 |
+
expected_output: A clear plan of delegated tasks or direct response if no delegation needed.
|
| 7 |
+
agent: manager
|
| 8 |
+
|
| 9 |
+
design_schema:
|
| 10 |
+
description: >
|
| 11 |
+
If schema changes or validation needed, design normalized schema with tables, columns, types, PKs, FKs.
|
| 12 |
+
expected_output: Detailed schema proposal in text format, ready for SQL generation.
|
| 13 |
+
agent: schema_agent
|
| 14 |
+
|
| 15 |
+
write_sql:
|
| 16 |
+
description: >
|
| 17 |
+
Based on user intent and schema, generate SQL query or script. Lint for issues. Save to .sql file via tool.
|
| 18 |
+
Default to SELECT unless DML/DDL explicitly requested.
|
| 19 |
+
expected_output: Generated SQL code saved to file, with lint report.
|
| 20 |
+
agent: sql_writing_agent
|
| 21 |
+
|
| 22 |
+
review_sql:
|
| 23 |
+
description: >
|
| 24 |
+
Review generated or existing SQL for best practices, anti-patterns, performance, safety.
|
| 25 |
+
Suggest improvements without writing new code unless requested.
|
| 26 |
+
expected_output: Review report with issues and suggestions.
|
| 27 |
+
agent: code_quality_agent
|
| 28 |
+
|
| 29 |
+
execute_sql:
|
| 30 |
+
description: >
|
| 31 |
+
If execution requested, run .sql files in order (schema first, then data). Report results and errors.
|
| 32 |
+
expected_output: Execution log with success/failure and output.
|
| 33 |
+
agent: sql_exec_agent
|
src/codeagent/src/codeagent/crews/SqlCrew/sqlcrew.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List
|
| 2 |
+
from crewai import Agent, Crew, Process, Task, LLM
|
| 3 |
+
from crewai.project import CrewBase, agent, crew, task
|
| 4 |
+
from crewai.agents.agent_builder.base_agent import BaseAgent
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")))
|
| 9 |
+
from app import socketio
|
| 10 |
+
|
| 11 |
+
from codeagent.src.codeagent.tools.custom_tools import execute_sql_file, get_files_and_schema, propose_code_changes, get_recent_query_result
|
| 12 |
+
|
| 13 |
+
llm = LLM(model="ollama/llama3.1:8b-instruct-q4_0", base_url="http://localhost:11434")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def send_chat_message(output: str) -> None:
|
| 17 |
+
socketio.emit("event", {'kind':'agent_message', 'output': output})
|
| 18 |
+
|
| 19 |
+
@CrewBase
|
| 20 |
+
class SqlCrew:
|
| 21 |
+
agents: List[BaseAgent]
|
| 22 |
+
tasks: List[Task]
|
| 23 |
+
|
| 24 |
+
# def manager(self) -> Agent:
|
| 25 |
+
# return Agent(
|
| 26 |
+
# role="Crew Manager",
|
| 27 |
+
# goal=(
|
| 28 |
+
# "Understand user instruction and delegate tasks according to the instruction, \"{task}\" "
|
| 29 |
+
# "Schema-related → Schema Agent, SQL writing → SQL Writing Agent, "
|
| 30 |
+
# "execution → SQL Execution Agent, reviews/errors → Code Quality Agent."
|
| 31 |
+
# ),
|
| 32 |
+
# backstory="A release manager who ensures the right agent handles the right task.",
|
| 33 |
+
# llm=llm,
|
| 34 |
+
# allow_delegation=True,
|
| 35 |
+
# verbose=True,
|
| 36 |
+
# )
|
| 37 |
+
|
| 38 |
+
@agent
|
| 39 |
+
def schema_agent(self) -> Agent:
|
| 40 |
+
return Agent(
|
| 41 |
+
role="Schema Agent",
|
| 42 |
+
goal=(
|
| 43 |
+
"Check if you have anything to do with the task else delegate to another Co-Worker, Task: {task}"
|
| 44 |
+
"Design or validate database schema, if it is asked by user"
|
| 45 |
+
"Focus on tables, columns, keys, and relations. "
|
| 46 |
+
"Do NOT write SQL files; just propose schema."
|
| 47 |
+
),
|
| 48 |
+
backstory="A careful database engineer ensuring solid relational design.",
|
| 49 |
+
llm=llm,
|
| 50 |
+
tools=[get_files_and_schema],
|
| 51 |
+
allow_delegation=True,
|
| 52 |
+
step_callback = send_chat_message,
|
| 53 |
+
verbose=True
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
@agent
|
| 57 |
+
def sql_writing_agent(self) -> Agent:
|
| 58 |
+
return Agent(
|
| 59 |
+
role="SQL Writing Agent",
|
| 60 |
+
goal=(
|
| 61 |
+
"Check if you have anything to do with the task else delegate to another Co-Worker, Task: {task}"
|
| 62 |
+
"Write safe, performant SQL queries. "
|
| 63 |
+
"Propose code changes for UI approval instead of direct execution."
|
| 64 |
+
),
|
| 65 |
+
backstory="An SQL analyst who produces clean, maintainable queries.",
|
| 66 |
+
llm=llm,
|
| 67 |
+
tools=[propose_code_changes, get_files_and_schema],
|
| 68 |
+
allow_delegation=True,
|
| 69 |
+
step_callback = send_chat_message,
|
| 70 |
+
verbose=True,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
@agent
|
| 74 |
+
def code_quality_agent(self) -> Agent:
|
| 75 |
+
return Agent(
|
| 76 |
+
role="Code Quality Agent",
|
| 77 |
+
goal=(
|
| 78 |
+
"Check if you have anything to do with the task else delegate to another Co-Worker, Task: {task}"
|
| 79 |
+
"Review SQL for performance, safety, and best practices. "
|
| 80 |
+
"Suggest improvements but don’t auto-correct unless asked."
|
| 81 |
+
),
|
| 82 |
+
backstory="A strict reviewer ensuring SQL is clean and safe.",
|
| 83 |
+
llm=llm,
|
| 84 |
+
tools=[get_recent_query_result, get_files_and_schema],
|
| 85 |
+
allow_delegation=True,
|
| 86 |
+
step_callback = send_chat_message,
|
| 87 |
+
verbose=True,
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
@agent
|
| 91 |
+
def sql_exec_agent(self) -> Agent:
|
| 92 |
+
return Agent(
|
| 93 |
+
role="SQL Execution Agent",
|
| 94 |
+
goal=(
|
| 95 |
+
"Check if you have anything to do with the task else delegate to another Co-Worker, Task: {task}"
|
| 96 |
+
"Execute .sql files when asked. "
|
| 97 |
+
"If execution fails → send to Code Quality Agent. "
|
| 98 |
+
"If successful → report back to Manager."
|
| 99 |
+
),
|
| 100 |
+
backstory="A release engineer who safely runs DB updates.",
|
| 101 |
+
llm=llm,
|
| 102 |
+
tools=[execute_sql_file, get_files_and_schema],
|
| 103 |
+
allow_delegation=True,
|
| 104 |
+
step_callback = send_chat_message,
|
| 105 |
+
verbose=True,
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
# @task
|
| 109 |
+
# def analyze_intent(self) -> Task:
|
| 110 |
+
# return Task(
|
| 111 |
+
# description="Analyze user request and route to the right agent.",
|
| 112 |
+
# expected_output="Clear delegation plan or direct response.",
|
| 113 |
+
# agent=self.manager(),
|
| 114 |
+
# )
|
| 115 |
+
|
| 116 |
+
@task
|
| 117 |
+
def design_schema(self) -> Task:
|
| 118 |
+
return Task(
|
| 119 |
+
description="Propose schema (tables, columns, PKs, FKs) if needed meaning if user specifically said so to create database or to change database.",
|
| 120 |
+
expected_output="Schema design in text format.",
|
| 121 |
+
agent=self.schema_agent(),
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
@task
|
| 125 |
+
def write_sql(self) -> Task:
|
| 126 |
+
return Task(
|
| 127 |
+
description="Generate SQL based on schema and intent. Save via propose_code_changes.",
|
| 128 |
+
expected_output="SQL file proposal + lint notes.",
|
| 129 |
+
agent=self.sql_writing_agent(),
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@task
|
| 134 |
+
def execute_sql(self) -> Task:
|
| 135 |
+
return Task(
|
| 136 |
+
description="Run SQL files if execution requested. Report results/errors.",
|
| 137 |
+
expected_output="Execution log with success/failure details.",
|
| 138 |
+
agent=self.sql_exec_agent(),
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
@task
|
| 142 |
+
def review_sql(self) -> Task:
|
| 143 |
+
return Task(
|
| 144 |
+
description="Check SQL for issues and suggest improvements.",
|
| 145 |
+
expected_output="Review report with issues & recommendations.",
|
| 146 |
+
agent=self.code_quality_agent(),
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
@crew
|
| 150 |
+
def crew(self) -> Crew:
|
| 151 |
+
return Crew(
|
| 152 |
+
agents=self.agents,
|
| 153 |
+
tasks=self.tasks,
|
| 154 |
+
process=Process.hierarchical,
|
| 155 |
+
manager_llm = llm,
|
| 156 |
+
verbose=True,
|
| 157 |
+
step_callback=send_chat_message,
|
| 158 |
+
task_callback=send_chat_message,
|
| 159 |
+
|
| 160 |
+
)
|
src/codeagent/src/codeagent/main.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
from pydantic import BaseModel, Field
|
| 3 |
+
|
| 4 |
+
from crewai.flow import Flow, listen, start
|
| 5 |
+
from crewai import Agent, LLM
|
| 6 |
+
|
| 7 |
+
from typing import Literal
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# from codeagent.src.codeagent.crews.git_crew.git_crew import GitCrew
|
| 11 |
+
from codeagent.src.codeagent.crews.SqlCrew.sqlcrew import SqlCrew
|
| 12 |
+
|
| 13 |
+
class FlowState(BaseModel):
|
| 14 |
+
user_input: str = ""
|
| 15 |
+
decision: str = ""
|
| 16 |
+
result: str = ""
|
| 17 |
+
|
| 18 |
+
class Decision(BaseModel):
|
| 19 |
+
decision:str = Field(..., description='identifer of the Crew to Call to process input further, ["GitCrew","SqlCrew"]')
|
| 20 |
+
|
| 21 |
+
class DebugLLM(LLM):
|
| 22 |
+
def __init__(self, *args, **kwargs):
|
| 23 |
+
super().__init__(*args, **kwargs)
|
| 24 |
+
|
| 25 |
+
def call(self, messages, *args, **kwargs):
|
| 26 |
+
import json
|
| 27 |
+
print("=== LLM Payload Sent ===")
|
| 28 |
+
print(json.dumps(messages, indent=2))
|
| 29 |
+
print("========================")
|
| 30 |
+
return super().call(messages, *args, **kwargs)
|
| 31 |
+
|
| 32 |
+
llm = DebugLLM(model="ollama/llama3.1:8b-instruct-q4_0", base_url="http://localhost:11434")
|
| 33 |
+
|
| 34 |
+
router_agent = Agent(
|
| 35 |
+
role="Task Router",
|
| 36 |
+
goal = 'Decide which crew (GitCrew or SqlCrew) should handle the user request. ' \
|
| 37 |
+
'Return the decision as a **JSON object** with one field "decision", ' \
|
| 38 |
+
'and make sure the value is a string in double quotes, like: {"decision": "GitCrew"} or {"decision": "SqlCrew"}',
|
| 39 |
+
backstory= "An intelligent orchestrator that analyzes user input and delegates tasks to specialized crews. You have two crews One is GitCrew and Other one is SqlCrew" \
|
| 40 |
+
"GitCrew description: Handles tasks related to git, repositories, commits, branches, merges, etc." \
|
| 41 |
+
"SqlCrew description: Handles tasks related to databases, SQL queries, tables, schemas, etc.",
|
| 42 |
+
llm=llm,
|
| 43 |
+
verbose=True,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
class CodeAgentFlow(Flow[FlowState]):
|
| 47 |
+
|
| 48 |
+
@start()
|
| 49 |
+
def decide_crew(self):
|
| 50 |
+
print("State ------:> ", self.state.user_input)
|
| 51 |
+
decision_obj = router_agent.kickoff(self.state.user_input, response_format=Decision)
|
| 52 |
+
print(decision_obj)
|
| 53 |
+
decision_obj = Decision.model_validate_json(decision_obj.raw)
|
| 54 |
+
self.state.decision = decision_obj.decision
|
| 55 |
+
if self.state.decision == "GitCrew":
|
| 56 |
+
# result = GitCrew().crew().kickoff(inputs={"task": self.state.user_input})
|
| 57 |
+
# self.state.result = result.raw
|
| 58 |
+
pass
|
| 59 |
+
elif self.state.decision == "SqlCrew":
|
| 60 |
+
result = SqlCrew().crew().kickoff(inputs={"task": self.state.user_input})
|
| 61 |
+
self.state.result = result.raw
|
| 62 |
+
else:
|
| 63 |
+
self.state.result = f"Invalid decision from router: {self.state.decision}"
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@listen(decide_crew)
|
| 67 |
+
def save_result(self):
|
| 68 |
+
print("Saving result...")
|
| 69 |
+
with open("crew_result.txt", "w") as f:
|
| 70 |
+
f.write(self.state.result)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def kickoff(user_input: str):
|
| 74 |
+
code_agent_flow = CodeAgentFlow()
|
| 75 |
+
code_agent_flow.state.user_input = user_input
|
| 76 |
+
code_agent_flow.kickoff()
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def plot():
|
| 80 |
+
code_agent_flow = CodeAgentFlow()
|
| 81 |
+
code_agent_flow.plot()
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
# Example kickoff
|
| 86 |
+
kickoff("please push my changes to git repository")
|
src/codeagent/src/codeagent/tools/__init__.py
ADDED
|
File without changes
|
src/codeagent/src/codeagent/tools/custom_tools.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
from crewai.tools import tool
|
| 4 |
+
import queue
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")))
|
| 8 |
+
from app import socketio
|
| 9 |
+
|
| 10 |
+
@tool("execute_sql_file")
|
| 11 |
+
def execute_sql_file(filename: str) -> str:
|
| 12 |
+
"""Executes the provided .sql file via Flask and returns the result."""
|
| 13 |
+
resp = requests.post("http://localhost:7860/execute_file", json={"filename": filename})
|
| 14 |
+
if resp.status_code == 200:
|
| 15 |
+
return json.dumps(resp.json())
|
| 16 |
+
return f"Error: {resp.text}"
|
| 17 |
+
|
| 18 |
+
@tool("get_files_and_schema")
|
| 19 |
+
def get_files_and_schema() -> str:
|
| 20 |
+
"""Retrieves all .sql files with contents and full DB schema (tables, columns, types) via Flask."""
|
| 21 |
+
files_resp = requests.get("http://localhost:7860/files")
|
| 22 |
+
sql_files = []
|
| 23 |
+
if files_resp.ok:
|
| 24 |
+
file_list = [f['name'] for f in files_resp.json() if f['name'].endswith('.sql')]
|
| 25 |
+
for fname in file_list:
|
| 26 |
+
content_resp = requests.get(f"http://localhost:7860/files/{fname}")
|
| 27 |
+
if content_resp.ok:
|
| 28 |
+
content = content_resp.json().get('content', '')
|
| 29 |
+
sql_files.append(f"File: {fname}\n{content}\n{'-'*50}")
|
| 30 |
+
|
| 31 |
+
schema = []
|
| 32 |
+
tables_resp = requests.get("http://localhost:7860/tables")
|
| 33 |
+
if tables_resp.ok:
|
| 34 |
+
tables = tables_resp.json()
|
| 35 |
+
for t in tables:
|
| 36 |
+
qresp = requests.post("http://localhost:7860/query", json={"query": f"PRAGMA table_info({t})"})
|
| 37 |
+
if qresp.ok:
|
| 38 |
+
cols = qresp.json()
|
| 39 |
+
col_str = '\n'.join([f"{c['name']} {c.get('type', 'UNKNOWN')}" for c in cols])
|
| 40 |
+
schema.append(f"Table: {t}\n{col_str}\n{'-'*50}")
|
| 41 |
+
|
| 42 |
+
return f"SQL Files:\n{chr(10).join(sql_files)}\n\nDB Schema:\n{chr(10).join(schema) or "Empty"}"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@tool("propose_code_changes")
|
| 46 |
+
def propose_code_changes(filename: str, code: str, is_new: bool = False, suggested_name: str = None) -> str:
|
| 47 |
+
"""Proposes code changes or new file for frontend UI via WebSocket."""
|
| 48 |
+
proposal = {
|
| 49 |
+
"kind": "code_change",
|
| 50 |
+
"proposedCode": code,
|
| 51 |
+
"newFileName": suggested_name if is_new else None,
|
| 52 |
+
"isNew": is_new,
|
| 53 |
+
"filename": filename
|
| 54 |
+
}
|
| 55 |
+
socketio.emit("event", proposal)
|
| 56 |
+
return "Proposal sent to frontend via WebSocket"
|
| 57 |
+
|
| 58 |
+
@tool("get_recent_query_result")
|
| 59 |
+
def get_recent_query_result() -> str:
|
| 60 |
+
"""Retrieves the most recent SQL query execution result via Flask."""
|
| 61 |
+
resp = requests.get("http://localhost:7860/recent_query")
|
| 62 |
+
if resp.status_code == 200:
|
| 63 |
+
return resp.json().get('result', 'No recent result')
|
| 64 |
+
return "Error fetching recent result"
|