1400 lines
50 KiB
HTML
1400 lines
50 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>NAC AI 资产估值引擎 | NewAssetChain</title>
|
||
<meta name="description" content="NAC AI多资产多辖区估值推理引擎 - 基于SDR锚定的XTZH统一定价,支持60+司法辖区,一键上链">
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>💎</text></svg>">
|
||
<style>
|
||
:root {
|
||
--bg-primary: #0a0e1a;
|
||
--bg-secondary: #0f1628;
|
||
--bg-card: #141c2e;
|
||
--bg-input: #1a2340;
|
||
--accent-gold: #d4a843;
|
||
--accent-gold-light: #f0c060;
|
||
--accent-blue: #3b82f6;
|
||
--accent-green: #10b981;
|
||
--accent-purple: #8b5cf6;
|
||
--text-primary: #e8eaf0;
|
||
--text-secondary: #8892a4;
|
||
--text-muted: #4a5568;
|
||
--border: #1e2d4a;
|
||
--border-active: #3b82f6;
|
||
--shadow: 0 4px 24px rgba(0,0,0,0.4);
|
||
--radius: 12px;
|
||
}
|
||
* { margin:0; padding:0; box-sizing:border-box; }
|
||
body {
|
||
font-family: 'PingFang SC', 'Microsoft YaHei', -apple-system, sans-serif;
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ===== HEADER ===== */
|
||
.header {
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
padding: 0 20px;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-shrink: 0;
|
||
}
|
||
.header-left { display:flex; align-items:center; gap:12px; }
|
||
.logo {
|
||
width: 36px; height: 36px;
|
||
background: linear-gradient(135deg, var(--accent-gold), #b8860b);
|
||
border-radius: 8px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 18px; font-weight: 900; color: #000;
|
||
}
|
||
.header-title { font-size: 16px; font-weight: 700; color: var(--text-primary); }
|
||
.header-subtitle { font-size: 11px; color: var(--text-secondary); margin-top: 1px; }
|
||
.header-right { display:flex; align-items:center; gap:12px; }
|
||
|
||
/* XTZH价格指示器 */
|
||
.xtzh-price-badge {
|
||
background: linear-gradient(135deg, rgba(212,168,67,0.15), rgba(212,168,67,0.05));
|
||
border: 1px solid rgba(212,168,67,0.3);
|
||
border-radius: 20px;
|
||
padding: 5px 14px;
|
||
display: flex; align-items: center; gap: 8px;
|
||
cursor: pointer;
|
||
}
|
||
.xtzh-price-badge .label { font-size: 10px; color: var(--text-secondary); }
|
||
.xtzh-price-badge .price { font-size: 14px; font-weight: 700; color: var(--accent-gold); }
|
||
.xtzh-price-badge .source { font-size: 9px; color: var(--text-muted); }
|
||
.price-dot { width:6px; height:6px; border-radius:50%; background:var(--accent-green); animation: pulse 2s infinite; }
|
||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
||
|
||
/* 语言选择 */
|
||
.lang-select {
|
||
background: var(--bg-card); border: 1px solid var(--border);
|
||
color: var(--text-primary); border-radius: 8px;
|
||
padding: 5px 10px; font-size: 12px; cursor: pointer;
|
||
outline: none;
|
||
}
|
||
|
||
/* ===== MAIN LAYOUT ===== */
|
||
.main {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ===== SIDEBAR ===== */
|
||
.sidebar {
|
||
width: 260px;
|
||
background: var(--bg-secondary);
|
||
border-right: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-header {
|
||
padding: 16px;
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.new-chat-btn {
|
||
flex: 1;
|
||
background: linear-gradient(135deg, var(--accent-gold), #b8860b);
|
||
color: #000;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 9px 16px;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
}
|
||
.new-chat-btn:hover { opacity: 0.85; }
|
||
.clear-btn {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
border-radius: 8px;
|
||
padding: 9px 12px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.clear-btn:hover { border-color: var(--accent-gold); color: var(--accent-gold); }
|
||
|
||
/* 辖区快速筛选 */
|
||
.sidebar-section { padding: 12px 16px 8px; }
|
||
.sidebar-section-title {
|
||
font-size: 10px; color: var(--text-muted);
|
||
text-transform: uppercase; letter-spacing: 1px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.jurisdiction-grid {
|
||
display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;
|
||
}
|
||
.juris-btn {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
border-radius: 6px;
|
||
padding: 5px 4px;
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
text-align: center;
|
||
transition: all 0.2s;
|
||
}
|
||
.juris-btn:hover, .juris-btn.active {
|
||
border-color: var(--accent-gold);
|
||
color: var(--accent-gold);
|
||
background: rgba(212,168,67,0.08);
|
||
}
|
||
|
||
/* 资产类型快速选择 */
|
||
.asset-type-list { display: flex; flex-direction: column; gap: 3px; }
|
||
.asset-type-btn {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
border-radius: 6px;
|
||
padding: 6px 10px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: all 0.2s;
|
||
display: flex; align-items: center; gap: 6px;
|
||
}
|
||
.asset-type-btn:hover, .asset-type-btn.active {
|
||
border-color: var(--accent-blue);
|
||
color: var(--text-primary);
|
||
background: rgba(59,130,246,0.08);
|
||
}
|
||
|
||
/* 历史对话 */
|
||
.history-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 8px;
|
||
}
|
||
.history-item {
|
||
padding: 8px 10px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
transition: all 0.2s;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
margin-bottom: 2px;
|
||
}
|
||
.history-item:hover { background: var(--bg-card); color: var(--text-primary); }
|
||
.history-item.active { background: rgba(212,168,67,0.1); color: var(--accent-gold); }
|
||
|
||
/* ===== CHAT AREA ===== */
|
||
.chat-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
/* XTZH实时价格面板(顶部) */
|
||
.xtzh-panel {
|
||
background: linear-gradient(135deg, rgba(212,168,67,0.08), rgba(59,130,246,0.05));
|
||
border-bottom: 1px solid rgba(212,168,67,0.15);
|
||
padding: 10px 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24px;
|
||
flex-shrink: 0;
|
||
}
|
||
.xtzh-stat { display: flex; flex-direction: column; }
|
||
.xtzh-stat .stat-label { font-size: 10px; color: var(--text-muted); }
|
||
.xtzh-stat .stat-value { font-size: 14px; font-weight: 700; color: var(--accent-gold); }
|
||
.xtzh-stat .stat-sub { font-size: 10px; color: var(--text-secondary); }
|
||
.xtzh-divider { width: 1px; height: 30px; background: var(--border); }
|
||
|
||
/* 消息列表 */
|
||
.messages {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
.messages::-webkit-scrollbar { width: 4px; }
|
||
.messages::-webkit-scrollbar-track { background: transparent; }
|
||
.messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||
|
||
/* 欢迎界面 */
|
||
.welcome {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
padding: 40px 20px;
|
||
text-align: center;
|
||
}
|
||
.welcome-icon {
|
||
width: 72px; height: 72px;
|
||
background: linear-gradient(135deg, var(--accent-gold), #b8860b);
|
||
border-radius: 20px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 36px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 8px 32px rgba(212,168,67,0.3);
|
||
}
|
||
.welcome h1 { font-size: 22px; font-weight: 800; margin-bottom: 8px; }
|
||
.welcome p { font-size: 14px; color: var(--text-secondary); max-width: 480px; line-height: 1.6; }
|
||
|
||
/* 快捷问题卡片 */
|
||
.quick-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
margin-top: 28px;
|
||
max-width: 560px;
|
||
width: 100%;
|
||
}
|
||
.quick-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: 14px 16px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-align: left;
|
||
}
|
||
.quick-card:hover {
|
||
border-color: var(--accent-gold);
|
||
background: rgba(212,168,67,0.05);
|
||
transform: translateY(-1px);
|
||
}
|
||
.quick-card .card-icon { font-size: 20px; margin-bottom: 6px; }
|
||
.quick-card .card-title { font-size: 13px; font-weight: 600; color: var(--text-primary); }
|
||
.quick-card .card-desc { font-size: 11px; color: var(--text-secondary); margin-top: 3px; line-height: 1.4; }
|
||
|
||
/* 消息气泡 */
|
||
.message { display: flex; gap: 10px; max-width: 100%; }
|
||
.message.user { flex-direction: row-reverse; }
|
||
.message-avatar {
|
||
width: 32px; height: 32px;
|
||
border-radius: 8px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 14px; flex-shrink: 0;
|
||
}
|
||
.message.user .message-avatar {
|
||
background: linear-gradient(135deg, var(--accent-blue), #1d4ed8);
|
||
}
|
||
.message.ai .message-avatar {
|
||
background: linear-gradient(135deg, var(--accent-gold), #b8860b);
|
||
color: #000; font-weight: 900;
|
||
}
|
||
.message-content {
|
||
max-width: 75%;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: 12px 16px;
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
}
|
||
.message.user .message-content {
|
||
background: rgba(59,130,246,0.12);
|
||
border-color: rgba(59,130,246,0.25);
|
||
}
|
||
.message-content pre {
|
||
background: rgba(0,0,0,0.3);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
overflow-x: auto;
|
||
font-size: 12px;
|
||
margin: 8px 0;
|
||
}
|
||
.message-content table {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
margin: 8px 0;
|
||
font-size: 13px;
|
||
}
|
||
.message-content th, .message-content td {
|
||
border: 1px solid var(--border);
|
||
padding: 6px 10px;
|
||
text-align: left;
|
||
}
|
||
.message-content th { background: rgba(212,168,67,0.1); color: var(--accent-gold); }
|
||
.message-content strong { color: var(--accent-gold-light); }
|
||
|
||
/* 估值结果卡片 */
|
||
.valuation-card {
|
||
background: linear-gradient(135deg, rgba(212,168,67,0.08), rgba(16,185,129,0.05));
|
||
border: 1px solid rgba(212,168,67,0.3);
|
||
border-radius: var(--radius);
|
||
padding: 16px;
|
||
margin-top: 12px;
|
||
}
|
||
.valuation-card .vc-header {
|
||
display: flex; align-items: center; gap: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.valuation-card .vc-title { font-size: 13px; font-weight: 700; color: var(--accent-gold); }
|
||
.valuation-card .vc-badge {
|
||
font-size: 10px; padding: 2px 8px;
|
||
border-radius: 10px; background: rgba(16,185,129,0.15);
|
||
color: var(--accent-green); border: 1px solid rgba(16,185,129,0.3);
|
||
}
|
||
.valuation-grid {
|
||
display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
|
||
}
|
||
.valuation-item { display: flex; flex-direction: column; }
|
||
.valuation-item .vi-label { font-size: 10px; color: var(--text-muted); }
|
||
.valuation-item .vi-value { font-size: 16px; font-weight: 800; color: var(--text-primary); }
|
||
.valuation-item .vi-value.gold { color: var(--accent-gold); }
|
||
.valuation-item .vi-value.green { color: var(--accent-green); }
|
||
.valuation-item .vi-sub { font-size: 10px; color: var(--text-secondary); }
|
||
.confidence-bar {
|
||
margin-top: 12px;
|
||
background: rgba(255,255,255,0.05);
|
||
border-radius: 4px;
|
||
height: 4px;
|
||
overflow: hidden;
|
||
}
|
||
.confidence-fill {
|
||
height: 100%;
|
||
border-radius: 4px;
|
||
background: linear-gradient(90deg, var(--accent-green), var(--accent-gold));
|
||
transition: width 1s ease;
|
||
}
|
||
.chain-actions {
|
||
display: flex; gap: 8px; margin-top: 12px;
|
||
}
|
||
.chain-btn {
|
||
flex: 1; padding: 8px 12px;
|
||
border-radius: 8px; border: none;
|
||
font-size: 12px; font-weight: 600;
|
||
cursor: pointer; transition: all 0.2s;
|
||
}
|
||
.chain-btn.primary {
|
||
background: linear-gradient(135deg, var(--accent-gold), #b8860b);
|
||
color: #000;
|
||
}
|
||
.chain-btn.primary:hover { opacity: 0.85; }
|
||
.chain-btn.secondary {
|
||
background: var(--bg-input);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
}
|
||
.chain-btn.secondary:hover { border-color: var(--accent-blue); color: var(--accent-blue); }
|
||
|
||
/* 打字动画 */
|
||
.typing-indicator {
|
||
display: flex; align-items: center; gap: 4px; padding: 4px 0;
|
||
}
|
||
.typing-dot {
|
||
width: 6px; height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--accent-gold);
|
||
animation: typing 1.4s infinite;
|
||
}
|
||
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
||
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
||
@keyframes typing { 0%,60%,100%{transform:translateY(0);opacity:0.4} 30%{transform:translateY(-6px);opacity:1} }
|
||
|
||
/* 流式输出光标 */
|
||
.stream-cursor {
|
||
display: inline-block;
|
||
width: 2px; height: 14px;
|
||
background: var(--accent-gold);
|
||
margin-left: 2px;
|
||
animation: blink 0.8s infinite;
|
||
vertical-align: middle;
|
||
}
|
||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
||
|
||
/* ===== INPUT AREA ===== */
|
||
.input-area {
|
||
padding: 16px 20px;
|
||
background: var(--bg-secondary);
|
||
border-top: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.input-wrapper {
|
||
background: var(--bg-input);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: 12px 16px;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 10px;
|
||
transition: border-color 0.2s;
|
||
}
|
||
.input-wrapper:focus-within { border-color: var(--accent-gold); }
|
||
.chat-input {
|
||
flex: 1;
|
||
background: transparent;
|
||
border: none;
|
||
outline: none;
|
||
color: var(--text-primary);
|
||
font-size: 14px;
|
||
resize: none;
|
||
min-height: 20px;
|
||
max-height: 120px;
|
||
line-height: 1.5;
|
||
font-family: inherit;
|
||
}
|
||
.chat-input::placeholder { color: var(--text-muted); }
|
||
.send-btn {
|
||
width: 36px; height: 36px;
|
||
background: linear-gradient(135deg, var(--accent-gold), #b8860b);
|
||
border: none; border-radius: 8px;
|
||
cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: opacity 0.2s; flex-shrink: 0;
|
||
}
|
||
.send-btn:hover { opacity: 0.85; }
|
||
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||
.send-btn svg { width: 16px; height: 16px; fill: #000; }
|
||
|
||
/* 底部工具栏 */
|
||
.toolbar {
|
||
display: flex; align-items: center; gap: 8px;
|
||
margin-top: 10px; flex-wrap: wrap;
|
||
}
|
||
.toolbar-hint { font-size: 11px; color: var(--text-muted); margin-left: auto; }
|
||
.tool-chip {
|
||
display: flex; align-items: center; gap: 4px;
|
||
padding: 4px 10px;
|
||
border-radius: 12px;
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-card);
|
||
color: var(--text-secondary);
|
||
transition: all 0.2s;
|
||
}
|
||
.tool-chip:hover { border-color: var(--accent-gold); color: var(--accent-gold); }
|
||
.tool-chip.active {
|
||
border-color: var(--accent-gold);
|
||
background: rgba(212,168,67,0.1);
|
||
color: var(--accent-gold);
|
||
}
|
||
|
||
/* 滚动条 */
|
||
.sidebar::-webkit-scrollbar,
|
||
.history-list::-webkit-scrollbar { width: 3px; }
|
||
.sidebar::-webkit-scrollbar-thumb,
|
||
.history-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||
|
||
/* 响应式 */
|
||
@media (max-width: 768px) {
|
||
.sidebar { display: none; }
|
||
.xtzh-panel { overflow-x: auto; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- HEADER -->
|
||
<div class="header">
|
||
<div class="header-left">
|
||
<div class="logo">V</div>
|
||
<div>
|
||
<div class="header-title" data-i18n="title">NAC AI 资产估值引擎</div>
|
||
<div class="header-subtitle" data-i18n="subtitle">多资产 · 多辖区 · SDR锚定 · 一键上链</div>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<div class="xtzh-price-badge" onclick="refreshXTZHPrice()" title="点击刷新XTZH实时价格">
|
||
<div class="price-dot"></div>
|
||
<div>
|
||
<div class="label">XTZH 实时价格</div>
|
||
<div class="price" id="header-xtzh-price">加载中...</div>
|
||
<div class="source" id="header-xtzh-source">NAC_SDR_MODEL_V4</div>
|
||
</div>
|
||
</div>
|
||
<select class="lang-select" id="langSelect" onchange="switchLanguage(this.value)">
|
||
<option value="zh">🇨🇳 中文</option>
|
||
<option value="en">🇺🇸 English</option>
|
||
<option value="ar">🇸🇦 العربية</option>
|
||
<option value="ja">🇯🇵 日本語</option>
|
||
<option value="ko">🇰🇷 한국어</option>
|
||
<option value="fr">🇫🇷 Français</option>
|
||
<option value="ru">🇷🇺 Русский</option>
|
||
<option value="es">🇪🇸 Español</option>
|
||
<option value="pt">🇧🇷 Português</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MAIN -->
|
||
<div class="main">
|
||
|
||
<!-- SIDEBAR -->
|
||
<div class="sidebar">
|
||
<div class="sidebar-header">
|
||
<button class="new-chat-btn" onclick="newChat()" data-i18n="newChat">+ 新建估值</button>
|
||
<button class="clear-btn" onclick="clearMemory()" title="清除对话记忆">🗑</button>
|
||
</div>
|
||
|
||
<!-- 辖区快速选择 -->
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-section-title" data-i18n="jurisdiction">司法辖区</div>
|
||
<div class="jurisdiction-grid">
|
||
<button class="juris-btn active" onclick="setJurisdiction('HK', this)" data-code="HK">🇭🇰 香港</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('SG', this)" data-code="SG">🇸🇬 新加坡</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('AE', this)" data-code="AE">🇦🇪 阿联酋</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('US', this)" data-code="US">🇺🇸 美国</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('UK', this)" data-code="UK">🇬🇧 英国</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('JP', this)" data-code="JP">🇯🇵 日本</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('CN', this)" data-code="CN">🇨🇳 中国</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('AU', this)" data-code="AU">🇦🇺 澳洲</button>
|
||
<button class="juris-btn" onclick="setJurisdiction('DE', this)" data-code="DE">🇩🇪 德国</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 资产类型 -->
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-section-title" data-i18n="assetType">资产类型</div>
|
||
<div class="asset-type-list">
|
||
<button class="asset-type-btn active" onclick="setAssetType('real_estate', this)">🏢 不动产</button>
|
||
<button class="asset-type-btn" onclick="setAssetType('financial', this)">💹 金融资产</button>
|
||
<button class="asset-type-btn" onclick="setAssetType('art', this)">🎨 艺术品</button>
|
||
<button class="asset-type-btn" onclick="setAssetType('commodity', this)">🏭 大宗商品</button>
|
||
<button class="asset-type-btn" onclick="setAssetType('ip', this)">💡 知识产权</button>
|
||
<button class="asset-type-btn" onclick="setAssetType('equity', this)">📊 股权</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 历史对话 -->
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-section-title" data-i18n="history">历史估值</div>
|
||
</div>
|
||
<div class="history-list" id="historyList"></div>
|
||
</div>
|
||
|
||
<!-- CHAT AREA -->
|
||
<div class="chat-area">
|
||
|
||
<!-- XTZH实时数据面板 -->
|
||
<div class="xtzh-panel" id="xtzhPanel">
|
||
<div class="xtzh-stat">
|
||
<div class="stat-label">XTZH/USD</div>
|
||
<div class="stat-value" id="panel-price">--</div>
|
||
<div class="stat-sub">SDR锚定</div>
|
||
</div>
|
||
<div class="xtzh-divider"></div>
|
||
<div class="xtzh-stat">
|
||
<div class="stat-label">SDR权重</div>
|
||
<div class="stat-value" id="panel-sdr">--</div>
|
||
<div class="stat-sub">五货币篮子</div>
|
||
</div>
|
||
<div class="xtzh-divider"></div>
|
||
<div class="xtzh-stat">
|
||
<div class="stat-label">黄金覆盖率</div>
|
||
<div class="stat-value" id="panel-gold">--</div>
|
||
<div class="stat-sub">储备保障</div>
|
||
</div>
|
||
<div class="xtzh-divider"></div>
|
||
<div class="xtzh-stat">
|
||
<div class="stat-label">当前辖区</div>
|
||
<div class="stat-value" id="panel-jurisdiction">HK</div>
|
||
<div class="stat-sub" id="panel-juris-name">香港</div>
|
||
</div>
|
||
<div class="xtzh-divider"></div>
|
||
<div class="xtzh-stat">
|
||
<div class="stat-label">资产类型</div>
|
||
<div class="stat-value" id="panel-asset-type">不动产</div>
|
||
<div class="stat-sub" id="panel-gnacs">GNACS-01</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 消息区域 -->
|
||
<div class="messages" id="messages">
|
||
<!-- 欢迎界面 -->
|
||
<div class="welcome" id="welcome">
|
||
<div class="welcome-icon">💎</div>
|
||
<h1 data-i18n="welcomeTitle">NAC AI 资产估值引擎</h1>
|
||
<p data-i18n="welcomeDesc">基于白皮书自研推理引擎,支持20大类资产、60+司法辖区。采用SDR锚定的XTZH统一定价机制,市场法/收益法/成本法多方法融合估值,一键上链生成TOKEN权证。</p>
|
||
<div class="quick-cards">
|
||
<div class="quick-card" onclick="quickAsk('香港九龙区120平米住宅公寓,市值800万港元,请估算XTZH价值')">
|
||
<div class="card-icon">🏠</div>
|
||
<div class="card-title">住宅不动产估值</div>
|
||
<div class="card-desc">香港/新加坡/阿联酋住宅资产XTZH换算</div>
|
||
</div>
|
||
<div class="quick-card" onclick="quickAsk('新加坡CBD商业写字楼1200平米,年租金收入180万SGD,请用收益法估值')">
|
||
<div class="card-icon">🏢</div>
|
||
<div class="card-title">商业地产收益法</div>
|
||
<div class="card-desc">基于年租金收益的资本化率估值</div>
|
||
</div>
|
||
<div class="quick-card" onclick="quickAsk('请解释XTZH的SDR锚定定价机制,当前价格如何计算')">
|
||
<div class="card-icon">💰</div>
|
||
<div class="card-title">XTZH定价机制</div>
|
||
<div class="card-desc">SDR五货币篮子+黄金储备保障原理</div>
|
||
</div>
|
||
<div class="quick-card" onclick="quickAsk('资产上链流程是什么?从估值到TOKEN生成需要哪些步骤')">
|
||
<div class="card-icon">⛓️</div>
|
||
<div class="card-title">一键上链流程</div>
|
||
<div class="card-desc">估值→合规→TOKEN→权证→代币发行</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 输入区域 -->
|
||
<div class="input-area">
|
||
<div class="input-wrapper">
|
||
<textarea
|
||
class="chat-input"
|
||
id="chatInput"
|
||
rows="1"
|
||
placeholder="描述您的资产(类型、位置、面积、价格等),AI将自动估算XTZH价值..."
|
||
onkeydown="handleKeyDown(event)"
|
||
oninput="autoResize(this)"
|
||
></textarea>
|
||
<button class="send-btn" id="sendBtn" onclick="sendMessage()" title="发送">
|
||
<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="toolbar">
|
||
<div class="tool-chip active" id="chip-valuation" onclick="toggleMode('valuation', this)">💎 估值模式</div>
|
||
<div class="tool-chip" id="chip-chain" onclick="toggleMode('chain', this)">⛓️ 上链申请</div>
|
||
<div class="tool-chip" id="chip-compare" onclick="toggleMode('compare', this)">📊 辖区对比</div>
|
||
<div class="tool-chip" id="chip-explain" onclick="toggleMode('explain', this)">📖 方法论</div>
|
||
<div class="toolbar-hint">Enter发送 · Shift+Enter换行</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ============================================================
|
||
// 配置
|
||
// ============================================================
|
||
const CONFIG = {
|
||
valuationApiBase: '/api/v4',
|
||
inferenceApiBase: '/api/inference',
|
||
maxMemory: 10,
|
||
lang: 'zh'
|
||
};
|
||
|
||
// 多语言
|
||
const I18N = {
|
||
zh: {
|
||
title: 'NAC AI 资产估值引擎',
|
||
subtitle: '多资产 · 多辖区 · SDR锚定 · 一键上链',
|
||
newChat: '+ 新建估值',
|
||
jurisdiction: '司法辖区',
|
||
assetType: '资产类型',
|
||
history: '历史估值',
|
||
welcomeTitle: 'NAC AI 资产估值引擎',
|
||
welcomeDesc: '基于白皮书自研推理引擎,支持20大类资产、60+司法辖区。采用SDR锚定的XTZH统一定价机制,市场法/收益法/成本法多方法融合估值,一键上链生成TOKEN权证。',
|
||
placeholder: '描述您的资产(类型、位置、面积、价格等),AI将自动估算XTZH价值...',
|
||
sending: '正在分析...',
|
||
chainBtn: '申请上链',
|
||
detailBtn: '查看详情',
|
||
confidence: '置信度',
|
||
finalXTZH: '最终XTZH',
|
||
finalUSD: '美元估值',
|
||
xtzhPrice: 'XTZH价格',
|
||
stakeRequired: '质押要求(80%)',
|
||
engineMode: '引擎模式',
|
||
methods: '估值方法',
|
||
jurisdiction_label: '辖区',
|
||
},
|
||
en: {
|
||
title: 'NAC AI Asset Valuation Engine',
|
||
subtitle: 'Multi-Asset · Multi-Jurisdiction · SDR-Pegged · One-Click On-Chain',
|
||
newChat: '+ New Valuation',
|
||
jurisdiction: 'Jurisdiction',
|
||
assetType: 'Asset Type',
|
||
history: 'History',
|
||
welcomeTitle: 'NAC AI Asset Valuation Engine',
|
||
welcomeDesc: 'Proprietary inference engine supporting 20+ asset categories and 60+ jurisdictions. Unified XTZH pricing via SDR basket, multi-method valuation fusion, one-click on-chain TOKEN generation.',
|
||
placeholder: 'Describe your asset (type, location, area, price...) and AI will estimate XTZH value...',
|
||
sending: 'Analyzing...',
|
||
chainBtn: 'Apply On-Chain',
|
||
detailBtn: 'View Details',
|
||
confidence: 'Confidence',
|
||
finalXTZH: 'Final XTZH',
|
||
finalUSD: 'USD Valuation',
|
||
xtzhPrice: 'XTZH Price',
|
||
stakeRequired: 'Stake Required (80%)',
|
||
engineMode: 'Engine Mode',
|
||
methods: 'Methods Used',
|
||
jurisdiction_label: 'Jurisdiction',
|
||
}
|
||
};
|
||
|
||
// ============================================================
|
||
// 状态管理
|
||
// ============================================================
|
||
let state = {
|
||
messages: [],
|
||
sessionId: generateSessionId(),
|
||
conversationMemory: [],
|
||
currentJurisdiction: 'HK',
|
||
currentAssetType: 'real_estate',
|
||
currentMode: 'valuation',
|
||
xtzhPrice: null,
|
||
isStreaming: false,
|
||
history: JSON.parse(localStorage.getItem('nac_valuation_history') || '[]')
|
||
};
|
||
|
||
function generateSessionId() {
|
||
return 'vs_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||
}
|
||
|
||
// ============================================================
|
||
// XTZH实时价格
|
||
// ============================================================
|
||
async function fetchXTZHPrice() {
|
||
try {
|
||
const res = await fetch(CONFIG.valuationApiBase + '/xtzh-price');
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
const data = await res.json();
|
||
state.xtzhPrice = data;
|
||
|
||
const usd = parseFloat(data.usd || data.price || 4.3944).toFixed(4);
|
||
const sdr = data.sdrBasket ? Object.values(data.sdrBasket).reduce((a,b)=>a+b,0).toFixed(2) : '1.00';
|
||
const gold = data.goldCoverage ? (data.goldCoverage * 100).toFixed(0) + '%' : '125%';
|
||
|
||
document.getElementById('header-xtzh-price').textContent = '$' + usd;
|
||
document.getElementById('header-xtzh-source').textContent = data.source || 'NAC_SDR_MODEL_V4';
|
||
document.getElementById('panel-price').textContent = '$' + usd;
|
||
document.getElementById('panel-sdr').textContent = sdr;
|
||
document.getElementById('panel-gold').textContent = gold;
|
||
} catch(e) {
|
||
// 降级显示
|
||
document.getElementById('header-xtzh-price').textContent = '$4.3944';
|
||
document.getElementById('panel-price').textContent = '$4.3944';
|
||
document.getElementById('panel-sdr').textContent = '1.00';
|
||
document.getElementById('panel-gold').textContent = '125%';
|
||
}
|
||
}
|
||
|
||
function refreshXTZHPrice() {
|
||
document.getElementById('header-xtzh-price').textContent = '刷新中...';
|
||
fetchXTZHPrice();
|
||
}
|
||
|
||
// ============================================================
|
||
// 辖区和资产类型
|
||
// ============================================================
|
||
const JURISDICTION_NAMES = {
|
||
HK: '香港', SG: '新加坡', AE: '阿联酋', US: '美国',
|
||
UK: '英国', JP: '日本', CN: '中国', AU: '澳洲', DE: '德国'
|
||
};
|
||
const GNACS_MAP = {
|
||
real_estate: 'GNACS-01', financial: 'GNACS-02', art: 'GNACS-03',
|
||
commodity: 'GNACS-04', ip: 'GNACS-05', equity: 'GNACS-06'
|
||
};
|
||
const ASSET_NAMES = {
|
||
real_estate: '不动产', financial: '金融资产', art: '艺术品',
|
||
commodity: '大宗商品', ip: '知识产权', equity: '股权'
|
||
};
|
||
|
||
function setJurisdiction(code, btn) {
|
||
state.currentJurisdiction = code;
|
||
document.querySelectorAll('.juris-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
document.getElementById('panel-jurisdiction').textContent = code;
|
||
document.getElementById('panel-juris-name').textContent = JURISDICTION_NAMES[code] || code;
|
||
}
|
||
|
||
function setAssetType(type, btn) {
|
||
state.currentAssetType = type;
|
||
document.querySelectorAll('.asset-type-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
document.getElementById('panel-asset-type').textContent = ASSET_NAMES[type] || type;
|
||
document.getElementById('panel-gnacs').textContent = GNACS_MAP[type] || 'GNACS-01';
|
||
}
|
||
|
||
function toggleMode(mode, btn) {
|
||
state.currentMode = mode;
|
||
document.querySelectorAll('.tool-chip').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
}
|
||
|
||
// ============================================================
|
||
// 消息发送与SSE流式输出
|
||
// ============================================================
|
||
async function sendMessage() {
|
||
const input = document.getElementById('chatInput');
|
||
const text = input.value.trim();
|
||
if (!text || state.isStreaming) return;
|
||
|
||
// 隐藏欢迎界面
|
||
const welcome = document.getElementById('welcome');
|
||
if (welcome) welcome.style.display = 'none';
|
||
|
||
// 添加用户消息
|
||
appendUserMessage(text);
|
||
input.value = '';
|
||
autoResize(input);
|
||
|
||
// 添加到记忆
|
||
state.conversationMemory.push({ role: 'user', content: text });
|
||
if (state.conversationMemory.length > CONFIG.maxMemory * 2) {
|
||
state.conversationMemory = state.conversationMemory.slice(-CONFIG.maxMemory * 2);
|
||
}
|
||
|
||
// 保存到历史
|
||
if (state.messages.length === 1) {
|
||
addToHistory(text.substring(0, 40) + (text.length > 40 ? '...' : ''));
|
||
}
|
||
|
||
state.isStreaming = true;
|
||
document.getElementById('sendBtn').disabled = true;
|
||
|
||
// 创建AI消息容器
|
||
const aiMsgId = 'ai_' + Date.now();
|
||
const aiDiv = appendAIMessage('', aiMsgId);
|
||
|
||
try {
|
||
// 判断是否为估值请求
|
||
const isValuationRequest = detectValuationIntent(text);
|
||
|
||
if (isValuationRequest && state.currentMode === 'valuation') {
|
||
// 调用独立估值引擎
|
||
await callValuationEngine(text, aiDiv, aiMsgId);
|
||
} else {
|
||
// 调用推理引擎SSE
|
||
await callInferenceSSE(text, aiDiv, aiMsgId);
|
||
}
|
||
} catch(e) {
|
||
updateAIMessage(aiMsgId, `⚠️ 服务暂时不可用,请稍后重试。\n\n错误:${e.message}`);
|
||
} finally {
|
||
state.isStreaming = false;
|
||
document.getElementById('sendBtn').disabled = false;
|
||
removeCursor(aiMsgId);
|
||
}
|
||
}
|
||
|
||
function detectValuationIntent(text) {
|
||
// 纯问答类关键词(优先排除,不触发估值引擎)
|
||
const qaKeywords = ['解释', '什么是', '如何', '为什么', '原理', '机制', '流程', '步骤',
|
||
'介绍', '说明', '讲解', '定义', '区别', '对比', '比较', '方法论',
|
||
'定价机制', '如何计算', '如何保障', '怎么', '怎样',
|
||
'explain', 'what is', 'how', 'why', 'process', 'methodology'];
|
||
const isQA = qaKeywords.some(k => text.toLowerCase().includes(k.toLowerCase()));
|
||
if (isQA) return false;
|
||
|
||
// 必须包含具体数值或资产描述才触发估值
|
||
const valuationKeywords = ['万元', '万港元', '万美元', '万新元', '万迪拉姆', '万英镑', '万欧元',
|
||
'平米', '平方米', '㎡', '亩', '英亩',
|
||
'市值', '售价', '成交价', '评估价',
|
||
'HKD', 'SGD', 'AED', 'USD', 'GBP', 'EUR', 'JPY', 'CNY',
|
||
'请估算', '请估值', '请评估', '帮我估'];
|
||
return valuationKeywords.some(k => text.toLowerCase().includes(k.toLowerCase()));
|
||
}
|
||
|
||
async function callValuationEngine(text, aiDiv, msgId) {
|
||
// 显示打字动画
|
||
showTyping(msgId);
|
||
|
||
try {
|
||
const res = await fetch(CONFIG.valuationApiBase + '/valuate', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
description: text,
|
||
jurisdiction: state.currentJurisdiction,
|
||
assetType: state.currentAssetType,
|
||
conversationMemory: state.conversationMemory.slice(-6),
|
||
language: CONFIG.lang
|
||
})
|
||
});
|
||
|
||
if (!res.ok) throw new Error('估值引擎返回 HTTP ' + res.status);
|
||
const data = await res.json();
|
||
|
||
removeTyping(msgId);
|
||
|
||
if (data.valuation && data.valuation.finalXTZH > 0) {
|
||
// 渲染估值结果
|
||
renderValuationResult(data, aiDiv, msgId);
|
||
// 添加到记忆
|
||
state.conversationMemory.push({
|
||
role: 'assistant',
|
||
content: `估值完成:${data.valuation.finalXTZH.toFixed(2)} XTZH,置信度 ${(data.valuation.confidence*100).toFixed(0)}%`
|
||
});
|
||
} else {
|
||
// 降级到推理引擎
|
||
await callInferenceSSE(text, aiDiv, msgId);
|
||
}
|
||
} catch(e) {
|
||
removeTyping(msgId);
|
||
// 降级到推理引擎
|
||
await callInferenceSSE(text, aiDiv, msgId);
|
||
}
|
||
}
|
||
|
||
async function callInferenceSSE(text, aiDiv, msgId) {
|
||
const lang = CONFIG.lang;
|
||
const systemPrompt = getSystemPrompt(lang);
|
||
|
||
const res = await fetch(CONFIG.inferenceApiBase + '/stream', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
question: text,
|
||
language: lang,
|
||
mode: state.currentMode,
|
||
jurisdiction: state.currentJurisdiction,
|
||
assetType: state.currentAssetType,
|
||
conversationMemory: state.conversationMemory.slice(-CONFIG.maxMemory * 2),
|
||
systemPrompt: systemPrompt,
|
||
sessionId: state.sessionId
|
||
})
|
||
});
|
||
|
||
if (!res.ok) throw new Error('推理引擎返回 HTTP ' + res.status);
|
||
|
||
const reader = res.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let buffer = '';
|
||
let fullContent = '';
|
||
|
||
showCursor(msgId);
|
||
|
||
while (true) {
|
||
const { done, value } = await reader.read();
|
||
if (done) break;
|
||
|
||
buffer += decoder.decode(value, { stream: true });
|
||
const lines = buffer.split('\n');
|
||
buffer = lines.pop() || '';
|
||
|
||
for (const line of lines) {
|
||
if (!line.startsWith('data: ')) continue;
|
||
const raw = line.slice(6).trim();
|
||
if (!raw || raw === '[DONE]') continue;
|
||
|
||
try {
|
||
const parsed = JSON.parse(raw);
|
||
if (parsed.type === 'chunk' && parsed.content) {
|
||
fullContent += parsed.content;
|
||
updateAIMessage(msgId, fullContent, true);
|
||
scrollToBottom();
|
||
} else if (parsed.type === 'done') {
|
||
break;
|
||
} else if (parsed.type === 'error') {
|
||
fullContent += '\n\n⚠️ ' + parsed.message;
|
||
updateAIMessage(msgId, fullContent, false);
|
||
}
|
||
} catch(e) {
|
||
// 非JSON数据,直接追加
|
||
if (raw && raw !== '[DONE]') {
|
||
fullContent += raw;
|
||
updateAIMessage(msgId, fullContent, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
state.conversationMemory.push({ role: 'assistant', content: fullContent });
|
||
}
|
||
|
||
function getSystemPrompt(lang) {
|
||
const prompts = {
|
||
zh: `你是NAC NewAssetChain的AI资产估值专家。专注于:
|
||
1. 多资产多辖区估值(住宅/商业/艺术品/金融资产等)
|
||
2. XTZH统一定价(SDR锚定,当前约$4.39/XTZH)
|
||
3. 估值方法论(市场法/收益法/成本法/DCF)
|
||
4. 一键上链流程(估值→合规→TOKEN→权证→代币)
|
||
5. 60+司法辖区合规要求
|
||
当前辖区:${state.currentJurisdiction},资产类型:${ASSET_NAMES[state.currentAssetType]}`,
|
||
en: `You are NAC NewAssetChain's AI Asset Valuation Expert. Focus on:
|
||
1. Multi-asset multi-jurisdiction valuation
|
||
2. XTZH unified pricing (SDR-pegged, ~$4.39/XTZH)
|
||
3. Valuation methods (Market/Income/Cost/DCF)
|
||
4. One-click on-chain process
|
||
Current jurisdiction: ${state.currentJurisdiction}, Asset type: ${state.currentAssetType}`
|
||
};
|
||
return prompts[lang] || prompts.zh;
|
||
}
|
||
|
||
// ============================================================
|
||
// 渲染估值结果
|
||
// ============================================================
|
||
function renderValuationResult(data, aiDiv, msgId) {
|
||
const v = data.valuation;
|
||
const input = data.input || {};
|
||
const lang = CONFIG.lang;
|
||
const t = I18N[lang] || I18N.zh;
|
||
|
||
const xtzhPrice = v.xtzhPrice ? v.xtzhPrice.usd : 4.3944;
|
||
const confidence = v.confidence || 0;
|
||
const finalXTZH = v.finalXTZH || 0;
|
||
const finalUSD = v.finalUSD || 0;
|
||
const stakeXTZH = finalXTZH * 0.8;
|
||
const methods = v.methodsUsed || [];
|
||
const rules = v.appliedRules || [];
|
||
|
||
// 构建方法摘要
|
||
let methodSummary = '';
|
||
if (methods.length > 0) {
|
||
methodSummary = `\n\n**估值方法融合:** ${methods.join(' + ')}`;
|
||
}
|
||
if (rules.length > 0) {
|
||
methodSummary += `\n**适用规则:** ${rules.join('、')}`;
|
||
}
|
||
|
||
// 估值摘要文本
|
||
const summaryText = `**资产估值完成** ✅
|
||
|
||
根据您描述的资产,AI估值引擎已完成多方法融合分析:${methodSummary}
|
||
|
||
> 以上估值基于NAC自研推理引擎(GNACS分类 + 辖区适配 + 规则引擎),采用SDR锚定XTZH统一定价机制。`;
|
||
|
||
updateAIMessage(msgId, summaryText, false);
|
||
|
||
// 添加估值结果卡片
|
||
const contentEl = document.querySelector(`#${msgId} .message-content`);
|
||
if (contentEl) {
|
||
const card = document.createElement('div');
|
||
card.className = 'valuation-card';
|
||
card.innerHTML = `
|
||
<div class="vc-header">
|
||
<span class="vc-title">💎 ${t.finalXTZH} 估值结果</span>
|
||
<span class="vc-badge">置信度 ${(confidence*100).toFixed(0)}%</span>
|
||
<span class="vc-badge" style="background:rgba(59,130,246,0.15);color:#60a5fa;border-color:rgba(59,130,246,0.3)">
|
||
${data.engineMode || 'SELF_INFERENCE_V4'}
|
||
</span>
|
||
</div>
|
||
<div class="valuation-grid">
|
||
<div class="valuation-item">
|
||
<div class="vi-label">${t.finalXTZH}</div>
|
||
<div class="vi-value gold">${finalXTZH.toLocaleString('zh-CN', {maximumFractionDigits:2})} XTZH</div>
|
||
<div class="vi-sub">≈ $${finalUSD.toLocaleString('en-US', {maximumFractionDigits:0})} USD</div>
|
||
</div>
|
||
<div class="valuation-item">
|
||
<div class="vi-label">${t.xtzhPrice}</div>
|
||
<div class="vi-value">$${parseFloat(xtzhPrice).toFixed(4)}</div>
|
||
<div class="vi-sub">${v.xtzhPrice ? v.xtzhPrice.source : 'NAC_SDR_MODEL_V4'}</div>
|
||
</div>
|
||
<div class="valuation-item">
|
||
<div class="vi-label">${t.stakeRequired}</div>
|
||
<div class="vi-value green">${stakeXTZH.toLocaleString('zh-CN', {maximumFractionDigits:2})} XTZH</div>
|
||
<div class="vi-sub">可发行权益代币数量</div>
|
||
</div>
|
||
<div class="valuation-item">
|
||
<div class="vi-label">辖区 / GNACS</div>
|
||
<div class="vi-value">${state.currentJurisdiction}</div>
|
||
<div class="vi-sub">${GNACS_MAP[state.currentAssetType] || 'GNACS-01'}</div>
|
||
</div>
|
||
</div>
|
||
<div class="confidence-bar">
|
||
<div class="confidence-fill" style="width:${confidence*100}%"></div>
|
||
</div>
|
||
<div class="chain-actions">
|
||
<button class="chain-btn primary" onclick="applyOnChain(${JSON.stringify({finalXTZH, finalUSD, jurisdiction: state.currentJurisdiction}).replace(/"/g,'"')})">
|
||
⛓️ ${t.chainBtn}
|
||
</button>
|
||
<button class="chain-btn secondary" onclick="viewValuationDetail()">
|
||
📄 ${t.detailBtn}
|
||
</button>
|
||
<button class="chain-btn secondary" onclick="compareJurisdictions()">
|
||
🌍 辖区对比
|
||
</button>
|
||
</div>
|
||
`;
|
||
contentEl.appendChild(card);
|
||
}
|
||
|
||
scrollToBottom();
|
||
}
|
||
|
||
function applyOnChain(data) {
|
||
const msg = `我希望将这个资产上链,估值为 ${data.finalXTZH.toFixed(2)} XTZH($${data.finalUSD.toFixed(0)} USD),辖区 ${data.jurisdiction}。请告诉我上链流程和需要准备的材料。`;
|
||
document.getElementById('chatInput').value = msg;
|
||
toggleMode('chain', document.getElementById('chip-chain'));
|
||
sendMessage();
|
||
}
|
||
|
||
function viewValuationDetail() {
|
||
quickAsk('请详细解释刚才的估值计算过程,包括使用的方法、权重分配和辖区调整系数');
|
||
}
|
||
|
||
function compareJurisdictions() {
|
||
quickAsk(`请对比香港、新加坡、阿联酋三个辖区对于${ASSET_NAMES[state.currentAssetType]}的估值差异和辖区乘数`);
|
||
}
|
||
|
||
// ============================================================
|
||
// DOM操作
|
||
// ============================================================
|
||
function appendUserMessage(text) {
|
||
const messages = document.getElementById('messages');
|
||
const div = document.createElement('div');
|
||
div.className = 'message user';
|
||
div.innerHTML = `
|
||
<div class="message-avatar">👤</div>
|
||
<div class="message-content">${escapeHtml(text)}</div>
|
||
`;
|
||
messages.appendChild(div);
|
||
state.messages.push({ role: 'user', content: text });
|
||
scrollToBottom();
|
||
}
|
||
|
||
function appendAIMessage(content, id) {
|
||
const messages = document.getElementById('messages');
|
||
const div = document.createElement('div');
|
||
div.className = 'message ai';
|
||
div.id = id;
|
||
div.innerHTML = `
|
||
<div class="message-avatar">V</div>
|
||
<div class="message-content">${content ? renderMarkdown(content) : '<div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div>'}</div>
|
||
`;
|
||
messages.appendChild(div);
|
||
scrollToBottom();
|
||
return div;
|
||
}
|
||
|
||
function updateAIMessage(id, content, streaming) {
|
||
const el = document.querySelector(`#${id} .message-content`);
|
||
if (!el) return;
|
||
el.innerHTML = renderMarkdown(content) + (streaming ? '<span class="stream-cursor"></span>' : '');
|
||
}
|
||
|
||
function showTyping(id) {
|
||
const el = document.querySelector(`#${id} .message-content`);
|
||
if (el) el.innerHTML = '<div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div>';
|
||
}
|
||
|
||
function removeTyping(id) {
|
||
const el = document.querySelector(`#${id} .message-content`);
|
||
if (el) el.innerHTML = '';
|
||
}
|
||
|
||
function showCursor(id) {
|
||
const el = document.querySelector(`#${id} .message-content`);
|
||
if (el && !el.querySelector('.stream-cursor')) {
|
||
el.innerHTML += '<span class="stream-cursor"></span>';
|
||
}
|
||
}
|
||
|
||
function removeCursor(id) {
|
||
const cursor = document.querySelector(`#${id} .stream-cursor`);
|
||
if (cursor) cursor.remove();
|
||
}
|
||
|
||
function scrollToBottom() {
|
||
const messages = document.getElementById('messages');
|
||
messages.scrollTop = messages.scrollHeight;
|
||
}
|
||
|
||
// ============================================================
|
||
// Markdown渲染
|
||
// ============================================================
|
||
function escapeInline(text) {
|
||
return text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
}
|
||
|
||
function renderMarkdown(text) {
|
||
if (!text) return '';
|
||
const lines = text.split('\n');
|
||
const output = [];
|
||
let inTable = false;
|
||
let tableRows = [];
|
||
let isFirstRow = true;
|
||
|
||
function flushTable() {
|
||
if (tableRows.length) {
|
||
output.push('<table style="border-collapse:collapse;width:100%;margin:8px 0;font-size:13px">' + tableRows.join('') + '</tbody></table>');
|
||
}
|
||
inTable = false; tableRows = []; isFirstRow = true;
|
||
}
|
||
|
||
function processLine(line) {
|
||
const e = escapeInline(line);
|
||
if (/^### /.test(e)) return e.replace(/^### (.+)$/, '<h3 style="color:var(--accent-gold-light);margin:10px 0 5px;font-size:14px">$1</h3>');
|
||
if (/^## /.test(e)) return e.replace(/^## (.+)$/, '<h2 style="color:var(--accent-gold);margin:12px 0 6px;font-size:15px">$1</h2>');
|
||
if (/^# /.test(e)) return e.replace(/^# (.+)$/, '<h1 style="color:var(--accent-gold);margin:14px 0 8px;font-size:16px">$1</h1>');
|
||
if (/^> /.test(e)) return e.replace(/^> (.+)$/, '<blockquote style="border-left:3px solid var(--accent-gold);padding-left:12px;color:var(--text-secondary);margin:8px 0">$1</blockquote>');
|
||
if (/^[-*] /.test(e)) return e.replace(/^[-*] (.+)$/, (_, content) => {
|
||
const inlined = content
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong style="color:var(--accent-gold-light)">$1</strong>')
|
||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||
.replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,0.3);padding:2px 6px;border-radius:4px;font-size:12px">$1</code>');
|
||
return `<li style="margin:3px 0 3px 16px">${inlined}</li>`;
|
||
});
|
||
return e
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong style="color:var(--accent-gold-light)">$1</strong>')
|
||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||
.replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,0.3);padding:2px 6px;border-radius:4px;font-size:12px">$1</code>');
|
||
}
|
||
|
||
for (let i = 0; i < lines.length; i++) {
|
||
const line = lines[i];
|
||
const trimmed = line.trim();
|
||
const isTableRow = /^\|.+\|$/.test(trimmed);
|
||
const isSeparator = /^\|[-|: ]+\|$/.test(trimmed);
|
||
|
||
if (isTableRow && !isSeparator) {
|
||
if (!inTable) { inTable = true; isFirstRow = true; tableRows = []; }
|
||
const cells = trimmed.split('|').filter(c => c !== '').map(c => escapeInline(c.trim())
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'));
|
||
if (isFirstRow) {
|
||
tableRows.push('<thead><tr>' + cells.map(c => `<th style="background:rgba(212,168,67,0.1);color:var(--accent-gold);border:1px solid var(--border);padding:6px 10px">${c}</th>`).join('') + '</tr></thead><tbody>');
|
||
isFirstRow = false;
|
||
} else {
|
||
tableRows.push('<tr>' + cells.map(c => `<td style="border:1px solid var(--border);padding:6px 10px">${c}</td>`).join('') + '</tr>');
|
||
}
|
||
} else if (isSeparator) {
|
||
// skip separator
|
||
} else {
|
||
if (inTable) flushTable();
|
||
if (trimmed === '') {
|
||
output.push('<br>');
|
||
} else {
|
||
output.push(processLine(line));
|
||
}
|
||
}
|
||
}
|
||
if (inTable) flushTable();
|
||
|
||
// 将连续的 li 包裹在 ul 中
|
||
return output.join('\n')
|
||
.replace(/(<li[^>]*>[\s\S]*?<\/li>\n?)+/g, m => `<ul style="margin:6px 0">${m}</ul>`)
|
||
.replace(/\n/g, '<br>');
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
return text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
}
|
||
|
||
// ============================================================
|
||
// 快捷问题
|
||
// ============================================================
|
||
function quickAsk(text) {
|
||
document.getElementById('chatInput').value = text;
|
||
sendMessage();
|
||
}
|
||
|
||
// ============================================================
|
||
// 新建对话 / 清除记忆
|
||
// ============================================================
|
||
function newChat() {
|
||
state.messages = [];
|
||
state.conversationMemory = [];
|
||
state.sessionId = generateSessionId();
|
||
state.isStreaming = false;
|
||
|
||
const messages = document.getElementById('messages');
|
||
messages.innerHTML = `
|
||
<div class="welcome" id="welcome">
|
||
<div class="welcome-icon">💎</div>
|
||
<h1>NAC AI 资产估值引擎</h1>
|
||
<p>基于白皮书自研推理引擎,支持20大类资产、60+司法辖区。采用SDR锚定的XTZH统一定价机制,一键上链生成TOKEN权证。</p>
|
||
<div class="quick-cards">
|
||
<div class="quick-card" onclick="quickAsk('香港九龙区120平米住宅公寓,市值800万港元,请估算XTZH价值')">
|
||
<div class="card-icon">🏠</div>
|
||
<div class="card-title">住宅不动产估值</div>
|
||
<div class="card-desc">香港/新加坡/阿联酋住宅资产XTZH换算</div>
|
||
</div>
|
||
<div class="quick-card" onclick="quickAsk('新加坡CBD商业写字楼1200平米,年租金收入180万SGD,请用收益法估值')">
|
||
<div class="card-icon">🏢</div>
|
||
<div class="card-title">商业地产收益法</div>
|
||
<div class="card-desc">基于年租金收益的资本化率估值</div>
|
||
</div>
|
||
<div class="quick-card" onclick="quickAsk('请解释XTZH的SDR锚定定价机制,当前价格如何计算')">
|
||
<div class="card-icon">💰</div>
|
||
<div class="card-title">XTZH定价机制</div>
|
||
<div class="card-desc">SDR五货币篮子+黄金储备保障原理</div>
|
||
</div>
|
||
<div class="quick-card" onclick="quickAsk('资产上链流程是什么?从估值到TOKEN生成需要哪些步骤')">
|
||
<div class="card-icon">⛓️</div>
|
||
<div class="card-title">一键上链流程</div>
|
||
<div class="card-desc">估值→合规→TOKEN→权证→代币发行</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.getElementById('sendBtn').disabled = false;
|
||
}
|
||
|
||
function clearMemory() {
|
||
state.conversationMemory = [];
|
||
newChat();
|
||
}
|
||
|
||
// ============================================================
|
||
// 历史记录
|
||
// ============================================================
|
||
function addToHistory(title) {
|
||
const item = { id: state.sessionId, title, time: Date.now() };
|
||
state.history.unshift(item);
|
||
if (state.history.length > 20) state.history = state.history.slice(0, 20);
|
||
localStorage.setItem('nac_valuation_history', JSON.stringify(state.history));
|
||
renderHistory();
|
||
}
|
||
|
||
function renderHistory() {
|
||
const list = document.getElementById('historyList');
|
||
list.innerHTML = state.history.map(item => `
|
||
<div class="history-item ${item.id === state.sessionId ? 'active' : ''}" onclick="loadHistory('${item.id}')">
|
||
💎 ${item.title}
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function loadHistory(id) {
|
||
// 简单切换激活状态(完整实现需要持久化消息)
|
||
document.querySelectorAll('.history-item').forEach(el => el.classList.remove('active'));
|
||
event.target.classList.add('active');
|
||
}
|
||
|
||
// ============================================================
|
||
// 语言切换
|
||
// ============================================================
|
||
function switchLanguage(lang) {
|
||
CONFIG.lang = lang;
|
||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||
const key = el.getAttribute('data-i18n');
|
||
const t = I18N[lang] || I18N.zh;
|
||
if (t[key]) el.textContent = t[key];
|
||
});
|
||
const input = document.getElementById('chatInput');
|
||
const t = I18N[lang] || I18N.zh;
|
||
if (t.placeholder) input.placeholder = t.placeholder;
|
||
}
|
||
|
||
// ============================================================
|
||
// 键盘事件
|
||
// ============================================================
|
||
function handleKeyDown(e) {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
sendMessage();
|
||
}
|
||
}
|
||
|
||
function autoResize(el) {
|
||
el.style.height = 'auto';
|
||
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
|
||
}
|
||
|
||
// ============================================================
|
||
// 初始化
|
||
// ============================================================
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
fetchXTZHPrice();
|
||
setInterval(fetchXTZHPrice, 60000); // 每分钟刷新
|
||
renderHistory();
|
||
document.getElementById('chatInput').focus();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|