Browse Source

project separation

sebi 1 month ago
parent
commit
3682084b57

.gitignore → backend/.gitignore


+ 0 - 1
Dockerfile

@@ -8,7 +8,6 @@ COPY . .
 
 RUN poetry install
 
-ENV GRADIO_SERVER_NAME="0.0.0.0"
 EXPOSE 7860
 
 CMD ["poetry", "run", "python", "-m", "interface.py"]

+ 1 - 0
README.md

@@ -6,6 +6,7 @@ To run this project, first create a ```.env``` file containing the following var
 - **OPENAI_API_KEY**: a OpenAI API key
 - **HA_URL**: the URL of your HomeAssistant app
 - **HA_API_KEY**: your HomeAssistant API key
+- **DB_URL**: mysql+pymysql://<username>:<password>@<host>:<port>/<database>
 
 Build the Docker image using the following command: 
 ```docker build -t <image_name> .```

+ 88 - 0
backend/app.py

@@ -0,0 +1,88 @@
+import os
+from datetime import timedelta
+from dotenv import load_dotenv
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker, Session
+from fastapi import FastAPI, HTTPException, Depends
+from werkzeug.security import generate_password_hash, check_password_hash
+
+from models.Base import Base
+from models.User import User
+from models.Message import Message
+from models.Conversation import Conversation
+from utils.validators import UserCreate, UserLogin, Token
+from utils.auth import create_access_token
+
+ACCESS_TOKEN_EXPIRE_MINUTES = 30  # Token expiration time
+
+
+load_dotenv()
+
+app = FastAPI()
+engine = create_engine(os.environ["DB_URL"])
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base.metadata.create_all(bind=engine)
+
+
+# Dependency to get the SQLAlchemy session
+def get_db():
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()
+
+
+@app.post("/register", response_model=dict)
+def register(user: UserCreate, db: Session = Depends(get_db)):
+    existing_user = db.query(User).filter(User.email == user.email).first()
+
+    if existing_user:
+        raise HTTPException(
+            status_code=400,
+            detail="The provided email is already taken.",
+        )
+
+    hashed_password = generate_password_hash(user.password)
+    new_user = User(
+        email=user.email,
+        password_hash=hashed_password,
+    )
+
+    db.add(new_user)
+    db.commit()
+    db.refresh(new_user)
+
+    return {"message": "User registered successfully."}
+
+
+@app.post("/login", response_model=Token)
+def login(user: UserLogin, db: Session = Depends(get_db)):
+    # Check if the user exists
+    db_user = db.query(User).filter(User.email == user.email).first()
+    if not db_user or not check_password_hash(db_user.password_hash, user.password):
+        raise HTTPException(status_code=401, detail="Invalid email or password")
+
+    # Create a token
+    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+    access_token = create_access_token(
+        data={
+            "email": db_user.email,
+            "userId": db_user.id,
+        },
+        expires_delta=access_token_expires,
+    )
+
+    return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/chats")
+def chats():
+    pass
+
+
+if __name__ == "__main__":
+    import uvicorn
+
+    uvicorn.run(app)

iva/__init__.py → backend/iva/__init__.py


iva/prompts.py → backend/iva/prompts.py


iva/tools.py → backend/iva/tools.py


+ 4 - 0
backend/models/Base.py

@@ -0,0 +1,4 @@
+from sqlalchemy.ext.declarative import declarative_base
+
+
+Base = declarative_base()

+ 16 - 0
backend/models/Conversation.py

@@ -0,0 +1,16 @@
+from .Base import Base
+from datetime import datetime
+from sqlalchemy.orm import relationship
+from sqlalchemy import Column, Integer, ForeignKey, DateTime, String
+
+
+class Conversation(Base):
+    __tablename__ = "conversations"
+
+    id = Column(Integer, primary_key=True)
+    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+    created_at = Column(DateTime, default=datetime.now)
+    summary = Column(String(50), nullable=False)
+
+    user = relationship("User", back_populates="conversations")
+    messages = relationship("Message", back_populates="conversation")

+ 16 - 0
backend/models/Message.py

@@ -0,0 +1,16 @@
+from .Base import Base
+from datetime import datetime
+from sqlalchemy.orm import relationship
+from sqlalchemy import Column, Integer, ForeignKey, DateTime, Text
+
+
+class Message(Base):
+    __tablename__ = "messages"
+
+    id = Column(Integer, primary_key=True)
+    conversation_id = Column(Integer, ForeignKey("conversations.id"), nullable=False)
+    content = Column(Text, nullable=False)
+    is_user = Column(Integer, nullable=False)  # 1 for user message, 0 for LLM response
+    created_at = Column(DateTime, default=datetime.now)
+
+    conversation = relationship("Conversation", back_populates="messages")

+ 15 - 0
backend/models/User.py

@@ -0,0 +1,15 @@
+from .Base import Base
+from datetime import datetime
+from sqlalchemy.orm import relationship
+from sqlalchemy import Column, Integer, String, DateTime
+
+
+class User(Base):
+    __tablename__ = "users"
+
+    id = Column(Integer, primary_key=True)
+    email = Column(String(100), unique=True, nullable=False)
+    password_hash = Column(String(256), nullable=False)
+    created_at = Column(DateTime, default=datetime.now)
+
+    conversations = relationship("Conversation", back_populates="user")

+ 0 - 0
backend/models/__init__.py


File diff suppressed because it is too large
+ 225 - 982
poetry.lock


+ 7 - 1
pyproject.toml

@@ -12,7 +12,13 @@ langchain = "^0.3.0"
 langchain-openai = "^0.2.0"
 homeassistant-api = "^4.2.2.post1"
 python-dotenv = "^1.0.1"
-gradio = "^4.44.0"
+fastapi = "^0.115.0"
+sqlalchemy = "^2.0.35"
+pymysql = "^1.1.1"
+pydantic = {extras = ["email"], version = "^2.9.2"}
+werkzeug = "^3.0.4"
+python-jose = {extras = ["cryptography"], version = "^3.3.0"}
+uvicorn = "^0.31.0"
 
 [tool.poetry.plugins.dotenv]
 ignore = "false"

+ 0 - 0
backend/utils/__init__.py


+ 15 - 0
backend/utils/auth.py

@@ -0,0 +1,15 @@
+import os
+from jose import JWTError, jwt
+from datetime import datetime, timedelta
+
+
+# Generate token function
+def create_access_token(data: dict, expires_delta: timedelta = None):
+    to_encode = data.copy()
+    if expires_delta:
+        expire = datetime.now() + expires_delta
+    else:
+        expire = datetime.now() + timedelta(minutes=15)
+    to_encode.update({"exp": expire})
+    encoded_jwt = jwt.encode(to_encode, os.environ["SECRET_KEY"], algorithm="HS256")
+    return encoded_jwt

+ 17 - 0
backend/utils/validators.py

@@ -0,0 +1,17 @@
+from pydantic import BaseModel, EmailStr
+
+
+# Pydantic model for user registration
+class UserCreate(BaseModel):
+    email: EmailStr
+    password: str
+
+
+class UserLogin(BaseModel):
+    email: EmailStr
+    password: str
+
+
+class Token(BaseModel):
+    access_token: str
+    token_type: str

+ 21 - 0
frontend/.gitignore

@@ -0,0 +1,21 @@
+node_modules
+
+# Output
+.output
+.vercel
+/.svelte-kit
+/build
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Env
+.env
+.env.*
+!.env.example
+!.env.test
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*

+ 1 - 0
frontend/.npmrc

@@ -0,0 +1 @@
+engine-strict=true

+ 38 - 0
frontend/README.md

@@ -0,0 +1,38 @@
+# create-svelte
+
+Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
+
+## Creating a project
+
+If you're seeing this, you've probably already done this step. Congrats!
+
+```bash
+# create a new project in the current directory
+npm create svelte@latest
+
+# create a new project in my-app
+npm create svelte@latest my-app
+```
+
+## Developing
+
+Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
+
+```bash
+npm run dev
+
+# or start the server and open the app in a new browser tab
+npm run dev -- --open
+```
+
+## Building
+
+To create a production version of your app:
+
+```bash
+npm run build
+```
+
+You can preview the production build with `npm run preview`.
+
+> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

+ 30 - 0
frontend/eslint.config.js

@@ -0,0 +1,30 @@
+import js from '@eslint/js';
+import ts from 'typescript-eslint';
+import svelte from 'eslint-plugin-svelte';
+import globals from 'globals';
+
+/** @type {import('eslint').Linter.Config[]} */
+export default [
+	js.configs.recommended,
+	...ts.configs.recommended,
+	...svelte.configs['flat/recommended'],
+	{
+		languageOptions: {
+			globals: {
+				...globals.browser,
+				...globals.node
+			}
+		}
+	},
+	{
+		files: ['**/*.svelte'],
+		languageOptions: {
+			parserOptions: {
+				parser: ts.parser
+			}
+		}
+	},
+	{
+		ignores: ['build/', '.svelte-kit/', 'dist/']
+	}
+];

File diff suppressed because it is too large
+ 3363 - 0
frontend/package-lock.json


+ 28 - 0
frontend/package.json

@@ -0,0 +1,28 @@
+{
+	"name": "frontend",
+	"version": "0.0.1",
+	"private": true,
+	"scripts": {
+		"dev": "vite dev",
+		"build": "vite build",
+		"preview": "vite preview",
+		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+		"lint": "eslint ."
+	},
+	"devDependencies": {
+		"@sveltejs/adapter-auto": "^3.0.0",
+		"@sveltejs/kit": "^2.0.0",
+		"@sveltejs/vite-plugin-svelte": "^3.0.0",
+		"@types/eslint": "^9.6.0",
+		"eslint": "^9.0.0",
+		"eslint-plugin-svelte": "^2.36.0",
+		"globals": "^15.0.0",
+		"svelte": "^4.2.7",
+		"svelte-check": "^4.0.0",
+		"typescript": "^5.0.0",
+		"typescript-eslint": "^8.0.0",
+		"vite": "^5.0.3"
+	},
+	"type": "module"
+}

+ 13 - 0
frontend/src/app.d.ts

@@ -0,0 +1,13 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+declare global {
+	namespace App {
+		// interface Error {}
+		// interface Locals {}
+		// interface PageData {}
+		// interface PageState {}
+		// interface Platform {}
+	}
+}
+
+export {};

+ 12 - 0
frontend/src/app.html

@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
+		<meta name="viewport" content="width=device-width, initial-scale=1" />
+		%sveltekit.head%
+	</head>
+	<body data-sveltekit-preload-data="hover">
+		<div style="display: contents">%sveltekit.body%</div>
+	</body>
+</html>

+ 1 - 0
frontend/src/lib/index.ts

@@ -0,0 +1 @@
+// place files you want to import through the `$lib` alias in this folder.

+ 2 - 0
frontend/src/routes/+page.svelte

@@ -0,0 +1,2 @@
+<h1>Welcome to SvelteKit</h1>
+<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

BIN
frontend/static/favicon.png


+ 18 - 0
frontend/svelte.config.js

@@ -0,0 +1,18 @@
+import adapter from '@sveltejs/adapter-auto';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+	// Consult https://kit.svelte.dev/docs/integrations#preprocessors
+	// for more information about preprocessors
+	preprocess: vitePreprocess(),
+
+	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
+		adapter: adapter()
+	}
+};
+
+export default config;

+ 19 - 0
frontend/tsconfig.json

@@ -0,0 +1,19 @@
+{
+	"extends": "./.svelte-kit/tsconfig.json",
+	"compilerOptions": {
+		"allowJs": true,
+		"checkJs": true,
+		"esModuleInterop": true,
+		"forceConsistentCasingInFileNames": true,
+		"resolveJsonModule": true,
+		"skipLibCheck": true,
+		"sourceMap": true,
+		"strict": true,
+		"moduleResolution": "bundler"
+	}
+	// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
+	// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
+	//
+	// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
+	// from the referenced tsconfig.json - TypeScript does not merge them in
+}

+ 6 - 0
frontend/vite.config.ts

@@ -0,0 +1,6 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+	plugins: [sveltekit()]
+});

+ 0 - 37
interface.py

@@ -1,37 +0,0 @@
-import gradio as gr
-from iva import IVA
-
-
-def IVA_demo(audio, text):
-    assistant = IVA()
-
-    if audio:
-        print("Am a")
-        transcript = assistant.transcript(audio)
-    else:
-        transcript = text
-
-    query_result = assistant.process_query(transcript)
-    audio_path = assistant.tts(query_result)
-
-    return query_result, audio_path
-
-
-demo = gr.Interface(
-    fn=IVA_demo,
-    inputs=[
-        gr.Audio(
-            type="filepath",
-            sources="microphone",
-            label="Voice input",
-            show_label=True,
-        ),
-        gr.Textbox(info="...or use text input"),
-    ],
-    outputs=[
-        "text",
-        gr.Audio(type="filepath"),
-    ],
-)
-
-demo.launch(share=True)