Spaces:
Sleeping
Sleeping
[ADD]README.md
Browse files- .dockerignore +8 -0
- .idea/.gitignore +5 -0
- .idea/ai_deal_sentiment_api.iml +10 -0
- .idea/copilot.data.migration.ask2agent.xml +6 -0
- .idea/inspectionProfiles/Project_Default.xml +20 -0
- .idea/inspectionProfiles/profiles_settings.xml +6 -0
- .idea/modules.xml +8 -0
- Dockerfile +16 -0
- README.md +60 -0
- app/__pycache__/main.cpython-310.pyc +0 -0
- app/main.py +97 -0
- requirements.txt +5 -0
- static/main.js +40 -0
- static/style.css +55 -0
- templates/index.html +31 -0
.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>
|