commit
This commit is contained in:
parent
4e1c7f2e8e
commit
41e75fd0ce
78
.gitignore
vendored
Normal file
78
.gitignore
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.egg-info/
|
||||
*.egg
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
*.whl
|
||||
|
||||
# Python virtual environment
|
||||
backend/venv/
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# SQLite database
|
||||
*.db
|
||||
*.db-journal
|
||||
*.sqlite3
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
frontend/node_modules/
|
||||
|
||||
# Next.js
|
||||
frontend/.next/
|
||||
frontend/out/
|
||||
|
||||
# Build / production
|
||||
*.tsbuildinfo
|
||||
.next/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Coverage / test
|
||||
coverage/
|
||||
htmlcov/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.pytest_cache/
|
||||
.tox/
|
||||
.nox/
|
||||
|
||||
# Misc
|
||||
*.bak
|
||||
*.tmp
|
||||
*.temp
|
||||
Binary file not shown.
@ -25,6 +25,10 @@ async def get_latest():
|
||||
"up_count": mt.up_count if mt else 0,
|
||||
"down_count": mt.down_count if mt else 0,
|
||||
"limit_up_count": mt.limit_up_count if mt else 0,
|
||||
"limit_down_count": mt.limit_down_count if mt else 0,
|
||||
"max_streak": mt.max_streak if mt else 0,
|
||||
"broken_rate": mt.broken_rate if mt else 0,
|
||||
"index_above_ma20": mt.index_above_ma20 if mt else False,
|
||||
} if mt else None,
|
||||
"recommendations": [
|
||||
{
|
||||
|
||||
Binary file not shown.
@ -29,6 +29,8 @@ async def init_db():
|
||||
"ALTER TABLE recommendations ADD COLUMN position_score REAL",
|
||||
"ALTER TABLE recommendations ADD COLUMN valuation_score REAL",
|
||||
"ALTER TABLE recommendations ADD COLUMN llm_analysis TEXT DEFAULT ''",
|
||||
"ALTER TABLE market_temperature ADD COLUMN max_streak INTEGER",
|
||||
"ALTER TABLE market_temperature ADD COLUMN broken_rate REAL",
|
||||
]:
|
||||
try:
|
||||
await conn.execute(
|
||||
|
||||
Binary file not shown.
@ -81,20 +81,23 @@ async def _save_to_db(result: dict):
|
||||
# 保存市场温度
|
||||
mt = result.get("market_temp")
|
||||
if mt:
|
||||
stmt = tables.market_temperature_table.insert().values(
|
||||
trade_date=mt.trade_date,
|
||||
up_count=mt.up_count,
|
||||
down_count=mt.down_count,
|
||||
limit_up_count=mt.limit_up_count,
|
||||
limit_down_count=mt.limit_down_count,
|
||||
max_streak=mt.max_streak,
|
||||
broken_rate=mt.broken_rate,
|
||||
temperature=mt.temperature,
|
||||
# 使用 INSERT OR REPLACE 确保重复扫描能更新数据
|
||||
stmt = text(
|
||||
"INSERT OR REPLACE INTO market_temperature "
|
||||
"(trade_date, up_count, down_count, limit_up_count, limit_down_count, "
|
||||
"max_streak, broken_rate, temperature) "
|
||||
"VALUES (:td, :up, :down, :lu, :ld, :ms, :br, :temp)"
|
||||
)
|
||||
try:
|
||||
await db.execute(stmt)
|
||||
except Exception:
|
||||
pass # 可能已存在(UNIQUE 约束)
|
||||
await db.execute(stmt, {
|
||||
"td": mt.trade_date,
|
||||
"up": mt.up_count,
|
||||
"down": mt.down_count,
|
||||
"lu": mt.limit_up_count,
|
||||
"ld": mt.limit_down_count,
|
||||
"ms": mt.max_streak,
|
||||
"br": mt.broken_rate,
|
||||
"temp": mt.temperature,
|
||||
})
|
||||
|
||||
# 保存板块热度(先清除同一 trade_date 的旧数据,避免重复)
|
||||
trade_date_val = mt.trade_date if mt else ""
|
||||
|
||||
Binary file not shown.
@ -1,25 +1,25 @@
|
||||
{
|
||||
"pages": {
|
||||
"/chat/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/chat/page.js"
|
||||
],
|
||||
"/layout": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/css/app/layout.css",
|
||||
"static/chunks/app/layout.js"
|
||||
],
|
||||
"/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/page.js"
|
||||
],
|
||||
"/recommendations/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/recommendations/page.js"
|
||||
],
|
||||
"/sectors/page": [
|
||||
"/stock/[code]/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/sectors/page.js"
|
||||
"static/chunks/app/stock/[code]/page.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
frontend/.next/cache/.tsbuildinfo
vendored
2
frontend/.next/cache/.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
@ -1 +1,20 @@
|
||||
{}
|
||||
{
|
||||
"components/capital-flow.tsx -> echarts": {
|
||||
"id": "components/capital-flow.tsx -> echarts",
|
||||
"files": [
|
||||
"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js"
|
||||
]
|
||||
},
|
||||
"components/kline-chart.tsx -> echarts": {
|
||||
"id": "components/kline-chart.tsx -> echarts",
|
||||
"files": [
|
||||
"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js"
|
||||
]
|
||||
},
|
||||
"components/score-radar.tsx -> echarts": {
|
||||
"id": "components/score-radar.tsx -> echarts",
|
||||
"files": [
|
||||
"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"/page": "app/page.js",
|
||||
"/chat/page": "app/chat/page.js",
|
||||
"/recommendations/page": "app/recommendations/page.js",
|
||||
"/sectors/page": "app/sectors/page.js"
|
||||
"/api/chat/stream/route": "app/api/chat/stream/route.js",
|
||||
"/stock/[code]/page": "app/stock/[code]/page.js"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
self.__REACT_LOADABLE_MANIFEST="{}"
|
||||
self.__REACT_LOADABLE_MANIFEST="{\"components/capital-flow.tsx -> echarts\":{\"id\":\"components/capital-flow.tsx -> echarts\",\"files\":[\"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js\"]},\"components/kline-chart.tsx -> echarts\":{\"id\":\"components/kline-chart.tsx -> echarts\",\"files\":[\"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js\"]},\"components/score-radar.tsx -> echarts\":{\"id\":\"components/score-radar.tsx -> echarts\",\"files\":[\"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js\"]}}"
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"node": {},
|
||||
"edge": {},
|
||||
"encryptionKey": "rmrljjdyNTjhpyAHQzkci4dGEXtuEnS7slbszZTpG4E="
|
||||
"encryptionKey": "YesDqzwu7U3Zt5BbI/COWZPvCGzAI5FFxbDk16RGGxQ="
|
||||
}
|
||||
@ -10,6 +10,17 @@ exports.id = "vendor-chunks/next";
|
||||
exports.ids = ["vendor-chunks/next"];
|
||||
exports.modules = {
|
||||
|
||||
/***/ "(ssr)/./node_modules/next/dist/api/navigation.js":
|
||||
/*!**************************************************!*\
|
||||
!*** ./node_modules/next/dist/api/navigation.js ***!
|
||||
\**************************************************/
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _client_components_navigation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../client/components/navigation */ \"(ssr)/./node_modules/next/dist/client/components/navigation.js\");\n/* harmony import */ var _client_components_navigation__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_client_components_navigation__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};\n/* harmony reexport (unknown) */ for(const __WEBPACK_IMPORT_KEY__ in _client_components_navigation__WEBPACK_IMPORTED_MODULE_0__) if(__WEBPACK_IMPORT_KEY__ !== \"default\") __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () => _client_components_navigation__WEBPACK_IMPORTED_MODULE_0__[__WEBPACK_IMPORT_KEY__]\n/* harmony reexport (unknown) */ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);\n\n\n//# sourceMappingURL=navigation.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2FwaS9uYXZpZ2F0aW9uLmpzIiwibWFwcGluZ3MiOiI7Ozs7OztBQUFnRDs7QUFFaEQiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hc3RvY2stYWdlbnQtZnJvbnRlbmQvLi9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2FwaS9uYXZpZ2F0aW9uLmpzP2MyNzIiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0ICogZnJvbSBcIi4uL2NsaWVudC9jb21wb25lbnRzL25hdmlnYXRpb25cIjtcblxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9bmF2aWdhdGlvbi5qcy5tYXAiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/next/dist/api/navigation.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/next/dist/client/add-base-path.js":
|
||||
/*!********************************************************!*\
|
||||
!*** ./node_modules/next/dist/client/add-base-path.js ***!
|
||||
@ -2097,6 +2108,17 @@ eval("\nmodule.exports = __webpack_require__(/*! ../../module.compiled */ \"(ssr
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(rsc)/./node_modules/next/dist/server/future/route-modules/app-route/module.compiled.js":
|
||||
/*!*****************************************************************************************!*\
|
||||
!*** ./node_modules/next/dist/server/future/route-modules/app-route/module.compiled.js ***!
|
||||
\*****************************************************************************************/
|
||||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||||
|
||||
"use strict";
|
||||
eval("\nif (false) {} else {\n if (false) {} else {\n if (true) {\n module.exports = __webpack_require__(/*! next/dist/compiled/next-server/app-route.runtime.dev.js */ \"next/dist/compiled/next-server/app-route.runtime.dev.js\");\n } else {}\n }\n}\n\n//# sourceMappingURL=module.compiled.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHJzYykvLi9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L3NlcnZlci9mdXR1cmUvcm91dGUtbW9kdWxlcy9hcHAtcm91dGUvbW9kdWxlLmNvbXBpbGVkLmpzIiwibWFwcGluZ3MiOiJBQUFhO0FBQ2IsSUFBSSxLQUFtQyxFQUFFLEVBRXhDLENBQUM7QUFDRixRQUFRLEtBQXFDLEVBQUUsRUFRMUMsQ0FBQztBQUNOLFlBQVksSUFBc0M7QUFDbEQsWUFBWSw4SkFBbUY7QUFDL0YsVUFBVSxLQUFLLEVBSU47QUFDVDtBQUNBOztBQUVBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vYXN0b2NrLWFnZW50LWZyb250ZW5kLy4vbm9kZV9tb2R1bGVzL25leHQvZGlzdC9zZXJ2ZXIvZnV0dXJlL3JvdXRlLW1vZHVsZXMvYXBwLXJvdXRlL21vZHVsZS5jb21waWxlZC5qcz8zYmM3Il0sInNvdXJjZXNDb250ZW50IjpbIlwidXNlIHN0cmljdFwiO1xuaWYgKHByb2Nlc3MuZW52Lk5FWFRfUlVOVElNRSA9PT0gXCJlZGdlXCIpIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoXCJuZXh0L2Rpc3Qvc2VydmVyL2Z1dHVyZS9yb3V0ZS1tb2R1bGVzL2FwcC1yb3V0ZS9tb2R1bGUuanNcIik7XG59IGVsc2Uge1xuICAgIGlmIChwcm9jZXNzLmVudi5fX05FWFRfRVhQRVJJTUVOVEFMX1JFQUNUKSB7XG4gICAgICAgIGlmIChwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gXCJkZXZlbG9wbWVudFwiKSB7XG4gICAgICAgICAgICBtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoXCJuZXh0L2Rpc3QvY29tcGlsZWQvbmV4dC1zZXJ2ZXIvYXBwLXJvdXRlLWV4cGVyaW1lbnRhbC5ydW50aW1lLmRldi5qc1wiKTtcbiAgICAgICAgfSBlbHNlIGlmIChwcm9jZXNzLmVudi5UVVJCT1BBQ0spIHtcbiAgICAgICAgICAgIG1vZHVsZS5leHBvcnRzID0gcmVxdWlyZShcIm5leHQvZGlzdC9jb21waWxlZC9uZXh0LXNlcnZlci9hcHAtcm91dGUtdHVyYm8tZXhwZXJpbWVudGFsLnJ1bnRpbWUucHJvZC5qc1wiKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIG1vZHVsZS5leHBvcnRzID0gcmVxdWlyZShcIm5leHQvZGlzdC9jb21waWxlZC9uZXh0LXNlcnZlci9hcHAtcm91dGUtZXhwZXJpbWVudGFsLnJ1bnRpbWUucHJvZC5qc1wiKTtcbiAgICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAgIGlmIChwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gXCJkZXZlbG9wbWVudFwiKSB7XG4gICAgICAgICAgICBtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoXCJuZXh0L2Rpc3QvY29tcGlsZWQvbmV4dC1zZXJ2ZXIvYXBwLXJvdXRlLnJ1bnRpbWUuZGV2LmpzXCIpO1xuICAgICAgICB9IGVsc2UgaWYgKHByb2Nlc3MuZW52LlRVUkJPUEFDSykge1xuICAgICAgICAgICAgbW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKFwibmV4dC9kaXN0L2NvbXBpbGVkL25leHQtc2VydmVyL2FwcC1yb3V0ZS10dXJiby5ydW50aW1lLnByb2QuanNcIik7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoXCJuZXh0L2Rpc3QvY29tcGlsZWQvbmV4dC1zZXJ2ZXIvYXBwLXJvdXRlLnJ1bnRpbWUucHJvZC5qc1wiKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9bW9kdWxlLmNvbXBpbGVkLmpzLm1hcCJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(rsc)/./node_modules/next/dist/server/future/route-modules/app-route/module.compiled.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(rsc)/./node_modules/next/dist/server/lib/clone-response.js":
|
||||
/*!*************************************************************!*\
|
||||
!*** ./node_modules/next/dist/server/lib/clone-response.js ***!
|
||||
|
||||
@ -125,7 +125,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("1c3e3006bc57475f")
|
||||
/******/ __webpack_require__.h = () => ("c8994b8599cfdbb2")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -25,7 +25,7 @@ eval(__webpack_require__.ts("Promise.resolve(/*! import() eager */).then(__webpa
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"66e5cf568d04\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6IjtBQUFBLCtEQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vX05fRS8uL3NyYy9hcHAvZ2xvYmFscy5jc3M/OTc1OSJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBcIjY2ZTVjZjU2OGQwNFwiXG5pZiAobW9kdWxlLmhvdCkgeyBtb2R1bGUuaG90LmFjY2VwdCgpIH1cbiJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"41402df187ff\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6IjtBQUFBLCtEQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vX05fRS8uL3NyYy9hcHAvZ2xvYmFscy5jc3M/OTc1OSJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBcIjQxNDAyZGYxODdmZlwiXG5pZiAobW9kdWxlLmhvdCkgeyBtb2R1bGUuaG90LmFjY2VwdCgpIH1cbiJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
||||
|
||||
/***/ })
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -163,7 +163,7 @@
|
||||
/******/ // This function allow to reference async chunks
|
||||
/******/ __webpack_require__.u = function(chunkId) {
|
||||
/******/ // return url for filenames based on template
|
||||
/******/ return undefined;
|
||||
/******/ return "static/chunks/" + chunkId + ".js";
|
||||
/******/ };
|
||||
/******/ }();
|
||||
/******/
|
||||
@ -192,7 +192,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "12b9b356febc7fba"; }
|
||||
/******/ __webpack_require__.h = function() { return "18bfcaa4d79c56aa"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/global */
|
||||
|
||||
@ -562,9 +562,6 @@ video {
|
||||
.right-0{
|
||||
right: 0px;
|
||||
}
|
||||
.top-0{
|
||||
top: 0px;
|
||||
}
|
||||
.z-40{
|
||||
z-index: 40;
|
||||
}
|
||||
@ -610,18 +607,21 @@ video {
|
||||
.ml-0\.5{
|
||||
margin-left: 0.125rem;
|
||||
}
|
||||
.ml-1{
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.ml-1\.5{
|
||||
margin-left: 0.375rem;
|
||||
}
|
||||
.ml-2{
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.mt-0\.5{
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
.mt-1{
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.mt-1\.5{
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
.mt-2{
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@ -682,6 +682,9 @@ video {
|
||||
.h-56{
|
||||
height: 14rem;
|
||||
}
|
||||
.h-6{
|
||||
height: 1.5rem;
|
||||
}
|
||||
.h-64{
|
||||
height: 16rem;
|
||||
}
|
||||
@ -700,9 +703,6 @@ video {
|
||||
.min-h-screen{
|
||||
min-height: 100vh;
|
||||
}
|
||||
.w-0\.5{
|
||||
width: 0.125rem;
|
||||
}
|
||||
.w-1{
|
||||
width: 0.25rem;
|
||||
}
|
||||
@ -721,6 +721,9 @@ video {
|
||||
.w-3{
|
||||
width: 0.75rem;
|
||||
}
|
||||
.w-6{
|
||||
width: 1.5rem;
|
||||
}
|
||||
.w-60{
|
||||
width: 15rem;
|
||||
}
|
||||
@ -733,6 +736,12 @@ video {
|
||||
.w-full{
|
||||
width: 100%;
|
||||
}
|
||||
.min-w-\[36px\]{
|
||||
min-width: 36px;
|
||||
}
|
||||
.min-w-\[60px\]{
|
||||
min-width: 60px;
|
||||
}
|
||||
.max-w-3xl{
|
||||
max-width: 48rem;
|
||||
}
|
||||
@ -828,6 +837,9 @@ video {
|
||||
.grid-cols-2{
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-3{
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-4{
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
@ -870,9 +882,6 @@ video {
|
||||
.gap-2{
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.gap-2\.5{
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.gap-3{
|
||||
gap: 0.75rem;
|
||||
}
|
||||
@ -892,6 +901,11 @@ video {
|
||||
margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
.space-y-2 > :not([hidden]) ~ :not([hidden]){
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
.space-y-4 > :not([hidden]) ~ :not([hidden]){
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
@ -919,9 +933,6 @@ video {
|
||||
.whitespace-nowrap{
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rounded{
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded-2xl{
|
||||
border-radius: 16px;
|
||||
}
|
||||
@ -955,6 +966,9 @@ video {
|
||||
.border-accent-indigo\/\[0\.12\]{
|
||||
border-color: rgb(129 140 248 / 0.12);
|
||||
}
|
||||
.border-amber-500\/20{
|
||||
border-color: rgb(245 158 11 / 0.2);
|
||||
}
|
||||
.border-amber-500\/\[0\.08\]{
|
||||
border-color: rgb(245 158 11 / 0.08);
|
||||
}
|
||||
@ -967,9 +981,15 @@ video {
|
||||
.border-orange-500\/15{
|
||||
border-color: rgb(249 115 22 / 0.15);
|
||||
}
|
||||
.border-orange-600\/15{
|
||||
border-color: rgb(234 88 12 / 0.15);
|
||||
}
|
||||
.border-red-500\/10{
|
||||
border-color: rgb(239 68 68 / 0.1);
|
||||
}
|
||||
.border-slate-400\/15{
|
||||
border-color: rgb(148 163 184 / 0.15);
|
||||
}
|
||||
.border-slate-800\/50{
|
||||
border-color: rgb(30 41 59 / 0.5);
|
||||
}
|
||||
@ -1031,11 +1051,15 @@ video {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(251 146 60 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-orange-500\/20{
|
||||
background-color: rgb(249 115 22 / 0.2);
|
||||
}
|
||||
.bg-orange-500\/60{
|
||||
background-color: rgb(249 115 22 / 0.6);
|
||||
}
|
||||
.bg-orange-500\/\[0\.08\]{
|
||||
background-color: rgb(249 115 22 / 0.08);
|
||||
.bg-red-400{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-red-500\/\[0\.08\]{
|
||||
background-color: rgb(239 68 68 / 0.08);
|
||||
@ -1073,6 +1097,11 @@ video {
|
||||
--tw-gradient-to: rgb(129 140 248 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-amber-500\/30{
|
||||
--tw-gradient-from: rgb(245 158 11 / 0.3) var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(245 158 11 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-orange-500{
|
||||
--tw-gradient-from: #f97316 var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(249 115 22 / 0) var(--tw-gradient-to-position);
|
||||
@ -1088,6 +1117,16 @@ video {
|
||||
--tw-gradient-to: rgb(249 115 22 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-orange-700\/20{
|
||||
--tw-gradient-from: rgb(194 65 12 / 0.2) var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(194 65 12 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-slate-400\/20{
|
||||
--tw-gradient-from: rgb(148 163 184 / 0.2) var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(148 163 184 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-transparent{
|
||||
--tw-gradient-from: transparent var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
|
||||
@ -1115,6 +1154,15 @@ video {
|
||||
.to-amber-600{
|
||||
--tw-gradient-to: #d97706 var(--tw-gradient-to-position);
|
||||
}
|
||||
.to-amber-600\/20{
|
||||
--tw-gradient-to: rgb(217 119 6 / 0.2) var(--tw-gradient-to-position);
|
||||
}
|
||||
.to-orange-800\/15{
|
||||
--tw-gradient-to: rgb(154 52 18 / 0.15) var(--tw-gradient-to-position);
|
||||
}
|
||||
.to-slate-500\/15{
|
||||
--tw-gradient-to: rgb(100 116 139 / 0.15) var(--tw-gradient-to-position);
|
||||
}
|
||||
.to-transparent{
|
||||
--tw-gradient-to: transparent var(--tw-gradient-to-position);
|
||||
}
|
||||
@ -1130,10 +1178,6 @@ video {
|
||||
.p-5{
|
||||
padding: 1.25rem;
|
||||
}
|
||||
.px-1\.5{
|
||||
padding-left: 0.375rem;
|
||||
padding-right: 0.375rem;
|
||||
}
|
||||
.px-2{
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
@ -1158,6 +1202,10 @@ video {
|
||||
padding-top: 0.125rem;
|
||||
padding-bottom: 0.125rem;
|
||||
}
|
||||
.py-1{
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
.py-1\.5{
|
||||
padding-top: 0.375rem;
|
||||
padding-bottom: 0.375rem;
|
||||
@ -1232,18 +1280,9 @@ video {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
.text-\[10px\]{
|
||||
font-size: 10px;
|
||||
}
|
||||
.text-\[11px\]{
|
||||
font-size: 11px;
|
||||
}
|
||||
.text-\[13px\]{
|
||||
font-size: 13px;
|
||||
}
|
||||
.text-\[9px\]{
|
||||
font-size: 9px;
|
||||
}
|
||||
.text-base{
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
@ -1308,6 +1347,10 @@ video {
|
||||
.text-accent-indigo\/80{
|
||||
color: rgb(129 140 248 / 0.8);
|
||||
}
|
||||
.text-amber-400{
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(251 191 36 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-amber-500\/60{
|
||||
color: rgb(245 158 11 / 0.6);
|
||||
}
|
||||
@ -1315,6 +1358,9 @@ video {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(52 211 153 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-emerald-400\/60{
|
||||
color: rgb(52 211 153 / 0.6);
|
||||
}
|
||||
.text-emerald-400\/80{
|
||||
color: rgb(52 211 153 / 0.8);
|
||||
}
|
||||
@ -1326,13 +1372,14 @@ video {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(251 146 60 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-orange-400\/80{
|
||||
color: rgb(251 146 60 / 0.8);
|
||||
}
|
||||
.text-red-400{
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-slate-300{
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(203 213 225 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-text-muted{
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(84 99 128 / var(--tw-text-opacity, 1));
|
||||
@ -1419,6 +1466,9 @@ video {
|
||||
.duration-300{
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
.duration-500{
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
.duration-700{
|
||||
transition-duration: 700ms;
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
{"c":["webpack"],"r":[],"m":[]}
|
||||
@ -1 +0,0 @@
|
||||
{"c":["app/layout","app/page","app/recommendations/page","webpack"],"r":[],"m":[]}
|
||||
@ -1 +0,0 @@
|
||||
{"c":["app/layout","webpack"],"r":["app/_not-found/page"],"m":["(app-pages-browser)/./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error.js&page=%2F_not-found%2Fpage!","(app-pages-browser)/./node_modules/next/dist/client/components/not-found-error.js"]}
|
||||
@ -1,22 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("app/layout",{
|
||||
|
||||
/***/ "(app-pages-browser)/./src/app/globals.css":
|
||||
/*!*****************************!*\
|
||||
!*** ./src/app/globals.css ***!
|
||||
\*****************************/
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"66e5cf568d04\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6IjtBQUFBLCtEQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vX05fRS8uL3NyYy9hcHAvZ2xvYmFscy5jc3M/OTc1OSJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBcIjY2ZTVjZjU2OGQwNFwiXG5pZiAobW9kdWxlLmhvdCkgeyBtb2R1bGUuaG90LmFjY2VwdCgpIH1cbiJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
||||
|
||||
/***/ })
|
||||
|
||||
});
|
||||
@ -1,22 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("app/layout",{
|
||||
|
||||
/***/ "(app-pages-browser)/./src/app/globals.css":
|
||||
/*!*****************************!*\
|
||||
!*** ./src/app/globals.css ***!
|
||||
\*****************************/
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"02fabde643f0\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6IjtBQUFBLCtEQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vX05fRS8uL3NyYy9hcHAvZ2xvYmFscy5jc3M/OTc1OSJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBcIjAyZmFiZGU2NDNmMFwiXG5pZiAobW9kdWxlLmhvdCkgeyBtb2R1bGUuaG90LmFjY2VwdCgpIH1cbiJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
||||
|
||||
/***/ })
|
||||
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"c":["webpack"],"r":[],"m":[]}
|
||||
@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "1b777160410063de"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "12b9b356febc7fba"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "0102527ee05174f3"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
@ -1,60 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/compat get default export */
|
||||
/******/ !function() {
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function() { return module['default']; } :
|
||||
/******/ function() { return module; };
|
||||
/******/ __webpack_require__.d(getter, { a: getter });
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/create fake namespace object */
|
||||
/******/ !function() {
|
||||
/******/ var getProto = Object.getPrototypeOf ? function(obj) { return Object.getPrototypeOf(obj); } : function(obj) { return obj.__proto__; };
|
||||
/******/ var leafPrototypes;
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 16: return value when it's Promise-like
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = this(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if(typeof value === 'object' && value) {
|
||||
/******/ if((mode & 4) && value.__esModule) return value;
|
||||
/******/ if((mode & 16) && typeof value.then === 'function') return value;
|
||||
/******/ }
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ var def = {};
|
||||
/******/ leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];
|
||||
/******/ for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {
|
||||
/******/ Object.getOwnPropertyNames(current).forEach(function(key) { def[key] = function() { return value[key]; }; });
|
||||
/******/ }
|
||||
/******/ def['default'] = function() { return value; };
|
||||
/******/ __webpack_require__.d(ns, def);
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "2eda11c00b2dd330"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
File diff suppressed because one or more lines are too long
@ -1,79 +0,0 @@
|
||||
// File: /Users/aaron/source_code/astock-agent/frontend/src/app/page.tsx
|
||||
import * as entry from '../../../src/app/page.js'
|
||||
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||
|
||||
type TEntry = typeof import('../../../src/app/page.js')
|
||||
|
||||
// Check that the entry is a valid entry
|
||||
checkFields<Diff<{
|
||||
default: Function
|
||||
config?: {}
|
||||
generateStaticParams?: Function
|
||||
revalidate?: RevalidateRange<TEntry> | false
|
||||
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||
dynamicParams?: boolean
|
||||
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||
maxDuration?: number
|
||||
|
||||
metadata?: any
|
||||
generateMetadata?: Function
|
||||
viewport?: any
|
||||
generateViewport?: Function
|
||||
|
||||
}, TEntry, ''>>()
|
||||
|
||||
// Check the prop type of the entry function
|
||||
checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||
|
||||
// Check the arguments and return type of the generateMetadata function
|
||||
if ('generateMetadata' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateViewport function
|
||||
if ('generateViewport' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateStaticParams function
|
||||
if ('generateStaticParams' in entry) {
|
||||
checkFields<Diff<{ params: PageParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||
}
|
||||
|
||||
type PageParams = any
|
||||
export interface PageProps {
|
||||
params?: any
|
||||
searchParams?: any
|
||||
}
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode
|
||||
|
||||
params?: any
|
||||
}
|
||||
|
||||
// =============
|
||||
// Utility types
|
||||
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||
|
||||
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||
|
||||
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||
|
||||
|
||||
|
||||
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||
|
||||
// https://github.com/sindresorhus/type-fest
|
||||
type Numeric = number | bigint
|
||||
type Zero = 0 | 0n
|
||||
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||
@ -1,79 +0,0 @@
|
||||
// File: /Users/aaron/source_code/astock-agent/frontend/src/app/sectors/page.tsx
|
||||
import * as entry from '../../../../src/app/sectors/page.js'
|
||||
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||
|
||||
type TEntry = typeof import('../../../../src/app/sectors/page.js')
|
||||
|
||||
// Check that the entry is a valid entry
|
||||
checkFields<Diff<{
|
||||
default: Function
|
||||
config?: {}
|
||||
generateStaticParams?: Function
|
||||
revalidate?: RevalidateRange<TEntry> | false
|
||||
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||
dynamicParams?: boolean
|
||||
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||
maxDuration?: number
|
||||
|
||||
metadata?: any
|
||||
generateMetadata?: Function
|
||||
viewport?: any
|
||||
generateViewport?: Function
|
||||
|
||||
}, TEntry, ''>>()
|
||||
|
||||
// Check the prop type of the entry function
|
||||
checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||
|
||||
// Check the arguments and return type of the generateMetadata function
|
||||
if ('generateMetadata' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateViewport function
|
||||
if ('generateViewport' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateStaticParams function
|
||||
if ('generateStaticParams' in entry) {
|
||||
checkFields<Diff<{ params: PageParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||
}
|
||||
|
||||
type PageParams = any
|
||||
export interface PageProps {
|
||||
params?: any
|
||||
searchParams?: any
|
||||
}
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode
|
||||
|
||||
params?: any
|
||||
}
|
||||
|
||||
// =============
|
||||
// Utility types
|
||||
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||
|
||||
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||
|
||||
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||
|
||||
|
||||
|
||||
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||
|
||||
// https://github.com/sindresorhus/type-fest
|
||||
type Numeric = number | bigint
|
||||
type Zero = 0 | 0n
|
||||
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||
35
frontend/src/app/api/chat/stream/route.ts
Normal file
35
frontend/src/app/api/chat/stream/route.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:8000";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const body = await req.json();
|
||||
|
||||
const backendRes = await fetch(`${BACKEND_URL}/api/chat/stream`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!backendRes.ok) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: `Backend error: ${backendRes.status}` }),
|
||||
{ status: backendRes.status, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
if (!backendRes.body) {
|
||||
return new Response(JSON.stringify({ error: "No response body" }), {
|
||||
status: 502,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(backendRes.body, {
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -96,7 +96,7 @@ export default function ChatPage() {
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-sm font-semibold">AI 投资顾问</h1>
|
||||
<p className="text-[10px] text-text-muted">
|
||||
<p className="text-xs text-text-muted">
|
||||
基于实时市场数据的智能问答
|
||||
</p>
|
||||
</div>
|
||||
@ -104,7 +104,7 @@ export default function ChatPage() {
|
||||
{messages.length > 0 && (
|
||||
<button
|
||||
onClick={() => setMessages([])}
|
||||
className="text-[10px] text-text-muted hover:text-text-primary px-3 py-1.5 rounded-lg hover:bg-white/[0.04] transition-all duration-200"
|
||||
className="text-xs text-text-muted hover:text-text-primary px-3 py-1.5 rounded-lg hover:bg-white/[0.04] transition-all duration-200"
|
||||
>
|
||||
清空对话
|
||||
</button>
|
||||
@ -121,7 +121,7 @@ export default function ChatPage() {
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold mb-1.5">有什么想了解的?</h2>
|
||||
<p className="text-[11px] text-text-muted mb-8 max-w-[240px] leading-relaxed">
|
||||
<p className="text-xs text-text-muted mb-8 max-w-[240px] leading-relaxed">
|
||||
我可以查询市场数据,分析个股走势,解读板块热度
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 w-full max-w-[280px]">
|
||||
@ -129,7 +129,7 @@ export default function ChatPage() {
|
||||
<button
|
||||
key={q}
|
||||
onClick={() => sendMessage(q)}
|
||||
className="text-[11px] px-4 py-2.5 bg-white/[0.03] rounded-xl text-text-secondary hover:text-text-primary hover:bg-white/[0.06] transition-all duration-200 border border-white/[0.04] text-left"
|
||||
className="text-xs px-4 py-2.5 bg-white/[0.03] rounded-xl text-text-secondary hover:text-text-primary hover:bg-white/[0.06] transition-all duration-200 border border-white/[0.04] text-left"
|
||||
>
|
||||
{q}
|
||||
</button>
|
||||
@ -175,7 +175,7 @@ export default function ChatPage() {
|
||||
{/* Status indicator during tool calls */}
|
||||
{streaming && status && messages[messages.length - 1]?.content && (
|
||||
<div className="flex justify-start">
|
||||
<div className="text-[11px] text-accent-indigo/50 flex items-center gap-2 px-3">
|
||||
<div className="text-xs text-accent-indigo/50 flex items-center gap-2 px-3">
|
||||
<span className="inline-block w-2.5 h-2.5 border border-accent-indigo/30 border-t-accent-indigo/70 rounded-full animate-spin" />
|
||||
{status}
|
||||
</div>
|
||||
@ -206,7 +206,7 @@ export default function ChatPage() {
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-[9px] text-text-muted/30 text-center mt-2">
|
||||
<div className="text-xs text-text-muted/30 text-center mt-2">
|
||||
AI 分析仅供参考,不构成投资建议
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -33,7 +33,7 @@ export default function RootLayout({
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-sm font-semibold tracking-tight">Dragon AI Agent</h1>
|
||||
<p className="text-[10px] text-text-muted mt-0.5 font-light tracking-wide">资金驱动 · 四层漏斗模型</p>
|
||||
<p className="text-xs text-text-muted mt-0.5 font-light tracking-wide">资金驱动 · 四层漏斗模型</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,7 +51,7 @@ export default function RootLayout({
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-5 border-t border-slate-800/50">
|
||||
<div className="text-[10px] text-text-muted leading-relaxed">
|
||||
<div className="text-xs text-text-muted leading-relaxed">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<span className="w-1 h-1 rounded-full bg-emerald-500" />
|
||||
<span>Tushare Pro + 腾讯行情</span>
|
||||
@ -105,7 +105,7 @@ function MobileNavItem({ href, label, children }: { href: string; label: string;
|
||||
className="flex flex-col items-center gap-1 text-text-muted hover:text-text-primary transition-colors active:scale-95"
|
||||
>
|
||||
<span className="text-lg">{children}</span>
|
||||
<span className="text-[10px] font-medium">{label}</span>
|
||||
<span className="text-xs font-medium">{label}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { fetchAPI, postAPI } from "@/lib/api";
|
||||
import type { LatestResult, SectorData } from "@/lib/api";
|
||||
import type { LatestResult, SectorData, IndexOverview } from "@/lib/api";
|
||||
import MarketTemp from "@/components/market-temp";
|
||||
import StockCard from "@/components/stock-card";
|
||||
import SectorHeatmap from "@/components/sector-heatmap";
|
||||
@ -22,19 +22,22 @@ export default function DashboardPage() {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [refreshResult, setRefreshResult] = useState<string | null>(null);
|
||||
const [llmEnabled, setLlmEnabled] = useState(false);
|
||||
const [indices, setIndices] = useState<IndexOverview[]>([]);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
const [latest, sectorData, status, health] = await Promise.all([
|
||||
const [latest, sectorData, status, health, overview] = await Promise.all([
|
||||
fetchAPI<LatestResult>("/api/recommendations/latest"),
|
||||
fetchAPI<SectorData[]>("/api/sectors/hot?limit=8"),
|
||||
fetchAPI<ScanStatus>("/api/recommendations/status"),
|
||||
fetchAPI<{ llm_enabled: boolean }>("/api/health"),
|
||||
fetchAPI<IndexOverview[]>("/api/market/overview").catch(() => []),
|
||||
]);
|
||||
setData(latest);
|
||||
setSectors(sectorData);
|
||||
setScanStatus(status);
|
||||
setLlmEnabled(health.llm_enabled);
|
||||
setIndices(overview);
|
||||
} catch (e) {
|
||||
console.error("加载数据失败:", e);
|
||||
} finally {
|
||||
@ -92,7 +95,7 @@ export default function DashboardPage() {
|
||||
<div>
|
||||
<h1 className="text-lg font-bold md:hidden tracking-tight">Dragon AI Agent</h1>
|
||||
{scanStatus && (
|
||||
<p className="text-[11px] text-text-muted mt-1">
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
{scanStatus.is_trading ? (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 bg-emerald-400 rounded-full animate-pulse" />
|
||||
@ -139,7 +142,7 @@ export default function DashboardPage() {
|
||||
|
||||
{/* Market temp + Sector heatmap */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<MarketTemp data={data?.market_temperature ?? null} />
|
||||
<MarketTemp data={data?.market_temperature ?? null} indices={indices} />
|
||||
<SectorHeatmap sectors={sectors} />
|
||||
</div>
|
||||
|
||||
@ -152,7 +155,7 @@ export default function DashboardPage() {
|
||||
<span className="text-text-primary ml-1.5 font-mono tabular-nums">{data.recommendations.length}</span>
|
||||
) : ""}
|
||||
</h2>
|
||||
<a href="/recommendations" className="text-[11px] text-text-muted hover:text-orange-400 transition-colors flex items-center gap-1">
|
||||
<a href="/recommendations" className="text-xs text-text-muted hover:text-orange-400 transition-colors flex items-center gap-1">
|
||||
查看全部
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
|
||||
@ -48,7 +48,7 @@ export default function RecommendationsPage() {
|
||||
<div className="flex items-center justify-between mb-5 animate-fade-in-up">
|
||||
<div>
|
||||
<h1 className="text-lg font-bold tracking-tight">推荐列表</h1>
|
||||
<p className="text-[11px] text-text-muted mt-0.5">
|
||||
<p className="text-xs text-text-muted mt-0.5">
|
||||
共 <span className="font-mono tabular-nums">{filtered.length}</span> 只
|
||||
</p>
|
||||
</div>
|
||||
@ -66,7 +66,7 @@ export default function RecommendationsPage() {
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => setFilter(key)}
|
||||
className={`text-[11px] px-4 py-1.5 rounded-xl whitespace-nowrap transition-all duration-200 font-medium ${
|
||||
className={`text-xs px-4 py-1.5 rounded-xl whitespace-nowrap transition-all duration-200 font-medium ${
|
||||
filter === key
|
||||
? "bg-gradient-to-r from-orange-500/25 to-amber-500/25 text-orange-400 border border-orange-500/15"
|
||||
: "bg-white/[0.03] text-text-muted hover:text-text-secondary hover:bg-white/[0.06] border border-transparent"
|
||||
|
||||
@ -16,7 +16,7 @@ export default function SectorsPage() {
|
||||
<div className="max-w-5xl mx-auto px-4 md:px-8 pt-6 pb-20 md:pb-10">
|
||||
<div className="mb-5 animate-fade-in-up">
|
||||
<h1 className="text-lg font-bold tracking-tight">板块分析</h1>
|
||||
<p className="text-[11px] text-text-muted mt-0.5">基于资金流向和涨跌表现的热度排名</p>
|
||||
<p className="text-xs text-text-muted mt-0.5">基于资金流向和涨跌表现的热度排名</p>
|
||||
</div>
|
||||
<SectorHeatmap sectors={sectors} />
|
||||
</div>
|
||||
|
||||
@ -69,7 +69,7 @@ export default function StockDetailPage() {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 md:px-8 pt-6 pb-20 md:pb-10 space-y-5">
|
||||
{/* Back */}
|
||||
<a href="/" className="inline-flex items-center gap-1.5 text-[11px] text-text-muted hover:text-text-primary transition-colors animate-fade-in-up">
|
||||
<a href="/" className="inline-flex items-center gap-1.5 text-xs text-text-muted hover:text-text-primary transition-colors animate-fade-in-up">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" />
|
||||
</svg>
|
||||
@ -84,7 +84,7 @@ export default function StockDetailPage() {
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-lg font-bold tracking-tight">{quote.name}</span>
|
||||
<span className="text-[11px] text-text-muted font-mono tabular-nums">{quote.ts_code}</span>
|
||||
<span className="text-xs text-text-muted font-mono tabular-nums">{quote.ts_code}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-baseline gap-3">
|
||||
@ -174,7 +174,7 @@ export default function StockDetailPage() {
|
||||
function QuoteStat({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="bg-white/[0.02] rounded-lg px-3 py-2 border border-white/[0.03]">
|
||||
<div className="text-[9px] text-text-muted uppercase tracking-wider mb-0.5">{label}</div>
|
||||
<div className="text-xs text-text-muted mb-0.5">{label}</div>
|
||||
<div className="text-xs font-mono tabular-nums">{value}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { getTempColor, getTempLabel } from "@/lib/utils";
|
||||
import type { MarketTemperatureData } from "@/lib/api";
|
||||
import type { MarketTemperatureData, IndexOverview } from "@/lib/api";
|
||||
|
||||
export default function MarketTemp({ data }: { data: MarketTemperatureData | null }) {
|
||||
interface MarketTempProps {
|
||||
data: MarketTemperatureData | null;
|
||||
indices?: IndexOverview[];
|
||||
}
|
||||
|
||||
export default function MarketTemp({ data, indices }: MarketTempProps) {
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="glass-card-static p-5 animate-fade-in-up">
|
||||
<div className="h-24 animate-shimmer rounded-lg" />
|
||||
<div className="h-32 animate-shimmer rounded-lg" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -15,16 +20,19 @@ export default function MarketTemp({ data }: { data: MarketTemperatureData | nul
|
||||
const color = getTempColor(data.temperature);
|
||||
const label = getTempLabel(data.temperature);
|
||||
const ratio = data.up_count / Math.max(data.down_count, 1);
|
||||
const hasBrokenRate = data.broken_rate != null && data.broken_rate > 0;
|
||||
const hasMaxStreak = data.max_streak != null && data.max_streak > 0;
|
||||
|
||||
return (
|
||||
<div className="glass-card-static p-5 animate-fade-in-up">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xs font-semibold text-text-muted uppercase tracking-wider">市场温度</h2>
|
||||
<span className="text-[10px] text-text-muted font-mono tabular-nums">{data.trade_date}</span>
|
||||
<span className="text-xs text-text-muted font-mono tabular-nums">{data.trade_date}</span>
|
||||
</div>
|
||||
|
||||
{/* Temperature gauge + stats */}
|
||||
<div className="flex items-center gap-5">
|
||||
{/* Top row: Gauge + Stats grid */}
|
||||
<div className="flex items-center gap-5 mb-4">
|
||||
{/* Circular gauge */}
|
||||
<div className="relative w-24 h-24 flex-shrink-0">
|
||||
<svg viewBox="0 0 100 100" className="w-full h-full -rotate-90">
|
||||
@ -42,7 +50,6 @@ export default function MarketTemp({ data }: { data: MarketTemperatureData | nul
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-1000 ease-out"
|
||||
/>
|
||||
{/* Glow effect */}
|
||||
<circle
|
||||
cx="50" cy="50" r="40" fill="none"
|
||||
stroke={color} strokeWidth="7"
|
||||
@ -54,30 +61,86 @@ export default function MarketTemp({ data }: { data: MarketTemperatureData | nul
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<span className="text-xl font-bold font-mono tabular-nums" style={{ color }}>{data.temperature}</span>
|
||||
<span className="text-[9px] text-text-muted font-medium mt-0.5">{label}</span>
|
||||
<span className="text-xs text-text-muted font-medium mt-0.5">{label}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats grid */}
|
||||
<div className="flex-1 grid grid-cols-2 gap-2">
|
||||
<StatCard label="涨/跌">
|
||||
<span className="text-red-400 font-mono tabular-nums">{data.up_count}</span>
|
||||
<span className="text-text-muted/40"> / </span>
|
||||
<span className="text-emerald-400 font-mono tabular-nums">{data.down_count}</span>
|
||||
{/* Key stats grid 3x2 */}
|
||||
<div className="flex-1 grid grid-cols-3 gap-2">
|
||||
<StatCard label="涨停">
|
||||
<span className="text-red-400 font-mono tabular-nums font-semibold">{data.limit_up_count}</span>
|
||||
</StatCard>
|
||||
<StatCard label="跌停">
|
||||
<span className="text-emerald-400 font-mono tabular-nums font-semibold">{data.limit_down_count ?? 0}</span>
|
||||
</StatCard>
|
||||
<StatCard label="涨跌比">
|
||||
<span className={`font-mono tabular-nums font-medium ${ratio > 1 ? "text-red-400" : "text-emerald-400"}`}>
|
||||
<span className={`font-mono tabular-nums font-semibold ${ratio > 1 ? "text-red-400" : "text-emerald-400"}`}>
|
||||
{ratio.toFixed(2)}
|
||||
</span>
|
||||
</StatCard>
|
||||
<StatCard label="涨停">
|
||||
<span className="text-red-400 font-mono tabular-nums font-medium">{data.limit_up_count}</span>
|
||||
<StatCard label="上涨">
|
||||
<span className="text-red-400 font-mono tabular-nums">{data.up_count}</span>
|
||||
</StatCard>
|
||||
<StatCard label="连板">
|
||||
<span className="text-orange-400 font-mono tabular-nums font-medium">{data.max_streak || "-"}</span>
|
||||
<StatCard label="下跌">
|
||||
<span className="text-emerald-400 font-mono tabular-nums">{data.down_count}</span>
|
||||
</StatCard>
|
||||
<StatCard label={hasMaxStreak ? "最高连板" : "连板"}>
|
||||
<span className="text-orange-400 font-mono tabular-nums font-semibold">
|
||||
{hasMaxStreak ? `${data.max_streak}连板` : "-"}
|
||||
</span>
|
||||
</StatCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Middle row: Broken rate + MA20 */}
|
||||
<div className="grid grid-cols-2 gap-2 mb-2">
|
||||
<div className="bg-white/[0.02] rounded-lg px-3 py-2 border border-white/[0.03]">
|
||||
<div className="text-xs text-text-muted mb-0.5">炸板率</div>
|
||||
{hasBrokenRate ? (
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-sm font-mono tabular-nums font-semibold text-amber-400">
|
||||
{data.broken_rate!.toFixed(1)}%
|
||||
</span>
|
||||
{data.broken_rate! > 50 && (
|
||||
<span className="text-xs text-amber-500/60">偏高</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-text-muted/40">-</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-white/[0.02] rounded-lg px-3 py-2 border border-white/[0.03]">
|
||||
<div className="text-xs text-text-muted mb-0.5">上证均线</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${data.index_above_ma20 ? "bg-red-400" : "bg-emerald-400"}`} />
|
||||
<span className={`text-sm font-semibold ${data.index_above_ma20 ? "text-red-400" : "text-emerald-400"}`}>
|
||||
{data.index_above_ma20 ? "均线之上" : "均线之下"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom row: 3 major indices in one row */}
|
||||
{indices && indices.length > 0 && (
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{indices.map((idx) => (
|
||||
<div key={idx.code} className="bg-white/[0.02] rounded-lg px-3 py-2 border border-white/[0.03]">
|
||||
<div className="text-xs text-text-muted mb-0.5">
|
||||
{idx.name}
|
||||
{idx.realtime && <span className="text-emerald-400/60 ml-1">· 实时</span>}
|
||||
</div>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className={`text-sm font-mono tabular-nums font-semibold ${idx.pct_chg > 0 ? "text-red-400" : idx.pct_chg < 0 ? "text-emerald-400" : "text-text-primary"}`}>
|
||||
{idx.close.toFixed(2)}
|
||||
</span>
|
||||
<span className={`text-xs font-mono tabular-nums ${idx.pct_chg > 0 ? "text-red-400" : idx.pct_chg < 0 ? "text-emerald-400" : "text-text-muted"}`}>
|
||||
{idx.pct_chg > 0 ? "+" : ""}{idx.pct_chg.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -85,8 +148,8 @@ export default function MarketTemp({ data }: { data: MarketTemperatureData | nul
|
||||
function StatCard({ label, children }: { label: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="bg-white/[0.02] rounded-lg px-3 py-2 border border-white/[0.03]">
|
||||
<div className="text-[9px] text-text-muted mb-0.5 font-medium uppercase tracking-wider">{label}</div>
|
||||
<div className="text-xs">{children}</div>
|
||||
<div className="text-xs text-text-muted mb-0.5 font-medium">{label}</div>
|
||||
<div className="text-sm">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ export default function SectorHeatmap({ sectors }: { sectors: SectorData[] }) {
|
||||
if (!sectors.length) {
|
||||
return (
|
||||
<div className="glass-card-static p-5">
|
||||
<h2 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-4">热门板块</h2>
|
||||
<div className="text-xs text-text-muted text-center py-6">暂无数据</div>
|
||||
<h2 className="text-sm font-semibold text-text-muted mb-4">热门板块</h2>
|
||||
<div className="text-sm text-text-muted text-center py-6">暂无数据</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -17,52 +17,73 @@ export default function SectorHeatmap({ sectors }: { sectors: SectorData[] }) {
|
||||
|
||||
return (
|
||||
<div className="glass-card-static p-5">
|
||||
<h2 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-4">热门板块</h2>
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold text-text-muted mb-4">热门板块</h2>
|
||||
<div className="space-y-2">
|
||||
{sectors.map((s, index) => {
|
||||
const intensity = s.heat_score / Math.max(maxScore, 1);
|
||||
const isUp = s.pct_change > 0;
|
||||
const barColor = isUp
|
||||
? `rgba(239, 68, 68, ${0.08 + intensity * 0.15})`
|
||||
: `rgba(34, 197, 94, ${0.08 + intensity * 0.15})`;
|
||||
const accentColor = isUp
|
||||
? `rgba(239, 68, 68, ${0.4 + intensity * 0.6})`
|
||||
: `rgba(34, 197, 94, ${0.4 + intensity * 0.6})`;
|
||||
const isTop3 = index < 3;
|
||||
|
||||
// Bar width based on score relative to max
|
||||
const barWidth = `${Math.max(intensity * 100, 15)}%`;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={s.sector_code}
|
||||
className="relative rounded-lg overflow-hidden animate-fade-in-up"
|
||||
style={{ animationDelay: `${index * 50}ms` }}
|
||||
className="relative rounded-xl overflow-hidden animate-fade-in-up"
|
||||
style={{ animationDelay: `${index * 40}ms` }}
|
||||
>
|
||||
{/* Background fill */}
|
||||
{/* Colored bar background - width proportional to heat score */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ backgroundColor: barColor }}
|
||||
className="absolute inset-y-0 left-0 rounded-xl transition-all duration-500"
|
||||
style={{
|
||||
width: barWidth,
|
||||
background: isUp
|
||||
? `linear-gradient(90deg, rgba(239, 68, 68, ${0.06 + intensity * 0.18}), rgba(239, 68, 68, ${0.02 + intensity * 0.06}))`
|
||||
: `linear-gradient(90deg, rgba(34, 197, 94, ${0.06 + intensity * 0.18}), rgba(34, 197, 94, ${0.02 + intensity * 0.06}))`,
|
||||
}}
|
||||
/>
|
||||
{/* Left accent line */}
|
||||
<div
|
||||
className="absolute left-0 top-0 bottom-0 w-0.5"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
/>
|
||||
<div className="relative flex items-center justify-between px-4 py-2.5">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="text-sm font-medium">{s.sector_name}</span>
|
||||
{s.limit_up_count > 0 && (
|
||||
<span className="text-[10px] text-text-muted bg-white/[0.04] px-1.5 py-0.5 rounded">
|
||||
涨停 {s.limit_up_count}
|
||||
|
||||
<div className="relative flex items-center justify-between px-3 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Rank number */}
|
||||
<span className={`w-6 h-6 rounded-lg flex items-center justify-center text-xs font-bold shrink-0 ${
|
||||
index === 0
|
||||
? "bg-gradient-to-br from-amber-500/30 to-amber-600/20 text-amber-400 border border-amber-500/20"
|
||||
: index === 1
|
||||
? "bg-gradient-to-br from-slate-400/20 to-slate-500/15 text-slate-300 border border-slate-400/15"
|
||||
: index === 2
|
||||
? "bg-gradient-to-br from-orange-700/20 to-orange-800/15 text-orange-400 border border-orange-600/15"
|
||||
: "bg-white/[0.03] text-text-muted border border-white/[0.04]"
|
||||
}`}>
|
||||
{index + 1}
|
||||
</span>
|
||||
<div>
|
||||
<span className={`text-sm font-medium ${isTop3 ? "text-text-primary" : "text-text-secondary"}`}>
|
||||
{s.sector_name}
|
||||
</span>
|
||||
)}
|
||||
{s.limit_up_count > 0 && (
|
||||
<span className="text-xs text-text-muted ml-2">
|
||||
涨停 {s.limit_up_count}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs">
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className={`font-mono tabular-nums ${s.capital_inflow > 0 ? "text-red-400" : "text-emerald-400"}`}>
|
||||
{s.capital_inflow > 0 ? "+" : ""}
|
||||
{formatNumber(s.capital_inflow)}
|
||||
</span>
|
||||
<span className={`font-mono tabular-nums font-medium ${isUp ? "text-red-400" : "text-emerald-400"}`}>
|
||||
<span className={`font-mono tabular-nums font-semibold min-w-[60px] text-right ${isUp ? "text-red-400" : "text-emerald-400"}`}>
|
||||
{s.pct_change > 0 ? "+" : ""}
|
||||
{s.pct_change.toFixed(2)}%
|
||||
</span>
|
||||
<span className="text-orange-400/80 font-mono tabular-nums text-[10px] bg-orange-500/[0.08] px-1.5 py-0.5 rounded">
|
||||
{/* Heat score pill */}
|
||||
<span className={`font-mono tabular-nums text-xs font-semibold px-2 py-1 rounded-lg min-w-[36px] text-center ${
|
||||
isTop3
|
||||
? "bg-orange-500/20 text-orange-400 border border-orange-500/15"
|
||||
: "bg-white/[0.04] text-text-muted border border-white/[0.04]"
|
||||
}`}>
|
||||
{s.heat_score.toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -28,6 +28,15 @@ export interface MarketTemperatureData {
|
||||
index_above_ma20?: boolean;
|
||||
}
|
||||
|
||||
export interface IndexOverview {
|
||||
name: string;
|
||||
code: string;
|
||||
close: number;
|
||||
pct_chg: number;
|
||||
volume: number;
|
||||
realtime: boolean;
|
||||
}
|
||||
|
||||
export interface RecommendationData {
|
||||
ts_code: string;
|
||||
name: string;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user