Spaces:
Sleeping
Sleeping
EL GHAFRAOUI AYOUB commited on
Commit Β·
6f14d8b
1
Parent(s): d2aae1f
C'
Browse files- .env +3 -2
- .gitattributes +0 -35
- .gitignore +0 -1
- tests/__init__.py β 0.26.0 +0 -0
- tests/test_api.py β 0.26.0' +0 -0
- alembic/__pycache__/env.cpython-312.pyc +0 -0
- alembic/env.py +0 -66
- alembic/script.py.mako +0 -26
- app.log +22 -0
- app/.env +3 -0
- app/__init__.py +1 -0
- app/__pycache__/__init__.cpython-312.pyc +0 -0
- app/__pycache__/main.cpython-312.pyc +0 -0
- tests/test_services.py β app/controllers/__init__.py +0 -0
- app/controllers/__pycache__/__init__.cpython-312.pyc +0 -0
- app/controllers/__pycache__/f5_model.cpython-312.pyc +0 -0
- app/controllers/__pycache__/plan_chat_controller.cpython-312.pyc +0 -0
- app/controllers/__pycache__/scraper_controller.cpython-312.pyc +0 -0
- app/controllers/f5_model.py +86 -0
- app/controllers/plan_chat_controller.py +64 -0
- app/controllers/scraper_controller.py +45 -0
- app/helpers/__init__.py +0 -0
- app/helpers/__pycache__/__init__.cpython-312.pyc +0 -0
- app/helpers/__pycache__/plan_chat.cpython-312.pyc +0 -0
- app/helpers/__pycache__/plan_parser.cpython-312.pyc +0 -0
- app/helpers/chat.py +38 -0
- app/helpers/generate_features.py +32 -0
- app/helpers/generate_plan.py +256 -0
- app/helpers/generate_soluction_stack.py +39 -0
- app/helpers/plan_chat.py +83 -0
- app/helpers/plan_parser.py +91 -0
- app/main.py +280 -84
- app/models/__init__.py +0 -0
- app/models/project_plan.py +24 -0
- app/models/scrape_log.py +0 -0
- app/services/__init__.py +0 -0
- app/services/__pycache__/__init__.cpython-312.pyc +0 -0
- app/services/__pycache__/flan_t5_service.cpython-312.pyc +0 -0
- app/services/__pycache__/scraper_service.cpython-312.pyc +0 -0
- app/services/flan_t5_service.py +18 -0
- app/services/scraper_service.py +45 -0
- app/static/js/main.js +45 -0
- app/templates/index.html +302 -1337
- migrations/versions/xxxx_add_invoice_sequence.py +0 -28
- re.md +27 -0
.env
CHANGED
|
@@ -1,2 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
| 1 |
+
AWS_ACCESS_KEY_ID="AKIAVRUVQMZRVWTZE4N4"
|
| 2 |
+
AWS_SECRET_ACCESS_KEY="K/YPo3hmFOcQcqnX2So00s1j1nUXfi/NgMaPph8o"
|
| 3 |
+
AWS_DEFAULT_REGION="eu-west-3"
|
.gitattributes
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
sql_app.db
|
|
|
|
|
|
tests/__init__.py β 0.26.0
RENAMED
|
File without changes
|
tests/test_api.py β 0.26.0'
RENAMED
|
File without changes
|
alembic/__pycache__/env.cpython-312.pyc
DELETED
|
Binary file (3.1 kB)
|
|
|
alembic/env.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
from logging.config import fileConfig
|
| 3 |
-
|
| 4 |
-
from sqlalchemy import pool
|
| 5 |
-
from sqlalchemy.engine import Connection
|
| 6 |
-
from sqlalchemy.ext.asyncio import async_engine_from_config
|
| 7 |
-
|
| 8 |
-
from alembic import context
|
| 9 |
-
|
| 10 |
-
# this is the Alembic Config object, which provides
|
| 11 |
-
# access to the values within the .ini file in use.
|
| 12 |
-
config = context.config
|
| 13 |
-
|
| 14 |
-
# Interpret the config file for Python logging.
|
| 15 |
-
# This line sets up loggers basically.
|
| 16 |
-
if config.config_file_name is not None:
|
| 17 |
-
fileConfig(config.config_file_name)
|
| 18 |
-
|
| 19 |
-
# add your model's MetaData object here
|
| 20 |
-
# for 'autogenerate' support
|
| 21 |
-
from app.db.models import Base
|
| 22 |
-
target_metadata = Base.metadata
|
| 23 |
-
|
| 24 |
-
def run_migrations_offline() -> None:
|
| 25 |
-
"""Run migrations in 'offline' mode."""
|
| 26 |
-
url = config.get_main_option("sqlalchemy.url")
|
| 27 |
-
context.configure(
|
| 28 |
-
url=url,
|
| 29 |
-
target_metadata=target_metadata,
|
| 30 |
-
literal_binds=True,
|
| 31 |
-
dialect_opts={"paramstyle": "named"},
|
| 32 |
-
)
|
| 33 |
-
|
| 34 |
-
with context.begin_transaction():
|
| 35 |
-
context.run_migrations()
|
| 36 |
-
|
| 37 |
-
def do_run_migrations(connection: Connection) -> None:
|
| 38 |
-
context.configure(connection=connection, target_metadata=target_metadata)
|
| 39 |
-
|
| 40 |
-
with context.begin_transaction():
|
| 41 |
-
context.run_migrations()
|
| 42 |
-
|
| 43 |
-
async def run_async_migrations() -> None:
|
| 44 |
-
"""In this scenario we need to create an Engine
|
| 45 |
-
and associate a connection with the context."""
|
| 46 |
-
|
| 47 |
-
connectable = async_engine_from_config(
|
| 48 |
-
config.get_section(config.config_ini_section, {}),
|
| 49 |
-
prefix="sqlalchemy.",
|
| 50 |
-
poolclass=pool.NullPool,
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
async with connectable.connect() as connection:
|
| 54 |
-
await connection.run_sync(do_run_migrations)
|
| 55 |
-
|
| 56 |
-
await connectable.dispose()
|
| 57 |
-
|
| 58 |
-
def run_migrations_online() -> None:
|
| 59 |
-
"""Run migrations in 'online' mode."""
|
| 60 |
-
|
| 61 |
-
asyncio.run(run_async_migrations())
|
| 62 |
-
|
| 63 |
-
if context.is_offline_mode():
|
| 64 |
-
run_migrations_offline()
|
| 65 |
-
else:
|
| 66 |
-
run_migrations_online()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
alembic/script.py.mako
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
"""${message}
|
| 2 |
-
|
| 3 |
-
Revision ID: ${up_revision}
|
| 4 |
-
Revises: ${down_revision | comma,n}
|
| 5 |
-
Create Date: ${create_date}
|
| 6 |
-
|
| 7 |
-
"""
|
| 8 |
-
from typing import Sequence, Union
|
| 9 |
-
|
| 10 |
-
from alembic import op
|
| 11 |
-
import sqlalchemy as sa
|
| 12 |
-
${imports if imports else ""}
|
| 13 |
-
|
| 14 |
-
# revision identifiers, used by Alembic.
|
| 15 |
-
revision: str = ${repr(up_revision)}
|
| 16 |
-
down_revision: Union[str, None] = ${repr(down_revision)}
|
| 17 |
-
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
| 18 |
-
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def upgrade() -> None:
|
| 22 |
-
${upgrades if upgrades else "pass"}
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def downgrade() -> None:
|
| 26 |
-
${downgrades if downgrades else "pass"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.log
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2025-02-28 18:27:16,754 - INFO - Chat request received with 1 messages
|
| 2 |
+
2025-02-28 18:27:16,755 - INFO - Formatted prompt: give me saas app
|
| 3 |
+
|
| 4 |
+
2025-02-28 18:27:29,908 - INFO - Generated response: i want to download saas app
|
| 5 |
+
2025-02-28 18:28:08,376 - INFO - Feature generation request received with requirements: gerneate me 10 feature to use in saas
|
| 6 |
+
2025-02-28 18:28:08,377 - INFO - Generated prompt: Generate 5 features based on the following requirements. Format each feature as a JSON object with 'feature' and 'short_description' fields.
|
| 7 |
+
|
| 8 |
+
Requirements: gerneate me 10 feature to use in saas
|
| 9 |
+
2025-02-28 18:29:29,392 - INFO - Model response: i would like 10 feature to use in saas
|
| 10 |
+
2025-02-28 18:29:29,392 - INFO - Returning features: [Feature(feature='Feature 1', short_description='Description 1'), Feature(feature='Feature 2', short_description='Description 2')]
|
| 11 |
+
2025-02-28 18:31:00,786 - INFO - Feature generation request received with requirements: gerneate me 10 feature to use in saas
|
| 12 |
+
2025-02-28 18:31:00,787 - INFO - Generated prompt: Generate 5 detailed SaaS features based on the following requirements. Each feature should be practical and implementation-ready. Format your response as a list of JSON objects, each with 'feature' and 'short_description' fields.
|
| 13 |
+
|
| 14 |
+
Example format:
|
| 15 |
+
{
|
| 16 |
+
'feature': 'User Authentication',
|
| 17 |
+
'short_description': 'Secure login system with OAuth2 and MFA support'
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
Requirements: gerneate me 10 feature to use in saas
|
| 21 |
+
|
| 22 |
+
Provide 5 features in the exact JSON format shown above.
|
app/.env
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
AWS_ACCESS_KEY_ID="AKIAVRUVQMZRVWTZE4N4"
|
| 2 |
+
AWS_SECRET_ACCESS_KEY="K/YPo3hmFOcQcqnX2So00s1j1nUXfi/NgMaPph8o"
|
| 3 |
+
AWS_DEFAULT_REGION="eu-west-3"
|
app/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
#uvicorn app.main:app --host 0.0.0.0 --port 8000
|
app/__pycache__/__init__.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/__init__.cpython-312.pyc and b/app/__pycache__/__init__.cpython-312.pyc differ
|
|
|
app/__pycache__/main.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/main.cpython-312.pyc and b/app/__pycache__/main.cpython-312.pyc differ
|
|
|
tests/test_services.py β app/controllers/__init__.py
RENAMED
|
File without changes
|
app/controllers/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (172 Bytes). View file
|
|
|
app/controllers/__pycache__/f5_model.cpython-312.pyc
ADDED
|
Binary file (4.17 kB). View file
|
|
|
app/controllers/__pycache__/plan_chat_controller.cpython-312.pyc
ADDED
|
Binary file (3.56 kB). View file
|
|
|
app/controllers/__pycache__/scraper_controller.cpython-312.pyc
ADDED
|
Binary file (2.62 kB). View file
|
|
|
app/controllers/f5_model.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Optional
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
import torch
|
| 4 |
+
import logging
|
| 5 |
+
from transformers import pipeline
|
| 6 |
+
|
| 7 |
+
class F5ModelHandler:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
logging.info("Initializing F5ModelHandler...")
|
| 10 |
+
try:
|
| 11 |
+
logging.info("Loading model 'google/flan-t5-small'...")
|
| 12 |
+
self.model_name = "google/flan-t5-small"
|
| 13 |
+
# Use pipeline for simpler model loading
|
| 14 |
+
self.generator = pipeline(
|
| 15 |
+
"text2text-generation",
|
| 16 |
+
model=self.model_name,
|
| 17 |
+
device="cuda" if torch.cuda.is_available() else "cpu",
|
| 18 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
|
| 19 |
+
)
|
| 20 |
+
logging.info(f"Model loaded successfully on {self.generator.device}")
|
| 21 |
+
except Exception as e:
|
| 22 |
+
logging.error(f"Error loading model: {str(e)}")
|
| 23 |
+
raise
|
| 24 |
+
|
| 25 |
+
async def generate_response(self, prompt: str, max_length: int = 2048) -> str:
|
| 26 |
+
try:
|
| 27 |
+
logging.info(f"Generating response for prompt: {prompt[:100]}...")
|
| 28 |
+
|
| 29 |
+
# Generate with more focused parameters
|
| 30 |
+
response = self.generator(
|
| 31 |
+
prompt,
|
| 32 |
+
max_length=max_length,
|
| 33 |
+
num_beams=5,
|
| 34 |
+
temperature=0.7,
|
| 35 |
+
top_p=0.95,
|
| 36 |
+
top_k=50,
|
| 37 |
+
repetition_penalty=1.2,
|
| 38 |
+
length_penalty=1.0,
|
| 39 |
+
do_sample=True,
|
| 40 |
+
num_return_sequences=1
|
| 41 |
+
)[0]['generated_text']
|
| 42 |
+
|
| 43 |
+
# Clean up the response
|
| 44 |
+
response = response.strip()
|
| 45 |
+
|
| 46 |
+
# Ensure minimum content length
|
| 47 |
+
if len(response) < 100:
|
| 48 |
+
logging.warning("Response too short, regenerating...")
|
| 49 |
+
return await self.generate_response(prompt, max_length)
|
| 50 |
+
|
| 51 |
+
logging.info(f"Generated response successfully: {response[:100]}...")
|
| 52 |
+
return response
|
| 53 |
+
|
| 54 |
+
except Exception as e:
|
| 55 |
+
logging.error(f"Error generating response: {str(e)}")
|
| 56 |
+
raise
|
| 57 |
+
|
| 58 |
+
async def stream_response(self, prompt: str, max_length: int = 1000):
|
| 59 |
+
try:
|
| 60 |
+
response = self.generator(
|
| 61 |
+
prompt,
|
| 62 |
+
max_length=max_length,
|
| 63 |
+
num_beams=4,
|
| 64 |
+
temperature=0.7,
|
| 65 |
+
top_p=0.9,
|
| 66 |
+
do_sample=True,
|
| 67 |
+
return_full_text=False
|
| 68 |
+
)[0]['generated_text']
|
| 69 |
+
|
| 70 |
+
# Simulate streaming by yielding chunks of the response
|
| 71 |
+
chunk_size = 20
|
| 72 |
+
for i in range(0, len(response), chunk_size):
|
| 73 |
+
chunk = response[i:i + chunk_size]
|
| 74 |
+
yield chunk
|
| 75 |
+
|
| 76 |
+
except Exception as e:
|
| 77 |
+
logging.error(f"Error in stream_response: {str(e)}")
|
| 78 |
+
raise
|
| 79 |
+
|
| 80 |
+
# Initialize the model handler
|
| 81 |
+
logging.basicConfig(
|
| 82 |
+
level=logging.INFO,
|
| 83 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
f5_model = F5ModelHandler()
|
app/controllers/plan_chat_controller.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 2 |
+
from fastapi.responses import StreamingResponse, PlainTextResponse
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
from typing import List, Literal
|
| 5 |
+
from app.helpers.plan_chat import ask_plan_question
|
| 6 |
+
from app.helpers.token_auth import get_token
|
| 7 |
+
from app.helpers.get_current_uesr import get_user_from_token
|
| 8 |
+
from app.models.project import Project
|
| 9 |
+
from app.helpers.vectorization import search_similar
|
| 10 |
+
|
| 11 |
+
router = APIRouter()
|
| 12 |
+
|
| 13 |
+
class HistoryItem(BaseModel):
|
| 14 |
+
message: str
|
| 15 |
+
from_: Literal["user", "ai"]
|
| 16 |
+
|
| 17 |
+
class PlanChatPayload(BaseModel):
|
| 18 |
+
query: str
|
| 19 |
+
history: List[HistoryItem]
|
| 20 |
+
project_id: str
|
| 21 |
+
|
| 22 |
+
@router.post("/plan-chat")
|
| 23 |
+
async def plan_chat(data: PlanChatPayload, token: str = Depends(get_token)):
|
| 24 |
+
"""
|
| 25 |
+
Handle chat messages for plan generation with context from scraped content
|
| 26 |
+
"""
|
| 27 |
+
try:
|
| 28 |
+
# Validate user
|
| 29 |
+
user = await get_user_from_token(token=token)
|
| 30 |
+
if not user:
|
| 31 |
+
raise HTTPException(status_code=401, detail="Invalid token")
|
| 32 |
+
|
| 33 |
+
# Get project context
|
| 34 |
+
project = await Project.get_or_none(id=data.project_id)
|
| 35 |
+
if not project:
|
| 36 |
+
raise HTTPException(status_code=404, detail="Project not found")
|
| 37 |
+
|
| 38 |
+
# Get relevant context from vectorstore
|
| 39 |
+
context = await search_similar(data.query)
|
| 40 |
+
|
| 41 |
+
# Prepare system prompt
|
| 42 |
+
system_prompt = (
|
| 43 |
+
"You are a solution architect assistant specialized in cloud architecture. "
|
| 44 |
+
"Use the following context to help answer questions about the project plan. "
|
| 45 |
+
"Focus on providing specific, actionable advice based on the project requirements "
|
| 46 |
+
"and scraped documentation.\n\n"
|
| 47 |
+
f"Project Context: {context}\n"
|
| 48 |
+
f"Project Requirements: {project.requirements}\n"
|
| 49 |
+
f"Project Features: {project.features}\n"
|
| 50 |
+
f"Solution Stack: {project.solution_stack}\n"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
async def response_stream():
|
| 54 |
+
async for chunk in ask_plan_question(
|
| 55 |
+
question=data.query,
|
| 56 |
+
history=data.history,
|
| 57 |
+
project_context=system_prompt
|
| 58 |
+
):
|
| 59 |
+
yield chunk
|
| 60 |
+
|
| 61 |
+
return StreamingResponse(response_stream(), media_type="text/plain")
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
return PlainTextResponse(str(e), status_code=500)
|
app/controllers/scraper_controller.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Request, HTTPException
|
| 2 |
+
from fastapi.templating import Jinja2Templates
|
| 3 |
+
from pydantic import BaseModel, HttpUrl
|
| 4 |
+
from app.services.scraper_service import ScraperService
|
| 5 |
+
from app.services.flan_t5_service import FlanT5Service
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
router = APIRouter()
|
| 9 |
+
templates = Jinja2Templates(directory="app/templates")
|
| 10 |
+
|
| 11 |
+
scraper_service = ScraperService()
|
| 12 |
+
flan_t5_service = FlanT5Service()
|
| 13 |
+
|
| 14 |
+
class ScrapeRequest(BaseModel):
|
| 15 |
+
url: HttpUrl
|
| 16 |
+
prompt_template: Optional[str] = "Summarize the following text: {text}"
|
| 17 |
+
|
| 18 |
+
@router.post("/api/scrape")
|
| 19 |
+
async def scrape_url(data: ScrapeRequest):
|
| 20 |
+
try:
|
| 21 |
+
# Scrape and process the URL
|
| 22 |
+
text, chunks = await scraper_service.scrape_and_process(str(data.url))
|
| 23 |
+
|
| 24 |
+
# Process each chunk with Flan-T5
|
| 25 |
+
results = []
|
| 26 |
+
for chunk in chunks:
|
| 27 |
+
prompt = data.prompt_template.format(text=chunk.page_content)
|
| 28 |
+
response = await flan_t5_service.generate_response(prompt)
|
| 29 |
+
results.append(response)
|
| 30 |
+
|
| 31 |
+
# Combine results
|
| 32 |
+
final_result = " ".join(results)
|
| 33 |
+
|
| 34 |
+
return {
|
| 35 |
+
"success": True,
|
| 36 |
+
"result": final_result,
|
| 37 |
+
"url": str(data.url)
|
| 38 |
+
}
|
| 39 |
+
except Exception as e:
|
| 40 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 41 |
+
|
| 42 |
+
@router.get("/history")
|
| 43 |
+
async def show_history(request: Request):
|
| 44 |
+
# You can add history functionality later if needed
|
| 45 |
+
return templates.TemplateResponse("history.html", {"request": request})
|
app/helpers/__init__.py
ADDED
|
File without changes
|
app/helpers/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (168 Bytes). View file
|
|
|
app/helpers/__pycache__/plan_chat.cpython-312.pyc
ADDED
|
Binary file (2.63 kB). View file
|
|
|
app/helpers/__pycache__/plan_parser.cpython-312.pyc
ADDED
|
Binary file (3.88 kB). View file
|
|
|
app/helpers/chat.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 2 |
+
from langchain_core.output_parsers import StrOutputParser
|
| 3 |
+
from helpers.generate_embbedings import vector_store
|
| 4 |
+
from helpers.f5_model import f5_model
|
| 5 |
+
|
| 6 |
+
def make_prompt(history, prompt, context=None):
|
| 7 |
+
formatted_history = ""
|
| 8 |
+
|
| 9 |
+
if context:
|
| 10 |
+
formatted_history += f"[CONTEXT] {context} [/CONTEXT]\n"
|
| 11 |
+
|
| 12 |
+
for history_item in history:
|
| 13 |
+
if history_item.from_ == 'user':
|
| 14 |
+
formatted_history += f"[INST] {history_item.message} [/INST]\n"
|
| 15 |
+
else:
|
| 16 |
+
formatted_history += f"{history_item.message}\n"
|
| 17 |
+
|
| 18 |
+
formatted_history += f"[INST] {prompt} [/INST]\n"
|
| 19 |
+
|
| 20 |
+
return formatted_history
|
| 21 |
+
|
| 22 |
+
async def ask_question(question: str, history: list = [], project_id=None):
|
| 23 |
+
"""
|
| 24 |
+
Generate a response using F5 model based on history and project-specific context.
|
| 25 |
+
"""
|
| 26 |
+
try:
|
| 27 |
+
context = ""
|
| 28 |
+
if project_id is not None:
|
| 29 |
+
context = vector_store.similarity_search(
|
| 30 |
+
query=question, k=4, filter={"project_id": project_id}
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
prompt = make_prompt(history, question, context)
|
| 34 |
+
|
| 35 |
+
async for chunk in f5_model.stream_response(prompt):
|
| 36 |
+
yield chunk
|
| 37 |
+
except Exception as e:
|
| 38 |
+
raise RuntimeError(f"Error generating response: {str(e)}")
|
app/helpers/generate_features.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from models.features import Feature as FeatureModel
|
| 3 |
+
from typing import List
|
| 4 |
+
from helpers.f5_model import f5_model
|
| 5 |
+
|
| 6 |
+
class Feature(BaseModel):
|
| 7 |
+
feature: str
|
| 8 |
+
short_description: str
|
| 9 |
+
|
| 10 |
+
class Features(BaseModel):
|
| 11 |
+
features: List[Feature]
|
| 12 |
+
|
| 13 |
+
async def generate_features(requirements: str):
|
| 14 |
+
query = (
|
| 15 |
+
"See the user requirements and propose him the features (it should be 20 features). Feature names should be short. "
|
| 16 |
+
"The user will then choose one or more needed features. \n"
|
| 17 |
+
"User Requirements:\n"
|
| 18 |
+
f"{requirements}"
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
response = await f5_model.generate_response(query)
|
| 22 |
+
# Parse the response into Features structure
|
| 23 |
+
# You might need to add additional parsing logic here
|
| 24 |
+
features_dict = parse_features_response(response)
|
| 25 |
+
return Features(**features_dict)
|
| 26 |
+
|
| 27 |
+
def parse_features_response(response: str) -> dict:
|
| 28 |
+
# Add parsing logic here to convert F5 model output to Features format
|
| 29 |
+
# This is a placeholder implementation
|
| 30 |
+
features_list = []
|
| 31 |
+
# Parse the response and create Feature objects
|
| 32 |
+
return {"features": features_list}
|
app/helpers/generate_plan.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
class ScopeObjective(BaseModel):
|
| 4 |
+
scope: str = Field(description="Define the core functionalities of the system")
|
| 5 |
+
objectives: List[str] = Field(description="List at least three objectives focusing on user needs and system capabilities")
|
| 6 |
+
|
| 7 |
+
class ArchitectureObjectives(BaseModel):
|
| 8 |
+
component: str = Field(description="architecture component for the project")
|
| 9 |
+
objectives: str = Field(description="project objectives, each associated with a key architecture component")
|
| 10 |
+
|
| 11 |
+
class ComponenetDesign(BaseModel):
|
| 12 |
+
component: str = Field(description="core component for the project")
|
| 13 |
+
purpose: str = Field(description="purpose of the core component")
|
| 14 |
+
interactions: str = Field(description="interactions between the core components")
|
| 15 |
+
specifications: str = Field(description="specifications for the core components")
|
| 16 |
+
|
| 17 |
+
class TeamRolesSkillsRequirements(BaseModel):
|
| 18 |
+
role: str = Field(description="role for the team member")
|
| 19 |
+
skills: str = Field(description="skills required for this particular job")
|
| 20 |
+
|
| 21 |
+
class CostEstimatesOptimization(BaseModel):
|
| 22 |
+
estimated_costs: str = Field(description="estimated cost for the project")
|
| 23 |
+
optimization_strategies: List[str] = Field(description="Optimized Strategy for the project, it will always be a list of strings")
|
| 24 |
+
|
| 25 |
+
class Tasks(BaseModel):
|
| 26 |
+
name: str = Field(description="Name of the task")
|
| 27 |
+
subtasks: List[str] = Field(description="sub tasks relevant for the task")
|
| 28 |
+
|
| 29 |
+
class Phases(BaseModel):
|
| 30 |
+
name: str = Field(description="name of the phase")
|
| 31 |
+
tasks: List[Tasks] = Field(description="Tasks for this phase")
|
| 32 |
+
|
| 33 |
+
class ProjectTasksMileStones(BaseModel):
|
| 34 |
+
phases: List[Phases] = Field(description="Phases for the project")
|
| 35 |
+
|
| 36 |
+
class PlanOutput(BaseModel):
|
| 37 |
+
executive_summary: str = Field(description="concise overview of the project objectives, highlighting key features, targeted users, and platform (e.g., AWS)")
|
| 38 |
+
scope_objectives: ScopeObjective = Field(description="Scope and Objective for the project")
|
| 39 |
+
architecture_overview: str = Field(description="Overview of the system architecture, detailing the primary components and how they interact")
|
| 40 |
+
architecture_objectives: List[ArchitectureObjectives] = Field(description="List at least three objectives, each associated with a key architecture component, such as compute, storage, or network.")
|
| 41 |
+
component_design: List[ComponenetDesign] = Field(description="Detail at least two core components, including their purpose, interactions, and specifications")
|
| 42 |
+
security_and_compliance: List[str] = Field(description="List at least three security and compliance measures as strings, it should always be list of strings")
|
| 43 |
+
deployment_testing_monitoring: List[str] = Field(description="List of different points of deployment, testing, and monitoring, describing CI/CD, testing types, and monitoring approaches. it should always be list of strings")
|
| 44 |
+
team_roles_skills_requirements: List[TeamRolesSkillsRequirements] = Field(description="Define the roles needed for the project and their respective skill sets")
|
| 45 |
+
cost_estimates_optimization: CostEstimatesOptimization = Field(description="Cost Estimates for the project and strategies to optimize resources: don't include the cost just the explanation for the cost")
|
| 46 |
+
project_tasks_milestones: ProjectTasksMileStones = Field(description="Major project tasks and milestones, structured by phases")
|
| 47 |
+
|
| 48 |
+
async def generate_rough_plan(data, token):
|
| 49 |
+
prompt = (
|
| 50 |
+
f"Requirements: {data.requirements}\n"
|
| 51 |
+
f"Backend: {data.backend}\n"
|
| 52 |
+
f"Frontend: {data.frontend}\n"
|
| 53 |
+
f"Database: {data.database}\n"
|
| 54 |
+
f"Features: {data.features} \n"
|
| 55 |
+
f"Additional Features: {data.additional_feature} \n"
|
| 56 |
+
+ "\n".join([
|
| 57 |
+
f"{qa.question}\n{qa.answer}"
|
| 58 |
+
for qa in data.question_answers
|
| 59 |
+
])
|
| 60 |
+
+ "\n\n"
|
| 61 |
+
"Based on the provided details, please generate a comprehensive project description with the following sections:\n"
|
| 62 |
+
"1. Executive Summary\n"
|
| 63 |
+
f" Provide a concise overview of the project objectives, highlighting key features, targeted users, and platform (e.g., AWS) it should be atleast 4, 5 lines long also add 3 key outcomes. it should also focus of platform {data.platform}\n"
|
| 64 |
+
"2. Project Scope and Objectives\n"
|
| 65 |
+
f" - Scope: Define the core functionalities of the system 3 scopes and 3 objectives. focusing of platform {data.platform}\n"
|
| 66 |
+
f" - Objectives: List at least three objectives focusing on user needs and system capabilities focusing on platofrm {data.platform}.\n"
|
| 67 |
+
"3. Architecture Overview\n"
|
| 68 |
+
f" - Provide an overview of the system architecture, detailing the primary components and how they interact.focusing mostly on {data.platform}\n"
|
| 69 |
+
"4. Architecture Objectives\n"
|
| 70 |
+
f" - List at least three objectives, each associated with a key architecture component, such as frontend, backend, database, media storage, Authentication, compute, storage, or network. include all 6, 7 points focusing mostly on platofrm {data.platform}\n"
|
| 71 |
+
"5. Component Design\n"
|
| 72 |
+
f" - Detail at least two core components, including their purpose, interactions, and specifications. focus mostly on {data.platform}\n"
|
| 73 |
+
"6. Security and Compliance\n"
|
| 74 |
+
f" - List at least three security and compliance measures, such as data encryption, authentication, and compliance standards. focus mostly on {data.platform}\n"
|
| 75 |
+
"7. Deployment, Testing, and Monitoring\n"
|
| 76 |
+
f" - Include at least three aspects of deployment, testing, and monitoring, describing CI/CD, testing types, and monitoring approaches. focus mostly on {data.platform}\n"
|
| 77 |
+
"8. Team Roles and Skills Requirements\n"
|
| 78 |
+
f" - Define the roles needed for the project and their respective skill sets.focus mostly on {data.platform}\n"
|
| 79 |
+
"9. Cost Estimates and Optimization\n"
|
| 80 |
+
f" - Provide explanation for the cost (not including any digits or estimate just the explanation) and strategies to optimize resources {data.platform} (e.g., autoscaling, storage management).\n"
|
| 81 |
+
"10. Project Tasks and Milestones\n"
|
| 82 |
+
f" - Outline major project tasks and milestones, structured by phases (e.g., architecture design, development, testing, deployment). {data.platform}\n\n"
|
| 83 |
+
"11. Architecture Design Phase \n"
|
| 84 |
+
" - Define Architecture Design Phase for the project with main task and subtasks (Include exactly 3 tasks and 3 subtasks for each tasks) \n"
|
| 85 |
+
"12. Development Phase \n"
|
| 86 |
+
" - Define Development Phase for the project with main task that can be represented on the kanman, (Include exactly 4 tasks and 3 subtasks for each tasks) \n"
|
| 87 |
+
"13. Testing Phase \n"
|
| 88 |
+
" - Define Testing Phase for the project with main task that can be represented on the kanman, (Include exactly 2 tasks and 3 subtasks for each tasks)"
|
| 89 |
+
"14. Deployment Phase \n"
|
| 90 |
+
" - Define Deployment Phase for the project with main task that can be represented with the kanman (Include exactly 3 tasks and 2 subtasks for each tasks)"
|
| 91 |
+
"Output format:\n"
|
| 92 |
+
"The output should be a valid JSON structure with each section represented as a JSON object. For example:\n\n"
|
| 93 |
+
|
| 94 |
+
f"Note: the description or content for each section should foces on platform and include data sepcific to that platoform{data.platform}"
|
| 95 |
+
f"{json_output_format}"
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
output = ""
|
| 100 |
+
user = await get_user_from_token(token=token)
|
| 101 |
+
async for chunk in ask_question(question= prompt, history=[], use_context=False):
|
| 102 |
+
output += chunk
|
| 103 |
+
project_title = data.project_title
|
| 104 |
+
json_response = parse_and_return_json(output)
|
| 105 |
+
if (data.project_title == "" or data.project_title == None):
|
| 106 |
+
generated_project_title = await generate_project_title(requirements=data.requirements)
|
| 107 |
+
project_title = generated_project_title.split(":")[0].strip('"')
|
| 108 |
+
try:
|
| 109 |
+
print(f"Json Response before generate_plan_html: {json_response}")
|
| 110 |
+
plan_html = generate_plan_html(data=json.loads(json_response))
|
| 111 |
+
print(f"Json Response before generate_plan_html:")
|
| 112 |
+
project_id = await save_final_plan(project_title=project_title,
|
| 113 |
+
user=user,
|
| 114 |
+
data=data,
|
| 115 |
+
json_response=json_response,
|
| 116 |
+
plan_html=plan_html)
|
| 117 |
+
|
| 118 |
+
print(f"Project ID: {project_id}")
|
| 119 |
+
return {
|
| 120 |
+
"project_title": project_title,
|
| 121 |
+
"project_id": project_id,
|
| 122 |
+
"plan_html": plan_html,
|
| 123 |
+
"data": json.loads(json_response)
|
| 124 |
+
}
|
| 125 |
+
except Exception as e:
|
| 126 |
+
raise Exception(str(e))
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
async def generate_final_plan(data, token):
|
| 130 |
+
print(f"Step 1111111")
|
| 131 |
+
user = await get_user_from_token(token= token)
|
| 132 |
+
output = ""
|
| 133 |
+
# async for chunk in ask_question(question=prompt, history=[], use_context=False):
|
| 134 |
+
# output += chunk
|
| 135 |
+
# json_response = parse_and_return_json(output)
|
| 136 |
+
# json_response = json.loads(json_response)
|
| 137 |
+
json_response = await extract_data_from_html(data.rough_plan_html)
|
| 138 |
+
print(f"JSON RESPONSE after extract_data_from_html.......")
|
| 139 |
+
json_response = json.loads(json_response)
|
| 140 |
+
project_title = data.project_title
|
| 141 |
+
if (data.project_title == "" or data.project_title == None):
|
| 142 |
+
generated_project_title = await generate_project_title(requirements=data.requirements)
|
| 143 |
+
project_title = generated_project_title.split(":")[0].strip('"')
|
| 144 |
+
project_name = project_title
|
| 145 |
+
# tasks_applications = "".join([phase['name'] for phase in json_response['project_tasks_milestones']['phases']])
|
| 146 |
+
tasks_applications = generate_task_appplication(json_response['project_tasks_milestones'])
|
| 147 |
+
business_objectives = ", ".join([obj for obj in json_response['scope_objectives']['objectives']])
|
| 148 |
+
existing_infrastructure = 'AWS',
|
| 149 |
+
scalability_performance = ", ".join([item['objective'] for item in json_response['architecture_objectives']])
|
| 150 |
+
other_requirements = "".join([feature for feature in data.features])
|
| 151 |
+
user_id = "e16575cd-e9d3-47d5-b3ba-d3ef612f5683"
|
| 152 |
+
request_body = {
|
| 153 |
+
"project_name": project_name,
|
| 154 |
+
"tasks_applications": tasks_applications,
|
| 155 |
+
"business_objectives": business_objectives,
|
| 156 |
+
"existing_infrastructure": "AWS",
|
| 157 |
+
"scalability_performance":scalability_performance,
|
| 158 |
+
"security_compliance": scalability_performance,
|
| 159 |
+
"other_requirements": other_requirements,
|
| 160 |
+
"user_id": "e16575cd-e9d3-47d5-b3ba-d3ef612f5683",
|
| 161 |
+
}
|
| 162 |
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
| 163 |
+
try:
|
| 164 |
+
external_response = await client.post(
|
| 165 |
+
"https://auto-board-workspace.vercel.app/api/plan/generate",
|
| 166 |
+
json=request_body,
|
| 167 |
+
timeout=60.0
|
| 168 |
+
)
|
| 169 |
+
except Exception as e:
|
| 170 |
+
raise Exception(f"Error generating auto-board:::::::::{e}")
|
| 171 |
+
if external_response.status_code != 200:
|
| 172 |
+
raise Exception("Error generating auto-board....")
|
| 173 |
+
else:
|
| 174 |
+
response_data = external_response.json()
|
| 175 |
+
print(f"Response Data: {response_data}")
|
| 176 |
+
print(f"Response Data: {response_data['data']}")
|
| 177 |
+
print(f"Response Data: {response_data['data']['id']}")
|
| 178 |
+
print(f"First API called.............")
|
| 179 |
+
gantt_request_body = {
|
| 180 |
+
"projectId": str(response_data['data']['id']),
|
| 181 |
+
"userId": "e16575cd-e9d3-47d5-b3ba-d3ef612f5683"
|
| 182 |
+
}
|
| 183 |
+
print(f"gantt request body: {gantt_request_body}")
|
| 184 |
+
try:
|
| 185 |
+
kamban = await client.post(
|
| 186 |
+
"https://auto-board-workspace.vercel.app/api/kanbans/generate",
|
| 187 |
+
json=gantt_request_body,
|
| 188 |
+
timeout=60.0
|
| 189 |
+
)
|
| 190 |
+
external_response = await client.post(
|
| 191 |
+
"https://auto-board-workspace.vercel.app/api/gantt/generate",
|
| 192 |
+
json=gantt_request_body,
|
| 193 |
+
timeout=60.0
|
| 194 |
+
)
|
| 195 |
+
except Exception as e:
|
| 196 |
+
raise Exception(f"Error generating gantt: {e}")
|
| 197 |
+
print(f"Second and Third API called.............")
|
| 198 |
+
kamban_response = kamban.json()
|
| 199 |
+
async with httpx.AsyncClient() as client:
|
| 200 |
+
if external_response.status_code == 200:
|
| 201 |
+
response_json = external_response.json()
|
| 202 |
+
db_instance = ProjectModel(requirements=data.requirements, features=data.features,
|
| 203 |
+
solution_stack = data.solution_stack, rough_plan = data.rough_plan,
|
| 204 |
+
final_plan = json_response, user_id = user.id,
|
| 205 |
+
project_title = project_title,
|
| 206 |
+
gantt_project_id = str(response_data['data']['id']),
|
| 207 |
+
user_uuid ="e16575cd-e9d3-47d5-b3ba-d3ef612f5683",
|
| 208 |
+
ganttDataID=str(response_json['data']['id']),
|
| 209 |
+
boardId=str(kamban_response['board']['id']),
|
| 210 |
+
rough_plan_html=data.rough_plan_html,
|
| 211 |
+
final_plan_html=data.rough_plan_html
|
| 212 |
+
)
|
| 213 |
+
await db_instance.save()
|
| 214 |
+
print(f"DB Instance: {db_instance.id}")
|
| 215 |
+
# str_data = ""
|
| 216 |
+
# if isinstance(json_response, dict):
|
| 217 |
+
# str_data = json.dumps(json_response)
|
| 218 |
+
# else:
|
| 219 |
+
# str_data = json.loads(json_response)
|
| 220 |
+
# await generate_embeddings(data=str_data, project_id=db_instance.id)
|
| 221 |
+
print(f"Finalizing............")
|
| 222 |
+
return {
|
| 223 |
+
"project_title": project_title,
|
| 224 |
+
"project_id": int(db_instance.id),
|
| 225 |
+
"data": json_response,
|
| 226 |
+
"final_plan": data.rough_plan_html
|
| 227 |
+
}
|
| 228 |
+
else:
|
| 229 |
+
print(f"Gantt Chart Task: External API responded with status code {external_response.status_code}: {external_response.text}")
|
| 230 |
+
return {
|
| 231 |
+
"success": False,
|
| 232 |
+
"status_code": external_response.status_code,
|
| 233 |
+
"message": external_response.text
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
async def update_final_plan_fun(project_id, data, token):
|
| 238 |
+
try:
|
| 239 |
+
project , json_response = await asyncio.gather(
|
| 240 |
+
ProjectModel.get(id=project_id),
|
| 241 |
+
extract_data_from_html(data.final_plan_html)
|
| 242 |
+
)
|
| 243 |
+
project.final_plan_html = data.final_plan_html
|
| 244 |
+
print(f"saving html content.........")
|
| 245 |
+
project.final_plan = json.loads(json_response)
|
| 246 |
+
await project.save()
|
| 247 |
+
|
| 248 |
+
return {
|
| 249 |
+
"success": True,
|
| 250 |
+
"message": "Final plan HTML updated successfully.",
|
| 251 |
+
"project_id": project.id,
|
| 252 |
+
"final_plan_html": project.final_plan_html
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
except Exception as e:
|
| 256 |
+
raise Exception(f"Error updating final plan HTML: {str(e)}")
|
app/helpers/generate_soluction_stack.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from helpers.f5_model import f5_model
|
| 2 |
+
from pydantic import BaseModel, Field
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
class StackComponent(BaseModel):
|
| 6 |
+
service: str
|
| 7 |
+
description: str
|
| 8 |
+
|
| 9 |
+
class SoluctionStack(BaseModel):
|
| 10 |
+
computer_processing: List[StackComponent]
|
| 11 |
+
data_management_storage: List[StackComponent]
|
| 12 |
+
network_security: List[StackComponent]
|
| 13 |
+
app_integration_management: List[StackComponent]
|
| 14 |
+
|
| 15 |
+
async def generate_soluction_stack(data):
|
| 16 |
+
prompt = (
|
| 17 |
+
f"Requirements: {data.requirements}\n"
|
| 18 |
+
f"Additional Features: {data.additional_feature} \n"
|
| 19 |
+
"Generate a comprehensive cloud solution stack with the following components:\n"
|
| 20 |
+
"1. Computer Processing\n"
|
| 21 |
+
"2. Data Management and Storage\n"
|
| 22 |
+
"3. Network Security\n"
|
| 23 |
+
"4. Application Integration and Management"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
response = await f5_model.generate_response(prompt)
|
| 27 |
+
# Parse the response into SoluctionStack structure
|
| 28 |
+
stack_dict = parse_stack_response(response)
|
| 29 |
+
return SoluctionStack(**stack_dict)
|
| 30 |
+
|
| 31 |
+
def parse_stack_response(response: str) -> dict:
|
| 32 |
+
# Add parsing logic here to convert F5 model output to SoluctionStack format
|
| 33 |
+
# This is a placeholder implementation
|
| 34 |
+
return {
|
| 35 |
+
"computer_processing": [],
|
| 36 |
+
"data_management_storage": [],
|
| 37 |
+
"network_security": [],
|
| 38 |
+
"app_integration_management": []
|
| 39 |
+
}
|
app/helpers/plan_chat.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 2 |
+
from langchain_core.output_parsers import StrOutputParser
|
| 3 |
+
from langchain_openai import ChatOpenAI
|
| 4 |
+
from helpers.generate_embbedings import vector_store
|
| 5 |
+
from langchain_aws import ChatBedrock
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
def make_prompt(history, prompt, context=None):
|
| 9 |
+
formatted_history = ""
|
| 10 |
+
|
| 11 |
+
if context:
|
| 12 |
+
formatted_history += f"[CONTEXT] {context} [/CONTEXT]\n"
|
| 13 |
+
|
| 14 |
+
for history_item in history:
|
| 15 |
+
if history_item.from_ == 'user':
|
| 16 |
+
formatted_history += f"[INST] {history_item.message} [/INST]\n"
|
| 17 |
+
else:
|
| 18 |
+
formatted_history += f"{history_item.message}\n"
|
| 19 |
+
|
| 20 |
+
formatted_history += f"[INST] {prompt} [/INST]\n"
|
| 21 |
+
|
| 22 |
+
return formatted_history
|
| 23 |
+
|
| 24 |
+
prompt = ChatPromptTemplate.from_template("{prompt}")
|
| 25 |
+
|
| 26 |
+
model = ChatBedrock(
|
| 27 |
+
model="mistral.mistral-7b-instruct-v0:2",
|
| 28 |
+
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
|
| 29 |
+
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
|
| 30 |
+
region=os.environ.get("AWS_DEFAULT_REGION"),
|
| 31 |
+
max_tokens=8000,
|
| 32 |
+
temperature=0
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
output_parser = StrOutputParser()
|
| 36 |
+
|
| 37 |
+
chain = prompt | model | output_parser
|
| 38 |
+
|
| 39 |
+
async def ask_question(question: str, history: list = [], project_id=None):
|
| 40 |
+
"""
|
| 41 |
+
Generate a response for a given question based on history and project-specific context.
|
| 42 |
+
"""
|
| 43 |
+
try:
|
| 44 |
+
context = ""
|
| 45 |
+
if project_id is not None:
|
| 46 |
+
context = vector_store.similarity_search(
|
| 47 |
+
query=question, k=4, filter={"project_id": project_id}
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
prompt = make_prompt(history, question, context)
|
| 51 |
+
|
| 52 |
+
stream = chain.astream({"prompt": prompt})
|
| 53 |
+
async for chunk in stream:
|
| 54 |
+
yield chunk
|
| 55 |
+
except Exception as e:
|
| 56 |
+
raise RuntimeError(f"Error generating response: {str(e)}")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
gpt_model = ChatOpenAI(
|
| 60 |
+
temperature=0.7,
|
| 61 |
+
model='gpt-4o-mini'
|
| 62 |
+
)
|
| 63 |
+
chain = prompt | gpt_model | StrOutputParser()
|
| 64 |
+
|
| 65 |
+
async def ask_openai(question: str, history: list = []):
|
| 66 |
+
"""
|
| 67 |
+
Generate a response for a given question based on history and project-specific context.
|
| 68 |
+
"""
|
| 69 |
+
ai_response = "I am the CloudMod Solutions Architect, an expert in AWS, Azure & GCP. How can I help you?"
|
| 70 |
+
try:
|
| 71 |
+
context = ("You are a AI Assistant for CloudMod Soluctions Architect, an expert in AWS, Azure & GCP \n"
|
| 72 |
+
"If asked question such as `what the chat does, what they are`\n"
|
| 73 |
+
"Answer question as per the context \n\n"
|
| 74 |
+
f"Here is the user query : {question}"
|
| 75 |
+
f"here is the previous chat history: {history}"
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
prompt = context
|
| 79 |
+
stream = chain.astream({"prompt": prompt})
|
| 80 |
+
async for chunk in stream:
|
| 81 |
+
yield chunk
|
| 82 |
+
except Exception as e:
|
| 83 |
+
raise RuntimeError(f"Error generating response: {str(e)}")
|
app/helpers/plan_parser.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def parse_plan_sections(content: str) -> dict:
|
| 2 |
+
"""Parse the generated plan content into structured sections."""
|
| 3 |
+
sections = {
|
| 4 |
+
"executive_summary": "No content generated",
|
| 5 |
+
"scope_objectives": "No content generated",
|
| 6 |
+
"architecture_overview": "No content generated",
|
| 7 |
+
"component_design": "No content generated",
|
| 8 |
+
"security_compliance": "No content generated",
|
| 9 |
+
"deployment_testing": "No content generated",
|
| 10 |
+
"team_roles": "No content generated",
|
| 11 |
+
"cost_estimates": "No content generated",
|
| 12 |
+
"project_phases": "No content generated"
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
current_section = None
|
| 16 |
+
lines = content.split('\n')
|
| 17 |
+
section_content = []
|
| 18 |
+
|
| 19 |
+
for line in lines:
|
| 20 |
+
line = line.strip()
|
| 21 |
+
if not line:
|
| 22 |
+
continue
|
| 23 |
+
|
| 24 |
+
# Check for section headers
|
| 25 |
+
if line.lower().startswith('1. executive summary') or line.lower().startswith('executive summary'):
|
| 26 |
+
if current_section and section_content:
|
| 27 |
+
sections[current_section] = '\n'.join(section_content)
|
| 28 |
+
current_section = 'executive_summary'
|
| 29 |
+
section_content = []
|
| 30 |
+
continue
|
| 31 |
+
elif line.lower().startswith('2. project scope') or line.lower().startswith('scope'):
|
| 32 |
+
if current_section and section_content:
|
| 33 |
+
sections[current_section] = '\n'.join(section_content)
|
| 34 |
+
current_section = 'scope_objectives'
|
| 35 |
+
section_content = []
|
| 36 |
+
continue
|
| 37 |
+
elif line.lower().startswith('3. architecture') or line.lower().startswith('architecture'):
|
| 38 |
+
if current_section and section_content:
|
| 39 |
+
sections[current_section] = '\n'.join(section_content)
|
| 40 |
+
current_section = 'architecture_overview'
|
| 41 |
+
section_content = []
|
| 42 |
+
continue
|
| 43 |
+
elif line.lower().startswith('4. component') or line.lower().startswith('component'):
|
| 44 |
+
if current_section and section_content:
|
| 45 |
+
sections[current_section] = '\n'.join(section_content)
|
| 46 |
+
current_section = 'component_design'
|
| 47 |
+
section_content = []
|
| 48 |
+
continue
|
| 49 |
+
elif line.lower().startswith('5. security') or line.lower().startswith('security'):
|
| 50 |
+
if current_section and section_content:
|
| 51 |
+
sections[current_section] = '\n'.join(section_content)
|
| 52 |
+
current_section = 'security_compliance'
|
| 53 |
+
section_content = []
|
| 54 |
+
continue
|
| 55 |
+
elif line.lower().startswith('6. deployment') or line.lower().startswith('deployment'):
|
| 56 |
+
if current_section and section_content:
|
| 57 |
+
sections[current_section] = '\n'.join(section_content)
|
| 58 |
+
current_section = 'deployment_testing'
|
| 59 |
+
section_content = []
|
| 60 |
+
continue
|
| 61 |
+
elif line.lower().startswith('7. team') or line.lower().startswith('team'):
|
| 62 |
+
if current_section and section_content:
|
| 63 |
+
sections[current_section] = '\n'.join(section_content)
|
| 64 |
+
current_section = 'team_roles'
|
| 65 |
+
section_content = []
|
| 66 |
+
continue
|
| 67 |
+
elif line.lower().startswith('8. cost') or line.lower().startswith('cost'):
|
| 68 |
+
if current_section and section_content:
|
| 69 |
+
sections[current_section] = '\n'.join(section_content)
|
| 70 |
+
current_section = 'cost_estimates'
|
| 71 |
+
section_content = []
|
| 72 |
+
continue
|
| 73 |
+
elif line.lower().startswith('9. project') or line.lower().startswith('project'):
|
| 74 |
+
if current_section and section_content:
|
| 75 |
+
sections[current_section] = '\n'.join(section_content)
|
| 76 |
+
current_section = 'project_phases'
|
| 77 |
+
section_content = []
|
| 78 |
+
continue
|
| 79 |
+
elif current_section:
|
| 80 |
+
section_content.append(line)
|
| 81 |
+
|
| 82 |
+
# Add the last section's content
|
| 83 |
+
if current_section and section_content:
|
| 84 |
+
sections[current_section] = '\n'.join(section_content)
|
| 85 |
+
|
| 86 |
+
# Clean up empty sections
|
| 87 |
+
for key, value in sections.items():
|
| 88 |
+
if not value or value.isspace():
|
| 89 |
+
sections[key] = "No content generated for this section."
|
| 90 |
+
|
| 91 |
+
return sections
|
app/main.py
CHANGED
|
@@ -1,102 +1,298 @@
|
|
| 1 |
-
from fastapi import FastAPI,
|
|
|
|
| 2 |
from fastapi.staticfiles import StaticFiles
|
| 3 |
from fastapi.templating import Jinja2Templates
|
| 4 |
-
from
|
| 5 |
-
from app.
|
| 6 |
-
import
|
| 7 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
-
from starlette.middleware.base import BaseHTTPMiddleware
|
| 9 |
-
from dotenv import load_dotenv
|
| 10 |
import logging
|
| 11 |
-
from
|
| 12 |
-
import time
|
| 13 |
|
| 14 |
-
#
|
| 15 |
-
logging.basicConfig(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
# Get the absolute path to the app directory
|
| 22 |
-
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 23 |
-
|
| 24 |
-
class RateLimitMiddleware(BaseHTTPMiddleware):
|
| 25 |
-
async def dispatch(self, request: Request, call_next: Callable):
|
| 26 |
-
# Get client IP
|
| 27 |
-
client_ip = request.client.host
|
| 28 |
-
|
| 29 |
-
# Check rate limit
|
| 30 |
-
if not rate_limiter.is_allowed(client_ip):
|
| 31 |
-
logger.warning(f"Rate limit exceeded for IP: {client_ip}")
|
| 32 |
-
raise HTTPException(
|
| 33 |
-
status_code=429,
|
| 34 |
-
detail="Too many requests. Please try again later."
|
| 35 |
-
)
|
| 36 |
-
|
| 37 |
-
# Process request
|
| 38 |
-
start_time = time.time()
|
| 39 |
-
response = await call_next(request)
|
| 40 |
-
process_time = time.time() - start_time
|
| 41 |
-
|
| 42 |
-
# Log request details
|
| 43 |
-
logger.info(
|
| 44 |
-
f"Request: {request.method} {request.url.path} "
|
| 45 |
-
f"Client: {client_ip} "
|
| 46 |
-
f"Process time: {process_time:.2f}s"
|
| 47 |
-
)
|
| 48 |
-
|
| 49 |
-
return response
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
description="API for generating invoices",
|
| 54 |
-
version="1.0.0"
|
| 55 |
-
)
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
allow_credentials=True,
|
| 65 |
-
allow_methods=["*"],
|
| 66 |
-
allow_headers=["*"],
|
| 67 |
-
max_age=3600, # Cache preflight requests for 1 hour
|
| 68 |
-
)
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
|
|
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
# Root endpoint to serve the HTML page
|
| 90 |
@app.get("/")
|
| 91 |
-
async def
|
| 92 |
return templates.TemplateResponse("index.html", {"request": request})
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
-
@app.get("/history")
|
| 100 |
-
async def history_page(request: Request):
|
| 101 |
-
return templates.TemplateResponse("history.html", {"request": request})
|
| 102 |
-
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, Request
|
| 2 |
+
from fastapi.responses import StreamingResponse
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from app.controllers.f5_model import F5ModelHandler
|
| 7 |
+
from typing import List, Optional
|
|
|
|
|
|
|
|
|
|
| 8 |
import logging
|
| 9 |
+
from app.helpers.plan_parser import parse_plan_sections
|
|
|
|
| 10 |
|
| 11 |
+
# Configure logging
|
| 12 |
+
logging.basicConfig(
|
| 13 |
+
level=logging.INFO,
|
| 14 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 15 |
+
handlers=[
|
| 16 |
+
logging.StreamHandler(),
|
| 17 |
+
logging.FileHandler('app.log')
|
| 18 |
+
]
|
| 19 |
+
)
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
|
| 22 |
+
app = FastAPI(title="F5 Model Test Application")
|
| 23 |
+
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
# Initialize the F5 model
|
| 26 |
+
model_handler = F5ModelHandler()
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
class ChatMessage(BaseModel):
|
| 29 |
+
role: str
|
| 30 |
+
content: str
|
| 31 |
+
|
| 32 |
+
class ChatRequest(BaseModel):
|
| 33 |
+
messages: List[ChatMessage]
|
| 34 |
+
stream: Optional[bool] = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
class FeatureRequest(BaseModel):
|
| 37 |
+
requirements: str
|
| 38 |
|
| 39 |
+
class Feature(BaseModel):
|
| 40 |
+
feature: str
|
| 41 |
+
short_description: str
|
| 42 |
|
| 43 |
+
class FeaturesResponse(BaseModel):
|
| 44 |
+
features: List[Feature]
|
| 45 |
|
| 46 |
+
class ProjectPlanRequest(BaseModel):
|
| 47 |
+
project_title: str
|
| 48 |
+
requirements: str
|
| 49 |
+
features: List[str]
|
| 50 |
+
platform: str = "AWS" # Default to AWS
|
| 51 |
+
additional_requirements: str = ""
|
| 52 |
|
| 53 |
+
class ProjectSection(BaseModel):
|
| 54 |
+
title: str
|
| 55 |
+
content: str
|
| 56 |
+
|
| 57 |
+
class ProjectPlan(BaseModel):
|
| 58 |
+
executive_summary: str
|
| 59 |
+
scope_objectives: dict
|
| 60 |
+
architecture_overview: str
|
| 61 |
+
component_design: List[dict]
|
| 62 |
+
security_compliance: List[str]
|
| 63 |
+
deployment_testing: List[str]
|
| 64 |
+
team_roles: List[dict]
|
| 65 |
+
cost_estimates: dict
|
| 66 |
+
project_phases: List[dict]
|
| 67 |
|
|
|
|
| 68 |
@app.get("/")
|
| 69 |
+
async def index(request: Request):
|
| 70 |
return templates.TemplateResponse("index.html", {"request": request})
|
| 71 |
|
| 72 |
+
@app.post("/chat")
|
| 73 |
+
async def chat(request: ChatRequest):
|
| 74 |
+
try:
|
| 75 |
+
logger.info(f"Chat request received with {len(request.messages)} messages")
|
| 76 |
+
|
| 77 |
+
# Improve the prompt with better context
|
| 78 |
+
prompt = (
|
| 79 |
+
"You are a helpful AI assistant specializing in SaaS applications and software development. "
|
| 80 |
+
"Please provide detailed and professional responses.\n\n"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
for msg in request.messages:
|
| 84 |
+
if msg.role == "user":
|
| 85 |
+
prompt += f"[INST] {msg.content} [/INST]\n"
|
| 86 |
+
else:
|
| 87 |
+
prompt += f"{msg.content}\n"
|
| 88 |
+
|
| 89 |
+
logger.info(f"Formatted prompt: {prompt}")
|
| 90 |
+
|
| 91 |
+
if request.stream:
|
| 92 |
+
logger.info("Starting streaming response")
|
| 93 |
+
async def generate():
|
| 94 |
+
async for chunk in model_handler.stream_response(prompt):
|
| 95 |
+
logger.debug(f"Streaming chunk: {chunk}")
|
| 96 |
+
yield f"data: {chunk}\n\n"
|
| 97 |
+
return StreamingResponse(generate(), media_type="text/event-stream")
|
| 98 |
+
else:
|
| 99 |
+
response = await model_handler.generate_response(prompt)
|
| 100 |
+
logger.info(f"Generated response: {response}")
|
| 101 |
+
return {"response": response}
|
| 102 |
+
except Exception as e:
|
| 103 |
+
logger.error(f"Error in chat endpoint: {str(e)}", exc_info=True)
|
| 104 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 105 |
+
|
| 106 |
+
@app.post("/generate-features", response_model=FeaturesResponse)
|
| 107 |
+
async def generate_features(request: FeatureRequest):
|
| 108 |
+
try:
|
| 109 |
+
logger.info(f"Feature generation request received with requirements: {request.requirements}")
|
| 110 |
+
|
| 111 |
+
# Improved prompt for better feature generation
|
| 112 |
+
prompt = (
|
| 113 |
+
"You are a SaaS product expert. Generate 20 practical features for a SaaS application.\n"
|
| 114 |
+
"For each feature:\n"
|
| 115 |
+
"1. Provide a short, clear feature name\n"
|
| 116 |
+
"2. Write a concise description explaining its value\n"
|
| 117 |
+
"Format each feature as:\n"
|
| 118 |
+
"Feature Name\n"
|
| 119 |
+
"Clear description of what the feature does and its benefits.\n\n"
|
| 120 |
+
f"Requirements: {request.requirements}\n\n"
|
| 121 |
+
"Generate 20 features in this exact format."
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
logger.info(f"Generated prompt: {prompt}")
|
| 125 |
+
|
| 126 |
+
response = await model_handler.generate_response(prompt)
|
| 127 |
+
logger.info(f"Model response: {response}")
|
| 128 |
+
|
| 129 |
+
# Parse the response into features
|
| 130 |
+
features = []
|
| 131 |
+
lines = response.split('\n')
|
| 132 |
+
current_feature = None
|
| 133 |
+
current_description = []
|
| 134 |
+
|
| 135 |
+
for line in lines:
|
| 136 |
+
line = line.strip()
|
| 137 |
+
if not line:
|
| 138 |
+
if current_feature and current_description:
|
| 139 |
+
features.append(Feature(
|
| 140 |
+
feature=current_feature,
|
| 141 |
+
short_description=' '.join(current_description)
|
| 142 |
+
))
|
| 143 |
+
current_feature = None
|
| 144 |
+
current_description = []
|
| 145 |
+
elif not current_feature:
|
| 146 |
+
current_feature = line
|
| 147 |
+
else:
|
| 148 |
+
current_description.append(line)
|
| 149 |
+
|
| 150 |
+
# Add the last feature if exists
|
| 151 |
+
if current_feature and current_description:
|
| 152 |
+
features.append(Feature(
|
| 153 |
+
feature=current_feature,
|
| 154 |
+
short_description=' '.join(current_description)
|
| 155 |
+
))
|
| 156 |
+
|
| 157 |
+
# If no features were parsed, provide fallback features
|
| 158 |
+
if not features:
|
| 159 |
+
features = [
|
| 160 |
+
Feature(
|
| 161 |
+
feature="User Management",
|
| 162 |
+
short_description="Complete user authentication and authorization system"
|
| 163 |
+
),
|
| 164 |
+
Feature(
|
| 165 |
+
feature="Subscription Billing",
|
| 166 |
+
short_description="Automated billing and subscription management"
|
| 167 |
+
),
|
| 168 |
+
Feature(
|
| 169 |
+
feature="Analytics Dashboard",
|
| 170 |
+
short_description="Real-time metrics and usage analytics"
|
| 171 |
+
),
|
| 172 |
+
Feature(
|
| 173 |
+
feature="API Integration",
|
| 174 |
+
short_description="RESTful API endpoints for third-party integration"
|
| 175 |
+
),
|
| 176 |
+
Feature(
|
| 177 |
+
feature="Multi-tenant Architecture",
|
| 178 |
+
short_description="Secure data isolation for multiple customers"
|
| 179 |
+
)
|
| 180 |
+
]
|
| 181 |
+
|
| 182 |
+
logger.info(f"Returning features: {features}")
|
| 183 |
+
return FeaturesResponse(features=features)
|
| 184 |
+
except Exception as e:
|
| 185 |
+
logger.error(f"Error in generate-features endpoint: {str(e)}", exc_info=True)
|
| 186 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 187 |
+
|
| 188 |
+
@app.post("/generate-plan")
|
| 189 |
+
async def generate_plan(request: ProjectPlanRequest):
|
| 190 |
+
try:
|
| 191 |
+
logger.info(f"Plan generation request received for project: {request.project_title}")
|
| 192 |
+
|
| 193 |
+
# Enhanced prompt for more detailed output
|
| 194 |
+
prompt = f"""Generate a comprehensive technical project plan for '{request.project_title}' with detailed sections.
|
| 195 |
+
Include specific {request.platform} services and implementation details in each section.
|
| 196 |
+
|
| 197 |
+
1. Executive Summary
|
| 198 |
+
- Provide a detailed overview of the project (4-5 paragraphs)
|
| 199 |
+
- Include project goals, target users, and key outcomes
|
| 200 |
+
- Highlight main {request.platform} services to be used
|
| 201 |
+
- Explain expected business impact
|
| 202 |
+
|
| 203 |
+
2. Project Scope and Objectives
|
| 204 |
+
- Define detailed scope including:
|
| 205 |
+
* Core functionalities
|
| 206 |
+
* System boundaries
|
| 207 |
+
* Integration points
|
| 208 |
+
- List at least 5 specific objectives
|
| 209 |
+
- Include measurable success criteria
|
| 210 |
+
|
| 211 |
+
3. Architecture Overview
|
| 212 |
+
- Detailed {request.platform} architecture including:
|
| 213 |
+
* Frontend architecture
|
| 214 |
+
* Backend services
|
| 215 |
+
* Database design
|
| 216 |
+
* Integration patterns
|
| 217 |
+
* Network topology
|
| 218 |
+
- Explain how components interact
|
| 219 |
+
- Include scalability considerations
|
| 220 |
+
|
| 221 |
+
4. Component Design
|
| 222 |
+
- Detail at least 3 core components with:
|
| 223 |
+
* Purpose and functionality
|
| 224 |
+
* Technical specifications
|
| 225 |
+
* Data flow
|
| 226 |
+
* Integration points
|
| 227 |
+
* Performance requirements
|
| 228 |
+
|
| 229 |
+
5. Security and Compliance
|
| 230 |
+
- List specific {request.platform} security services
|
| 231 |
+
- Detail authentication and authorization
|
| 232 |
+
- Describe data protection measures
|
| 233 |
+
- Include compliance requirements
|
| 234 |
+
- Specify security monitoring
|
| 235 |
+
|
| 236 |
+
6. Deployment, Testing, and Monitoring
|
| 237 |
+
- Detailed CI/CD pipeline
|
| 238 |
+
- Test strategy including:
|
| 239 |
+
* Unit testing
|
| 240 |
+
* Integration testing
|
| 241 |
+
* Performance testing
|
| 242 |
+
- Monitoring setup with {request.platform} tools
|
| 243 |
+
- Alerting and logging strategy
|
| 244 |
+
|
| 245 |
+
7. Team Roles and Skills
|
| 246 |
+
- List all required roles
|
| 247 |
+
- Detail specific skills needed
|
| 248 |
+
- Include {request.platform} certifications
|
| 249 |
+
- Define team structure
|
| 250 |
+
- Specify responsibilities
|
| 251 |
+
|
| 252 |
+
8. Cost Estimates and Optimization
|
| 253 |
+
- Break down {request.platform} service costs
|
| 254 |
+
- Resource optimization strategies
|
| 255 |
+
- Scaling considerations
|
| 256 |
+
- Cost monitoring approach
|
| 257 |
+
- Budget optimization tips
|
| 258 |
+
|
| 259 |
+
9. Project Tasks and Milestones
|
| 260 |
+
- Detailed project phases
|
| 261 |
+
- Specific tasks for each phase
|
| 262 |
+
- Timeline estimates
|
| 263 |
+
- Dependencies
|
| 264 |
+
- Critical path items
|
| 265 |
+
|
| 266 |
+
Project Requirements:
|
| 267 |
+
{request.requirements}
|
| 268 |
+
|
| 269 |
+
Features to implement:
|
| 270 |
+
{', '.join(request.features)}
|
| 271 |
+
|
| 272 |
+
Additional Requirements:
|
| 273 |
+
{request.additional_requirements}
|
| 274 |
+
|
| 275 |
+
Generate a detailed plan following this structure with specific {request.platform} implementation details."""
|
| 276 |
+
|
| 277 |
+
logger.info("Generating plan with model...")
|
| 278 |
+
response = await model_handler.generate_response(prompt)
|
| 279 |
+
logger.info(f"Response generated successfully: {response[:100]}...")
|
| 280 |
+
|
| 281 |
+
# Parse the response into structured sections
|
| 282 |
+
sections = parse_plan_sections(response)
|
| 283 |
+
|
| 284 |
+
logger.info("Plan generated successfully")
|
| 285 |
+
return {
|
| 286 |
+
"project_title": request.project_title,
|
| 287 |
+
"sections": sections,
|
| 288 |
+
"raw_content": response
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
except Exception as e:
|
| 292 |
+
logger.error(f"Error generating plan: {str(e)}", exc_info=True)
|
| 293 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 294 |
+
|
| 295 |
+
if __name__ == "__main__":
|
| 296 |
+
import uvicorn
|
| 297 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
| 298 |
|
|
|
|
|
|
|
|
|
|
|
|
app/models/__init__.py
ADDED
|
File without changes
|
app/models/project_plan.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import List
|
| 3 |
+
|
| 4 |
+
class ProjectPlanRequest(BaseModel):
|
| 5 |
+
project_title: str
|
| 6 |
+
requirements: str
|
| 7 |
+
features: List[str]
|
| 8 |
+
platform: str = "AWS" # Default to AWS
|
| 9 |
+
additional_requirements: str = ""
|
| 10 |
+
|
| 11 |
+
class ProjectSection(BaseModel):
|
| 12 |
+
title: str
|
| 13 |
+
content: str
|
| 14 |
+
|
| 15 |
+
class ProjectPlan(BaseModel):
|
| 16 |
+
executive_summary: str
|
| 17 |
+
scope_objectives: dict
|
| 18 |
+
architecture_overview: str
|
| 19 |
+
component_design: List[dict]
|
| 20 |
+
security_compliance: List[str]
|
| 21 |
+
deployment_testing: List[str]
|
| 22 |
+
team_roles: List[dict]
|
| 23 |
+
cost_estimates: dict
|
| 24 |
+
project_phases: List[dict]
|
app/models/scrape_log.py
ADDED
|
File without changes
|
app/services/__init__.py
ADDED
|
File without changes
|
app/services/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (169 Bytes). View file
|
|
|
app/services/__pycache__/flan_t5_service.cpython-312.pyc
ADDED
|
Binary file (1.56 kB). View file
|
|
|
app/services/__pycache__/scraper_service.cpython-312.pyc
ADDED
|
Binary file (2.8 kB). View file
|
|
|
app/services/flan_t5_service.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import T5ForConditionalGeneration, T5Tokenizer
|
| 2 |
+
|
| 3 |
+
class FlanT5Service:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.model_name = "google/flan-t5-base"
|
| 6 |
+
self.tokenizer = T5Tokenizer.from_pretrained(self.model_name)
|
| 7 |
+
self.model = T5ForConditionalGeneration.from_pretrained(self.model_name)
|
| 8 |
+
|
| 9 |
+
async def generate_response(self, prompt: str, max_length: int = 512) -> str:
|
| 10 |
+
inputs = self.tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
|
| 11 |
+
outputs = self.model.generate(
|
| 12 |
+
**inputs,
|
| 13 |
+
max_length=max_length,
|
| 14 |
+
num_beams=4,
|
| 15 |
+
temperature=0.7,
|
| 16 |
+
top_p=0.9
|
| 17 |
+
)
|
| 18 |
+
return self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
app/services/scraper_service.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from httpx import AsyncClient
|
| 2 |
+
from bs4 import BeautifulSoup
|
| 3 |
+
from typing import Tuple
|
| 4 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 5 |
+
|
| 6 |
+
class ScraperService:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.text_splitter = RecursiveCharacterTextSplitter(
|
| 9 |
+
chunk_size=1000,
|
| 10 |
+
chunk_overlap=20,
|
| 11 |
+
length_function=len,
|
| 12 |
+
is_separator_regex=False,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
async def scrape_website(self, url: str) -> str:
|
| 16 |
+
async with AsyncClient() as client:
|
| 17 |
+
chrome_headers = {
|
| 18 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
|
| 19 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
| 20 |
+
"Accept-Language": "en-US,en;q=0.9",
|
| 21 |
+
}
|
| 22 |
+
response = await client.get(url, headers=chrome_headers)
|
| 23 |
+
return response.text
|
| 24 |
+
|
| 25 |
+
def extract_text_from_html(self, html: str) -> str:
|
| 26 |
+
soup = BeautifulSoup(html, 'html.parser')
|
| 27 |
+
|
| 28 |
+
# Remove script and style elements
|
| 29 |
+
for element in soup(['script', 'style', 'header', 'footer', 'nav']):
|
| 30 |
+
element.decompose()
|
| 31 |
+
|
| 32 |
+
text = soup.get_text(separator='\n', strip=True)
|
| 33 |
+
return text
|
| 34 |
+
|
| 35 |
+
async def scrape_and_process(self, url: str) -> Tuple[str, list]:
|
| 36 |
+
# Scrape the website
|
| 37 |
+
html = await self.scrape_website(url)
|
| 38 |
+
|
| 39 |
+
# Extract text
|
| 40 |
+
text = self.extract_text_from_html(html)
|
| 41 |
+
|
| 42 |
+
# Split into chunks
|
| 43 |
+
documents = self.text_splitter.create_documents([text])
|
| 44 |
+
|
| 45 |
+
return text, documents
|
app/static/js/main.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 2 |
+
const form = document.getElementById('scrapeForm');
|
| 3 |
+
const loadingIndicator = document.getElementById('loadingIndicator');
|
| 4 |
+
const results = document.getElementById('results');
|
| 5 |
+
const resultContent = document.getElementById('resultContent');
|
| 6 |
+
|
| 7 |
+
form.addEventListener('submit', async function(e) {
|
| 8 |
+
e.preventDefault();
|
| 9 |
+
|
| 10 |
+
const url = document.getElementById('url').value;
|
| 11 |
+
const promptTemplate = document.getElementById('promptTemplate').value;
|
| 12 |
+
|
| 13 |
+
loadingIndicator.classList.remove('hidden');
|
| 14 |
+
results.classList.add('hidden');
|
| 15 |
+
|
| 16 |
+
try {
|
| 17 |
+
const response = await fetch('/api/scrape', {
|
| 18 |
+
method: 'POST',
|
| 19 |
+
headers: {
|
| 20 |
+
'Content-Type': 'application/json',
|
| 21 |
+
},
|
| 22 |
+
body: JSON.stringify({
|
| 23 |
+
url: url,
|
| 24 |
+
prompt_template: promptTemplate
|
| 25 |
+
})
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
const data = await response.json();
|
| 29 |
+
|
| 30 |
+
if (data.success) {
|
| 31 |
+
resultContent.textContent = data.result;
|
| 32 |
+
} else {
|
| 33 |
+
resultContent.textContent = 'Failed to analyze content: ' + data.detail;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
results.classList.remove('hidden');
|
| 37 |
+
} catch (error) {
|
| 38 |
+
console.error('Error:', error);
|
| 39 |
+
resultContent.textContent = 'An error occurred while processing your request.';
|
| 40 |
+
results.classList.remove('hidden');
|
| 41 |
+
} finally {
|
| 42 |
+
loadingIndicator.classList.add('hidden');
|
| 43 |
+
}
|
| 44 |
+
});
|
| 45 |
+
});
|
app/templates/index.html
CHANGED
|
@@ -3,1407 +3,372 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
-
|
| 8 |
-
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 9 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 10 |
<style>
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
.
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
.card-body {
|
| 45 |
-
padding: 1.5rem;
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
.form-control {
|
| 49 |
-
border: 1px solid #dee2e6;
|
| 50 |
-
border-radius: var(--border-radius);
|
| 51 |
-
padding: 0.75rem;
|
| 52 |
-
transition: all 0.3s ease;
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
.form-control:focus {
|
| 56 |
-
border-color: var(--accent-color);
|
| 57 |
-
box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25);
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
.btn-primary {
|
| 61 |
-
background-color: var(--accent-color);
|
| 62 |
-
border: none;
|
| 63 |
-
padding: 0.75rem 1.5rem;
|
| 64 |
-
border-radius: var(--border-radius);
|
| 65 |
-
transition: all 0.3s ease;
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
.btn-primary:hover {
|
| 69 |
-
background-color: #2980b9;
|
| 70 |
-
transform: translateY(-1px);
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
.btn-success {
|
| 74 |
-
background-color: #2ecc71;
|
| 75 |
-
border: none;
|
| 76 |
-
padding: 1rem 2rem;
|
| 77 |
-
border-radius: var(--border-radius);
|
| 78 |
-
font-weight: 500;
|
| 79 |
-
transition: all 0.3s ease;
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
.btn-success:hover {
|
| 83 |
-
background-color: #27ae60;
|
| 84 |
-
transform: translateY(-2px);
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
.table {
|
| 88 |
-
background-color: white;
|
| 89 |
-
border-radius: var(--border-radius);
|
| 90 |
-
overflow: hidden;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
.table-primary {
|
| 94 |
-
background-color: var(--primary-color);
|
| 95 |
-
color: white;
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
.table th {
|
| 99 |
-
font-weight: 500;
|
| 100 |
-
padding: 1rem;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
.table td {
|
| 104 |
-
padding: 0.75rem;
|
| 105 |
-
vertical-align: middle;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
.section-title {
|
| 109 |
-
background-color: var(--secondary-color);
|
| 110 |
-
color: white;
|
| 111 |
-
padding: 1rem;
|
| 112 |
-
margin: 2rem 0 1rem;
|
| 113 |
-
border-radius: var(--border-radius);
|
| 114 |
-
display: flex;
|
| 115 |
-
align-items: center;
|
| 116 |
-
justify-content: space-between;
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
.section-title h4 {
|
| 120 |
-
margin: 0;
|
| 121 |
-
font-weight: 500;
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
.btn-group {
|
| 125 |
-
background-color: white;
|
| 126 |
-
border-radius: var(--border-radius);
|
| 127 |
-
padding: 0.25rem;
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
.btn-check + .btn-outline-primary {
|
| 131 |
-
color: var(--primary-color);
|
| 132 |
-
border-color: var(--primary-color);
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
.btn-check:checked + .btn-outline-primary {
|
| 136 |
-
background-color: var (--primary-color);
|
| 137 |
-
color: white;
|
| 138 |
-
}
|
| 139 |
-
|
| 140 |
-
.modal-content {
|
| 141 |
-
border-radius: var(--border-radius);
|
| 142 |
-
border: none;
|
| 143 |
-
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
.modal-header {
|
| 147 |
-
background-color: var(--primary-color);
|
| 148 |
-
color: white;
|
| 149 |
-
border-radius: var(--border-radius) var (--border-radius) 0 0;
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
.btn-close {
|
| 153 |
-
filter: brightness(0) invert(1);
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
.btn-danger {
|
| 157 |
-
background-color: #e74c3c;
|
| 158 |
-
border: none;
|
| 159 |
-
border-radius: var(--border-radius);
|
| 160 |
-
transition: all 0.3s ease;
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
.btn-danger:hover {
|
| 164 |
-
background-color: #c0392b;
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
/* Responsive adjustments */
|
| 168 |
-
@media (max-width: 768px) {
|
| 169 |
-
.container {
|
| 170 |
-
padding: 1rem;
|
| 171 |
-
}
|
| 172 |
-
|
| 173 |
-
.card-body {
|
| 174 |
-
padding: 1rem;
|
| 175 |
-
}
|
| 176 |
-
|
| 177 |
-
.btn {
|
| 178 |
-
padding: 0.5rem 1rem;
|
| 179 |
-
}
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
.preview-modal .modal-dialog {
|
| 183 |
-
max-width: 80%;
|
| 184 |
-
}
|
| 185 |
-
.status-pending { color: #ffc107; }
|
| 186 |
-
.status-completed { color: #28a745; }
|
| 187 |
-
|
| 188 |
-
header {
|
| 189 |
-
background: linear-gradient(135deg, #0d6efd 0%, #0a58ca 100%);
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
header .btn-light {
|
| 193 |
-
transition: all 0.3s ease;
|
| 194 |
-
border-radius: 20px;
|
| 195 |
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 196 |
-
}
|
| 197 |
-
|
| 198 |
-
header .btn-light:hover {
|
| 199 |
-
transform: translateY(-2px);
|
| 200 |
-
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
header h1 {
|
| 204 |
-
font-size: 2rem;
|
| 205 |
-
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
|
| 206 |
}
|
| 207 |
</style>
|
| 208 |
</head>
|
| 209 |
-
<body class="bg-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
</div>
|
| 217 |
-
<div
|
| 218 |
-
<
|
| 219 |
-
<
|
| 220 |
-
<i class="fas fa-history"></i> Historique des Devis
|
| 221 |
-
</a>
|
| 222 |
</div>
|
| 223 |
-
<div
|
| 224 |
-
<
|
| 225 |
-
|
| 226 |
-
<small class="d-block">Version 1.0</small>
|
| 227 |
-
</div>
|
| 228 |
-
</div>
|
| 229 |
-
</div>
|
| 230 |
-
</div>
|
| 231 |
-
</header>
|
| 232 |
-
|
| 233 |
-
<div class="container">
|
| 234 |
-
<!-- Main Content -->
|
| 235 |
-
<div class="row">
|
| 236 |
-
<div class="col-12">
|
| 237 |
-
<div class="card shadow-sm mb-4">
|
| 238 |
-
<div class="card-body">
|
| 239 |
-
<form id="invoiceForm">
|
| 240 |
-
<!-- Client and Invoice Information -->
|
| 241 |
-
<div class="row mb-4">
|
| 242 |
-
<!-- Client Information Card -->
|
| 243 |
-
<div class="col-md-6">
|
| 244 |
-
<div class="card h-100">
|
| 245 |
-
<div class="card-header d-flex justify-content-between align-items-center">
|
| 246 |
-
<h5 class="mb-0">Information Client</h5>
|
| 247 |
-
<i class="fas fa-user"></i>
|
| 248 |
-
</div>
|
| 249 |
-
<div class="card-body">
|
| 250 |
-
<div class="mb-3">
|
| 251 |
-
<label for="clientName" class="form-label">Nom du Client</label>
|
| 252 |
-
<input type="text" class="form-control" id="clientName" required>
|
| 253 |
-
</div>
|
| 254 |
-
<div class="mb-3">
|
| 255 |
-
<label for="clientPhone" class="form-label">TΓ©lΓ©phone</label>
|
| 256 |
-
<div class="input-group">
|
| 257 |
-
<input type="tel" class="form-control" id="clientPhone" name="phone1" placeholder="Premier numΓ©ro" required>
|
| 258 |
-
<span class="input-group-text">/</span>
|
| 259 |
-
<input type="tel" class="form-control" id="clientPhone2" name="phone2" placeholder="Deuxième numéro (optionnel)">
|
| 260 |
-
</div>
|
| 261 |
-
</div>
|
| 262 |
-
<div class="mb-3">
|
| 263 |
-
<label for="clientAddress" class="form-label">Adresse</label>
|
| 264 |
-
<input type="text" class="form-control" id="clientAddress" required>
|
| 265 |
-
</div>
|
| 266 |
-
<div class="mb-3">
|
| 267 |
-
<label for="plancher" class="form-label">PLANCHER</label>
|
| 268 |
-
<input type="text" class="form-control" id="plancher" placeholder="PH RDC" value="PH RDC">
|
| 269 |
-
</div>
|
| 270 |
-
<div class="mb-3">
|
| 271 |
-
<label class="form-label">Type Client</label>
|
| 272 |
-
<div class="btn-group w-100" role="group">
|
| 273 |
-
<input type="radio" class="btn-check" name="clientType" id="EE" value="EE" autocomplete="off">
|
| 274 |
-
<label class="btn btn-outline-primary" for="EE">EE</label>
|
| 275 |
-
|
| 276 |
-
<input type="radio" class="btn-check" name="clientType" id="MED" value="MED" autocomplete="off">
|
| 277 |
-
<label class="btn btn-outline-primary" for="MED">MED</label>
|
| 278 |
-
|
| 279 |
-
<input type="radio" class="btn-check" name="clientType" id="AM" value="AM" autocomplete="off">
|
| 280 |
-
<label class="btn btn-outline-primary" for="AM">AM</label>
|
| 281 |
-
|
| 282 |
-
<input type="radio" class="btn-check" name="clientType" id="DIV" value="DIV" autocomplete="off">
|
| 283 |
-
<label class="btn btn-outline-primary" for="DIV">DIV</label>
|
| 284 |
-
</div>
|
| 285 |
-
</div>
|
| 286 |
-
<div class="mb-3">
|
| 287 |
-
<label class="form-label">Commercial</label>
|
| 288 |
-
<select class="form-select" id="commercial" required>
|
| 289 |
-
<option value="">SΓ©lectionner un commercial</option>
|
| 290 |
-
<option value="khaled">Khaled</option>
|
| 291 |
-
<option value="salah">Salah</option>
|
| 292 |
-
<option value="ismail">Ismail</option>
|
| 293 |
-
<option value="jamal">Jamal</option>
|
| 294 |
-
<option value="divers">Divers</option>
|
| 295 |
-
</select>
|
| 296 |
-
</div>
|
| 297 |
-
</div>
|
| 298 |
-
</div>
|
| 299 |
-
</div>
|
| 300 |
-
|
| 301 |
-
<!-- Invoice Information Card -->
|
| 302 |
-
<div class="col-md-6">
|
| 303 |
-
<div class="card h-100">
|
| 304 |
-
<div class="card-header d-flex justify-content-between align-items-center">
|
| 305 |
-
<h5 class="mb-0">Information Devis</h5>
|
| 306 |
-
<i class="fas fa-file-invoice"></i>
|
| 307 |
-
</div>
|
| 308 |
-
<div class="card-body">
|
| 309 |
-
<div class="mb-3">
|
| 310 |
-
<label for="invoiceNumber" class="form-label">NΒ° Devis</label>
|
| 311 |
-
<input type="text" class="form-control" id="invoiceNumber" required>
|
| 312 |
-
</div>
|
| 313 |
-
<div class="mb-3">
|
| 314 |
-
<label for="date" class="form-label">Date</label>
|
| 315 |
-
<input type="date" class="form-control" id="date" readonly>
|
| 316 |
-
</div>
|
| 317 |
-
<div class="mb-3">
|
| 318 |
-
<label for="project" class="form-label">Type d'ouvrage</label>
|
| 319 |
-
<input type="text" class="form-control" id="project" required>
|
| 320 |
-
</div>
|
| 321 |
-
</div>
|
| 322 |
-
</div>
|
| 323 |
-
</div>
|
| 324 |
-
</div>
|
| 325 |
-
|
| 326 |
-
<!-- Items Sections -->
|
| 327 |
-
<div class="card shadow-sm mb-4">
|
| 328 |
-
<div class="card-body">
|
| 329 |
-
<!-- Poutrelles Section -->
|
| 330 |
-
<div class="section-title">
|
| 331 |
-
<h4>POUTRELLES</h4>
|
| 332 |
-
<button type="button" class="btn btn-light" id="addPoutrelles">
|
| 333 |
-
<i class="fas fa-plus"></i> Ajouter Poutrelle
|
| 334 |
-
</button>
|
| 335 |
-
</div>
|
| 336 |
-
<div class="table-responsive">
|
| 337 |
-
<table class="table table-bordered table-hover">
|
| 338 |
-
<thead class="table-primary">
|
| 339 |
-
<tr>
|
| 340 |
-
<th style="width: 30%">Description</th>
|
| 341 |
-
<th style="width: 10%">UnitΓ©</th>
|
| 342 |
-
<th style="width: 10%">QuantitΓ©</th>
|
| 343 |
-
<th style="width: 15%">Longueur</th>
|
| 344 |
-
<th style="width: 15%">P.U</th>
|
| 345 |
-
<th style="width: 15%">Total HT</th>
|
| 346 |
-
<th style="width: 5%"></th>
|
| 347 |
-
</tr>
|
| 348 |
-
</thead>
|
| 349 |
-
<tbody id="poutrellesTable"></tbody>
|
| 350 |
-
</table>
|
| 351 |
-
</div>
|
| 352 |
-
|
| 353 |
-
<!-- Hourdis Section -->
|
| 354 |
-
<div class="section-title mt-4">
|
| 355 |
-
<h4>HOURDIS</h4>
|
| 356 |
-
<button type="button" class="btn btn-light" id="addHourdis">
|
| 357 |
-
<i class="fas fa-plus"></i> Ajouter Hourdis
|
| 358 |
-
</button>
|
| 359 |
-
</div>
|
| 360 |
-
<div class="table-responsive">
|
| 361 |
-
<table class="table table-bordered table-hover">
|
| 362 |
-
<thead class="table-primary">
|
| 363 |
-
<tr>
|
| 364 |
-
<th style="width: 30%">Description</th>
|
| 365 |
-
<th style="width: 10%">UnitΓ©</th>
|
| 366 |
-
<th style="width: 10%">QuantitΓ©</th>
|
| 367 |
-
<th style="width: 15%">Longueur</th>
|
| 368 |
-
<th style="width: 15%">P.U</th>
|
| 369 |
-
<th style="width: 15%">Total HT</th>
|
| 370 |
-
<th style="width: 5%"></th>
|
| 371 |
-
</tr>
|
| 372 |
-
</thead>
|
| 373 |
-
<tbody id="hourdisTable"></tbody>
|
| 374 |
-
</table>
|
| 375 |
-
</div>
|
| 376 |
-
|
| 377 |
-
<!-- Panneau Section -->
|
| 378 |
-
<div class="section-title mt-4">
|
| 379 |
-
<h4>PANNEAU TREILLIS SOUDES</h4>
|
| 380 |
-
<button type="button" class="btn btn-light" id="addPanneau">
|
| 381 |
-
<i class="fas fa-plus"></i> Ajouter Panneau
|
| 382 |
-
</button>
|
| 383 |
-
</div>
|
| 384 |
-
<div class="table-responsive">
|
| 385 |
-
<table class="table table-bordered table-hover">
|
| 386 |
-
<thead class="table-primary">
|
| 387 |
-
<tr>
|
| 388 |
-
<th style="width: 30%">Description</th>
|
| 389 |
-
<th style="width: 10%">UnitΓ©</th>
|
| 390 |
-
<th style="width: 10%">QuantitΓ©</th>
|
| 391 |
-
<th style="width: 15%">Longueur</th>
|
| 392 |
-
<th style="width: 15%">P.U</th>
|
| 393 |
-
<th style="width: 15%">Total HT</th>
|
| 394 |
-
<th style="width: 5%"></th>
|
| 395 |
-
</tr>
|
| 396 |
-
</thead>
|
| 397 |
-
<tbody id="panneauTable"></tbody>
|
| 398 |
-
</table>
|
| 399 |
-
</div>
|
| 400 |
-
|
| 401 |
-
<!-- Agglos Section -->
|
| 402 |
-
<div class="section-title mt-4">
|
| 403 |
-
<h4>AGGLOS</h4>
|
| 404 |
-
<button type="button" class="btn btn-light" id="addAgglos">
|
| 405 |
-
<i class="fas fa-plus"></i> Ajouter Agglos
|
| 406 |
-
</button>
|
| 407 |
-
</div>
|
| 408 |
-
<div class="table-responsive">
|
| 409 |
-
<table class="table table-bordered table-hover">
|
| 410 |
-
<thead class="table-primary">
|
| 411 |
-
<tr>
|
| 412 |
-
<th style="width: 30%">Description</th>
|
| 413 |
-
<th style="width: 10%">UnitΓ©</th>
|
| 414 |
-
<th style="width: 10%">QuantitΓ©</th>
|
| 415 |
-
<th style="width: 15%">Longueur</th>
|
| 416 |
-
<th style="width: 15%">P.U</th>
|
| 417 |
-
<th style="width: 15%">Total HT</th>
|
| 418 |
-
<th style="width: 5%"></th>
|
| 419 |
-
</tr>
|
| 420 |
-
</thead>
|
| 421 |
-
<tbody id="agglosTable"></tbody>
|
| 422 |
-
</table>
|
| 423 |
-
</div>
|
| 424 |
-
</div>
|
| 425 |
-
</div>
|
| 426 |
-
|
| 427 |
-
<!-- Totals Section -->
|
| 428 |
-
<div class="row">
|
| 429 |
-
<div class="col-md-6">
|
| 430 |
-
<!-- Empty for spacing -->
|
| 431 |
-
</div>
|
| 432 |
-
<div class="col-md-6">
|
| 433 |
-
<div class="card shadow-sm">
|
| 434 |
-
<div class="card-header">
|
| 435 |
-
<h5 class="mb-0">Totaux</h5>
|
| 436 |
-
</div>
|
| 437 |
-
<div class="card-body">
|
| 438 |
-
<div class="mb-3 form-check form-switch">
|
| 439 |
-
<input class="form-check-input" type="checkbox" id="tvaSwitch" checked>
|
| 440 |
-
<label class="form-check-label" for="tvaSwitch">Appliquer TVA 20%</label>
|
| 441 |
-
</div>
|
| 442 |
-
<div class="mb-3">
|
| 443 |
-
<label for="totalHT" class="form-label">Total HT</label>
|
| 444 |
-
<input type="number" class="form-control" id="totalHT" readonly>
|
| 445 |
-
</div>
|
| 446 |
-
<div class="mb-3">
|
| 447 |
-
<label for="tax" class="form-label">TVA 20%</label>
|
| 448 |
-
<input type="number" class="form-control" id="tax" readonly>
|
| 449 |
-
</div>
|
| 450 |
-
<div class="mb-3">
|
| 451 |
-
<label for="totalTTC" class="form-label">Total TTC</label>
|
| 452 |
-
<input type="number" class="form-control" id="totalTTC" readonly>
|
| 453 |
-
</div>
|
| 454 |
-
</div>
|
| 455 |
-
</div>
|
| 456 |
-
</div>
|
| 457 |
-
</div>
|
| 458 |
-
|
| 459 |
-
<!-- Submit Button -->
|
| 460 |
-
<div class="d-grid gap-2 col-md-6 mx-auto mt-4">
|
| 461 |
-
<div class="row">
|
| 462 |
-
<div class="col-md-6">
|
| 463 |
-
<button type="submit" class="btn btn-success btn-lg w-100">
|
| 464 |
-
<i class="fas fa-file-pdf"></i> GΓ©nΓ©rer PDF
|
| 465 |
-
</button>
|
| 466 |
-
</div>
|
| 467 |
-
<div class="col-md-6">
|
| 468 |
-
<button type="button" class="btn btn-primary btn-lg w-100" id="generateExcel">
|
| 469 |
-
<i class="fas fa-file-excel"></i> GΓ©nΓ©rer Excel
|
| 470 |
-
</button>
|
| 471 |
-
</div>
|
| 472 |
-
</div>
|
| 473 |
-
</div>
|
| 474 |
-
</form>
|
| 475 |
-
</div>
|
| 476 |
</div>
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
<!-- Poutrelles Modal -->
|
| 485 |
-
<div class="modal fade" id="poutrellesModal" tabindex="-1">
|
| 486 |
-
<div class="modal-dialog modal-lg">
|
| 487 |
-
<div class="modal-content">
|
| 488 |
-
<div class="modal-header">
|
| 489 |
-
<h5 class="modal-title">SΓ©lectionner Poutrelle</h5>
|
| 490 |
-
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 491 |
</div>
|
| 492 |
-
<div
|
| 493 |
-
<
|
| 494 |
-
|
| 495 |
-
<thead>
|
| 496 |
-
<tr>
|
| 497 |
-
<th>Description</th>
|
| 498 |
-
<th>UnitΓ©</th>
|
| 499 |
-
<th>QuantitΓ©</th>
|
| 500 |
-
<th>Longueur</th>
|
| 501 |
-
<th>P.U</th>
|
| 502 |
-
<th>Action</th>
|
| 503 |
-
</tr>
|
| 504 |
-
</thead>
|
| 505 |
-
<tbody></tbody>
|
| 506 |
-
</table>
|
| 507 |
-
</div>
|
| 508 |
</div>
|
|
|
|
|
|
|
|
|
|
| 509 |
</div>
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
<div class="modal fade" id="hourdisModal" tabindex="-1">
|
| 515 |
-
<div class="modal-dialog modal-lg">
|
| 516 |
-
<div class="modal-content">
|
| 517 |
-
<div class="modal-header">
|
| 518 |
-
<h5 class="modal-title">SΓ©lectionner Hourdis</h5>
|
| 519 |
-
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 520 |
-
</div>
|
| 521 |
-
<div class="modal-body">
|
| 522 |
-
<div class="table-responsive">
|
| 523 |
-
<table class="table">
|
| 524 |
-
<thead>
|
| 525 |
-
<tr>
|
| 526 |
-
<th>Description</th>
|
| 527 |
-
<th>UnitΓ©</th>
|
| 528 |
-
<th>QuantitΓ©</th>
|
| 529 |
-
<th>Longueur</th>
|
| 530 |
-
<th>P.U</th>
|
| 531 |
-
<th>Action</th>
|
| 532 |
-
</tr>
|
| 533 |
-
</thead>
|
| 534 |
-
<tbody></tbody>
|
| 535 |
-
</table>
|
| 536 |
-
</div>
|
| 537 |
-
</div>
|
| 538 |
</div>
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
<!-- Panneau Modal -->
|
| 543 |
-
<div class="modal fade" id="panneauModal" tabindex="-1">
|
| 544 |
-
<div class="modal-dialog modal-lg">
|
| 545 |
-
<div class="modal-content">
|
| 546 |
-
<div class="modal-header">
|
| 547 |
-
<h5 class="modal-title">SΓ©lectionner Panneau</h5>
|
| 548 |
-
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 549 |
-
</div>
|
| 550 |
-
<div class="modal-body">
|
| 551 |
-
<div class="table-responsive">
|
| 552 |
-
<table class="table">
|
| 553 |
-
<thead>
|
| 554 |
-
<tr>
|
| 555 |
-
<th>Description</th>
|
| 556 |
-
<th>UnitΓ©</th>
|
| 557 |
-
<th>QuantitΓ©</th>
|
| 558 |
-
<th>Longueur</th>
|
| 559 |
-
<th>P.U</th>
|
| 560 |
-
<th>Action</th>
|
| 561 |
-
</tr>
|
| 562 |
-
</thead>
|
| 563 |
-
<tbody></tbody>
|
| 564 |
-
</table>
|
| 565 |
-
</div>
|
| 566 |
-
</div>
|
| 567 |
</div>
|
| 568 |
</div>
|
| 569 |
-
</div>
|
| 570 |
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
<div class="
|
| 575 |
-
<div class="
|
| 576 |
-
|
| 577 |
-
<
|
|
|
|
|
|
|
| 578 |
</div>
|
| 579 |
-
<div class="
|
| 580 |
-
<
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
</thead>
|
| 592 |
-
<tbody></tbody>
|
| 593 |
-
</table>
|
| 594 |
-
</div>
|
| 595 |
</div>
|
| 596 |
</div>
|
| 597 |
</div>
|
| 598 |
-
</div>
|
| 599 |
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
<
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
<button
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
</div>
|
| 612 |
-
<div class="
|
| 613 |
-
<
|
| 614 |
-
<
|
| 615 |
-
<
|
| 616 |
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
| 617 |
</div>
|
|
|
|
| 618 |
</div>
|
| 619 |
</div>
|
| 620 |
-
</div>
|
| 621 |
|
| 622 |
-
|
| 623 |
-
<!-- Bootstrap JS -->
|
| 624 |
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
| 625 |
-
|
| 626 |
<script>
|
| 627 |
-
|
| 628 |
-
const defaultItems = {
|
| 629 |
-
poutrelles: [
|
| 630 |
-
{ description: 'PCP 158B SISMIQUE', unit: 'ML', quantity: 0, length: 0, unit_price: 0 },
|
| 631 |
-
{ description: 'PCP 158B', unit: 'ML', quantity: 0, length: 0, unit_price: 0 },
|
| 632 |
-
{ description: 'PCP 158', unit: 'ML', quantity: 0, length: 0, unit_price: 0 },
|
| 633 |
-
{ description: 'PCP 130', unit: 'ML', quantity: 0, length: 0, unit_price: 0 },
|
| 634 |
-
{ description: 'PCP 110', unit: 'ML', quantity: 0, length: 0, unit_price: 0 }
|
| 635 |
-
],
|
| 636 |
-
hourdis: [
|
| 637 |
-
{ description: 'HOURDIS 13+4', unit: 'U', quantity: 0, length: 0, unit_price: 0 },
|
| 638 |
-
{ description: 'HOURDIS 16+4', unit: 'U', quantity: 0, length: 0, unit_price: 0 },
|
| 639 |
-
{ description: 'HOURDIS 20+4', unit: 'U', quantity: 0, length: 0, unit_price: 0 }
|
| 640 |
-
],
|
| 641 |
-
panneaux: [
|
| 642 |
-
{ description: 'PTS 3.5X2.1', unit: 'U', quantity: 0, length: 0, unit_price: 0 },
|
| 643 |
-
{ description: 'PTS 4.2X2.1', unit: 'U', quantity: 0, length: 0, unit_price: 0 }
|
| 644 |
-
],
|
| 645 |
-
agglos: [
|
| 646 |
-
{ description: 'AGGLOS 15', unit: 'U', quantity: 0, length: 0, unit_price: 0 },
|
| 647 |
-
{ description: 'AGGLOS 20', unit: 'U', quantity: 0, length: 0, unit_price: 0 }
|
| 648 |
-
]
|
| 649 |
-
};
|
| 650 |
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
if (
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
// For other inputs (like length), use decimal step
|
| 659 |
-
const value = parseFloat(input.value);
|
| 660 |
-
if (!isNaN(value)) {
|
| 661 |
-
const nearestStep = Math.round(value / step) * step;
|
| 662 |
-
input.value = nearestStep.toFixed(2);
|
| 663 |
-
}
|
| 664 |
-
}
|
| 665 |
|
| 666 |
-
//
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
updateTotal(input);
|
| 670 |
-
}
|
| 671 |
-
}
|
| 672 |
|
| 673 |
-
|
| 674 |
try {
|
| 675 |
-
|
| 676 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
const unitPriceInput = row.querySelector('input[name="unitPrice"]');
|
| 682 |
-
const totalInput = row.querySelector('input[name="totalHT"]');
|
| 683 |
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
}
|
| 693 |
} catch (error) {
|
| 694 |
-
console.error('Error
|
|
|
|
|
|
|
|
|
|
|
|
|
| 695 |
}
|
| 696 |
}
|
| 697 |
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
const totalHT = Array.from(document.querySelectorAll('input[name="totalHT"]'))
|
| 702 |
-
.reduce((sum, input) => sum + (parseFloat(input.value) || 0), 0);
|
| 703 |
-
|
| 704 |
-
const formattedTotalHT = totalHT.toFixed(2);
|
| 705 |
-
const tvaEnabled = document.querySelector("#tvaSwitch")?.checked;
|
| 706 |
-
|
| 707 |
-
const tax = tvaEnabled ? (totalHT * 0.20).toFixed(2) : "0.00";
|
| 708 |
-
const totalTTC = tvaEnabled ? (totalHT * 1.20).toFixed(2) : formattedTotalHT;
|
| 709 |
-
|
| 710 |
-
const totalHTInput = document.querySelector("#totalHT");
|
| 711 |
-
const taxInput = document.querySelector("#tax");
|
| 712 |
-
const totalTTCInput = document.querySelector("#totalTTC");
|
| 713 |
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 717 |
} catch (error) {
|
| 718 |
-
console.error('Error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
}
|
| 720 |
}
|
| 721 |
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
const agglosTable = document.getElementById("agglosTable");
|
| 727 |
-
const invoiceForm = document.getElementById("invoiceForm");
|
| 728 |
-
const invoiceNumberInput = document.getElementById("invoiceNumber");
|
| 729 |
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
],
|
| 739 |
-
panneaux: [
|
| 740 |
-
{ description: "PTS SISMIQUE 5*3,5", unit: "U", quantity: 0, length: 0, unit_price: 0 }
|
| 741 |
-
]
|
| 742 |
-
};
|
| 743 |
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
|
|
|
|
|
|
|
|
|
| 755 |
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
<p>Voulez-vous remplacer l'existant ou annuler ?</p>
|
| 764 |
-
`;
|
| 765 |
-
|
| 766 |
-
// Handle replace action
|
| 767 |
-
document.getElementById('replaceItem').onclick = () => {
|
| 768 |
-
existingRow.remove();
|
| 769 |
-
insertNewRow(item, table);
|
| 770 |
-
confirmationModal.hide();
|
| 771 |
-
};
|
| 772 |
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
|
|
|
| 778 |
|
| 779 |
-
|
|
|
|
|
|
|
| 780 |
}
|
| 781 |
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
const getNumber = (desc) => {
|
| 787 |
-
const match = desc.match(/\d+/);
|
| 788 |
-
return match ? parseInt(match[0]) : 0;
|
| 789 |
-
};
|
| 790 |
-
|
| 791 |
-
// Helper function to get the type (SISMIQUE or not)
|
| 792 |
-
const getType = (desc) => {
|
| 793 |
-
return desc.includes('SISMIQUE') ? 1 : 0;
|
| 794 |
-
};
|
| 795 |
-
|
| 796 |
-
// Helper function to check if item ends with N
|
| 797 |
-
const endsWithN = (desc) => {
|
| 798 |
-
return desc.trim().endsWith('N') ? 0 : 1; // 0 for N (to sort first), 1 for others
|
| 799 |
-
};
|
| 800 |
-
|
| 801 |
-
const numA = getNumber(a.description);
|
| 802 |
-
const numB = getNumber(b.description);
|
| 803 |
-
const typeA = getType(a.description);
|
| 804 |
-
const typeB = getType(b.description);
|
| 805 |
-
const nEndingA = endsWithN(a.description);
|
| 806 |
-
const nEndingB = endsWithN(b.description);
|
| 807 |
-
|
| 808 |
-
// First sort by N ending
|
| 809 |
-
if (nEndingA !== nEndingB) {
|
| 810 |
-
return nEndingA - nEndingB;
|
| 811 |
-
}
|
| 812 |
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
return typeA - typeB;
|
| 821 |
-
}
|
| 822 |
-
|
| 823 |
-
// Finally sort by length
|
| 824 |
-
return a.length - b.length;
|
| 825 |
});
|
| 826 |
-
}
|
| 827 |
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
// Add new item to array
|
| 840 |
-
existingItems.push(item);
|
| 841 |
-
|
| 842 |
-
// Sort all items
|
| 843 |
-
const sortedItems = sortItemsByDescriptionAndLength(existingItems);
|
| 844 |
-
|
| 845 |
-
// Clear table
|
| 846 |
-
table.innerHTML = '';
|
| 847 |
-
|
| 848 |
-
// Insert all items in sorted order
|
| 849 |
-
sortedItems.forEach(sortedItem => {
|
| 850 |
-
const row = document.createElement('tr');
|
| 851 |
-
const isPoutrelles = sortedItem.description.includes('PCP');
|
| 852 |
-
|
| 853 |
-
let lengthAttrs;
|
| 854 |
-
if (isPoutrelles) {
|
| 855 |
-
lengthAttrs = 'step="0.05" type="number" min="0" oninput="adjustToNearestStep(this, 0.05)"';
|
| 856 |
-
} else {
|
| 857 |
-
lengthAttrs = 'step="1" type="number" min="0" onkeydown="return event.keyCode !== 190" oninput="this.value=Math.floor(this.value)"';
|
| 858 |
-
}
|
| 859 |
-
|
| 860 |
-
const total = ((sortedItem.quantity || 0) * (sortedItem.length || 0) * (sortedItem.unit_price || 0)).toFixed(2);
|
| 861 |
-
|
| 862 |
-
row.innerHTML = `
|
| 863 |
-
<td>
|
| 864 |
-
<input type="text" class="form-control" name="description" value="${sortedItem.description || ''}" readonly>
|
| 865 |
-
</td>
|
| 866 |
-
<td>
|
| 867 |
-
<input type="text" class="form-control" name="unit" value="${sortedItem.unit || ''}" readonly>
|
| 868 |
-
</td>
|
| 869 |
-
<td>
|
| 870 |
-
<input step="1" type="number" min="0" class="form-control" name="quantity"
|
| 871 |
-
value="${sortedItem.quantity || 0}"
|
| 872 |
-
onkeydown="return event.keyCode !== 190 && event.keyCode !== 188"
|
| 873 |
-
oninput="this.value=Math.floor(this.value)"
|
| 874 |
-
onchange="updateTotal(this)">
|
| 875 |
-
</td>
|
| 876 |
-
<td>
|
| 877 |
-
<input ${lengthAttrs} class="form-control" name="length"
|
| 878 |
-
value="${sortedItem.length || 0}"
|
| 879 |
-
onchange="updateTotal(this)">
|
| 880 |
-
</td>
|
| 881 |
-
<td>
|
| 882 |
-
<input type="number" class="form-control" name="unitPrice"
|
| 883 |
-
value="${sortedItem.unit_price || 0}"
|
| 884 |
-
step="0.01" min="0"
|
| 885 |
-
onchange="updateTotal(this)">
|
| 886 |
-
</td>
|
| 887 |
-
<td>
|
| 888 |
-
<input type="number" class="form-control" name="totalHT" value="${total}" readonly>
|
| 889 |
-
</td>
|
| 890 |
-
<td>
|
| 891 |
-
<button type="button" class="btn btn-danger btn-sm" onclick="this.closest('tr').remove(); updateGrandTotal();">
|
| 892 |
-
<i class="fas fa-trash"></i>
|
| 893 |
-
</button>
|
| 894 |
-
</td>
|
| 895 |
-
`;
|
| 896 |
-
|
| 897 |
-
table.appendChild(row);
|
| 898 |
});
|
| 899 |
-
|
| 900 |
-
updateGrandTotal();
|
| 901 |
-
}
|
| 902 |
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
document.querySelector("#totalHT").value = formattedTotalHT;
|
| 915 |
-
document.querySelector("#tax").value = tax;
|
| 916 |
-
document.querySelector("#totalTTC").value = totalTTC;
|
| 917 |
}
|
|
|
|
| 918 |
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
/*
|
| 925 |
-
defaultItems.poutrelles.forEach(item => addItemRow(item, poutrellesTable));
|
| 926 |
-
defaultItems.hourdis.forEach(item => addItemRow(item, hourdisTable));
|
| 927 |
-
defaultItems.panneaux.forEach(item => addItemRow(item, panneauTable));
|
| 928 |
-
*/
|
| 929 |
-
|
| 930 |
-
// Predefined items data - Reset all default values to zero
|
| 931 |
-
const predefinedItems = {
|
| 932 |
-
poutrelles: [
|
| 933 |
-
{ description: "PCP 113N", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 934 |
-
{ description: "PCP 114N", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 935 |
-
{ description: "PCP 113B SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 936 |
-
{ description: "PCP 114B SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 937 |
-
{ description: "PCP 135 SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 938 |
-
{ description: "PCP 156 SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 939 |
-
{ description: "PCP 157 SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 940 |
-
{ description: "PCP 158 SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 },
|
| 941 |
-
{ description: "PCP 158B SISMIQUE", unit: "ML", quantity: 0, length: 0, unit_price: 0 }
|
| 942 |
-
],
|
| 943 |
-
hourdis: [
|
| 944 |
-
{ description: "HOURDIS TYPE 08", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 945 |
-
{ description: "HOURDIS TYPE 12", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 946 |
-
{ description: "HOURDIS TYPE 16", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 947 |
-
{ description: "HOURDIS TYPE 20", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 948 |
-
{ description: "HOURDIS TYPE 25", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 949 |
-
{ description: "HOURDIS TYPE 30", unit: "U", quantity: 0, length: 0, unit_price: 0 }
|
| 950 |
-
],
|
| 951 |
-
panneaux: [
|
| 952 |
-
{ description: "PTS Normal 3,5*3,5", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 953 |
-
{ description: "PTS SISMIQUE 5,0*3,5", unit: "U", quantity: 0, length: 0, unit_price: 0 }
|
| 954 |
-
],
|
| 955 |
-
agglos: [
|
| 956 |
-
{ description: "AGGLOS TYPE 07", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 957 |
-
{ description: "AGGLOS TYPE 10", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 958 |
-
{ description: "AGGLOS TYPE 15", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 959 |
-
{ description: "AGGLOS TYPE 20", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 960 |
-
{ description: "AGGLOS TYPE 25", unit: "U", quantity: 0, length: 0, unit_price: 0 },
|
| 961 |
-
{ description: "AGGLOS A BRANCHER 20", unit: "U", quantity: 0, length: 0, unit_price: 0 }
|
| 962 |
-
]
|
| 963 |
-
};
|
| 964 |
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
|
| 971 |
-
|
|
|
|
|
|
|
| 972 |
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
name="modal-quantity"
|
| 986 |
-
min="0"
|
| 987 |
-
step="1"
|
| 988 |
-
placeholder="QuantitΓ©"
|
| 989 |
-
tabindex="${baseTabIndex}"
|
| 990 |
-
onkeydown="if(event.key === 'Enter') { event.preventDefault(); this.closest('tr').querySelector('[name=modal-length]').focus(); }">
|
| 991 |
-
<div class="invalid-feedback">Veuillez entrer une quantitΓ© valide.</div>
|
| 992 |
-
</td>
|
| 993 |
-
<td>
|
| 994 |
-
<input type="text"
|
| 995 |
-
class="form-control form-control-sm"
|
| 996 |
-
value=""
|
| 997 |
-
name="modal-length"
|
| 998 |
-
placeholder="Longueur"
|
| 999 |
-
tabindex="${baseTabIndex + 1}"
|
| 1000 |
-
onkeydown="if(event.key === 'Enter') { event.preventDefault(); this.closest('tr').querySelector('[name=modal-price]').focus(); }"
|
| 1001 |
-
oninput="this.value = this.value.replace(',', '.');">
|
| 1002 |
-
<div class="invalid-feedback">Veuillez entrer une longueur valide.</div>
|
| 1003 |
-
</td>
|
| 1004 |
-
<td>
|
| 1005 |
-
<input type="text"
|
| 1006 |
-
class="form-control form-control-sm"
|
| 1007 |
-
value=""
|
| 1008 |
-
name="modal-price"
|
| 1009 |
-
placeholder="Prix Unitaire"
|
| 1010 |
-
tabindex="${baseTabIndex + 2}"
|
| 1011 |
-
onkeydown="if(event.key === 'Enter') { event.preventDefault(); this.closest('tr').querySelector('.add-item').click(); }"
|
| 1012 |
-
oninput="this.value = this.value.replace(',', '.');">
|
| 1013 |
-
<div class="invalid-feedback">Veuillez entrer un prix unitaire valide.</div>
|
| 1014 |
-
</td>
|
| 1015 |
-
<td>
|
| 1016 |
-
<button class="btn btn-primary btn-sm add-item" tabindex="${baseTabIndex + 3}">
|
| 1017 |
-
Ajouter
|
| 1018 |
-
</button>
|
| 1019 |
-
</td>
|
| 1020 |
-
`;
|
| 1021 |
-
|
| 1022 |
-
// Add click handler for the add button
|
| 1023 |
-
row.querySelector('.add-item').addEventListener('click', () => {
|
| 1024 |
-
const quantityInput = row.querySelector('[name="modal-quantity"]');
|
| 1025 |
-
const lengthInput = row.querySelector('[name="modal-length"]');
|
| 1026 |
-
const unitPriceInput = row.querySelector('[name="modal-price"]');
|
| 1027 |
-
|
| 1028 |
-
const quantity = parseInt(quantityInput.value) || 0;
|
| 1029 |
-
const length = parseFloat(lengthInput.value.replace(',', '.')) || 0;
|
| 1030 |
-
const unitPrice = parseFloat(unitPriceInput.value.replace(',', '.')) || 0;
|
| 1031 |
-
|
| 1032 |
-
let isValid = true;
|
| 1033 |
-
|
| 1034 |
-
if (quantity === 0) {
|
| 1035 |
-
quantityInput.classList.add('is-invalid');
|
| 1036 |
-
isValid = false;
|
| 1037 |
-
} else {
|
| 1038 |
-
quantityInput.classList.remove('is-invalid');
|
| 1039 |
-
}
|
| 1040 |
-
|
| 1041 |
-
if (length === 0) {
|
| 1042 |
-
lengthInput.classList.add('is-invalid');
|
| 1043 |
-
isValid = false;
|
| 1044 |
-
} else {
|
| 1045 |
-
lengthInput.classList.remove('is-invalid');
|
| 1046 |
-
}
|
| 1047 |
-
|
| 1048 |
-
if (unitPrice === 0) {
|
| 1049 |
-
unitPriceInput.classList.add('is-invalid');
|
| 1050 |
-
isValid = false;
|
| 1051 |
-
} else {
|
| 1052 |
-
unitPriceInput.classList.remove('is-invalid');
|
| 1053 |
-
}
|
| 1054 |
-
|
| 1055 |
-
if (!isValid) {
|
| 1056 |
-
return;
|
| 1057 |
-
}
|
| 1058 |
-
|
| 1059 |
-
const newItem = {
|
| 1060 |
-
...item,
|
| 1061 |
-
quantity: quantity,
|
| 1062 |
-
length: length,
|
| 1063 |
-
unit_price: unitPrice
|
| 1064 |
-
};
|
| 1065 |
-
addItemRow(newItem, targetTable);
|
| 1066 |
-
// The modal will remain open, allowing the user to add more items
|
| 1067 |
-
});
|
| 1068 |
-
|
| 1069 |
-
tbody.appendChild(row);
|
| 1070 |
-
});
|
| 1071 |
-
}
|
| 1072 |
-
|
| 1073 |
-
// Update button click handlers
|
| 1074 |
-
document.querySelector("#addPoutrelles").addEventListener("click", () => {
|
| 1075 |
-
populateModal('#poutrellesModal', predefinedItems.poutrelles, poutrellesTable);
|
| 1076 |
-
new bootstrap.Modal('#poutrellesModal').show();
|
| 1077 |
-
});
|
| 1078 |
-
|
| 1079 |
-
document.querySelector("#addHourdis").addEventListener("click", () => {
|
| 1080 |
-
populateModal('#hourdisModal', predefinedItems.hourdis, hourdisTable);
|
| 1081 |
-
new bootstrap.Modal('#hourdisModal').show();
|
| 1082 |
-
});
|
| 1083 |
-
|
| 1084 |
-
document.querySelector("#addPanneau").addEventListener("click", () => {
|
| 1085 |
-
populateModal('#panneauModal', predefinedItems.panneaux, panneauTable);
|
| 1086 |
-
new bootstrap.Modal('#panneauModal').show();
|
| 1087 |
-
});
|
| 1088 |
-
|
| 1089 |
-
document.querySelector("#addAgglos").addEventListener("click", () => {
|
| 1090 |
-
populateModal('#agglosModal', predefinedItems.agglos, agglosTable);
|
| 1091 |
-
new bootstrap.Modal('#agglosModal').show();
|
| 1092 |
-
});
|
| 1093 |
-
|
| 1094 |
-
// Form submission
|
| 1095 |
-
invoiceForm.addEventListener("submit", async (event) => {
|
| 1096 |
-
event.preventDefault();
|
| 1097 |
-
|
| 1098 |
-
const urlParams = new URLSearchParams(window.location.search);
|
| 1099 |
-
const editId = urlParams.get('edit');
|
| 1100 |
-
|
| 1101 |
-
const getAllItems = () => {
|
| 1102 |
-
const items = [];
|
| 1103 |
-
[poutrellesTable, hourdisTable, panneauTable, agglosTable].forEach(table => {
|
| 1104 |
-
items.push(...Array.from(table.querySelectorAll("tr")).map(row => ({
|
| 1105 |
-
description: row.querySelector("input[name='description']").value,
|
| 1106 |
-
unit: row.querySelector("input[name='unit']").value,
|
| 1107 |
-
quantity: parseInt(row.querySelector("input[name='quantity']").value, 10),
|
| 1108 |
-
length: parseFloat(row.querySelector("input[name='length']").value),
|
| 1109 |
-
unit_price: parseFloat(row.querySelector("input[name='unitPrice']").value),
|
| 1110 |
-
total_price: parseFloat(row.querySelector("input[name='totalHT']").value)
|
| 1111 |
-
})));
|
| 1112 |
-
});
|
| 1113 |
-
return items;
|
| 1114 |
-
};
|
| 1115 |
-
|
| 1116 |
-
const data = {
|
| 1117 |
-
invoice_number: document.querySelector("#invoiceNumber").value,
|
| 1118 |
-
date: new Date(document.querySelector("#date").value).toISOString(),
|
| 1119 |
-
project: document.querySelector("#project").value,
|
| 1120 |
-
client_name: document.querySelector("#clientName").value,
|
| 1121 |
-
phone1: document.querySelector("#clientPhone").value,
|
| 1122 |
-
phone2: document.querySelector("#clientPhone2").value || null,
|
| 1123 |
-
address: document.querySelector("#clientAddress").value,
|
| 1124 |
-
total_ht: parseFloat(document.querySelector("#totalHT").value || 0),
|
| 1125 |
-
tax: parseFloat(document.querySelector("#tax").value || 0),
|
| 1126 |
-
total_ttc: parseFloat(document.querySelector("#totalTTC").value || 0),
|
| 1127 |
-
items: getAllItems(),
|
| 1128 |
-
frame_number: document.querySelector("#plancher").value || "PH RDC",
|
| 1129 |
-
status: "pending",
|
| 1130 |
-
created_at: new Date().toISOString(),
|
| 1131 |
-
commercial: document.querySelector("#commercial").value || "divers"
|
| 1132 |
-
};
|
| 1133 |
-
|
| 1134 |
-
console.log("Sending invoice data:", data);
|
| 1135 |
-
|
| 1136 |
-
try {
|
| 1137 |
-
// Determine if we're creating or updating
|
| 1138 |
-
const url = editId ? `/api/invoices/${editId}` : "/api/invoices/";
|
| 1139 |
-
const method = editId ? "PUT" : "POST";
|
| 1140 |
-
|
| 1141 |
-
const response = await fetch(url, {
|
| 1142 |
-
method: method,
|
| 1143 |
-
headers: { "Content-Type": "application/json" },
|
| 1144 |
-
body: JSON.stringify(data)
|
| 1145 |
-
});
|
| 1146 |
-
|
| 1147 |
-
if (!response.ok) {
|
| 1148 |
-
const errorData = await response.json();
|
| 1149 |
-
console.error("Server error:", errorData);
|
| 1150 |
-
throw new Error(`Failed to ${editId ? 'update' : 'create'} invoice: ${JSON.stringify(errorData)}`);
|
| 1151 |
-
}
|
| 1152 |
-
|
| 1153 |
-
const invoice = await response.json();
|
| 1154 |
-
console.log(editId ? "Updated invoice:" : "Created invoice:", invoice);
|
| 1155 |
-
|
| 1156 |
-
// Generate PDF
|
| 1157 |
-
const pdfResponse = await fetch(`/api/invoices/${invoice.id}/generate-pdf`, {
|
| 1158 |
-
method: "POST",
|
| 1159 |
-
headers: { "Content-Type": "application/json" },
|
| 1160 |
-
body: JSON.stringify(invoice)
|
| 1161 |
-
});
|
| 1162 |
-
|
| 1163 |
-
if (!pdfResponse.ok) {
|
| 1164 |
-
const errorData = await pdfResponse.text();
|
| 1165 |
-
console.error("PDF generation error:", errorData);
|
| 1166 |
-
throw new Error(`Failed to generate PDF: ${errorData}`);
|
| 1167 |
-
}
|
| 1168 |
-
|
| 1169 |
-
// Handle PDF download
|
| 1170 |
-
const blob = await pdfResponse.blob();
|
| 1171 |
-
const downloadUrl = window.URL.createObjectURL(blob);
|
| 1172 |
-
const a = document.createElement("a");
|
| 1173 |
-
a.href = downloadUrl;
|
| 1174 |
-
a.download = `devis_${invoice.invoice_number}.pdf`;
|
| 1175 |
-
document.body.appendChild(a);
|
| 1176 |
-
a.click();
|
| 1177 |
-
document.body.removeChild(a);
|
| 1178 |
-
window.URL.revokeObjectURL(downloadUrl);
|
| 1179 |
-
|
| 1180 |
-
// Update status to success after PDF generation
|
| 1181 |
-
try {
|
| 1182 |
-
const updateStatusResponse = await fetch(`/api/invoices/${invoice.id}/status`, {
|
| 1183 |
-
method: "PUT",
|
| 1184 |
-
headers: { "Content-Type": "application/json" },
|
| 1185 |
-
body: JSON.stringify({ status: "completed" })
|
| 1186 |
-
});
|
| 1187 |
-
|
| 1188 |
-
if (!updateStatusResponse.ok) {
|
| 1189 |
-
console.error("Failed to update status:", await updateStatusResponse.text());
|
| 1190 |
-
} else {
|
| 1191 |
-
console.log("Status updated successfully");
|
| 1192 |
-
}
|
| 1193 |
-
} catch (error) {
|
| 1194 |
-
console.error("Error updating status:", error);
|
| 1195 |
-
}
|
| 1196 |
-
|
| 1197 |
-
// Redirect to history page after successful update
|
| 1198 |
-
if (editId) {
|
| 1199 |
-
window.location.href = '/history';
|
| 1200 |
-
}
|
| 1201 |
-
|
| 1202 |
-
} catch (error) {
|
| 1203 |
-
console.error("Error:", error);
|
| 1204 |
-
alert(`Error: ${error.message}`);
|
| 1205 |
-
}
|
| 1206 |
-
});
|
| 1207 |
-
|
| 1208 |
-
// Set today's date automatically
|
| 1209 |
-
const today = new Date();
|
| 1210 |
-
const formattedDate = today.toISOString().split('T')[0];
|
| 1211 |
-
document.querySelector("#date").value = formattedDate;
|
| 1212 |
-
|
| 1213 |
-
// Function to get and update the invoice number
|
| 1214 |
-
async function updateInvoiceNumber(selectedType) {
|
| 1215 |
-
try {
|
| 1216 |
-
// Disable the input while fetching
|
| 1217 |
-
invoiceNumberInput.disabled = true;
|
| 1218 |
-
|
| 1219 |
-
const response = await fetch(`/api/invoices/last-number/${selectedType}`);
|
| 1220 |
-
|
| 1221 |
-
if (response.ok) {
|
| 1222 |
-
const data = await response.json();
|
| 1223 |
-
invoiceNumberInput.value = data.formatted_number;
|
| 1224 |
-
} else {
|
| 1225 |
-
throw new Error('Failed to get invoice number');
|
| 1226 |
-
}
|
| 1227 |
-
} catch (error) {
|
| 1228 |
-
console.error('Error:', error);
|
| 1229 |
-
alert('Error getting invoice number');
|
| 1230 |
-
} finally {
|
| 1231 |
-
// Re-enable the input
|
| 1232 |
-
invoiceNumberInput.disabled = false;
|
| 1233 |
}
|
| 1234 |
-
}
|
| 1235 |
-
|
| 1236 |
-
// Update invoice number when client type is selected
|
| 1237 |
-
document.querySelectorAll('input[name="clientType"]').forEach(input => {
|
| 1238 |
-
input.addEventListener('change', () => {
|
| 1239 |
-
updateInvoiceNumber(input.value);
|
| 1240 |
-
});
|
| 1241 |
-
});
|
| 1242 |
-
|
| 1243 |
-
// Update the number when the page loads if a type is already selected
|
| 1244 |
-
const selectedType = document.querySelector('input[name="clientType"]:checked')?.value;
|
| 1245 |
-
if (selectedType) {
|
| 1246 |
-
updateInvoiceNumber(selectedType);
|
| 1247 |
-
}
|
| 1248 |
-
|
| 1249 |
-
// Replace the Excel generation handler with this updated version
|
| 1250 |
-
document.getElementById('generateExcel').addEventListener('click', async () => {
|
| 1251 |
-
try {
|
| 1252 |
-
console.log('Starting Excel generation process...');
|
| 1253 |
-
|
| 1254 |
-
// Get all form data
|
| 1255 |
-
const getAllItems = () => {
|
| 1256 |
-
const items = [];
|
| 1257 |
-
[poutrellesTable, hourdisTable, panneauTable, agglosTable].forEach(table => {
|
| 1258 |
-
const tableItems = Array.from(table.querySelectorAll("tr")).map(row => ({
|
| 1259 |
-
description: row.querySelector("input[name='description']").value,
|
| 1260 |
-
unit: row.querySelector("input[name='unit']").value,
|
| 1261 |
-
quantity: parseInt(row.querySelector("input[name='quantity']").value, 10),
|
| 1262 |
-
length: parseFloat(row.querySelector("input[name='length']").value),
|
| 1263 |
-
unit_price: parseFloat(row.querySelector("input[name='unitPrice']").value),
|
| 1264 |
-
total_price: parseFloat(row.querySelector("input[name='totalHT']").value)
|
| 1265 |
-
}));
|
| 1266 |
-
console.log(`Found ${tableItems.length} items in ${table.id}`);
|
| 1267 |
-
items.push(...tableItems);
|
| 1268 |
-
});
|
| 1269 |
-
return items;
|
| 1270 |
-
};
|
| 1271 |
-
|
| 1272 |
-
const data = {
|
| 1273 |
-
invoice_number: document.querySelector("#invoiceNumber").value,
|
| 1274 |
-
date: new Date(document.querySelector("#date").value).toISOString(),
|
| 1275 |
-
project: document.querySelector("#project").value,
|
| 1276 |
-
client_name: document.querySelector("#clientName").value,
|
| 1277 |
-
phone1: document.querySelector("#clientPhone").value,
|
| 1278 |
-
phone2: document.querySelector("#clientPhone2").value || null,
|
| 1279 |
-
address: document.querySelector("#clientAddress").value,
|
| 1280 |
-
total_ht: parseFloat(document.querySelector("#totalHT").value || 0),
|
| 1281 |
-
tax: parseFloat(document.querySelector("#tax").value || 0),
|
| 1282 |
-
total_ttc: parseFloat(document.querySelector("#totalTTC").value || 0),
|
| 1283 |
-
items: getAllItems(),
|
| 1284 |
-
frame_number: document.querySelector("#plancher").value || "PH RDC",
|
| 1285 |
-
status: "pending",
|
| 1286 |
-
created_at: new Date().toISOString(),
|
| 1287 |
-
commercial: document.querySelector("#commercial").value || "divers"
|
| 1288 |
-
};
|
| 1289 |
|
| 1290 |
-
|
| 1291 |
-
|
| 1292 |
-
|
| 1293 |
-
|
| 1294 |
-
|
| 1295 |
-
|
| 1296 |
-
|
| 1297 |
-
|
| 1298 |
-
|
| 1299 |
-
const createResponse = await fetch("/api/invoices/", {
|
| 1300 |
-
method: "POST",
|
| 1301 |
-
headers: { "Content-Type": "application/json" },
|
| 1302 |
-
body: JSON.stringify(data)
|
| 1303 |
-
});
|
| 1304 |
-
|
| 1305 |
-
if (!createResponse.ok) {
|
| 1306 |
-
throw new Error('Failed to create invoice');
|
| 1307 |
-
}
|
| 1308 |
-
|
| 1309 |
-
const invoice = await createResponse.json();
|
| 1310 |
-
console.log("Created invoice:", invoice.id);
|
| 1311 |
-
|
| 1312 |
-
// Then generate the Excel file
|
| 1313 |
-
console.log('Generating Excel file...');
|
| 1314 |
-
const response = await fetch(`/api/invoices/${invoice.id}/generate-excel`, {
|
| 1315 |
-
method: 'POST',
|
| 1316 |
-
headers: { 'Content-Type': 'application/json' },
|
| 1317 |
-
body: JSON.stringify(invoice)
|
| 1318 |
-
});
|
| 1319 |
-
|
| 1320 |
-
if (!response.ok) {
|
| 1321 |
-
throw new Error('Failed to generate Excel file');
|
| 1322 |
-
}
|
| 1323 |
-
|
| 1324 |
-
console.log('Excel file generated successfully');
|
| 1325 |
-
|
| 1326 |
-
// Handle file download
|
| 1327 |
-
const blob = await response.blob();
|
| 1328 |
-
const url = window.URL.createObjectURL(blob);
|
| 1329 |
-
const a = document.createElement('a');
|
| 1330 |
-
a.href = url;
|
| 1331 |
-
a.download = `devis_${invoice.invoice_number}.xlsx`;
|
| 1332 |
-
document.body.appendChild(a);
|
| 1333 |
-
a.click();
|
| 1334 |
-
document.body.removeChild(a);
|
| 1335 |
-
window.URL.revokeObjectURL(url);
|
| 1336 |
-
console.log('Excel file downloaded');
|
| 1337 |
-
|
| 1338 |
-
// Update invoice number after successful generation
|
| 1339 |
-
const selectedType = document.querySelector('input[name="clientType"]:checked')?.value;
|
| 1340 |
-
if (selectedType) {
|
| 1341 |
-
console.log('Updating invoice number...');
|
| 1342 |
-
await updateInvoiceNumber(selectedType);
|
| 1343 |
-
}
|
| 1344 |
-
|
| 1345 |
-
console.log('Excel generation process completed successfully');
|
| 1346 |
-
|
| 1347 |
-
} catch (error) {
|
| 1348 |
-
console.error('Error in Excel generation:', error);
|
| 1349 |
-
alert('Error generating Excel file: ' + error.message);
|
| 1350 |
-
}
|
| 1351 |
});
|
| 1352 |
|
| 1353 |
-
|
| 1354 |
-
|
| 1355 |
-
|
| 1356 |
-
|
| 1357 |
-
if (editId) {
|
| 1358 |
-
console.log('Loading invoice for editing, ID:', editId);
|
| 1359 |
-
fetch(`/api/invoices/${editId}`)
|
| 1360 |
-
.then(response => response.json())
|
| 1361 |
-
.then(invoice => {
|
| 1362 |
-
console.log('Loaded invoice data:', invoice);
|
| 1363 |
-
|
| 1364 |
-
// Populate form fields
|
| 1365 |
-
document.querySelector("#clientName").value = invoice.client_name;
|
| 1366 |
-
document.querySelector("#clientPhone").value = invoice.phone1;
|
| 1367 |
-
document.querySelector("#clientPhone2").value = invoice.phone2;
|
| 1368 |
-
document.querySelector("#clientAddress").value = invoice.address;
|
| 1369 |
-
document.querySelector("#plancher").value = invoice.frame_number || 'PH RDC';
|
| 1370 |
-
document.querySelector("#invoiceNumber").value = invoice.invoice_number;
|
| 1371 |
-
document.querySelector("#date").value = invoice.date.split('T')[0];
|
| 1372 |
-
|
| 1373 |
-
// Clear existing tables first
|
| 1374 |
-
console.log('Clearing existing tables');
|
| 1375 |
-
[poutrellesTable, hourdisTable, panneauTable, agglosTable].forEach(table => {
|
| 1376 |
-
table.innerHTML = '';
|
| 1377 |
-
});
|
| 1378 |
-
|
| 1379 |
-
// Populate items
|
| 1380 |
-
console.log('Populating items:', invoice.items);
|
| 1381 |
-
invoice.items.forEach(item => {
|
| 1382 |
-
console.log('Processing item:', item);
|
| 1383 |
-
if (item.description.includes('PCP')) {
|
| 1384 |
-
console.log('Adding to poutrelles table');
|
| 1385 |
-
addItemRow(item, poutrellesTable);
|
| 1386 |
-
} else if (item.description.includes('HOURDIS')) {
|
| 1387 |
-
console.log('Adding to hourdis table');
|
| 1388 |
-
addItemRow(item, hourdisTable);
|
| 1389 |
-
} else if (item.description.includes('PTS')) {
|
| 1390 |
-
console.log('Adding to panneau table');
|
| 1391 |
-
addItemRow(item, panneauTable);
|
| 1392 |
-
} else {
|
| 1393 |
-
console.log('Adding to agglos table');
|
| 1394 |
-
addItemRow(item, agglosTable);
|
| 1395 |
-
}
|
| 1396 |
-
});
|
| 1397 |
-
})
|
| 1398 |
-
.catch(error => {
|
| 1399 |
-
console.error("Error loading invoice:", error);
|
| 1400 |
-
alert("Error loading invoice data");
|
| 1401 |
-
});
|
| 1402 |
-
}
|
| 1403 |
-
|
| 1404 |
-
// Update current date in header
|
| 1405 |
-
document.getElementById('currentDate').textContent = new Date().toLocaleDateString('fr-FR');
|
| 1406 |
-
});
|
| 1407 |
</script>
|
| 1408 |
</body>
|
| 1409 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>F5 Model Test Interface</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
|
|
|
| 8 |
<style>
|
| 9 |
+
.loading {
|
| 10 |
+
display: none;
|
| 11 |
+
position: relative;
|
| 12 |
+
width: 80px;
|
| 13 |
+
height: 80px;
|
| 14 |
+
margin: 0 auto;
|
| 15 |
+
}
|
| 16 |
+
.loading div {
|
| 17 |
+
position: absolute;
|
| 18 |
+
width: 16px;
|
| 19 |
+
height: 16px;
|
| 20 |
+
border-radius: 50%;
|
| 21 |
+
background: #4B5563;
|
| 22 |
+
animation: loading 1.2s linear infinite;
|
| 23 |
+
}
|
| 24 |
+
.loading div:nth-child(1) {
|
| 25 |
+
top: 8px;
|
| 26 |
+
left: 8px;
|
| 27 |
+
animation-delay: 0s;
|
| 28 |
+
}
|
| 29 |
+
.loading div:nth-child(2) {
|
| 30 |
+
top: 8px;
|
| 31 |
+
left: 32px;
|
| 32 |
+
animation-delay: -0.4s;
|
| 33 |
+
}
|
| 34 |
+
.loading div:nth-child(3) {
|
| 35 |
+
top: 8px;
|
| 36 |
+
left: 56px;
|
| 37 |
+
animation-delay: -0.8s;
|
| 38 |
+
}
|
| 39 |
+
@keyframes loading {
|
| 40 |
+
0%, 100% { opacity: 1; }
|
| 41 |
+
50% { opacity: 0.5; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
}
|
| 43 |
</style>
|
| 44 |
</head>
|
| 45 |
+
<body class="bg-gray-100">
|
| 46 |
+
<div class="container mx-auto px-4 py-8">
|
| 47 |
+
<h1 class="text-3xl font-bold mb-8">F5 Model Test Interface</h1>
|
| 48 |
+
|
| 49 |
+
<!-- Project Plan Generation Section -->
|
| 50 |
+
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
|
| 51 |
+
<h2 class="text-xl font-semibold mb-4">Project Plan Generation</h2>
|
| 52 |
+
<div class="space-y-4">
|
| 53 |
+
<div>
|
| 54 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Project Title</label>
|
| 55 |
+
<input type="text" id="projectTitle" class="w-full border rounded px-3 py-2" placeholder="Enter project title">
|
| 56 |
</div>
|
| 57 |
+
<div>
|
| 58 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Requirements</label>
|
| 59 |
+
<textarea id="planRequirements" class="w-full border rounded px-3 py-2 h-32" placeholder="Enter project requirements"></textarea>
|
|
|
|
|
|
|
| 60 |
</div>
|
| 61 |
+
<div>
|
| 62 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Features (comma-separated)</label>
|
| 63 |
+
<input type="text" id="planFeatures" class="w-full border rounded px-3 py-2" placeholder="Feature 1, Feature 2, Feature 3">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
+
<div>
|
| 66 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Platform</label>
|
| 67 |
+
<select id="platform" class="w-full border rounded px-3 py-2">
|
| 68 |
+
<option value="AWS">AWS</option>
|
| 69 |
+
<option value="Azure">Azure</option>
|
| 70 |
+
<option value="GCP">Google Cloud</option>
|
| 71 |
+
</select>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</div>
|
| 73 |
+
<div>
|
| 74 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Additional Requirements</label>
|
| 75 |
+
<textarea id="additionalRequirements" class="w-full border rounded px-3 py-2 h-24" placeholder="Enter any additional requirements"></textarea>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
</div>
|
| 77 |
+
<button onclick="generatePlan()" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
| 78 |
+
Generate Plan
|
| 79 |
+
</button>
|
| 80 |
</div>
|
| 81 |
+
<div id="planLoading" class="loading mt-4">
|
| 82 |
+
<div></div>
|
| 83 |
+
<div></div>
|
| 84 |
+
<div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
</div>
|
| 86 |
+
<div id="planOutput" class="mt-6 bg-gray-50 rounded p-4 hidden">
|
| 87 |
+
<h3 class="text-lg font-semibold mb-2" id="planTitle"></h3>
|
| 88 |
+
<div id="planSections" class="space-y-4"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
</div>
|
| 90 |
</div>
|
|
|
|
| 91 |
|
| 92 |
+
<!-- Chat Section -->
|
| 93 |
+
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
|
| 94 |
+
<h2 class="text-xl font-semibold mb-4">Chat Test</h2>
|
| 95 |
+
<div class="mb-4">
|
| 96 |
+
<div id="chatHistory" class="bg-gray-50 rounded p-4 h-64 overflow-y-auto mb-4"></div>
|
| 97 |
+
<div id="chatLoading" class="loading mb-4">
|
| 98 |
+
<div></div>
|
| 99 |
+
<div></div>
|
| 100 |
+
<div></div>
|
| 101 |
</div>
|
| 102 |
+
<div class="flex gap-2">
|
| 103 |
+
<input type="text" id="chatInput"
|
| 104 |
+
class="flex-1 border rounded px-3 py-2"
|
| 105 |
+
placeholder="Type your message...">
|
| 106 |
+
<button onclick="sendMessage()"
|
| 107 |
+
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
| 108 |
+
Send
|
| 109 |
+
</button>
|
| 110 |
+
<label class="flex items-center gap-2">
|
| 111 |
+
<input type="checkbox" id="streamToggle">
|
| 112 |
+
Stream
|
| 113 |
+
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
</div>
|
| 115 |
</div>
|
| 116 |
</div>
|
|
|
|
| 117 |
|
| 118 |
+
<!-- Feature Generation Section -->
|
| 119 |
+
<div class="bg-white rounded-lg shadow-md p-6">
|
| 120 |
+
<h2 class="text-xl font-semibold mb-4">Feature Generation Test</h2>
|
| 121 |
+
<div class="mb-4">
|
| 122 |
+
<textarea id="requirementsInput"
|
| 123 |
+
class="w-full border rounded p-3 mb-4 h-32"
|
| 124 |
+
placeholder="Enter requirements..."></textarea>
|
| 125 |
+
<button onclick="generateFeatures()"
|
| 126 |
+
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
| 127 |
+
Generate Features
|
| 128 |
+
</button>
|
| 129 |
</div>
|
| 130 |
+
<div id="featuresLoading" class="loading mb-4">
|
| 131 |
+
<div></div>
|
| 132 |
+
<div></div>
|
| 133 |
+
<div></div>
|
|
|
|
| 134 |
</div>
|
| 135 |
+
<div id="featuresOutput" class="bg-gray-50 rounded p-4 min-h-32"></div>
|
| 136 |
</div>
|
| 137 |
</div>
|
|
|
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
<script>
|
| 140 |
+
let chatHistory = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
+
async function sendMessage() {
|
| 143 |
+
const input = document.getElementById('chatInput');
|
| 144 |
+
const message = input.value.trim();
|
| 145 |
+
if (!message) return;
|
| 146 |
+
|
| 147 |
+
// Show loading indicator
|
| 148 |
+
document.getElementById('chatLoading').style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
+
// Add user message to chat
|
| 151 |
+
appendMessage('user', message);
|
| 152 |
+
input.value = '';
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
+
const streamMode = document.getElementById('streamToggle').checked;
|
| 155 |
try {
|
| 156 |
+
if (streamMode) {
|
| 157 |
+
const response = await fetch('/chat', {
|
| 158 |
+
method: 'POST',
|
| 159 |
+
headers: { 'Content-Type': 'application/json' },
|
| 160 |
+
body: JSON.stringify({
|
| 161 |
+
messages: chatHistory,
|
| 162 |
+
stream: true
|
| 163 |
+
})
|
| 164 |
+
});
|
| 165 |
|
| 166 |
+
const reader = response.body.getReader();
|
| 167 |
+
const decoder = new TextDecoder();
|
| 168 |
+
let assistantMessage = '';
|
|
|
|
|
|
|
| 169 |
|
| 170 |
+
while (true) {
|
| 171 |
+
const { value, done } = await reader.read();
|
| 172 |
+
if (done) break;
|
| 173 |
+
|
| 174 |
+
const chunk = decoder.decode(value);
|
| 175 |
+
const lines = chunk.split('\n');
|
| 176 |
+
for (const line of lines) {
|
| 177 |
+
if (line.startsWith('data: ')) {
|
| 178 |
+
const content = line.slice(6);
|
| 179 |
+
assistantMessage += content;
|
| 180 |
+
updateLastMessage('assistant', assistantMessage);
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
} else {
|
| 185 |
+
const response = await fetch('/chat', {
|
| 186 |
+
method: 'POST',
|
| 187 |
+
headers: { 'Content-Type': 'application/json' },
|
| 188 |
+
body: JSON.stringify({
|
| 189 |
+
messages: chatHistory,
|
| 190 |
+
stream: false
|
| 191 |
+
})
|
| 192 |
+
});
|
| 193 |
+
const data = await response.json();
|
| 194 |
+
appendMessage('assistant', data.response);
|
| 195 |
}
|
| 196 |
} catch (error) {
|
| 197 |
+
console.error('Error:', error);
|
| 198 |
+
appendMessage('system', 'Error: Failed to get response');
|
| 199 |
+
} finally {
|
| 200 |
+
// Hide loading indicator
|
| 201 |
+
document.getElementById('chatLoading').style.display = 'none';
|
| 202 |
}
|
| 203 |
}
|
| 204 |
|
| 205 |
+
async function generateFeatures() {
|
| 206 |
+
const requirements = document.getElementById('requirementsInput').value.trim();
|
| 207 |
+
if (!requirements) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
+
// Show loading indicator
|
| 210 |
+
document.getElementById('featuresLoading').style.display = 'block';
|
| 211 |
+
document.getElementById('featuresOutput').innerHTML = '';
|
| 212 |
+
|
| 213 |
+
try {
|
| 214 |
+
const response = await fetch('/generate-features', {
|
| 215 |
+
method: 'POST',
|
| 216 |
+
headers: { 'Content-Type': 'application/json' },
|
| 217 |
+
body: JSON.stringify({ requirements })
|
| 218 |
+
});
|
| 219 |
+
const data = await response.json();
|
| 220 |
+
displayFeatures(data.features);
|
| 221 |
} catch (error) {
|
| 222 |
+
console.error('Error:', error);
|
| 223 |
+
document.getElementById('featuresOutput').innerHTML =
|
| 224 |
+
'<p class="text-red-500">Error generating features</p>';
|
| 225 |
+
} finally {
|
| 226 |
+
// Hide loading indicator
|
| 227 |
+
document.getElementById('featuresLoading').style.display = 'none';
|
| 228 |
}
|
| 229 |
}
|
| 230 |
|
| 231 |
+
function appendMessage(role, content) {
|
| 232 |
+
chatHistory.push({ role, content });
|
| 233 |
+
updateChatDisplay();
|
| 234 |
+
}
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
+
function updateLastMessage(role, content) {
|
| 237 |
+
if (chatHistory.length > 0 && chatHistory[chatHistory.length - 1].role === role) {
|
| 238 |
+
chatHistory[chatHistory.length - 1].content = content;
|
| 239 |
+
} else {
|
| 240 |
+
chatHistory.push({ role, content });
|
| 241 |
+
}
|
| 242 |
+
updateChatDisplay();
|
| 243 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
+
function updateChatDisplay() {
|
| 246 |
+
const chatDiv = document.getElementById('chatHistory');
|
| 247 |
+
chatDiv.innerHTML = chatHistory.map(msg => `
|
| 248 |
+
<div class="mb-2">
|
| 249 |
+
<strong class="${msg.role === 'user' ? 'text-blue-600' : 'text-green-600'}">
|
| 250 |
+
${msg.role}:
|
| 251 |
+
</strong>
|
| 252 |
+
<span class="ml-2">${msg.content}</span>
|
| 253 |
+
</div>
|
| 254 |
+
`).join('');
|
| 255 |
+
chatDiv.scrollTop = chatDiv.scrollHeight;
|
| 256 |
+
}
|
| 257 |
|
| 258 |
+
function displayFeatures(features) {
|
| 259 |
+
const output = document.getElementById('featuresOutput');
|
| 260 |
+
output.innerHTML = features.map(feature => `
|
| 261 |
+
<div class="mb-4 p-4 bg-white rounded shadow">
|
| 262 |
+
<h3 class="font-semibold text-lg mb-2">${feature.feature}</h3>
|
| 263 |
+
<p class="text-gray-600">${feature.short_description}</p>
|
| 264 |
+
</div>
|
| 265 |
+
`).join('');
|
| 266 |
+
}
|
| 267 |
|
| 268 |
+
// Handle Enter key in chat input
|
| 269 |
+
document.getElementById('chatInput').addEventListener('keypress', function(e) {
|
| 270 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 271 |
+
e.preventDefault();
|
| 272 |
+
sendMessage();
|
| 273 |
+
}
|
| 274 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
+
async function generatePlan() {
|
| 277 |
+
const projectTitle = document.getElementById('projectTitle').value.trim();
|
| 278 |
+
const requirements = document.getElementById('planRequirements').value.trim();
|
| 279 |
+
const features = document.getElementById('planFeatures').value.split(',').map(f => f.trim()).filter(f => f);
|
| 280 |
+
const platform = document.getElementById('platform').value;
|
| 281 |
+
const additionalRequirements = document.getElementById('additionalRequirements').value.trim();
|
| 282 |
|
| 283 |
+
if (!projectTitle || !requirements || features.length === 0) {
|
| 284 |
+
alert('Please fill in all required fields');
|
| 285 |
+
return;
|
| 286 |
}
|
| 287 |
|
| 288 |
+
const loading = document.getElementById('planLoading');
|
| 289 |
+
const output = document.getElementById('planOutput');
|
| 290 |
+
loading.style.display = 'block';
|
| 291 |
+
output.classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
+
try {
|
| 294 |
+
console.log('Sending request with data:', {
|
| 295 |
+
project_title: projectTitle,
|
| 296 |
+
requirements,
|
| 297 |
+
features,
|
| 298 |
+
platform,
|
| 299 |
+
additional_requirements: additionalRequirements
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
});
|
|
|
|
| 301 |
|
| 302 |
+
const response = await fetch('/generate-plan', {
|
| 303 |
+
method: 'POST',
|
| 304 |
+
headers: { 'Content-Type': 'application/json' },
|
| 305 |
+
body: JSON.stringify({
|
| 306 |
+
project_title: projectTitle,
|
| 307 |
+
requirements,
|
| 308 |
+
features,
|
| 309 |
+
platform,
|
| 310 |
+
additional_requirements: additionalRequirements
|
| 311 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
});
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
+
const data = await response.json();
|
| 315 |
+
console.log('Raw API Response:', data);
|
| 316 |
+
console.log('Sections:', data.sections);
|
| 317 |
+
console.log('Raw Content:', data.raw_content);
|
| 318 |
|
| 319 |
+
displayPlan(data);
|
| 320 |
+
} catch (error) {
|
| 321 |
+
console.error('Error generating plan:', error);
|
| 322 |
+
alert('Error generating plan');
|
| 323 |
+
} finally {
|
| 324 |
+
loading.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
| 325 |
}
|
| 326 |
+
}
|
| 327 |
|
| 328 |
+
function displayPlan(data) {
|
| 329 |
+
console.log('Displaying plan data:', data);
|
| 330 |
+
const output = document.getElementById('planOutput');
|
| 331 |
+
const title = document.getElementById('planTitle');
|
| 332 |
+
const sections = document.getElementById('planSections');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
|
| 334 |
+
title.textContent = `Technical Project Plan: ${data.project_title}`;
|
| 335 |
+
console.log('Set title to:', title.textContent);
|
| 336 |
+
|
| 337 |
+
// Clear previous sections
|
| 338 |
+
sections.innerHTML = '';
|
| 339 |
|
| 340 |
+
// Display each section
|
| 341 |
+
Object.entries(data.sections).forEach(([key, content]) => {
|
| 342 |
+
console.log(`Processing section ${key}:`, content);
|
| 343 |
|
| 344 |
+
const sectionTitle = key
|
| 345 |
+
.split('_')
|
| 346 |
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
| 347 |
+
.join(' ');
|
| 348 |
+
|
| 349 |
+
// Format the content based on type
|
| 350 |
+
let formattedContent = content;
|
| 351 |
+
if (typeof content === 'object') {
|
| 352 |
+
console.log(`Section ${key} is an object:`, content);
|
| 353 |
+
formattedContent = Object.entries(content)
|
| 354 |
+
.map(([k, v]) => `<strong>${k}:</strong> ${v}`)
|
| 355 |
+
.join('<br>');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
+
const section = document.createElement('div');
|
| 359 |
+
section.className = 'mb-6 bg-white p-4 rounded shadow';
|
| 360 |
+
section.innerHTML = `
|
| 361 |
+
<h4 class="text-lg font-semibold mb-3 text-blue-600">${sectionTitle}</h4>
|
| 362 |
+
<div class="whitespace-pre-wrap text-gray-700 prose max-w-none">
|
| 363 |
+
${formattedContent}
|
| 364 |
+
</div>
|
| 365 |
+
`;
|
| 366 |
+
sections.appendChild(section);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
});
|
| 368 |
|
| 369 |
+
console.log('Final HTML output:', sections.innerHTML);
|
| 370 |
+
output.classList.remove('hidden');
|
| 371 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
</script>
|
| 373 |
</body>
|
| 374 |
</html>
|
migrations/versions/xxxx_add_invoice_sequence.py
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
"""add invoice sequence and client type
|
| 2 |
-
|
| 3 |
-
Revision ID: xxxx
|
| 4 |
-
Revises: previous_revision_id
|
| 5 |
-
Create Date: 2024-xx-xx
|
| 6 |
-
"""
|
| 7 |
-
from alembic import op
|
| 8 |
-
import sqlalchemy as sa
|
| 9 |
-
|
| 10 |
-
def upgrade():
|
| 11 |
-
# Create sequence for invoice IDs
|
| 12 |
-
op.execute('CREATE SEQUENCE IF NOT EXISTS invoice_id_seq START 1')
|
| 13 |
-
|
| 14 |
-
# Add client_type column if not exists
|
| 15 |
-
op.add_column('invoices', sa.Column('client_type', sa.String(10), nullable=True))
|
| 16 |
-
|
| 17 |
-
# Make invoice_number unique
|
| 18 |
-
op.create_unique_constraint('uq_invoice_number', 'invoices', ['invoice_number'])
|
| 19 |
-
|
| 20 |
-
def downgrade():
|
| 21 |
-
# Remove unique constraint
|
| 22 |
-
op.drop_constraint('uq_invoice_number', 'invoices')
|
| 23 |
-
|
| 24 |
-
# Drop client_type column
|
| 25 |
-
op.drop_column('invoices', 'client_type')
|
| 26 |
-
|
| 27 |
-
# Drop sequence
|
| 28 |
-
op.execute('DROP SEQUENCE IF EXISTS invoice_id_seq')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
re.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Flan-T5 Testing Architecture
|
| 2 |
+
|
| 3 |
+
## Project Structure
|
| 4 |
+
```
|
| 5 |
+
flan-t5-testing/
|
| 6 |
+
βββ backend/
|
| 7 |
+
β βββ app/
|
| 8 |
+
β β βββ main.py # FastAPI application entry point
|
| 9 |
+
β β βββ controllers/
|
| 10 |
+
β β β βββ scraper_controller.py
|
| 11 |
+
β β βββ services/
|
| 12 |
+
β β β βββ scraper_service.py
|
| 13 |
+
β β β βββ flan_t5_service.py
|
| 14 |
+
β β βββ models/
|
| 15 |
+
β β βββ scrape_log.py
|
| 16 |
+
β βββ utils/
|
| 17 |
+
β β βββ text_extractor.py
|
| 18 |
+
β β βββ html_cleaner.py
|
| 19 |
+
β βββ requirements.txt
|
| 20 |
+
βββ frontend/
|
| 21 |
+
βββ src/
|
| 22 |
+
β βββ components/
|
| 23 |
+
β β βββ ScrapingForm.jsx
|
| 24 |
+
β β βββ ResultDisplay.jsx
|
| 25 |
+
β βββ App.jsx
|
| 26 |
+
β βββ main.jsx
|
| 27 |
+
βββ package.json
|