File size: 4,894 Bytes
ca27728
 
 
bef92dd
ca27728
 
 
bef92dd
ca27728
9fd9d5e
ca27728
 
 
 
 
 
 
 
 
9fd9d5e
 
 
 
 
 
 
 
 
 
 
ca27728
 
 
 
 
 
9fd9d5e
ca27728
 
 
 
 
 
 
 
 
 
 
 
dd37c03
9fd9d5e
b6fca69
 
 
 
dd37c03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9fd9d5e
ca27728
dd37c03
 
ca27728
 
 
9fd9d5e
 
ca27728
 
b3f8be2
ca27728
b3f8be2
9fd9d5e
b3f8be2
9fd9d5e
ca27728
 
9fd9d5e
 
ca27728
 
b3f8be2
 
9fd9d5e
b3f8be2
ca27728
 
9fd9d5e
 
b3f8be2
9fd9d5e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import os
import re
import requests
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
from fastapi.middleware.cors import CORSMiddleware
from huggingface_hub import InferenceClient

app = FastAPI(title="Yuyu - LibraryLuxe AI")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ================== CONFIG ==================
HF_TOKEN = os.getenv("HF_TOKEN")
client = InferenceClient(api_key=HF_TOKEN)

# Better model (try these in order)
MODEL = "Qwen/Qwen2.5-7B-Instruct"          # Best quality
# MODEL = "mistralai/Mistral-7B-Instruct-v0.3"
# MODEL = "microsoft/Phi-3.5-mini-instruct"

# ===========================================

class ChatHistoryItem(BaseModel):
    role: str
    content: str

class ChatRequest(BaseModel):
    message: str
    history: List[ChatHistoryItem] = []

class BookEnrichment(BaseModel):
    title: Optional[str] = None
    author: Optional[str] = None
    cover_url: Optional[str] = None
    description: Optional[str] = None
    isbn: Optional[str] = None

class ChatResponse(BaseModel):
    response: str
    book_data: Optional[BookEnrichment] = None

# ================== MASTER LIBRARIAN KNOWLEDGE BASE ==================
SYSTEM_PROMPT = """
You are Yuyu, the Master Librarian at LibraryLuxe.
### CORE RULES:
- ALL prices and balances must be mentioned in MMK (Myanmar Kyat).
- Use the 'Actual Library Plans' and 'User Context' provided in the message for precision.
### 1. MEMBERSHIP & SUBSCRIPTIONS
- We have different plans (Basic, Premium, Basic yearly, Premium yearly.) with different book limits and prices.
(Basic: maxed books=3, Borrow days=14 days, Premium: maxed books=5, Borrow days=21days)
- Users can subscribe using their Wallet or other payment methods.
- IMPORTANT: When a user subscribes, it might stay "Pending" until an Admin approves it.
- To Renew/Upgrade: Go to the 'Membership' page and click 'Renew' or 'Upgrade'.
### 2. WALLET & PAYMENTS
- Users have a digital Wallet. They can 'Top Up' money into it.
- Wallet balance can be used to buy subscriptions instantly.
- To Top Up: Go to the 'Wallet' section in the profile menu.
### 3. LOYALTY & REWARDS
- Users earn 'Loyalty Points' for borrowing books and buying membership plans.
- Points can be redeemed for 'Rewards' in 'Rewards' page.
- To check points: Visit the 'Rewards' page.
### 4. BORROWING & RETURNS
- Each plan has a 'Max Books' limit and 'Borrowing Days' limit.
- To Return: Users must 'Request Return' from their 'My Books' page OR go to the library and return it. A librarian will then verify the physical return.
- Overdue books might prevent new borrowings.
### 5. PERSONALITY & TONE
- Be warm, helpful, and professional.
- Use the user's name if provided.
- ALWAYS use the 'Actual Library Plans' and 'User Context' provided in the message for precision.
- If you don't know a specific detail about their account, ask them to check their 'Dashboard' or email aiyurina1610@gmail.com.
Keep responses helpful but concise (max 2 paragraphs).
"""



def get_book_details(isbn: str) -> Optional[BookEnrichment]:
    try:
        url = f"https://openlibrary.org/api/books?bibkeys=ISBN:{isbn}&format=json&jscmd=data"
        resp = requests.get(url, timeout=6)
        data = resp.json()
        key = f"ISBN:{isbn}"
        if key in data:
            b = data[key]
            return BookEnrichment(
                title=b.get("title"),
                author=", ".join([a.get("name", "") for a in b.get("authors", [])]),
                cover_url=b.get("cover", {}).get("large"),
                description=b.get("notes") or b.get("description"),
                isbn=isbn
            )
    except:
        pass
    return None

@app.get("/")
def home():
    return {"status": "online", "message": "Yuyu is ready!"}

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    # Extract ISBN
    isbn_match = re.search(r"\b(?:97[89])?\d{9}[\dX]\b", request.message)
    book_data = get_book_details(isbn_match.group(0)) if isbn_match else None

    # Build messages
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    
    # Add recent history
    for item in request.history[-6:]:
        messages.append({"role": item.role, "content": item.content})
    
    messages.append({"role": "user", "content": request.message})

    try:
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            max_tokens=450,
            temperature=0.75,
            top_p=0.9,
        )
        ai_reply = response.choices[0].message.content.strip()
        
    except Exception as e:
        ai_reply = "I'm a bit overloaded right now... Please try again in a few seconds."

    return ChatResponse(response=ai_reply, book_data=book_data)