diff --git a/Config/Config.py b/Config/Config.py index a60510b..de13f46 100644 --- a/Config/Config.py +++ b/Config/Config.py @@ -1,16 +1,16 @@ # 黄海在公司内网开发时的配置信息 -# DORIS_HOST = "10.10.14.204" -# DORIS_PORT = 9030 -# DORIS_FENODES = "10.10.14.204:8030" -# REDIS_HOST = '10.10.14.14' -# REDIS_PASSWORD = None # 如果没有密码则设为 None +DORIS_HOST = "10.10.14.204" +DORIS_PORT = 9030 +DORIS_FENODES = "10.10.14.204:8030" +REDIS_HOST = '10.10.14.14' +REDIS_PASSWORD = None # 如果没有密码则设为 None # 黄海在家开发时的配置信息 -DORIS_HOST = "www.hzkjai.com" -DORIS_PORT = 27025 -DORIS_FENODES = "www.hzkjai.com:27024" -REDIS_HOST = '127.0.0.1' -REDIS_PASSWORD = "DsideaL147258369" +# DORIS_HOST = "www.hzkjai.com" +# DORIS_PORT = 27025 +# DORIS_FENODES = "www.hzkjai.com:27024" +# REDIS_HOST = '127.0.0.1' +# REDIS_PASSWORD = "DsideaL147258369" # 视觉模型配置 VL_MODEL_NAME = "qwen3-vl-flash" diff --git a/Config/__pycache__/Config.cpython-310.pyc b/Config/__pycache__/Config.cpython-310.pyc index 696a5c7..beab9d5 100644 Binary files a/Config/__pycache__/Config.cpython-310.pyc and b/Config/__pycache__/Config.cpython-310.pyc differ diff --git a/static/css/dashboard.css b/static/css/dashboard.css new file mode 100644 index 0000000..b6a0fe7 --- /dev/null +++ b/static/css/dashboard.css @@ -0,0 +1,255 @@ +/* Dashboard CSS */ +:root { + --bg-color: #0f172a; + --card-bg: #1e293b; + --card-border: #334155; + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --accent-color: #3b82f6; + --accent-hover: #2563eb; + --success-color: #10b981; + --table-header-bg: #0f172a; + --table-row-hover: #334155; + --scrollbar-thumb: #475569; + --scrollbar-track: #0f172a; +} + +body { + margin: 0; + font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background-color: var(--bg-color); + color: var(--text-primary); + height: 100vh; + overflow: hidden; + background-image: radial-gradient(circle at 50% 0%, #1e293b 0%, #0f172a 100%); +} + +#app { + display: flex; + flex-direction: column; + height: 100vh; + padding: 16px; + gap: 16px; + box-sizing: border-box; +} + +/* Header */ +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px; + flex: 0 0 auto; +} + +.dashboard-title { + font-size: 24px; + font-weight: 700; + background: linear-gradient(to right, #60a5fa, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + cursor: pointer; + text-decoration: none; +} + +.home-link { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + text-decoration: none; + font-size: 14px; + font-weight: 600; + transition: color 0.2s; +} + +.home-link:hover { + color: var(--text-primary); +} + +/* Controls */ +.controls { + display: flex; + gap: 12px; + align-items: center; +} + +.btn-primary { + padding: 8px 16px; + border-radius: 8px; + border: none; + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + color: white; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); + font-size: 14px; +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 6px rgba(59, 130, 246, 0.4); +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + filter: grayscale(0.5); +} + +/* Layout */ +.main-content { + display: flex; + flex: 1; + gap: 16px; + min-height: 0; +} + +.left-panel { + flex: 3; + display: flex; + flex-direction: column; + gap: 16px; + min-width: 0; +} + +.right-panel { + flex: 2; + display: flex; + flex-direction: column; + background-color: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--card-border); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + padding: 20px; + min-width: 0; +} + +/* Station List */ +.station-list { + flex: 1; + min-height: 0; + background-color: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 12px; + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +.station-list-header { + padding: 16px 20px; + border-bottom: 1px solid var(--card-border); + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + background-color: rgba(15, 23, 42, 0.5); +} + +.station-table-container { + flex: 1; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +.station-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; +} + +.station-table th { + background-color: var(--table-header-bg); + color: var(--text-secondary); + font-weight: 600; + padding: 12px 16px; + position: sticky; + top: 0; + z-index: 10; + border-bottom: 1px solid var(--card-border); + text-align: center; +} + +.station-table td { + padding: 12px 16px; + border-bottom: 1px solid var(--card-border); + color: var(--text-primary); + text-align: center; +} + +.station-table tbody tr:hover td { + background-color: var(--table-row-hover); +} + +/* Chart */ +#chart { + flex: 1; + min-height: 300px; + background-color: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 12px; + padding: 16px; + box-sizing: border-box; +} + +/* AI Analysis */ +.ai-title { + font-size: 18px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 16px; + display: flex; + align-items: center; + gap: 8px; +} + +.ai-title::before { + content: ''; + display: block; + width: 4px; + height: 18px; + background-color: var(--accent-color); + border-radius: 2px; +} + +.ai-box { + flex: 1; + background-color: rgba(15, 23, 42, 0.3); + border-radius: 8px; + border: 1px solid var(--card-border); + padding: 16px; + overflow-y: auto; + font-size: 14px; + line-height: 1.7; + color: #cbd5e1; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) transparent; +} + +/* Markdown */ +.markdown-body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.6; + word-wrap: break-word; + color: #cbd5e1; +} + +.markdown-body ul, .markdown-body ol { + padding-left: 2em; + margin-top: 0; + margin-bottom: 16px; +} +.markdown-body li { margin: 0.25em 0; } +.markdown-body strong { font-weight: 600; color: #f1f5f9; } +.markdown-body h3 { font-size: 1.1em; font-weight: bold; margin-top: 16px; margin-bottom: 8px; color: #f1f5f9; } + +/* Custom Scrollbar */ +::-webkit-scrollbar { width: 8px; height: 8px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 4px; } +::-webkit-scrollbar-thumb:hover { background: #64748b; } diff --git a/static/css/douyin.css b/static/css/douyin.css new file mode 100644 index 0000000..7991c5a --- /dev/null +++ b/static/css/douyin.css @@ -0,0 +1,273 @@ +/* Douyin CSS */ +:root { + --bg-color: #0f172a; + --card-bg: #1e293b; + --card-border: #334155; + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --accent-color: #3b82f6; + --accent-hover: #2563eb; + --success-color: #10b981; + --scrollbar-thumb: #475569; + --scrollbar-track: #0f172a; +} + +body { + margin: 0; + font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background-color: var(--bg-color); + color: var(--text-primary); + height: 100vh; + overflow: hidden; + background-image: radial-gradient(circle at 50% 0%, #1e293b 0%, #0f172a 100%); +} + +#app { + display: flex; + flex-direction: column; + height: 100vh; + padding: 16px; + gap: 16px; + box-sizing: border-box; +} + +/* Header */ +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px; + flex: 0 0 auto; +} + +.dashboard-title { + font-size: 24px; + font-weight: 700; + background: linear-gradient(to right, #60a5fa, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + cursor: pointer; + text-decoration: none; +} + +.home-link { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + text-decoration: none; + font-size: 14px; + font-weight: 600; + transition: color 0.2s; +} + +.home-link:hover { + color: var(--text-primary); +} + +.btn-primary { + padding: 8px 16px; + border-radius: 8px; + border: none; + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + color: white; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); + font-size: 14px; +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 6px rgba(59, 130, 246, 0.4); +} + +/* Douyin Specific Styles */ +.douyin-container { + padding: 24px; + max-width: 95%; + margin: 0 auto; + width: 100%; + flex: 1; + overflow-y: auto; + scrollbar-width: thin; +} + +.douyin-header { + text-align: center; + margin-bottom: 40px; +} + +.douyin-title { + font-size: 2.5rem; + font-weight: bold; + color: #f1f5f9; + margin-bottom: 0.5rem; + display: flex; + justify-content: center; + align-items: center; + gap: 12px; +} + +.douyin-subtitle { + color: #94a3b8; +} + +.summary-btn { + background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); + border: none; +} + +.records-list { + display: flex; + flex-direction: column; + gap: 20px; +} + +.record-card { + background-color: #fff; + border-radius: 4px; + overflow: hidden; + box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); +} + +.record-content { + display: flex; + border-left: 4px solid; + background-color: #fff; +} + +.record-main { + padding: 24px; + flex: 1; +} + +.record-header { + display: flex; + justify-content: space-between; + align-items: start; + margin-bottom: 16px; +} + +.record-meta { + display: flex; + align-items: center; + margin-bottom: 8px; +} + +.record-date { + color: #9ca3af; + font-size: 12px; +} + +.record-title { + font-size: 1.25rem; + font-weight: bold; + color: #1f2937; + line-height: 1.4; + margin: 0; +} + +.record-video-link { + color: #3b82f6; + font-size: 14px; + margin-top: 4px; + display: inline-block; + text-decoration: none; +} + +.record-error { + margin-top: 12px; + color: #ef4444; + font-size: 13px; + background: #fee2e2; + padding: 8px; + border-radius: 4px; + word-break: break-all; +} + +.transcript-box { + background-color: #f9fafb; + border-radius: 8px; + padding: 16px; + margin-top: 16px; +} + +.transcript-title { + font-size: 14px; + font-weight: 600; + color: #4b5563; + margin-bottom: 8px; + text-transform: uppercase; + margin-top: 0; +} + +.transcript-text { + color: #374151; + white-space: pre-wrap; + font-size: 14px; + line-height: 1.6; + margin: 0; +} + +.original-text-toggle { + margin-top: 16px; +} + +.original-text-btn { + cursor: pointer; + color: #9ca3af; + font-size: 12px; +} + +.original-text-content { + color: #6b7280; + font-size: 12px; + background: #f3f4f6; + padding: 8px; + border-radius: 4px; + margin-top: 4px; +} + +.cursor-blink { + display: inline-block; + width: 2px; + height: 1em; + background-color: #38bdf8; + vertical-align: text-bottom; + animation: blink 1s step-end infinite; +} + +/* Markdown Styles */ +.markdown-body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.6; + word-wrap: break-word; + color: #334155; +} + +.markdown-body ul, .markdown-body ol { + padding-left: 2em; + margin-top: 0; + margin-bottom: 16px; +} +.markdown-body li { margin: 0.25em 0; } +.markdown-body strong { font-weight: 600; color: #0f172a; } + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* Element Plus Overrides */ +.el-card { + background-color: #fff; + color: #303133; + transition: .3s; +} + +.el-dialog__body { + padding: 10px 20px; +} diff --git a/static/css/index.css b/static/css/index.css new file mode 100644 index 0000000..a4f9fb5 --- /dev/null +++ b/static/css/index.css @@ -0,0 +1,152 @@ +:root { + --bg-color: #0f172a; + --card-bg: rgba(30, 41, 59, 0.7); + --card-border: rgba(148, 163, 184, 0.1); + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --accent-blue: #3b82f6; + --accent-purple: #8b5cf6; + --accent-cyan: #06b6d4; + --accent-emerald: #10b981; +} + +body { + margin: 0; + padding: 0; + background-color: var(--bg-color); + color: var(--text-primary); + font-family: 'Inter', system-ui, -apple-system, sans-serif; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + background-image: + radial-gradient(circle at 15% 50%, rgba(59, 130, 246, 0.08), transparent 25%), + radial-gradient(circle at 85% 30%, rgba(139, 92, 246, 0.08), transparent 25%); +} + +.main-container { + width: 100%; + max-width: 1200px; + padding: 60px 20px; + box-sizing: border-box; + text-align: center; +} + +.header { + margin-bottom: 80px; +} + +.title { + font-size: 3.5rem; + font-weight: 800; + margin: 0 0 20px 0; + background: linear-gradient(135deg, #60a5fa 0%, #c084fc 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + letter-spacing: -0.02em; +} + +.subtitle { + font-size: 1.25rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto; + line-height: 1.6; +} + +.cards-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 24px; + padding: 20px; +} + +.card { + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 20px; + padding: 40px 30px; + text-align: left; + text-decoration: none; + color: inherit; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + backdrop-filter: blur(10px); + display: flex; + flex-direction: column; + height: 100%; + box-sizing: border-box; +} + +.card:hover { + transform: translateY(-5px); + background: rgba(30, 41, 59, 0.9); + border-color: rgba(148, 163, 184, 0.3); + box-shadow: 0 20px 40px -5px rgba(0, 0, 0, 0.3); +} + +.card-icon { + font-size: 3rem; + margin-bottom: 24px; + display: inline-block; + padding: 16px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.05); + width: fit-content; +} + +.card h2 { + font-size: 1.5rem; + font-weight: 700; + margin: 0 0 12px 0; + color: var(--text-primary); +} + +.card p { + font-size: 0.95rem; + color: var(--text-secondary); + line-height: 1.6; + margin: 0; + flex-grow: 1; +} + +.card-arrow { + margin-top: 24px; + font-weight: 600; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 8px; + opacity: 0; + transform: translateX(-10px); + transition: all 0.3s ease; +} + +.card:hover .card-arrow { + opacity: 1; + transform: translateX(0); +} + +/* Specific Card Colors */ +.card.dashboard .card-icon { color: var(--accent-blue); background: rgba(59, 130, 246, 0.1); } +.card.dashboard:hover .card-arrow { color: var(--accent-blue); } + +.card.query .card-icon { color: var(--accent-emerald); background: rgba(16, 185, 129, 0.1); } +.card.query:hover .card-arrow { color: var(--accent-emerald); } + +.card.douyin .card-icon { color: var(--accent-purple); background: rgba(139, 92, 246, 0.1); } +.card.douyin:hover .card-arrow { color: var(--accent-purple); } + +.card.haibao .card-icon { color: var(--accent-cyan); background: rgba(6, 182, 212, 0.1); } +.card.haibao:hover .card-arrow { color: var(--accent-cyan); } + +@media (max-width: 1024px) { + .cards-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 768px) { + .title { font-size: 2.5rem; } + .cards-grid { grid-template-columns: 1fr; } +} diff --git a/static/css/query.css b/static/css/query.css new file mode 100644 index 0000000..5cdac8e --- /dev/null +++ b/static/css/query.css @@ -0,0 +1,287 @@ +/* Query CSS */ +:root { + --bg-color: #0f172a; + --card-bg: #1e293b; + --card-border: #334155; + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --accent-color: #3b82f6; + --accent-hover: #2563eb; + --success-color: #10b981; + --table-header-bg: #0f172a; + --table-row-hover: #334155; + --scrollbar-thumb: #475569; + --scrollbar-track: #0f172a; +} + +body { + margin: 0; + font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background-color: var(--bg-color); + color: var(--text-primary); + height: 100vh; + overflow: hidden; + background-image: radial-gradient(circle at 50% 0%, #1e293b 0%, #0f172a 100%); +} + +#app { + display: flex; + flex-direction: column; + height: 100vh; + padding: 16px; + gap: 16px; + box-sizing: border-box; +} + +/* Header */ +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px; + flex: 0 0 auto; +} + +.dashboard-title { + font-size: 24px; + font-weight: 700; + background: linear-gradient(to right, #60a5fa, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + cursor: pointer; + text-decoration: none; +} + +.home-link { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + text-decoration: none; + font-size: 14px; + font-weight: 600; + transition: color 0.2s; +} + +.home-link:hover { + color: var(--text-primary); +} + +/* Query Specific Styles */ +.degree-container { + max-width: 1000px; + margin: 40px auto; + padding: 0 20px; + width: 100%; + flex: 1; + overflow-y: auto; + scrollbar-width: thin; +} + +.query-section { + background: rgba(15, 23, 42, 0.96); + border-radius: 12px; + padding: 30px; + box-shadow: 0 24px 60px rgba(15, 23, 42, 0.9); + margin-bottom: 30px; + text-align: center; + border: 1px solid var(--card-border); +} + +.page-title { + font-size: 32px; + background: linear-gradient(135deg, #fff 0%, #94a3b8 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 16px; + font-weight: 700; + letter-spacing: -0.025em; +} + +.page-subtitle { + margin-bottom: 32px; + color: #94a3b8; + font-size: 16px; + opacity: 0.9; +} + +.input-wrapper { + max-width: 700px; + margin: 0 auto; + position: relative; +} + +/* Element Plus Overrides for Dark Theme */ +.search-input.el-input { + --el-input-border-color: rgba(148, 163, 184, 0.5); + --el-input-hover-border-color: rgba(56, 189, 248, 0.9); + --el-input-focus-border-color: rgba(56, 189, 248, 0.9); + --el-input-bg-color: rgba(15, 23, 42, 0.9); + --el-input-text-color: #e5e7eb; +} + +.search-input .el-input__wrapper { + padding-left: 20px; + padding-right: 20px; + box-shadow: 0 2px 18px rgba(15, 23, 42, 0.6); + background-color: rgba(15, 23, 42, 0.9); + border: 1px solid rgba(148, 163, 184, 0.5); + transition: all 0.3s ease; + border-radius: 24px 0 0 24px; +} + +.search-input .el-input-group__append { + background-color: rgba(56, 189, 248, 0.9); + border: 1px solid rgba(56, 189, 248, 0.9); + border-left: none; + border-radius: 0 24px 24px 0; + padding: 0; + box-shadow: 0 2px 18px rgba(15, 23, 42, 0.6); +} + +.search-input .el-input-group__append .el-button { + border: none; + margin: 0; + padding: 0 30px; + height: 48px; + color: #fff; + background-color: transparent; + font-weight: 600; + font-size: 16px; + transition: all 0.2s; + border-radius: 0; +} + +.search-input .el-input-group__append .el-button:hover { + background-color: rgba(14, 165, 233, 1); + color: #fff; +} + +.search-input .el-input__inner { + height: 48px; + font-size: 16px; + color: #e5e7eb; +} + +.example-tags { + margin-top: 16px; + display: flex; + justify-content: center; + gap: 10px; + flex-wrap: wrap; +} + +.example-tag { + cursor: pointer; + transition: all 0.2s; + border-color: rgba(148, 163, 184, 0.6); + color: #cbd5f5; + background-color: rgba(15, 23, 42, 0.8); +} + +.example-tag:hover { + transform: translateY(-2px); + border-color: rgba(56, 189, 248, 0.9); + color: #e0f2fe; + background-color: rgba(15, 23, 42, 1); +} + +.result-section { + background: rgba(15, 23, 42, 0.96); + border-radius: 12px; + padding: 30px; + box-shadow: 0 24px 60px rgba(15, 23, 42, 0.9); + min-height: 200px; + border: 1px solid var(--card-border); + margin-bottom: 30px; +} + +.result-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + border-bottom: 1px solid #334155; + padding-bottom: 15px; +} + +.result-title { + font-size: 18px; + font-weight: 600; + color: #e5e7eb; + display: flex; + align-items: center; + gap: 8px; +} + +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 0; + color: rgba(148, 163, 184, 0.9); +} + +.cursor-blink { + display: inline-block; + width: 2px; + height: 1em; + background-color: #38bdf8; + vertical-align: text-bottom; + animation: blink 1s step-end infinite; +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* Markdown Styles */ +.markdown-body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.6; + word-wrap: break-word; + color: #e5e7eb; +} + +.markdown-body ul, .markdown-body ol { + padding-left: 2em; + margin-top: 0; + margin-bottom: 16px; +} +.markdown-body li { margin: 0.25em 0; } +.markdown-body strong { font-weight: 600; color: #60a5fa; } + +.markdown-body table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + font-size: 14px; +} + +.markdown-body th, .markdown-body td { + padding: 12px; + border: 1px solid rgba(51, 65, 85, 0.9); +} + +.markdown-body th { + background-color: rgba(30, 64, 175, 0.5); + font-weight: 600; +} + +.markdown-body tr:nth-child(even) { + background-color: rgba(15, 23, 42, 0.5); +} + +/* ECharts Container in Result */ +.echarts-container { + width: 100%; + height: 400px; + margin: 24px 0; + background: rgba(15, 23, 42, 0.4); + border: 1px solid rgba(56, 189, 248, 0.1); + border-radius: 12px; + overflow: hidden; +} diff --git a/static/dashboard.html b/static/dashboard.html new file mode 100644 index 0000000..efb4e9a --- /dev/null +++ b/static/dashboard.html @@ -0,0 +1,90 @@ + + + + + + 分时电价分析 - 驿来特AI智能大脑 + + + +
+
+ ⚡ 驿来特AI智能大脑 + ↩ 返回首页 + +
+ + +
+
+ +
+ +
+
+
+ 🕒 分时电价明细 +
+
+ + + + + + + + + + + + + +
时间{{ op.label }}
{{ row.hour }} + {{ formatCell(val.price) }} +
+
+
+
+ + +
+
+
📈 价格趋势对比
+
+ + +
+
+ +
+ +
+ +
+ 🤖 AI 调价策略建议 + +
+
+
+ 点击“开始分析”获取AI智能定价建议 +
+
+
+
+
+
+ + + + + + + diff --git a/static/douyin.html b/static/douyin.html new file mode 100644 index 0000000..bbfcd0e --- /dev/null +++ b/static/douyin.html @@ -0,0 +1,168 @@ + + + + + + 抖音知识库 - 驿来特AI智能大脑 + + + + + + +
+ + ← 返回首页 + + + + +
+
+

⚡ AI正在阅读您的知识库并提炼精华,请稍候...

+
+
+ | +
+ +
+ +
+
+

+ 抖音知识库 + + +

+

自动解析视频、提取文案,构建企业充电知识图谱

+
+ + + + + +
+ + {{ douyinLoading ? '解析处理中...' : '开始解析' }} + +
+
+ + +
+ +
+
+
+
+
+ + {{ record.status }} + + + {{ formatDate(record.create_time) }} + +
+

+ {{ record.video_name || '处理中...' }} +

+ + 📺 点击观看视频 + +
+ + Del + +
+ + +
+ Error: {{ record.error_msg }} +
+ + +
+

视频文案

+

+ {{ record.transcript }} +

+ + {{ record.expanded ? '收起' : '展开全文' }} + +
+ + +
+
+ {{ record.showOriginal ? '收起原始链接' : '查看原始链接信息' }} +
+
+ {{ record.original_text }} +
+
+
+
+
+ +
+

暂无记录,请粘贴链接开始解析

+
+
+
+
+ + + + + + + + diff --git a/static/index.html b/static/index.html index 9a6f451..c2d9334 100644 --- a/static/index.html +++ b/static/index.html @@ -1,352 +1,53 @@ - + - - -驿来特AI智能数据分析平台 - - - - - + + + 驿来特AI智能大脑系统 + + -
- - -
-
-
-

⚡ 系统特性介绍

-
-
-
-
- 📱 -
-

本系统采用 手机爬虫 获取4家充电供应商准实时各时段电价

-
-
-
- 🧠 -
-

结合 数据仓库与AI技术,对我司电价进行智能分析,给出定价建议

-
-
-
- 📊 -
-

对我司的各场站营业情况进行 分析,查询

-
-
-
- 🎨 -
-

新增 智能海报生成 功能,未来将结合业务数据,一键生成精美的数据战报与营销海报

-
-
-
- 🎥 -
-

新增 抖音知识库:支持视频解析、知识获取与总结、博主专栏订阅,自动生成 充电企业知识日报,助力企业构建专属知识库

-
-
-
- 🎯 -
-

未来:可以根据用户充电信息,形成用户画像,结合企业微信,实现 用户广告的精准推送

-
-
-
- 🧭 -
-

未来:基于 LBS位置服务,智能对比周边竞对场站的价格与配套(快充、休息室等),精准引导用户选择我司优势站点

-
-
- -
-
-
- -
-
⚡ 驿来特AI智能数据分析平台
- - - -
- - -
-
- -
-
- - -
-
-
-
全网供应商24小时电价监控
-
- - - - - - - - - - - - - - - - -
时段{{ op.label }}
{{ row.hour }} - {{ formatCell(cell.price) }} -
- 数据加载中... -
-
-
-
-
- -
-
- 智能决策分析助手 - -
-
-
-
当前分析任务
-
- 请根据爬取的各供应商分时电价等信息,对各司的定价策略, - 与我司(驿来特)的定价策略进行综合对比,分析我司可能存在的潜在问题。 -
-
+
+
+

驿来特AI智能大脑系统

+

集成多源数据采集、智能分析决策、知识图谱构建与自动化营销的一站式AI赋能平台

+
-
-
-
-
+
+ + +
📊
+

分时电价分析

+

实时监控4家主流充电供应商电价,结合AI智能算法分析竞对策略,为您提供精准的定价调整建议。

+
进入系统 →
+
- -
- - -
-
-

⚡ AI正在阅读您的知识库并提炼精华,请稍候...

-
-
- | -
- -
+ + +
🧠
+

智能数据查询

+

基于企业私有数据的AI问答助手。通过自然语言交互,快速查询场站运营数据、生成图表与分析报告。

+
开始查询 →
+
-
-

- 抖音知识库 - - -

-

自动解析视频、提取文案,构建企业充电知识图谱

-
+ + +
🎥
+

抖音知识库

+

全自动解析抖音视频,提取核心文案与知识点。支持博主订阅、每日知识简报生成,构建企业专属知识库。

+
管理知识库 →
+
- - - - -
- - {{ douyinLoading ? '解析处理中...' : '开始解析' }} - -
-
- - -
- -
-
-
-
-
- - {{ record.status }} - - - {{ formatDate(record.create_time) }} - -
-

- {{ record.video_name || '处理中...' }} -

- - 📺 点击观看视频 - -
- - Del - -
- - -
- Error: {{ record.error_msg }} -
- - -
-

视频文案

-

- {{ record.transcript }} -

- - {{ record.expanded ? '收起' : '展开全文' }} - -
- - -
-
- {{ record.showOriginal ? '收起原始链接' : '查看原始链接信息' }} -
-
- {{ record.original_text }} -
-
-
-
-
- -
-
- - -
- -
-
-

手机扫码访问

-
- -
-

驿来特AI智能数据查询

-

基于大语言模型,为您提供实时、精准的业务数据分析

- -
- - - -
- -
- - {{ text }} - -
-
- -
-
-
- 分析结果 - 完成 - 生成中 -
- - 停止生成 - -
- -
-
-

正在分析数据,请稍候...

-
- | -
-
- -
- - - - - - - diff --git a/static/index_old.html b/static/index_old.html new file mode 100644 index 0000000..9a6f451 --- /dev/null +++ b/static/index_old.html @@ -0,0 +1,352 @@ + + + + + +驿来特AI智能数据分析平台 + + + + + + + +
+ + +
+
+
+

⚡ 系统特性介绍

+
+
+
+
+ 📱 +
+

本系统采用 手机爬虫 获取4家充电供应商准实时各时段电价

+
+
+
+ 🧠 +
+

结合 数据仓库与AI技术,对我司电价进行智能分析,给出定价建议

+
+
+
+ 📊 +
+

对我司的各场站营业情况进行 分析,查询

+
+
+
+ 🎨 +
+

新增 智能海报生成 功能,未来将结合业务数据,一键生成精美的数据战报与营销海报

+
+
+
+ 🎥 +
+

新增 抖音知识库:支持视频解析、知识获取与总结、博主专栏订阅,自动生成 充电企业知识日报,助力企业构建专属知识库

+
+
+
+ 🎯 +
+

未来:可以根据用户充电信息,形成用户画像,结合企业微信,实现 用户广告的精准推送

+
+
+
+ 🧭 +
+

未来:基于 LBS位置服务,智能对比周边竞对场站的价格与配套(快充、休息室等),精准引导用户选择我司优势站点

+
+
+ +
+
+
+ +
+
⚡ 驿来特AI智能数据分析平台
+ + + +
+ + +
+
+ +
+
+ + +
+
+
+
全网供应商24小时电价监控
+
+ + + + + + + + + + + + + + + + +
时段{{ op.label }}
{{ row.hour }} + {{ formatCell(cell.price) }} +
+ 数据加载中... +
+
+
+
+
+ +
+
+ 智能决策分析助手 + +
+
+
+
当前分析任务
+
+ 请根据爬取的各供应商分时电价等信息,对各司的定价策略, + 与我司(驿来特)的定价策略进行综合对比,分析我司可能存在的潜在问题。 +
+
+ +
+
+
+
+ + +
+ + +
+
+

⚡ AI正在阅读您的知识库并提炼精华,请稍候...

+
+
+ | +
+ +
+ +
+

+ 抖音知识库 + + +

+

自动解析视频、提取文案,构建企业充电知识图谱

+
+ + + + + +
+ + {{ douyinLoading ? '解析处理中...' : '开始解析' }} + +
+
+ + +
+ +
+
+
+
+
+ + {{ record.status }} + + + {{ formatDate(record.create_time) }} + +
+

+ {{ record.video_name || '处理中...' }} +

+ + 📺 点击观看视频 + +
+ + Del + +
+ + +
+ Error: {{ record.error_msg }} +
+ + +
+

视频文案

+

+ {{ record.transcript }} +

+ + {{ record.expanded ? '收起' : '展开全文' }} + +
+ + +
+
+ {{ record.showOriginal ? '收起原始链接' : '查看原始链接信息' }} +
+
+ {{ record.original_text }} +
+
+
+
+
+ +
+

暂无记录,请粘贴链接开始解析

+
+
+
+ + +
+ +
+
+

手机扫码访问

+
+ +
+

驿来特AI智能数据查询

+

基于大语言模型,为您提供实时、精准的业务数据分析

+ +
+ + + +
+ +
+ + {{ text }} + +
+
+ +
+
+
+ 分析结果 + 完成 + 生成中 +
+ + 停止生成 + +
+ +
+
+

正在分析数据,请稍候...

+
+ | +
+
+ +
+ + + + + + + + + diff --git a/static/js/dashboard.js b/static/js/dashboard.js new file mode 100644 index 0000000..1a99de4 --- /dev/null +++ b/static/js/dashboard.js @@ -0,0 +1,313 @@ +const { createApp, ref, computed, watch, nextTick, onMounted } = Vue; + +createApp({ + setup() { + // ========================================== + // Common State + // ========================================== + const apiBase = ref(window.location.origin || "http://localhost:8000"); + const isMobile = ref(window.innerWidth <= 768); + + // Handle window resize + window.addEventListener('resize', () => { + isMobile.value = window.innerWidth <= 768; + if (chartInstance) chartInstance.resize(); + }); + + // ========================================== + // Dashboard State & Logic + // ========================================== + const operators = ref([ + {label:"新电途",value:"新电途"}, + {label:"特来电",value:"特来电"}, + {label:"驿来特",value:"驿来特"}, + {label:"艾特吉易充",value:"艾特吉易充"} + ]); + // Default select all or specific one? App.js had "驿来特" + const selectedOperator = ref("驿来特"); + + const aiText = ref(""); + const loading = ref(false); + const exporting = ref(false); + const exportingReport = ref(false); + const aiLoading = ref(false); + const aiBoxRef = ref(null); + const priceTableRows = ref([]); + const hourlyPricesByOperator = ref({}); + let chartInstance = null; + const chartType = ref('line'); + + // ECharts Initialization + const initChart = () => { + const dom = document.getElementById("chart"); + if (dom && !chartInstance) { + if (typeof echarts === 'undefined') { + console.error("ECharts not loaded"); + return; + } + chartInstance = echarts.init(dom); + } + }; + + const renderChart = () => { + if (!chartInstance) return; + + const series = operators.value.map(op => { + const seriesData = []; + for (let h = 0; h < 24; h++) { + const list = hourlyPricesByOperator.value[op.value] || []; + seriesData.push(list[h] !== undefined ? list[h] : null); + } + + if (chartType.value === 'bar') { + return { + name: op.label, + type: "bar", + barGap: 0, + emphasis: { focus: 'series' }, + data: seriesData + }; + } else { + return { + name: op.label, + type: "line", + smooth: true, + emphasis: { focus: 'series' }, + data: seriesData + }; + } + }); + + const hours = []; + for (let h = 0; h < 24; h++) { + hours.push(h.toString().padStart(2,"0") + ":00"); + } + + const option = { + backgroundColor: 'transparent', + tooltip: { + trigger: "axis", + backgroundColor: 'rgba(30, 41, 59, 0.9)', + borderColor: '#334155', + textStyle: { color: '#f1f5f9' }, + axisPointer: { type: chartType.value === 'bar' ? 'shadow' : 'line' } + }, + legend: { + data: operators.value.map(o => o.label), + textStyle: { color: "#94a3b8" }, + bottom: 0 + }, + xAxis: { + type: "category", + data: hours, + axisLine: { lineStyle: { color: "#475569" } }, + axisLabel: { color: "#94a3b8" } + }, + yAxis: { + type: "value", + name: "元/度", + nameTextStyle: { color: "#94a3b8" }, + axisLine: { lineStyle: { color: "#475569" } }, + axisLabel: { color: "#94a3b8" }, + splitLine: { lineStyle: { color: "#334155", type: 'dashed' } } + }, + grid: { left: 50, right: 30, top: 40, bottom: 40 }, + series: series + }; + + chartInstance.setOption(option, true); + }; + + const buildPriceTable = () => { + const rows = []; + for (let h = 0; h < 24; h++) { + const row = {hour: (h.toString().padStart(2,"0") + ":00"), values: []}; + operators.value.forEach(op => { + const series = hourlyPricesByOperator.value[op.value] || []; + const price = series[h] !== undefined ? series[h] : null; + row.values.push({operator: op.value, price}); + }); + rows.push(row); + } + priceTableRows.value = rows; + renderChart(); + }; + + const loadAllOperatorsPrices = async () => { + loading.value = true; + try{ + const res = await axios.get(apiBase.value + "/api/operators/hourly-prices"); + if (res && res.data && Array.isArray(res.data.operators)) { + const dict = {}; + res.data.operators.forEach(item => { + dict[item.operator] = item.series || []; + }); + hourlyPricesByOperator.value = dict; + buildPriceTable(); + } + }catch(e){ + console.error(e); + }finally{ + loading.value = false; + } + }; + + const exportAllPrices = async () => { + try{ + exporting.value = true; + const res = await axios.get(apiBase.value + "/api/export/prices-zip",{responseType:"blob"}); + const blob = new Blob([res.data],{type:"application/zip"}); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "多供应商电价导出.zip"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }catch(e){ + console.error(e); + }finally{ + exporting.value = false; + } + }; + + const exportAiReport = async () => { + if (!aiText.value) { + alert("请先生成AI分析报告"); + return; + } + try { + exportingReport.value = true; + const res = await axios.post(apiBase.value + "/api/export/ai-report-docx", { + content: aiText.value + }, {responseType: "blob"}); + + const blob = new Blob([res.data], {type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "AI分析报告.docx"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + } catch (e) { + console.error(e); + alert("导出失败,请检查是否安装Pandoc"); + } finally { + exportingReport.value = false; + } + }; + + const startAiAnalysis = async () => { + if (aiLoading.value) return; + aiText.value = ""; + aiLoading.value = true; + try{ + const response = await fetch(apiBase.value + "/api/ai/pricing/strategy-summary"); + if (!response.body) return; + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const {done, value} = await reader.read(); + if (done) break; + const chunk = decoder.decode(value, {stream: true}); + aiText.value += chunk; + await Vue.nextTick(); + if (aiBoxRef.value) { + aiBoxRef.value.scrollTop = aiBoxRef.value.scrollHeight; + } + } + }catch(e){ + console.error(e); + aiText.value += "\n(分析过程出错: " + e.message + ")"; + }finally{ + aiLoading.value = false; + } + }; + + const formatCell = v => { + if (v === null || v === undefined || v === "") return "-"; + if (typeof v === "number") { + if (Number.isNaN(v)) return "-"; + return v.toFixed(2); + } + return v; + }; + + const getPriceColor = (price) => { + if (price === null || price === undefined) return 'inherit'; + if (price > 1.2) return '#ef4444'; // Red-500 + if (price < 0.8) return '#10b981'; // Emerald-500 + return '#f1f5f9'; // Slate-100 + }; + + // 内置简易 Markdown 解析器 + const simpleMarkdown = (text) => { + if (!text) return ''; + let lines = text.split('\n'); + let html = ''; + let inList = false; + + const parseInline = (str) => { + return str + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/`(.*?)`/g, '$1'); + }; + + for (let line of lines) { + let trimmed = line.trim(); + if (!trimmed) continue; + + if (trimmed.startsWith('### ')) { + if (inList) { html += ''; inList = false; } + html += `

${parseInline(trimmed.substring(4))}

`; + } + else if (trimmed.startsWith('- ') || /^\d+\./.test(trimmed)) { + if (!inList) { html += ''; inList = false; } + html += `

${parseInline(trimmed)}

`; + } + } + if (inList) html += ''; + return html; + }; + + const renderedAiText = computed(() => { + return simpleMarkdown(aiText.value); + }); + + onMounted(() => { + initChart(); + loadAllOperatorsPrices(); + }); + + return { + operators, + loading, + exporting, + exportingReport, + aiLoading, + aiText, + aiBoxRef, + priceTableRows, + hourlyPricesByOperator, + chartType, + + // Actions + loadAllOperatorsPrices, + exportAllPrices, + exportAiReport, + startAiAnalysis, + formatCell, + getPriceColor, + renderedAiText + }; + } +}).mount('#app'); diff --git a/static/js/douyin.js b/static/js/douyin.js new file mode 100644 index 0000000..a031d8d --- /dev/null +++ b/static/js/douyin.js @@ -0,0 +1,202 @@ +const { createApp, ref, computed, onMounted } = Vue; + +createApp({ + setup() { + const apiBase = ref(window.location.origin || "http://localhost:8000"); + + // Douyin State + const shareText = ref(''); + const douyinLoading = ref(false); + const douyinRecords = ref([]); + let douyinTimer = null; + + // Douyin Summary State + const showSummaryDialog = ref(false); + const summaryLoading = ref(false); + const summaryText = ref(''); + + // Simple Markdown Parser (Zero Dependency) + const simpleMarkdown = (text) => { + if (!text) return ''; + let lines = text.split('\n'); + let html = ''; + let inList = false; + + // Helper: Parse inline styles + const parseInline = (str) => { + return str + .replace(/\*\*(.*?)\*\*/g, '$1') // Bold + .replace(/`(.*?)`/g, '$1'); // Code + }; + + for (let line of lines) { + let trimmed = line.trim(); + if (!trimmed) continue; + + // Headers + if (trimmed.startsWith('### ')) { + if (inList) { html += ''; inList = false; } + html += `

${parseInline(trimmed.substring(4))}

`; + } + // Lists + else if (trimmed.startsWith('- ') || /^\d+\./.test(trimmed)) { + if (!inList) { html += ''; inList = false; } + html += `

${parseInline(trimmed)}

`; + } + } + if (inList) html += ''; + return html; + }; + + const renderedSummary = computed(() => { + if (!summaryText.value) return ''; + try { + return simpleMarkdown(summaryText.value); + } catch (e) { + console.error("Simple markdown error:", e); + return summaryText.value; + } + }); + + // Methods + const startParsing = async () => { + if (!shareText.value.trim()) return; + douyinLoading.value = true; + try { + const response = await axios.post(apiBase.value + '/api/parse', { text: shareText.value }); + if (response.data.id || (response.data.ids && response.data.ids.length > 0)) { + shareText.value = ''; + fetchDouyinRecords(); + if (typeof ElementPlus !== 'undefined') { + const count = response.data.ids ? response.data.ids.length : 1; + ElementPlus.ElMessage.success(`成功提交 ${count} 个解析任务`); + } + } + } catch (error) { + console.error('Error:', error); + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('解析请求失败'); + } finally { + douyinLoading.value = false; + } + }; + + const fetchDouyinRecords = async (isManual) => { + try { + const response = await axios.get(apiBase.value + '/api/records'); + const newRecords = response.data; + douyinRecords.value = newRecords.map(newRec => { + const oldRec = douyinRecords.value.find(r => r.id === newRec.id); + return { + ...newRec, + expanded: oldRec ? oldRec.expanded : false, + showOriginal: oldRec ? oldRec.showOriginal : false + }; + }); + if (isManual === true || (isManual && isManual.type === 'click')) { + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.success('列表已刷新'); + } + } catch (error) { + console.error('Error fetching records:', error); + if (isManual === true || (isManual && isManual.type === 'click')) { + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('刷新失败'); + } + } + }; + + const deleteRecord = async (id) => { + if (!confirm('确定要删除这条记录吗?')) return; + try { + await axios.delete(apiBase.value + `/api/records/${id}`); + fetchDouyinRecords(); + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.success('删除成功'); + } catch (error) { + console.error('Error deleting:', error); + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('删除失败'); + } + }; + + const openSummaryDialog = () => { + showSummaryDialog.value = true; + if (!summaryText.value) { + fetchDouyinSummary(); + } + }; + + const handleSummaryClose = () => { + showSummaryDialog.value = false; + }; + + const fetchDouyinSummary = async () => { + if (summaryLoading.value) return; + summaryText.value = ""; + summaryLoading.value = true; + try { + const response = await fetch(apiBase.value + "/api/douyin/summary", { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ids: []}) + }); + + if (!response.body) return; + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const {done, value} = await reader.read(); + if (done) break; + const chunk = decoder.decode(value, {stream: true}); + summaryText.value += chunk; + } + } catch (e) { + console.error(e); + summaryText.value += "\n(总结过程出错: " + e.message + ")"; + } finally { + summaryLoading.value = false; + } + }; + + const statusColor = (status) => { + switch(status) { + case 'COMPLETED': return '#10b981'; + case 'FAILED': return '#ef4444'; + case 'PROCESSING': return '#3b82f6'; + default: return '#9ca3af'; + } + }; + + const statusType = (status) => { + switch(status) { + case 'COMPLETED': return 'success'; + case 'FAILED': return 'danger'; + case 'PROCESSING': return 'primary'; + default: return 'info'; + } + }; + + const formatDate = (dateStr) => { + if (!dateStr) return ''; + return new Date(dateStr).toLocaleString(); + }; + + onMounted(() => { + fetchDouyinRecords(); + if (douyinTimer) clearInterval(douyinTimer); + douyinTimer = setInterval(() => { + fetchDouyinRecords(); + }, 3000); + }); + + return { + shareText, douyinLoading, douyinRecords, + startParsing, fetchDouyinRecords, deleteRecord, statusColor, statusType, formatDate, + showSummaryDialog, summaryLoading, summaryText, renderedSummary, + openSummaryDialog, handleSummaryClose, fetchDouyinSummary + }; + } +}).use(ElementPlus).mount("#app"); diff --git a/static/js/query.js b/static/js/query.js new file mode 100644 index 0000000..7e67c9a --- /dev/null +++ b/static/js/query.js @@ -0,0 +1,216 @@ +const { createApp, ref, computed, watch, nextTick, onMounted } = Vue; + +createApp({ + setup() { + const apiBase = ref(window.location.origin || "http://localhost:8000"); + + // Query State + const userQuery = ref(''); + const queryLoading = ref(false); + const queryResult = ref(''); + const eventSource = ref(null); + const rowsAcc = ref([]); + const bufferRow = ref(null); + const examples = [ + "查询12月份充电量TOP 10场站的充电情况", + "查询净月商贸城站12月的充电量", + "查询12月企业充电量排名的TOP 10", + "查询所有场站的近3个月的充电情况,找出变化最大的前10名" + ]; + + // Helpers + const isPlainObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]'; + const colSynonyms = { + name: ['name', 'station', 'station_name', 'site', '场站', '场站名称', '站点', '站名'], + total: ['total', 'total_kwh', 'sum_kwh', 'energy_total', '总电量', '总充电量', '总度', '总度数'], + peak: ['peak', 'peak_kwh', 'energy_peak', '峰', '峰时电量'], + flat: ['flat', 'flat_kwh', 'energy_flat', '平', '平时电量'], + valley: ['valley', 'valley_kwh', 'energy_valley', '谷', '谷时电量'], + }; + const pickKey = (obj, keys) => keys.find(k => Object.prototype.hasOwnProperty.call(obj, k)); + const pickVal = (obj, keys) => { + const k = pickKey(obj, keys); + return k ? obj[k] : undefined; + }; + + const formatNumber = (n) => { + if (n === null || n === undefined || n === '') return ''; + const v = parseFloat(String(n).replace(/,/g, '')); + if (isNaN(v)) return String(n); + return v.toLocaleString('zh-CN', { maximumFractionDigits: 2 }); + }; + + const pushRow = (row) => { + if (!row || !row.name) return; + rowsAcc.value.push(row); + const r = row; + const line = `| ${r.name ?? ''} | ${formatNumber(r.total)} | ${formatNumber(r.peak)} | ${formatNumber(r.flat)} | ${formatNumber(r.valley)} |`; + + if (rowsAcc.value.length === 1) { + const header = `| 场站名称 | 总电量 | 峰 | 平 | 谷 |\n| --- | ---: | ---: | ---: | ---: |`; + if (queryResult.value && !queryResult.value.endsWith('\n')) { + queryResult.value += '\n\n'; + } else if (queryResult.value) { + queryResult.value += '\n'; + } + queryResult.value += header + '\n'; + } + queryResult.value += line + '\n'; + }; + + const handleDegreeSearch = () => { + if (!userQuery.value.trim()) { + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.warning('请输入您的问题'); + else alert('请输入您的问题'); + return; + } + + if (queryLoading.value) { + if (eventSource.value) { + eventSource.value.close(); + } + } + + queryLoading.value = true; + queryResult.value = ''; + rowsAcc.value = []; + bufferRow.value = null; + + const url = `${apiBase.value}/degree/chat?q=${encodeURIComponent(userQuery.value)}`; + + try { + const es = new EventSource(url); + eventSource.value = es; + + es.onmessage = (event) => { + const eventData = event.data.trim(); + if (eventData === '[DONE]') { + queryLoading.value = false; + es.close(); + renderCharts(); + return; + } + + try { + const data = JSON.parse(event.data); + + let chunk = ''; + if (data.content) chunk = data.content; + else if (data.markdown) chunk = data.markdown; + else if (data.chunk) chunk = data.chunk; + else if (typeof data === 'string') chunk = data; + + if (chunk) { + queryResult.value += chunk; + } + } catch (e) { + if (eventData && eventData !== '[DONE]') { + queryResult.value += event.data; + } + } + }; + + es.onerror = (err) => { + console.error('SSE Error:', err); + queryLoading.value = false; + es.close(); + if (!queryResult.value) { + queryResult.value = '查询过程出现异常,未能获取到数据'; + } + }; + } catch (err) { + console.error('Failed to create EventSource:', err); + queryLoading.value = false; + if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('无法建立连接'); + else alert('无法建立连接'); + } + }; + + const stopDegreeGeneration = () => { + if (eventSource.value) { + eventSource.value.close(); + eventSource.value = null; + } + queryLoading.value = false; + }; + + const setExample = (text) => { + userQuery.value = text; + handleDegreeSearch(); + }; + + const renderCharts = () => { + if (typeof echarts === 'undefined') return; + nextTick(() => { + const containers = document.querySelectorAll('.echarts-container'); + containers.forEach(container => { + if (container.getAttribute('data-rendered') === 'true') return; + try { + const configStr = decodeURIComponent(container.getAttribute('data-config')).trim(); + if (!configStr.startsWith('{') || !configStr.endsWith('}')) return; + const config = JSON.parse(configStr); + let chart = echarts.getInstanceByDom(container); + if (!chart) { + chart = echarts.init(container, 'dark'); + window.addEventListener('resize', () => chart.resize()); + } + chart.setOption(config); + container.setAttribute('data-rendered', 'true'); + container.style.opacity = '1'; + } catch (e) { + if (queryLoading.value) return; + console.error('ECharts rendering error:', e); + container.innerHTML = `

图表渲染失败: ${e.message}

`; + } + }); + }); + }; + + // Configure Marked + if (typeof marked !== 'undefined') { + marked.use({ + gfm: true, + breaks: true, + renderer: { + code(code, language) { + if (language === 'echarts') { + const id = 'chart-' + Math.random().toString(36).substr(2, 9); + return `
`; + } + return false; + } + } + }); + } + + const renderedResult = computed(() => { + if (!queryResult.value) return ''; + try { + // Remove escapes that might break markdown + const cleanText = queryResult.value.replace(/\\([\*_`#\[\]\(\)!>-])/g, '$1'); + if (typeof marked !== 'undefined') { + return marked.parse(cleanText); + } + return cleanText; + } catch (e) { + console.error('Markdown parsing error:', e); + return queryResult.value; + } + }); + + watch(queryResult, () => { + renderCharts(); + }); + + return { + userQuery, + queryLoading, + queryResult, + examples, + handleDegreeSearch, + stopDegreeGeneration, + setExample, + renderedResult + }; + } +}).use(ElementPlus).mount('#app'); diff --git a/static/query.html b/static/query.html new file mode 100644 index 0000000..dc1ade9 --- /dev/null +++ b/static/query.html @@ -0,0 +1,88 @@ + + + + + + 智能数据查询 - 驿来特AI智能大脑 + + + + +
+
+ ⚡ 驿来特AI智能大脑 + ↩ 返回首页 +
+ +
+
+

驿来特AI智能数据查询

+

基于大语言模型,为您提供实时、精准的业务数据分析

+ +
+ + + +
+ +
+ + {{ text }} + +
+
+ +
+
+
+ 分析结果 + 完成 + 生成中 +
+ + 停止生成 + +
+ +
+
+

正在分析数据,请稍候...

+
+ | +
+
+
+ + + + + + + + + +