Commit 662e55c8 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: Memos-style Settings, History Viewer v2, Roadmap page, modular CSS

parent b0bae89f
......@@ -92,7 +92,7 @@ GROUP BY ngay LIMIT 20;
Table: `{LANGFUSE_TABLE}`
{LANGFUSE_SCHEMA}
### 2. sql_langfuse — CleverTap events on StarRocks
### 2. sql_langfuse — CleverTap events on StarRocks
Table: `{CLEVERTAP_TABLE}`
{CLEVERTAP_SCHEMA}
......
......@@ -34,21 +34,35 @@ class ClearHistoryResponse(BaseModel):
@router.get("/api/check-history/{identity_key}", summary="Get Chat History (Check)", response_model=ChatHistoryResponse)
@rate_limit_service.limiter.limit("30/minute")
async def get_chat_history(request: Request, identity_key: str, limit: int | None = 50, before_id: int | None = None):
async def get_chat_history(
request: Request,
identity_key: str,
limit: int | None = 50,
before_id: int | None = None,
date_from: str | None = None,
date_to: str | None = None,
):
"""
Lấy lịch sử chat theo identity_key (có phân trang).
Note: Dùng identity_key từ URL path trực tiếp (không resolve từ middleware).
Chỉ dùng cho show/display lịch sử.
- date_from: YYYY-MM-DD (inclusive)
- date_to: YYYY-MM-DD (inclusive)
"""
try:
if not identity_key or identity_key.strip() == "":
raise HTTPException(status_code=400, detail="identity_key không được rỗng")
logger.info(f"GET History: identity={identity_key} | limit={limit}")
logger.info(f"GET History: identity={identity_key} | limit={limit} | date={date_from}~{date_to}")
manager = await get_conversation_manager()
history = await manager.get_chat_history(identity_key, limit=limit, before_id=before_id, skip_date_filter=True)
history = await manager.get_chat_history(
identity_key,
limit=limit,
before_id=before_id,
skip_date_filter=True,
date_from=date_from,
date_to=date_to,
)
next_cursor = history[-1]["id"] if history else None
logger.info(f"✅ Fetched {len(history)} messages for {identity_key}")
......
......@@ -127,6 +127,8 @@ class ConversationManager:
before_id: int | None = None,
include_product_ids: bool = True,
skip_date_filter: bool = False,
date_from: str | None = None,
date_to: str | None = None,
) -> list[dict[str, Any]]:
"""
Retrieve chat history for an identity (user_id or device_id) using cursor-based pagination.
......@@ -135,9 +137,29 @@ class ConversationManager:
skip_date_filter: True to get all history, False to get only today's messages
include_product_ids: True for API (frontend needs product cards),
False for AI context (only text needed)
date_from: Optional YYYY-MM-DD string (inclusive start)
date_to: Optional YYYY-MM-DD string (inclusive end)
"""
try:
if skip_date_filter:
if date_from or date_to:
# Explicit date range filter
base_query = sql.SQL("""
SELECT message, is_human, timestamp, id
FROM {table}
WHERE identity_key = %s
""").format(table=sql.Identifier(self.table_name))
params = [identity_key]
if date_from:
base_query = sql.SQL("{} AND timestamp >= %s").format(base_query)
params.append(date_from + " 00:00:00+07")
if date_to:
base_query = sql.SQL("{} AND timestamp < %s").format(base_query)
# date_to inclusive: add 1 day
from datetime import datetime as dt, timedelta
end = dt.strptime(date_to, "%Y-%m-%d") + timedelta(days=1)
params.append(end.strftime("%Y-%m-%d") + " 00:00:00+07")
elif skip_date_filter:
# Get all history without date filter
base_query = sql.SQL("""
SELECT message, is_human, timestamp, id
......
This diff is collapsed.
......@@ -4,9 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cache Manager — Canifa AI</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/product.css">
<script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<link rel="stylesheet" href="/static/css/product.css">
<script src="/static/js/frame-detect.js"></script>
<style>
/* ═══ CACHE PAGE ═══ */
.cache-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 24px; }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* ═══════════════════════════════════════════════════════════
Canifa AI Platform — Shared Theme Variables
Inspired by Memos (usememos.com) Light Theme
═══════════════════════════════════════════════════════════ */
:root {
/* ── Surfaces ── */
--background: #f8f7f4;
--foreground: #1c1917;
--card: #ffffff;
--card-foreground: #1c1917;
/* ── Muted / Secondary ── */
--muted: #f0efec;
--muted-fg: #78716c;
--secondary: #f0efec;
--secondary-fg: #57534e;
/* ── Primary (Calm Blue) ── */
--primary: #3b5998;
--primary-hover: #2d4a7a;
--primary-light: #eef1f8;
--primary-fg: #ffffff;
/* ── Accent ── */
--accent: #eef1f8;
--accent-fg: #1c1917;
/* ── Borders & Input ── */
--border: #e7e5e2;
--input: #d6d3ce;
--ring: #3b5998;
/* ── Sidebar ── */
--sidebar: #f2f0ed;
--sidebar-fg: #57534e;
--sidebar-accent: #e7e5e2;
--sidebar-accent-fg:#1c1917;
/* ── Semantic ── */
--success: #16a34a;
--success-light: #ecfdf5;
--error: #dc2626;
--error-light: #fef2f2;
--warn: #ca8a04;
--warn-light: #fefce8;
--info: #2563eb;
--info-light: #eff6ff;
/* ── Dimensions ── */
--radius: 0.5rem;
--radius-sm: 0.25rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
/* ── Shadows ── */
--shadow-xs: 0 1px 2px rgba(0,0,0,0.04);
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow-md: 0 2px 8px rgba(0,0,0,0.06);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.08);
/* ── Typography ── */
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
}
/* ── Global Reset ── */
*, *::before, *::after { box-sizing: border-box; }
/* ── Thin Scrollbar (Memos-style) ── */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--input); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--muted-fg); }
/* ── Font Smoothing ── */
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
......@@ -4,8 +4,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chi tiết thử nghiệm - Canifa AI</title>
<link rel="stylesheet" href="/static/lab.css">
<script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<script src="/static/js/frame-detect.js"></script>
<style>
* { box-sizing: border-box; }
body {
......
This diff is collapsed.
......@@ -7,8 +7,10 @@
<title>Sơ đồ hoạt động - Canifa AI</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="/static/lab.css"> <script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/flow.css">
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css"> <script src="/static/js/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/flow.css">
</head>
<body>
......
This diff is collapsed.
This diff is collapsed.
......@@ -5,9 +5,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canifa Chatbot Test</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/warm-override.css">
<script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<link rel="stylesheet" href="/static/css/warm-override.css">
<script src="/static/js/frame-detect.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
......
......@@ -4,7 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Realtime Monitor</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<style>
* { box-sizing: border-box; }
......
This diff is collapsed.
This diff is collapsed.
......@@ -4,9 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hướng dẫn sử dụng — Product Performance</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/product.css">
<script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<link rel="stylesheet" href="/static/css/product.css">
<script src="/static/js/frame-detect.js"></script>
<style>
.guide-content { max-width: 900px; padding: 32px 40px; }
.guide-content h1 { font-size: 1.6em; color: var(--gold); margin-bottom: 8px; font-family: 'Fraunces', serif; }
......
......@@ -4,11 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Product Performance — Canifa AI</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/product.css">
<script src="/static/frame-detect.js"></script>
<script src="/static/stock-loader.js"></script>
<script src="/static/product-render.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<link rel="stylesheet" href="/static/css/product.css">
<script src="/static/js/frame-detect.js"></script>
<script src="/static/js/stock-loader.js"></script>
<script src="/static/js/product-render.js"></script>
</head>
<body>
......
......@@ -4,7 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Prompt Optimizer</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{min-height:100vh;color:#18181B;background:radial-gradient(circle at top right,rgba(180,83,9,.10),transparent 28%),linear-gradient(180deg,#F7F4EE 0%,#F3EFE6 100%);font-family:'Outfit',sans-serif}
......
This diff is collapsed.
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kế hoạch phát triển — Canifa AI</title>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<script src="/static/js/frame-detect.js"></script>
<style>
body { margin:0; min-height:100vh; background:var(--bg); font-family:var(--font-sans); color:var(--foreground); }
.page { max-width:800px; margin:0 auto; padding:28px 24px 60px; }
.page-header { margin-bottom:32px; }
.page-header h1 { font-size:24px; font-weight:700; margin:0 0 6px; }
.page-header p { font-size:14px; color:var(--muted-fg); margin:0; }
.version-block { margin-bottom:32px; }
.version-title { display:flex; align-items:center; gap:10px; margin-bottom:14px; }
.version-title h2 { font-size:16px; font-weight:700; margin:0; }
.version-badge { font-size:11px; font-weight:700; padding:2px 8px; border-radius:999px; text-transform:uppercase; letter-spacing:.04em; }
.badge-current { background:var(--success-bg,#dcfce7); color:var(--success,#16a34a); }
.badge-next { background:var(--primary-light,#e0f2fe); color:var(--primary,#3b5998); }
.badge-future { background:var(--muted,#f0efec); color:var(--muted-fg,#78716c); }
.version-date { font-size:12px; color:var(--muted-fg); }
.feature-list { list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:8px; }
.feature-item { display:flex; align-items:flex-start; gap:10px; padding:10px 14px; background:var(--card); border:1px solid var(--border); border-radius:10px; }
.feature-check { width:18px; height:18px; border-radius:4px; border:2px solid var(--border); display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-top:1px; font-size:11px; }
.feature-check.done { background:var(--success,#16a34a); border-color:var(--success,#16a34a); color:white; }
.feature-check.wip { background:var(--warning-bg,#fef3c7); border-color:var(--warning,#d97706); color:var(--warning); }
.feature-body { flex:1; }
.feature-name { font-size:13px; font-weight:600; }
.feature-desc { font-size:12px; color:var(--muted-fg); margin-top:2px; }
hr.section-sep { border:none; border-top:1px solid var(--border); margin:0; }
</style>
</head>
<body>
<div class="page">
<div class="page-header">
<h1>📋 Kế hoạch phát triển</h1>
<p>Roadmap phát triển hệ thống Canifa AI Platform</p>
</div>
<!-- v2.5.0 - Current -->
<div class="version-block">
<div class="version-title">
<h2>v2.5.0</h2>
<span class="version-badge badge-current">Current</span>
<span class="version-date">Mar 2026</span>
</div>
<ul class="feature-list">
<li class="feature-item"><div class="feature-check done"></div><div class="feature-body"><div class="feature-name">AI SQL Agent (Text-to-SQL)</div><div class="feature-desc">Truy vấn dữ liệu bằng ngôn ngữ tự nhiên với Human-in-the-Loop</div></div></li>
<li class="feature-item"><div class="feature-check done"></div><div class="feature-body"><div class="feature-name">Memos-inspired UI Redesign</div><div class="feature-desc">Theme mới, modular CSS, Settings modal giống Memos</div></div></li>
<li class="feature-item"><div class="feature-check done"></div><div class="feature-body"><div class="feature-name">Prompt Optimizer</div><div class="feature-desc">Tối ưu prompt tự động với AI feedback loop</div></div></li>
<li class="feature-item"><div class="feature-check done"></div><div class="feature-body"><div class="feature-name">User Simulator & Stress Test</div><div class="feature-desc">Mô phỏng user và stress test hệ thống</div></div></li>
<li class="feature-item"><div class="feature-check done"></div><div class="feature-body"><div class="feature-name">Realtime Monitor</div><div class="feature-desc">Dashboard theo dõi hệ thống real-time</div></div></li>
<li class="feature-item"><div class="feature-check wip"></div><div class="feature-body"><div class="feature-name">History Viewer v2</div><div class="feature-desc">Date picker, product cards, cải thiện UX xem lịch sử</div></div></li>
</ul>
</div>
<hr class="section-sep">
<!-- v2.6.0 - Next -->
<div class="version-block" style="margin-top:28px;">
<div class="version-title">
<h2>v2.6.0</h2>
<span class="version-badge badge-next">Planned</span>
<span class="version-date">Q2 2026</span>
</div>
<ul class="feature-list">
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">Multi-Agent Orchestration</div><div class="feature-desc">Nhiều agent phối hợp: SQL Agent, Report Agent, Analytics Agent</div></div></li>
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">AI Data Analyst v2</div><div class="feature-desc">Tự động phân tích dữ liệu và tạo báo cáo insights</div></div></li>
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">Compact Sidebar (Memos-style)</div><div class="feature-desc">Sidebar thu gọn icon-only, expand on hover</div></div></li>
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">Dark Mode</div><div class="feature-desc">Theme tối cho platform</div></div></li>
</ul>
</div>
<hr class="section-sep">
<!-- v3.0.0 - Future -->
<div class="version-block" style="margin-top:28px;">
<div class="version-title">
<h2>v3.0.0</h2>
<span class="version-badge badge-future">Future</span>
<span class="version-date">Q3-Q4 2026</span>
</div>
<ul class="feature-list">
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">Customer Analytics Dashboard</div><div class="feature-desc">Phân tích hành vi khách hàng từ chat data</div></div></li>
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">A/B Testing Engine</div><div class="feature-desc">So sánh prompt/model variants trên live traffic</div></div></li>
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">Self-Learning System</div><div class="feature-desc">Auto-tune prompts dựa trên feedback data</div></div></li>
<li class="feature-item"><div class="feature-check"></div><div class="feature-body"><div class="feature-name">Mobile Admin App</div><div class="feature-desc">Quản lý chatbot từ điện thoại</div></div></li>
</ul>
</div>
</div>
</body>
</html>
This diff is collapsed.
......@@ -5,9 +5,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔍 DB Direct Query Tester</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/warm-override.css">
<script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<link rel="stylesheet" href="/static/css/warm-override.css">
<script src="/static/js/frame-detect.js"></script>
<style>
* {
box-sizing: border-box;
......
......@@ -5,9 +5,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🤖 Text-to-SQL Chatbot — Bản 2</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/warm-override.css">
<script src="/static/frame-detect.js"></script>
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<link rel="stylesheet" href="/static/css/warm-override.css">
<script src="/static/js/frame-detect.js"></script>
<style>
* {
box-sizing: border-box;
......
......@@ -4,7 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Insight — CDP</title>
<link rel="stylesheet" href="/static/lab.css">
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lab.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
*{box-sizing:border-box;margin:0;padding:0}
......@@ -179,7 +181,7 @@ body{font-family:'Inter',system-ui,sans-serif;background:#F8F9FC;min-height:100v
<div class="profile-content" id="profileContent" style="display:none"></div>
</div>
</div>
<script src="/static/user-insight-data.js"></script>
<script src="/static/user-insight-render.js"></script>
<script src="/static/js/user-insight-data.js"></script>
<script src="/static/js/user-insight-render.js"></script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment