Skip to content

Commit 37de337

Browse files
committed
Works
1 parent e2845c3 commit 37de337

File tree

12 files changed

+968
-5
lines changed

12 files changed

+968
-5
lines changed

dev/NS-core/nova/config/settings.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from pydantic_settings import BaseSettings
2+
from functools import lru_cache
3+
4+
class Settings(BaseSettings):
5+
# API Keys
6+
openai_api_key: str
7+
8+
# Database
9+
database_url: str = "sqlite+aiosqlite:///./nova.db"
10+
11+
# LLM Settings
12+
ollama_host: str = "http://localhost:11434"
13+
default_model: str = "gpt-4"
14+
default_temperature: float = 0.7
15+
16+
# Application Settings
17+
debug: bool = False
18+
log_level: str = "INFO"
19+
20+
class Config:
21+
env_file = ".env"
22+
env_file_encoding = "utf-8"
23+
24+
@lru_cache()
25+
def get_settings() -> Settings:
26+
return Settings()

dev/NS-core/nova/database/crud.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from typing import List, Optional, Dict, Any
2+
from sqlalchemy import select
3+
from sqlalchemy.ext.asyncio import AsyncSession
4+
from .models import User, Session, Message
5+
import logging
6+
from datetime import datetime
7+
8+
logger = logging.getLogger(__name__)
9+
10+
# User operations
11+
async def create_user(db: AsyncSession, user_id: str, username: str):
12+
"""Create a new user"""
13+
db_user = User(
14+
id=user_id,
15+
username=username,
16+
created_at=datetime.utcnow(),
17+
meta_data={}
18+
)
19+
db.add(db_user)
20+
await db.commit()
21+
await db.refresh(db_user)
22+
return db_user
23+
24+
async def get_user(db: AsyncSession, user_id: str):
25+
"""Get a user by ID"""
26+
result = await db.execute(select(User).filter(User.id == user_id))
27+
return result.scalar_one_or_none()
28+
29+
# Session operations
30+
async def create_session(db: AsyncSession, session_id: str, user_id: str):
31+
"""Create a new chat session"""
32+
db_session = Session(
33+
id=session_id,
34+
user_id=user_id,
35+
status="active",
36+
start_time=datetime.utcnow(),
37+
last_activity=datetime.utcnow(),
38+
meta_data={}
39+
)
40+
db.add(db_session)
41+
await db.commit()
42+
await db.refresh(db_session)
43+
return db_session
44+
45+
async def get_session(db: AsyncSession, session_id: str):
46+
"""Get a session by ID"""
47+
result = await db.execute(select(Session).filter(Session.id == session_id))
48+
return result.scalar_one_or_none()
49+
50+
async def update_session_status(db: AsyncSession, session_id: str, status: str):
51+
"""Update a session's status"""
52+
session = await get_session(db, session_id)
53+
if session:
54+
session.status = status
55+
session.last_activity = datetime.utcnow()
56+
await db.commit()
57+
return session
58+
59+
# Message operations
60+
async def create_message(
61+
db: AsyncSession,
62+
message_id: str,
63+
session_id: str,
64+
role: str,
65+
content: str,
66+
model: str = None,
67+
tokens_used: int = None
68+
) -> Message:
69+
try:
70+
message = Message(
71+
id=message_id,
72+
session_id=session_id,
73+
role=role,
74+
content=content,
75+
model=model,
76+
tokens_used=tokens_used
77+
)
78+
db.add(message)
79+
await db.commit()
80+
await db.refresh(message)
81+
return message
82+
except Exception as e:
83+
logger.error(f"Failed to create message: {str(e)}")
84+
await db.rollback()
85+
raise
86+
87+
async def get_session_messages(db: AsyncSession, session_id: str) -> List[Message]:
88+
try:
89+
result = await db.execute(
90+
select(Message)
91+
.where(Message.session_id == session_id)
92+
.order_by(Message.timestamp)
93+
)
94+
return result.scalars().all()
95+
except Exception as e:
96+
logger.error(f"Failed to get session messages: {str(e)}")
97+
raise

dev/NS-core/nova/database/database.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
2+
from sqlalchemy.orm import sessionmaker, declarative_base
3+
from sqlalchemy.pool import StaticPool
4+
import os
5+
6+
# Create async engine
7+
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite+aiosqlite:///./nova.db")
8+
engine = create_async_engine(
9+
DATABASE_URL,
10+
connect_args={"check_same_thread": False},
11+
poolclass=StaticPool,
12+
echo=True
13+
)
14+
15+
# Create async session factory
16+
async_session = sessionmaker(
17+
engine,
18+
class_=AsyncSession,
19+
expire_on_commit=False
20+
)
21+
22+
# Create declarative base
23+
Base = declarative_base()
24+
25+
async def init_db():
26+
"""Initialize the database"""
27+
async with engine.begin() as conn:
28+
await conn.run_sync(Base.metadata.create_all)
29+
30+
async def get_db():
31+
"""Dependency to get database session"""
32+
async with async_session() as session:
33+
try:
34+
yield session
35+
finally:
36+
await session.close()

dev/NS-core/nova/database/models.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from datetime import datetime
2+
from typing import Optional, Dict
3+
from sqlalchemy import Column, String, DateTime, Text, Integer, ForeignKey, JSON
4+
from sqlalchemy.orm import relationship
5+
from .database import Base
6+
7+
class User(Base):
8+
__tablename__ = "users"
9+
10+
id = Column(String, primary_key=True)
11+
username = Column(String, unique=True, index=True)
12+
created_at = Column(DateTime, default=datetime.utcnow)
13+
meta_data = Column(JSON, nullable=True)
14+
15+
# Relationship
16+
sessions = relationship("Session", back_populates="user")
17+
18+
class Session(Base):
19+
__tablename__ = "sessions"
20+
21+
id = Column(String, primary_key=True)
22+
user_id = Column(String, ForeignKey("users.id"), nullable=False)
23+
start_time = Column(DateTime, default=datetime.utcnow)
24+
end_time = Column(DateTime, nullable=True)
25+
status = Column(String, nullable=False) # active/completed/error
26+
last_activity = Column(DateTime, default=datetime.utcnow)
27+
meta_data = Column(JSON, nullable=True)
28+
29+
# Relationships
30+
user = relationship("User", back_populates="sessions")
31+
messages = relationship("Message", back_populates="session")
32+
33+
class Message(Base):
34+
__tablename__ = "messages"
35+
36+
id = Column(String, primary_key=True)
37+
session_id = Column(String, ForeignKey("sessions.id"), nullable=False)
38+
role = Column(String, nullable=False) # user/assistant/system
39+
content = Column(Text, nullable=False)
40+
timestamp = Column(DateTime, default=datetime.utcnow)
41+
tokens_used = Column(Integer, nullable=True)
42+
model = Column(String, nullable=True)
43+
meta_data = Column(JSON, nullable=True)
44+
45+
# Relationship
46+
session = relationship("Session", back_populates="messages")

dev/NS-core/nova/main.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from fastapi import FastAPI
2+
from fastapi.middleware.cors import CORSMiddleware
3+
from fastapi.staticfiles import StaticFiles
4+
from fastapi.templating import Jinja2Templates
5+
from fastapi.responses import HTMLResponse
6+
from fastapi.requests import Request
7+
from typing import Dict, List
8+
import os
9+
from pydantic import BaseModel
10+
11+
from nova.services.llm_service import LLMService
12+
13+
# Initialize FastAPI app
14+
app = FastAPI(title="NovaSystem LITE")
15+
16+
# Add CORS middleware
17+
app.add_middleware(
18+
CORSMiddleware,
19+
allow_origins=["*"],
20+
allow_methods=["*"],
21+
allow_headers=["*"],
22+
)
23+
24+
# Mount static files and templates
25+
static_dir = os.path.join(os.path.dirname(__file__), "static")
26+
templates_dir = os.path.join(os.path.dirname(__file__), "templates")
27+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
28+
templates = Jinja2Templates(directory=templates_dir)
29+
30+
# Initialize LLM service
31+
llm_service = LLMService()
32+
33+
@app.get("/", response_class=HTMLResponse)
34+
async def root(request: Request):
35+
"""Serve the chat interface"""
36+
return templates.TemplateResponse("index.html", {"request": request})
37+
38+
class MessageRequest(BaseModel):
39+
messages: List[Dict[str, str]]
40+
model: str = "gpt4o" # Default to gpt4o
41+
42+
@app.post("/chat")
43+
async def chat(request: MessageRequest) -> Dict[str, str]:
44+
"""Get a response from the selected model"""
45+
response = await llm_service.get_completion(
46+
messages=request.messages,
47+
model=request.model
48+
)
49+
return {"response": response}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from typing import List, Dict, Union, Any
2+
import os
3+
from openai import AsyncOpenAI
4+
from dotenv import load_dotenv
5+
import ollama
6+
import httpx
7+
import asyncio
8+
9+
# Load environment variables
10+
load_dotenv()
11+
12+
class LLMService:
13+
def __init__(self):
14+
self.openai_client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
15+
self.ollama_client = ollama.AsyncClient(host="http://localhost:11434")
16+
17+
async def get_completion(self, messages: List[Dict[str, Any]], model: str = "gpt-4o") -> str:
18+
"""Get completion from either OpenAI or Ollama"""
19+
try:
20+
if model.startswith("gpt"):
21+
# OpenAI API call with exact format
22+
response = await self.openai_client.chat.completions.create(
23+
model="gpt-4o", # Use gpt-4o exactly as requested
24+
messages=messages
25+
)
26+
return response.choices[0].message.content
27+
28+
else: # Ollama models
29+
try:
30+
# Convert OpenAI format to Ollama format
31+
ollama_messages = []
32+
for msg in messages:
33+
content = msg.get("content", [])
34+
if isinstance(content, list):
35+
# Extract text from content array
36+
text = " ".join(item.get("text", "") for item in content if item.get("type") == "text")
37+
else:
38+
text = str(content)
39+
ollama_messages.append({"role": msg["role"], "content": text})
40+
41+
# Ollama API call
42+
response = await self.ollama_client.chat(
43+
model=model,
44+
messages=ollama_messages
45+
)
46+
return response['message']['content']
47+
except Exception as e:
48+
if "model not found" in str(e).lower():
49+
return f"Error: Please run 'ollama pull {model}' first to download the model."
50+
elif "connection refused" in str(e).lower():
51+
return "Error: Cannot connect to Ollama. Is it running? Start with 'ollama serve'"
52+
else:
53+
return f"Ollama Error: {str(e)}"
54+
55+
except Exception as e:
56+
if isinstance(e, httpx.ReadTimeout):
57+
return "Error: Request timed out. Please try again."
58+
return f"Error: {str(e)}"

0 commit comments

Comments
 (0)