| 时间 | +{{ op.label }} | +
|---|---|
| {{ row.hour }} | ++ {{ formatCell(val.price) }} + | +
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 @@ + + +
+ + +| 时间 | +{{ op.label }} | +
|---|---|
| {{ row.hour }} | ++ {{ formatCell(val.price) }} + | +
⚡ AI正在阅读您的知识库并提炼精华,请稍候...
+| 时段 | -{{ op.label }} | -
|---|---|
| {{ row.hour }} | -- {{ formatCell(cell.price) }} - | -
| - 数据加载中... - | -
集成多源数据采集、智能分析决策、知识图谱构建与自动化营销的一站式AI赋能平台
+实时监控4家主流充电供应商电价,结合AI智能算法分析竞对策略,为您提供精准的定价调整建议。
+基于大语言模型,为您提供实时、精准的业务数据分析
- -正在分析数据,请稍候...
-| 时段 | +{{ op.label }} | +
|---|---|
| {{ row.hour }} | ++ {{ formatCell(cell.price) }} + | +
| + 数据加载中... + | +
基于大语言模型,为您提供实时、精准的业务数据分析
+ +正在分析数据,请稍候...
+$1');
+ };
+
+ for (let line of lines) {
+ let trimmed = line.trim();
+ if (!trimmed) continue;
+
+ if (trimmed.startsWith('### ')) {
+ 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)}
`; + } + } + 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 @@ + + + + + +基于大语言模型,为您提供实时、精准的业务数据分析
+ +正在分析数据,请稍候...
+