meet1432 commited on
Commit
c8be292
·
1 Parent(s): dbb74a5

[ADD]README.md

Browse files
.dockerignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .env
6
+ .venv
7
+ .git
8
+ .gitignore
.idea/.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
.idea/ai_deal_sentiment_api.iml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+ </content>
7
+ <orderEntry type="inheritedJdk" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
.idea/copilot.data.migration.ask2agent.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Ask2AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
.idea/inspectionProfiles/Project_Default.xml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="ignoredPackages">
6
+ <list>
7
+ <option value="fastapi" />
8
+ <option value="uvicorn" />
9
+ <option value="transformers" />
10
+ <option value="torch" />
11
+ <option value="pypdf" />
12
+ <option value="python-docx" />
13
+ <option value="python-multipart" />
14
+ <option value="requests" />
15
+ <option value="python-dotenv" />
16
+ </list>
17
+ </option>
18
+ </inspection_tool>
19
+ </profile>
20
+ </component>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/ai_deal_sentiment_api.iml" filepath="$PROJECT_DIR$/.idea/ai_deal_sentiment_api.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt /app/requirements.txt
6
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
7
+ RUN pip install --no-cache-dir -r /app/requirements.txt
8
+
9
+ COPY app /app/app
10
+ COPY templates /app/templates
11
+ COPY static /app/static
12
+
13
+ ENV PORT=7860
14
+ EXPOSE 7860
15
+
16
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,5 +1,6 @@
1
  ---
2
  title: AI Deal Sentiment API
 
3
  emoji: 🐨
4
  colorFrom: gray
5
  colorTo: purple
@@ -8,3 +9,62 @@ pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: AI Deal Sentiment API
3
+ <<<<<<< HEAD
4
  emoji: 🐨
5
  colorFrom: gray
6
  colorTo: purple
 
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
12
+ =======
13
+ emoji: 🤖
14
+ colorFrom: gray
15
+ colorTo: blue
16
+ sdk: docker
17
+ pinned: false
18
+ short_description: Sentiment API with a simple web UI
19
+ ---
20
+
21
+ # AI Deal Sentiment API
22
+
23
+ A FastAPI service that analyzes customer chat/email sentiment and returns a label, score, and brief reason. Includes a simple web UI.
24
+
25
+ ## Features
26
+ - `/sentiment` API for positive/neutral/negative detection
27
+ - `/health` for readiness checks
28
+ - Simple web UI at `/`
29
+
30
+ ## Run (Local)
31
+
32
+ ```bash
33
+ python -m venv .venv
34
+ . .venv/bin/activate
35
+ pip install -r requirements.txt
36
+ uvicorn app.main:app --host 0.0.0.0 --port 7860
37
+ ```
38
+
39
+ ## Run (Docker)
40
+
41
+ ```bash
42
+ docker build -t ai-deal-sentiment-api .
43
+ docker run -p 7860:7860 ai-deal-sentiment-api
44
+ ```
45
+
46
+ ## API
47
+
48
+ ### POST `/sentiment`
49
+
50
+ ```json
51
+ {
52
+ "text": "Customer message here"
53
+ }
54
+ ```
55
+
56
+ Response:
57
+ ```json
58
+ {
59
+ "label": "positive",
60
+ "score": 0.82,
61
+ "brief_reason": "Customer intent to buy detected"
62
+ }
63
+ ```
64
+
65
+ ### GET `/health`
66
+ Returns:
67
+ ```json
68
+ { "status": "ok" }
69
+ ```
70
+ >>>>>>> ff9fb03 ([ADD]Initial Commit)
app/__pycache__/main.cpython-310.pyc ADDED
Binary file (3.55 kB). View file
 
app/main.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+ import os
3
+
4
+ from fastapi import FastAPI, HTTPException, Request
5
+ from fastapi.responses import HTMLResponse
6
+ from fastapi.staticfiles import StaticFiles
7
+ from fastapi.templating import Jinja2Templates
8
+ from pydantic import BaseModel, Field
9
+ import re
10
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer, pipeline
11
+
12
+ app = FastAPI(title="AI Deal Sentiment API")
13
+ app.mount("/static", StaticFiles(directory="static"), name="static")
14
+ templates = Jinja2Templates(directory="templates")
15
+
16
+ MODEL_NAME = os.getenv("SENTIMENT_MODEL", "IberaSoft/customer-sentiment-analyzer")
17
+
18
+
19
+ class SentimentRequest(BaseModel):
20
+ text: str = Field(..., min_length=1)
21
+
22
+
23
+ class SentimentResponse(BaseModel):
24
+ label: str
25
+ score: float
26
+ brief_reason: str
27
+
28
+
29
+ @lru_cache(maxsize=1)
30
+ def get_sentiment_pipeline():
31
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
32
+ model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)
33
+ return pipeline("text-classification", model=model, tokenizer=tokenizer)
34
+
35
+
36
+ @app.get("/health")
37
+ def health() -> dict:
38
+ return {"status": "ok"}
39
+
40
+
41
+ @app.get("/", response_class=HTMLResponse)
42
+ def root(request: Request) -> HTMLResponse:
43
+ return templates.TemplateResponse("index.html", {"request": request})
44
+
45
+
46
+ @app.post("/sentiment", response_model=SentimentResponse)
47
+ def sentiment(payload: SentimentRequest) -> SentimentResponse:
48
+ if not payload.text.strip():
49
+ return SentimentResponse(label="neutral", score=0.0, brief_reason="No customer chat to assess")
50
+
51
+ classifier = get_sentiment_pipeline()
52
+ result = classifier(payload.text[:4000])
53
+ print("result:", result)
54
+ if not result:
55
+ raise HTTPException(status_code=500, detail="Sentiment model returned no result")
56
+
57
+ data = result[0]
58
+ label = (data.get("label") or "neutral").lower()
59
+ score = float(data.get("score", 0.0))
60
+
61
+ normalized = payload.text.lower()
62
+ normalized = normalized.replace("’", "'")
63
+ normalized = re.sub(r"[^a-z0-9\\s']", " ", normalized)
64
+ normalized = re.sub(r"\\s+", " ", normalized).strip()
65
+
66
+ disinterest = [
67
+ "not interested",
68
+ "dont want to buy",
69
+ "don't want to buy",
70
+ "do not want to buy",
71
+ "wont buy",
72
+ "won't buy",
73
+ "not good",
74
+ ]
75
+ positive_intent = [
76
+ "interested",
77
+ "want to buy",
78
+ "would like to buy",
79
+ "ready to buy",
80
+ "buy in cash",
81
+ "purchase",
82
+ "proceed",
83
+ ]
84
+
85
+ if any(k in normalized for k in disinterest):
86
+ return SentimentResponse(label="negative", score=1.0, brief_reason="Explicit disinterest from customer")
87
+ if any(k in normalized for k in positive_intent):
88
+ return SentimentResponse(label="positive", score=max(score, 0.7), brief_reason="Customer intent to buy detected")
89
+
90
+ if label == "negative" and score >= 0.6:
91
+ reason = f"Negative sentiment detected (score {score:.2f})"
92
+ elif label == "positive" and score >= 0.6:
93
+ reason = f"Positive sentiment detected (score {score:.2f})"
94
+ else:
95
+ reason = f"Neutral sentiment (score {score:.2f})"
96
+
97
+ return SentimentResponse(label=label, score=score, brief_reason=reason)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi==0.115.8
2
+ uvicorn==0.30.6
3
+ transformers==4.44.2
4
+ torch==2.4.1
5
+ jinja2==3.1.4
static/main.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const inputEl = document.getElementById("inputText");
2
+ const analyzeBtn = document.getElementById("analyzeBtn");
3
+ const statusEl = document.getElementById("status");
4
+ const resultEl = document.getElementById("result");
5
+ const labelEl = document.getElementById("label");
6
+ const scoreEl = document.getElementById("score");
7
+ const reasonEl = document.getElementById("reason");
8
+
9
+ async function analyze() {
10
+ const text = inputEl.value.trim();
11
+ if (!text) {
12
+ statusEl.textContent = "Please paste some text.";
13
+ return;
14
+ }
15
+ statusEl.textContent = "Analyzing...";
16
+ analyzeBtn.disabled = true;
17
+ try {
18
+ const res = await fetch("/sentiment", {
19
+ method: "POST",
20
+ headers: { "Content-Type": "application/json" },
21
+ body: JSON.stringify({ text }),
22
+ });
23
+ const data = await res.json();
24
+ if (!res.ok) {
25
+ statusEl.textContent = data.detail || "Request failed";
26
+ return;
27
+ }
28
+ labelEl.textContent = `Label: ${data.label}`;
29
+ scoreEl.textContent = `Score: ${data.score}`;
30
+ reasonEl.textContent = data.brief_reason || "";
31
+ resultEl.style.display = "block";
32
+ statusEl.textContent = "";
33
+ } catch (err) {
34
+ statusEl.textContent = "Network error.";
35
+ } finally {
36
+ analyzeBtn.disabled = false;
37
+ }
38
+ }
39
+
40
+ analyzeBtn.addEventListener("click", analyze);
static/style.css ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg: #0f1115;
3
+ --panel: #171a21;
4
+ --text: #f5f7fb;
5
+ --muted: #a7b0c0;
6
+ --accent: #59f2a8;
7
+ --accent-2: #5ac8ff;
8
+ --border: #2a303c;
9
+ }
10
+ * { box-sizing: border-box; }
11
+ body {
12
+ margin: 0;
13
+ font-family: "Space Grotesk", "Segoe UI", sans-serif;
14
+ background: radial-gradient(1200px 800px at 10% 10%, #1a2030, #0f1115);
15
+ color: var(--text);
16
+ }
17
+ .wrap {
18
+ max-width: 900px;
19
+ margin: 0 auto;
20
+ padding: 32px 20px 48px;
21
+ }
22
+ header { margin-bottom: 16px; }
23
+ h1 { margin: 0 0 8px 0; font-size: 28px; }
24
+ .lead { margin: 0; color: var(--muted); }
25
+ textarea {
26
+ width: 100%;
27
+ min-height: 200px;
28
+ padding: 12px;
29
+ border: 1px solid var(--border);
30
+ background: var(--panel);
31
+ color: var(--text);
32
+ border-radius: 10px;
33
+ resize: vertical;
34
+ font-size: 14px;
35
+ line-height: 1.4;
36
+ }
37
+ .actions { display: flex; gap: 12px; margin-top: 12px; align-items: center; }
38
+ button {
39
+ background: linear-gradient(120deg, var(--accent), var(--accent-2));
40
+ border: none;
41
+ color: #0b0f16;
42
+ font-weight: 700;
43
+ padding: 10px 16px;
44
+ border-radius: 10px;
45
+ cursor: pointer;
46
+ }
47
+ .panel {
48
+ margin-top: 16px;
49
+ padding: 16px;
50
+ background: var(--panel);
51
+ border: 1px solid var(--border);
52
+ border-radius: 12px;
53
+ }
54
+ .score { font-size: 20px; font-weight: 700; }
55
+ .muted { color: var(--muted); }
templates/index.html ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>AI Deal Sentiment</title>
7
+ <link rel="stylesheet" href="/static/style.css" />
8
+ </head>
9
+ <body>
10
+ <div class="wrap">
11
+ <header>
12
+ <h1>AI Deal Sentiment</h1>
13
+ <p class="lead">Paste customer chat or email text to get sentiment.</p>
14
+ </header>
15
+
16
+ <textarea id="inputText" placeholder="Paste customer chat here..."></textarea>
17
+ <div class="actions">
18
+ <button id="analyzeBtn">Analyze</button>
19
+ <span id="status" class="muted"></span>
20
+ </div>
21
+
22
+ <div id="result" class="panel" style="display:none;">
23
+ <div class="score" id="label">Label: --</div>
24
+ <p id="score" class="muted"></p>
25
+ <p id="reason"></p>
26
+ </div>
27
+ </div>
28
+
29
+ <script src="/static/main.js"></script>
30
+ </body>
31
+ </html>