feat: add GUI
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(python run_tests.py:*)",
|
||||
"Bash(source:*)",
|
||||
"Bash(pytest:*)"
|
||||
"Bash(pytest:*)",
|
||||
"Bash(python:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -2,6 +2,7 @@ import uuid
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Any
|
||||
from sqlmodel import Field, SQLModel, Relationship, JSON, Column
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class StartSessionRequest(SQLModel):
|
||||
@@ -70,4 +71,30 @@ class Session(SQLModel, table=True):
|
||||
transcript: List[dict] = Field(default=[], sa_column=Column(JSON))
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
notes: List["Note"] = Relationship(back_populates="session")
|
||||
notes: List["Note"] = Relationship(back_populates="session")
|
||||
|
||||
|
||||
# Response models for visualization
|
||||
class NoteWithLinks(BaseModel):
|
||||
id: uuid.UUID
|
||||
title: str
|
||||
content: str
|
||||
tags: List[str]
|
||||
created_at: datetime
|
||||
links: List["LinkResponse"] = []
|
||||
|
||||
|
||||
class LinkResponse(BaseModel):
|
||||
id: int
|
||||
source_note_id: uuid.UUID
|
||||
target_note_id: uuid.UUID
|
||||
relationship: str
|
||||
confidence: float = 1.0
|
||||
|
||||
|
||||
class SessionDataResponse(BaseModel):
|
||||
session_id: uuid.UUID
|
||||
status: str
|
||||
notes: List[NoteWithLinks]
|
||||
created_at: datetime
|
||||
summary: str = ""
|
||||
@@ -95,4 +95,10 @@ class LinkRepository:
|
||||
async def get_by_target(self, target_id: uuid.UUID) -> List[Link]:
|
||||
statement = select(Link).where(Link.target_id == target_id)
|
||||
result = await self.session.exec(statement)
|
||||
return result.all()
|
||||
|
||||
async def get_by_session(self, session_id: uuid.UUID) -> List[Link]:
|
||||
"""Get all links for notes in a session."""
|
||||
statement = select(Link).join(Note, Link.source_id == Note.id).where(Note.session_id == session_id)
|
||||
result = await self.session.exec(statement)
|
||||
return result.all()
|
||||
50
app/main.py
50
app/main.py
@@ -5,6 +5,7 @@ from typing import Annotated
|
||||
|
||||
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from app.core.config import settings
|
||||
from app.data.database import init_db
|
||||
@@ -12,7 +13,8 @@ from app.data.models import (
|
||||
StartSessionRequest,
|
||||
SendMessageRequest,
|
||||
SessionResponse,
|
||||
SessionStatusResponse
|
||||
SessionStatusResponse,
|
||||
SessionDataResponse
|
||||
)
|
||||
from app.services.session_service import SessionService
|
||||
|
||||
@@ -159,6 +161,37 @@ async def get_session_status(
|
||||
raise HTTPException(status_code=500, detail="Failed to get session status")
|
||||
|
||||
|
||||
@app.get("/sessions/getData", response_model=SessionDataResponse)
|
||||
async def get_session_data(
|
||||
session_id: uuid.UUID,
|
||||
session_service: Annotated[SessionService, Depends(get_session_service)]
|
||||
) -> SessionDataResponse:
|
||||
"""Get session data with notes and links for visualization.
|
||||
|
||||
Args:
|
||||
session_id: UUID of the session
|
||||
session_service: Injected SessionService instance
|
||||
|
||||
Returns:
|
||||
SessionDataResponse with notes and their links
|
||||
"""
|
||||
try:
|
||||
result = await session_service.get_session_data(session_id)
|
||||
return SessionDataResponse(
|
||||
session_id=result["session_id"],
|
||||
status=result["status"],
|
||||
notes=result["notes"],
|
||||
created_at=result["created_at"],
|
||||
summary=result["summary"]
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error(f"Session not found: {e}")
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting session data: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get session data")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint."""
|
||||
@@ -167,13 +200,14 @@ async def health_check():
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Root endpoint with API information."""
|
||||
return {
|
||||
"message": "Welcome to SkyTalk API",
|
||||
"description": "AI-powered conversational idea exploration and knowledge synthesis",
|
||||
"version": "1.0.0",
|
||||
"docs": "/docs"
|
||||
}
|
||||
"""Serve the GUI interface at root."""
|
||||
return FileResponse("gui.html")
|
||||
|
||||
|
||||
@app.get("/session/{session_id}")
|
||||
async def view_session(session_id: uuid.UUID):
|
||||
"""Serve the GUI with a specific session loaded."""
|
||||
return FileResponse("gui.html")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -202,6 +202,66 @@ class SessionService:
|
||||
"created_at": session.created_at
|
||||
}
|
||||
|
||||
async def get_session_data(self, session_id: uuid.UUID) -> Dict[str, any]:
|
||||
"""Get session data with notes and links for visualization.
|
||||
|
||||
Args:
|
||||
session_id: UUID of the session
|
||||
|
||||
Returns:
|
||||
Dictionary containing session data with notes and links
|
||||
"""
|
||||
from app.data.models import NoteWithLinks, LinkResponse
|
||||
|
||||
async with get_session() as db:
|
||||
session_repo = SessionRepository(db)
|
||||
note_repo = NoteRepository(db)
|
||||
link_repo = LinkRepository(db)
|
||||
|
||||
session = await session_repo.get(session_id)
|
||||
if not session:
|
||||
raise ValueError(f"Session {session_id} not found")
|
||||
|
||||
notes = await note_repo.get_by_session(session_id)
|
||||
links = await link_repo.get_by_session(session_id)
|
||||
|
||||
# Create a map of note_id -> links for that note
|
||||
links_by_source = {}
|
||||
for link in links:
|
||||
if link.source_id not in links_by_source:
|
||||
links_by_source[link.source_id] = []
|
||||
links_by_source[link.source_id].append(LinkResponse(
|
||||
id=link.id,
|
||||
source_note_id=link.source_id,
|
||||
target_note_id=link.target_id,
|
||||
relationship=link.context,
|
||||
confidence=1.0 # Default confidence since we don't store it
|
||||
))
|
||||
|
||||
# Create NoteWithLinks objects
|
||||
notes_with_links = []
|
||||
for note in notes:
|
||||
note_links = links_by_source.get(note.id, [])
|
||||
notes_with_links.append(NoteWithLinks(
|
||||
id=note.id,
|
||||
title=note.title,
|
||||
content=note.content,
|
||||
tags=note.tags,
|
||||
created_at=note.created_at,
|
||||
links=note_links
|
||||
))
|
||||
|
||||
# Generate session summary
|
||||
summary = await self._generate_session_summary(notes)
|
||||
|
||||
return {
|
||||
"session_id": session.id,
|
||||
"status": session.status,
|
||||
"notes": notes_with_links,
|
||||
"created_at": session.created_at,
|
||||
"summary": summary
|
||||
}
|
||||
|
||||
async def _create_links_for_notes(self, notes: List[Note], link_repo: LinkRepository) -> None:
|
||||
"""Create semantic links for a list of new notes."""
|
||||
for note in notes:
|
||||
@@ -243,4 +303,30 @@ class SessionService:
|
||||
logger.info(f"Created {len(links_to_create)} links for note {note.id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating links for note {note.id}: {e}")
|
||||
logger.error(f"Error creating links for note {note.id}: {e}")
|
||||
|
||||
async def _generate_session_summary(self, notes: List[Note]) -> str:
|
||||
"""Generate a high-level summary of the session's notes."""
|
||||
if not notes:
|
||||
return "No notes available for this session."
|
||||
|
||||
try:
|
||||
# Create summary of all note titles and key themes
|
||||
note_summaries = []
|
||||
all_tags = set()
|
||||
|
||||
for note in notes:
|
||||
note_summaries.append(f"• {note.title}")
|
||||
all_tags.update(note.tags)
|
||||
|
||||
summary_text = f"This session explored {len(notes)} key concepts:\n\n"
|
||||
summary_text += "\n".join(note_summaries)
|
||||
|
||||
if all_tags:
|
||||
summary_text += f"\n\nKey themes: {', '.join(sorted(all_tags))}"
|
||||
|
||||
return summary_text
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating session summary: {e}")
|
||||
return f"Session contains {len(notes)} synthesized notes on various topics."
|
||||
87
tmp.txt
Normal file
87
tmp.txt
Normal file
@@ -0,0 +1,87 @@
|
||||
You
|
||||
copyright policy and its impact on society
|
||||
AI
|
||||
AI Interviewer
|
||||
That's a very broad and important topic. To begin, what specific aspects of copyright policy's impact on society are you most interested in exploring?
|
||||
U
|
||||
You
|
||||
let's talk about the ways it actually hurts artists
|
||||
AI
|
||||
AI Interviewer
|
||||
That's an interesting perspective. Could you elaborate on some of the ways you believe copyright policy might actually harm artists, rather than protect them?
|
||||
U
|
||||
You
|
||||
copyright is predicated on the idea that remedies will occur through the legal system rather than through social reward/punishment; as society currently stands, the former biases towards entities with access to better lawyers, which are almost always those who are already making a lot of money on their art because it's a nonlinear distribution of success. the latter would allow the art from the most popular artists to have spinoffs more easily (e.g. song remixes, legal public performances, clip shows, etc) that would benefit smaller performers
|
||||
AI
|
||||
AI Interviewer
|
||||
That's a clear articulation of the challenge with the legal system's bias. Could you elaborate on what "social reward/punishment" would look like in practice, and how it would function to incentivize creators and regulate the use of their work without relying on legal remedies?
|
||||
U
|
||||
You
|
||||
something like cc-by-sa -- attribution would be the legal requirement and the real stick would be the social cost that came with not giving someone credit. this is how, for instance, recipes work -- due to essentially the randomness of case law, recipes aren't protected by copyright, so recipe creators are shamed into not ripping off someone else's recipe entirely without credit, and it seems to function well there
|
||||
|
||||
|
||||
You
|
||||
I want to explore the topic: copyright policy and its impact on society
|
||||
AI
|
||||
AI Interviewer
|
||||
That's a very broad and important topic. To start, what aspect of copyright policy's impact on society do you find most compelling or problematic, and why?
|
||||
U
|
||||
You
|
||||
let's talk about the ways it actually hurts artists
|
||||
AI
|
||||
AI Interviewer
|
||||
That's an interesting direction. To begin, could you describe one or two specific ways you believe current copyright policy harms artists?
|
||||
U
|
||||
You
|
||||
copyright is predicated on the idea that remedies will occur through the legal system rather than through social reward/punishment; as society currently stands, the former biases towards entities with access to better lawyers, which are almost always those who are already making a lot of money on their art because it's a nonlinear distribution of success. the latter would allow the art from the most popular artists to have spinoffs more easily (e.g. song remixes, legal public performances, clip shows, etc) that would benefit smaller performers
|
||||
AI
|
||||
AI Interviewer
|
||||
You've articulated a clear distinction between legal and social enforcement mechanisms. Could you elaborate on how the current legal system, by favoring those with access to better lawyers, specifically *hinders* the ability of less-resourced artists to protect or leverage their own creative work?
|
||||
U
|
||||
You
|
||||
I mean that's definitionally true isn't it? if they have fewer resources to defend their art, they will not be able to defend it as well
|
||||
AI
|
||||
AI Interviewer
|
||||
That's a fair point about the general principle. Could you give an example of a specific scenario where a less-resourced artist's inability to defend their work, due to legal costs or complexity, directly impacts their career or creative output?
|
||||
U
|
||||
You
|
||||
hm let's talk about how attribution would have more benefits to artists
|
||||
AI
|
||||
AI Interviewer
|
||||
That's an interesting pivot to attribution. Could you elaborate on how increased attribution, perhaps in a system emphasizing social reward, would specifically offer more benefits to artists than the current legal framework?
|
||||
U
|
||||
You
|
||||
because it's more accessible to smaller artists and there are more eyes on the bigger ones so the social costs of ripping off small artists would be higher if we didn't have the legal punishment as a social crutch
|
||||
AI
|
||||
AI Interviewer
|
||||
You're suggesting that the existence of legal punishment might actually *reduce* the social pressure to attribute or avoid infringement. Could you explain how the current legal framework acts as a "social crutch" in this context, effectively diminishing the social costs of appropriation?
|
||||
U
|
||||
You
|
||||
because people view the "way to protect small artists" as "they should do it themselves and sue" so they're less attuned to ripoffs and therefore there aren't as many organized boycotts of ripoffs in music/film whereas that's more common in industries which, due to the randomness of case law, aren't protected by copyright like recipes or fashion
|
||||
AI
|
||||
AI Interviewer
|
||||
You're suggesting that the perceived availability of legal recourse shifts the responsibility for enforcement away from the community and onto the individual artist. How does this shift in perceived responsibility specifically lead to the public being "less attuned to ripoffs" in copyright-protected fields like music and film?
|
||||
U
|
||||
You
|
||||
because they don't view it as their responsibility to boycott/police
|
||||
AI
|
||||
AI Interviewer
|
||||
So, if the public doesn't view it as their responsibility to boycott or police, what *does* influence their perception of responsibility in these areas?
|
||||
U
|
||||
You
|
||||
the copyright system as it currently exists, therefore alleviating them of their social duty to hold artists to an attributive norm
|
||||
AI
|
||||
AI Interviewer
|
||||
You're suggesting that the existence of the copyright system itself, by its very nature, absolves the public of this social duty. Could you elaborate on how the *design* or *operation* of the current copyright system specifically communicates this message to the public, leading them to feel less responsible for enforcing attributive norms?
|
||||
U
|
||||
You
|
||||
well the copyright lobby has spent the last hundred years convincing society (through ad campaigns etc) that copyright is the solution and pirates are the problem, when it's stifling creativity from smaller artists because they're afraid of being sued even if they would eventually win in court because even a lawsuit would be too expensive so they don't do things like remix or clip or whatever
|
||||
AI
|
||||
AI Interviewer
|
||||
You're suggesting that the message from the copyright lobby has framed the issue as "copyright is the solution and pirates are the problem." How does this specific framing, where "pirates" are the primary concern, lead the public to be less attuned to or less willing to act on instances where attribution is lacking, or where established artists might be "ripping off" smaller ones, rather than focusing on "piracy"?
|
||||
U
|
||||
You
|
||||
okay let's wrap this up
|
||||
AI
|
||||
AI Interviewer
|
||||
Thank you for this thoughtful conversation! I believe we've explored your ideas thoroughly.
|
||||
Reference in New Issue
Block a user