@ai.weget.jp/bot 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/dist/src/config/systemConfig.js +1 -0
- package/dist/src/ipc/registerHandlers.js +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/preload.cjs +26 -8
- package/dist/src/renderer/app.js +1 -1
- package/dist/src/renderer/index.html +317 -13
- package/dist/src/renderer/styles.css +451 -2
- package/dist/src/services/aiChatService.js +1 -0
- package/dist/src/services/authApiService.js +1 -1
- package/dist/src/services/trade/gmoCoinGateway.js +1 -0
- package/dist/src/services/trade/gmoKlineService.js +1 -0
- package/dist/src/services/trade/gmoWindowService.js +1 -0
- package/dist/src/services/trade/tradeConfigService.js +1 -0
- package/dist/src/services/trade/tradeStatusService.js +1 -0
- package/dist/src/services/trade/types.js +1 -0
- package/dist/src/services/windowManagerService.js +1 -1
- package/dist/src/wsClient.js +1 -1
- package/package.json +1 -7
- package/dist/src/renderer/chat.css +0 -128
- package/dist/src/renderer/chat.html +0 -27
- package/dist/src/renderer/chat.js +0 -1
- package/dist/src/services/aiMemoryService.js +0 -1
- package/dist/src/services/commandOrchestratorService.js +0 -1
- package/dist/src/services/debugLogService.js +0 -1
- package/dist/src/services/taskExecutorService.js +0 -1
- package/dist/src/services/taskParsingService.js +0 -1
- package/dist/src/services/textService.js +0 -1
- package/dist/src/services/workflowService.js +0 -1
- package/scripts/backtest-loop.cjs +0 -137
- package/scripts/prepare-playwright-browsers.cjs +0 -181
- package/scripts/run-workflow.cjs +0 -381
- package/scripts/workflows/kaitori-login.json +0 -17
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ WeGet Bot(Electron + CLI)。
|
|
|
6
6
|
```bash
|
|
7
7
|
npm i -g @ai.weget.jp/bot
|
|
8
8
|
```
|
|
9
|
-
|
|
9
|
+
Bot 当前为交易执行端。
|
|
10
10
|
|
|
11
11
|
## 快速使用
|
|
12
12
|
打开 UI:
|
|
@@ -19,10 +19,19 @@ weget-bot ui
|
|
|
19
19
|
2. 执行 `weget-bot ui`
|
|
20
20
|
|
|
21
21
|
默认内置:
|
|
22
|
-
- `
|
|
23
|
-
- `
|
|
22
|
+
- `loginApiUrl=https://api.weget.jp/login`
|
|
23
|
+
- `gmoApiBaseUrl=https://api.coin.z.com/private`
|
|
24
|
+
- `gmoKlineBaseUrl=https://coin.z.com/jp/corp/chart`
|
|
25
|
+
- `aiModel=gpt-4.1-mini`
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
覆盖方式:直接在 Bot UI 的 Config 页面修改并保存。
|
|
28
|
+
|
|
29
|
+
GMO 账户数据查询通过 API Key 完成:
|
|
30
|
+
- 暗号資産 API Key / Secret
|
|
31
|
+
- FX API Key / Secret
|
|
32
|
+
|
|
33
|
+
Bot UI 支持保存并记住两组 key(Crypto/FX),保存位置在本机 `userData/trade-api-config.json`。
|
|
34
|
+
同一配置文件也会保存 AI 设置。系统 URL/Path 使用内置固定值。
|
|
26
35
|
|
|
27
36
|
查看命令帮助:
|
|
28
37
|
```bash
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SYSTEM_CONFIG={loginApiUrl:"https://api.weget.jp/login",botUploadApiUrl:"https://api.weget.jp/bot/upload-url",gmoCryptoPrivateApiBaseUrl:"https://api.coin.z.com/private",gmoCryptoPublicApiBaseUrl:"https://api.coin.z.com/public",gmoFxPrivateApiBaseUrl:"https://forex-api.coin.z.com/private",gmoFxPublicApiBaseUrl:"https://forex-api.coin.z.com/public",gmoAssetsPath:"/v1/account/assets",gmoFxAssetsPath:"/v1/account/assets",gmoCoinMarginPath:"/v1/account/margin",gmoPositionsPath:"/v1/openPositions",gmoPositionSummaryPath:"/v1/positionSummary",gmoFxOrderPath:"/v1/order",gmoCoinCloseOrderPath:"/v1/closeOrder",gmoFxCloseOrderPath:"/v1/closeOrder"};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,getTradeApiConfig:s,saveTradeApiConfig:a,setCurrentSession:c,getCurrentSession:m,emitLog:g,openGmoKlineWindow:u,getGmoMarketQuotes:d,getGmoSymbolRules:l,getGmoBuyingPower:S,getGmoAccountMetrics:y,getGmoPositionSummary:k,getGmoOpenPositions:p,placeGmoFxOrder:f,placeGmoCoinOrder:h,placeGmoFxCloseOrder:b,placeGmoCoinCloseOrder:w,getGmoApiState:C,testGmoApi:L,buildTradeStatusSnapshot:E,closeTradeWindows:I,startWsClient:x,stopWsClient:v,handleBotChatMessage:U,writeErrorLog:A,writeRuntimeLog:G})=>{e.handle("auth:login",async(e,t)=>{const n=r(t),s=String(n.botId||"").trim();if(!s)return{ok:!1,error:"Bot ID is required"};try{const r=await o(String(n.email||""),String(n.password||""));await i({botId:s,email:String(n.email||"").trim(),password:String(n.password||""),remember:Boolean(n.remember)});const e={...r,botId:s};return c(e),x({botId:s,userId:String(r.userId||""),accessToken:String(r.accessToken||"")}),g("[auth] login success",{userId:r.userId||"",email:r.email||"",botId:s}),{ok:!0,session:{userId:String(r.userId||""),email:String(r.email||""),botId:s}}}catch(r){const e=r instanceof Error?r.message:String(r);return g("[auth] login failed",{error:e}),{ok:!1,error:e}}}),e.handle("auth:logout",async()=>(v(),c(null),I(),g("[auth] logout"),{ok:!0})),e.handle("auth:getSavedProfile",async()=>{try{return{ok:!0,profile:await n()}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getApiConfig",async()=>({ok:!0,config:s()})),e.handle("trade:saveApiConfig",async(r,e)=>{try{return{ok:!0,config:await a(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("bot:getRuntimeInfo",async()=>{const e=m(),o=r(e);return{ok:!0,status:e?"connected":"disconnected",session:e?{userId:String(o.userId||""),email:String(o.email||""),botId:String(o.botId||"")}:null,runtime:{loginApiUrl:t.loginApiUrl,capabilities:t.capabilities,gmoApiState:C(),aiModel:s().aiModel}}}),e.handle("trade:openGmoKlineWindow",async(e,t)=>{const o=r(t),i=String(o.symbol||"BTC_JPY").trim()||"BTC_JPY",n=String(o.interval||"1m").trim()||"1m",s="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await u({symbol:i,interval:n,market:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getMarketQuotes",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=Array.isArray(o.symbols)?o.symbols.map(r=>String(r||"").trim().toUpperCase()).filter(Boolean):void 0;try{return{ok:!0,...await d({market:i,symbols:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getSymbolRules",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await l({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getBuyingPower",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await S({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getAccountMetrics",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await y({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getPositionSummary",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=String(o.symbol||"").trim().toUpperCase();if("coin"===i&&!n)return{ok:!1,error:"symbol is required for coin positionSummary"};try{return{ok:!0,...await k({market:i,symbol:n||void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getOpenPositions",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=String(o.symbol||"").trim().toUpperCase();if("coin"===i&&!n)return{ok:!1,error:"symbol is required for coin openPositions"};const s=Number(o.page),a=Number(o.prevId),c=Number(o.count);try{return{ok:!0,...await p({market:i,symbol:n||void 0,page:Number.isFinite(s)?s:void 0,prevId:Number.isFinite(a)?a:void 0,count:Number.isFinite(c)?c:void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeFxOrder",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await f({symbol:i,side:n,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeCoinOrder",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await h({symbol:i,side:n,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeFxPosition",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=Number(o.positionId),a=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(s)||s<=0)return{ok:!1,error:"positionId is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await b({symbol:i,side:n,positionId:s,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeCoinPosition",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=Number(o.positionId),a=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(s)||s<=0)return{ok:!1,error:"positionId is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await w({symbol:i,side:n,positionId:s,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:testGmoApi",async()=>L()),e.handle("trade:getStatusSnapshot",async()=>{try{return{ok:!0,snapshot:await E()}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("chat:send",async(e,t)=>{const o=r(t),i=String(o.message||"").trim();if(!i)return{ok:!1,error:"message is required"};try{return{ok:!0,answer:(await U({text:i})).answer}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:writeError",async(e,t)=>{const o=r(t),i=String(o.source||"").trim()||"ui",n=String(o.message||"").trim()||"unknown error";try{return await A({source:i,message:n,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:write",async(e,t)=>{const o=r(t),i=String(o.level||"").trim().toLowerCase(),n="error"===i?"error":"debug"===i?"debug":"info",s=String(o.source||"").trim()||"ui",a=String(o.message||"").trim()||"log";try{return await G({level:n,source:s,message:a,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}})};
|
package/dist/src/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"dotenv";import r from"node:path";import t from"node:os";import o from"node:crypto";import{existsSync as s}from"node:fs";import n from"node:fs/promises";import{fileURLToPath as a}from"node:url";import{createRequire as i}from"node:module";import{BotWsClient as l}from"./wsClient.js";import{detectLanguage as c,normalizeIncomingText as p,isLikelyMojibake as m,buildLanguageInstruction as d,buildEmptyReply as u,buildActionFailedReply as w,buildMojibakeReply as g,buildConfirmPrompt as f,buildCancelReply as v,buildStepStartReply as _,buildStepFallbackSummary as h,isNumericChoice as S,isConfirmText as T,isCancelText as y,isLikelyNewTask as b,matchWorkflowByCommand as A}from"./services/textService.js";import{parseJsonLoose as O,normalizeActionUrl as L,resolveHostFromUrl as I,isValidOpenUrl as B,extractFirstUrl as k,extractBasicCredentials as E,isBasicAuthStep as C}from"./services/taskParsingService.js";import{normalizeWorkflow as P}from"./services/workflowService.js";import{createDebugLogger as R}from"./services/debugLogService.js";import{createAiMemoryService as N}from"./services/aiMemoryService.js";import{registerIpcHandlers as U}from"./ipc/registerHandlers.js";import{createTaskExecutorService as x}from"./services/taskExecutorService.js";import{createAuthApiService as M}from"./services/authApiService.js";import{createWindowManager as W}from"./services/windowManagerService.js";import{createCommandOrchestratorService as j}from"./services/commandOrchestratorService.js";const F=i(import.meta.url),{app:D,BrowserWindow:H,ipcMain:G,dialog:J,shell:$,nativeImage:q}=F("electron"),z=a(import.meta.url),Y=r.dirname(z),K=e=>{const r=e&&"object"==typeof e?e:null;return r&&"string"==typeof r.code?r.code:""},X=(()=>{const t=[...[r.resolve(process.cwd(),".env"),r.resolve(Y,"..",".env"),r.resolve(r.dirname(process.execPath),".env"),r.resolve(process.resourcesPath||"",".env")].filter(Boolean),...[r.resolve(process.cwd(),".env.example"),r.resolve(Y,"..",".env.example"),r.resolve(r.dirname(process.execPath),".env.example"),r.resolve(process.resourcesPath||"",".env.example")].filter(Boolean)],o=[];let n=!1,a=!1;for(const r of t)s(r)&&(e.config({path:r,override:!1}),o.push(r),r.endsWith(".env")&&(n=!0),r.endsWith(".env.example")&&(a=!0));return{loadedFiles:o,loadedFromEnv:n,loadedFromExample:a}})(),V=(()=>{const e=String(process.env.PLAYWRIGHT_BROWSERS_PATH||"").trim();if(e)return e;const o=["win32"===process.platform?r.join(process.env.LOCALAPPDATA||r.join(t.homedir(),"AppData","Local"),"weget-bot","pw-browsers"):"darwin"===process.platform?r.join(t.homedir(),"Library","Caches","weget-bot","pw-browsers"):r.join(t.homedir(),".cache","weget-bot","pw-browsers"),r.resolve(process.cwd(),"pw-browsers"),r.resolve(r.dirname(process.execPath),"pw-browsers"),r.resolve(process.resourcesPath||"","pw-browsers")];for(const e of o)if(s(e))return e;return""})();V&&(process.env.PLAYWRIGHT_BROWSERS_PATH=V);const Z="https://api.weget.jp",Q={wsUrl:process.env.BOT_WS_URL||"wss://ws.weget.jp",loginApiUrl:process.env.BOT_LOGIN_API_URL||`${Z}/login`,botUploadApiUrl:process.env.BOT_UPLOAD_API_URL||`${Z}/bot/upload-url`,botWorkflowListApiUrl:process.env.BOT_WORKFLOW_LIST_API_URL||`${Z}/bot/workflow/list`,botRuntimeConfigApiUrl:process.env.BOT_RUNTIME_CONFIG_API_URL||`${Z}/bot/runtime-config`,version:process.env.BOT_VERSION||"0.1.0",heartbeatSec:Number(process.env.BOT_HEARTBEAT_SEC||30),reconnectMs:Number(process.env.BOT_RECONNECT_MS||3e3),reconnectMaxAttempts:Number(process.env.BOT_RECONNECT_MAX_ATTEMPTS||10),maxOutputTokens:Number(process.env.BOT_MAX_OUTPUT_TOKENS||120),parseMaxOutputTokens:Number(process.env.BOT_TASK_PARSE_MAX_OUTPUT_TOKENS||220),debugLogPath:String(process.env.BOT_DEBUG_LOG_PATH||"").trim(),debugLogLevel:String(process.env.BOT_DEBUG_LOG_LEVEL||"info").trim().toLowerCase(),debugLogMaxMb:Number(process.env.BOT_DEBUG_LOG_MAX_MB||50),debugLogRedact:"true"===String(process.env.BOT_DEBUG_LOG_REDACT||"true").toLowerCase(),playwrightHeadless:"true"===String(process.env.BOT_PLAYWRIGHT_HEADLESS||"true").toLowerCase(),playwrightNavigationTimeoutMs:Number(process.env.BOT_PLAYWRIGHT_NAV_TIMEOUT_MS||3e4),playwrightJsSettleMaxMs:Number(process.env.BOT_PLAYWRIGHT_JS_SETTLE_MAX_MS||3e3),playwrightChannel:String(process.env.BOT_PLAYWRIGHT_CHANNEL||"").trim(),plannerConfidenceThreshold:Number(process.env.BOT_PLANNER_CONFIDENCE_THRESHOLD||.55),plannerInstructions:process.env.BOT_PLANNER_INSTRUCTIONS||["You are an execution planner for a desktop bot.","Default behavior: infer intent, auto-complete missing details with reasonable assumptions, then return executable actions.","Return strict JSON only.","JSON keys: mode, confidence, actions, assumptions, response.","mode enum: action, chat.","confidence: number between 0 and 1.","actions must be an array when mode=action.","Allowed action.type: open_url, wait, screenshot, read_page_text, http_get, write_file, set_http_credentials, auto_login_form.","For wait use params.ms.","For screenshot use params.filename(optional).","For read_page_text use params.max_chars(optional).","For http_get use params.url.","For write_file use params.filename and params.content.","For set_http_credentials use params.username and params.password, optionally params.url or params.host.","For auto_login_form use params.username and params.password.","Prefer mode=action unless request is pure conversational chat.","No tutorial, no user guide, no explanatory prose.","No markdown, no extra keys."].join(" "),capabilities:(process.env.BOT_CAPABILITIES||"playwright,ai").split(",").map(e=>e.trim()).filter(Boolean)},ee=process.argv.includes("--script");0===X.loadedFiles.length&&console.log(`${(new Date).toISOString()} [env] no .env or .env.example file loaded from known paths`);let re=null;const te=()=>r.join(D.getPath("userData"),"login-profile.json"),oe=()=>r.join(D.getPath("userData"),"memory","action-memory.json"),se=(e,r)=>{let t=0;const o=Math.min(e.length,r.length);for(let s=0;s<o;s+=1)t+=Number(e[s]||0)*Number(r[s]||0);return t},ne=e=>Math.sqrt(se(e,e)),ae=W({BrowserWindow:H,preloadPath:r.join(Y,"preload.cjs"),rendererDir:r.join(Y,"renderer"),windowIconPath:r.join(Y,"renderer","logo.png")}),ie=r.join(Y,"renderer","logo.png");let le=Q.playwrightHeadless;const{emitDebugEvent:ce}=R({env:Q,getRuntimeContext:()=>({botId:re?.botId||"",userId:re?.userId||""})}),pe=(e,r)=>{ae.emitBotLog({message:e,data:r||null,ts:(new Date).toISOString()})},me=(e,r="")=>{const t=`--${e}`,o=process.argv.findIndex(e=>e===t);if(o>=0&&o+1<process.argv.length)return String(process.argv[o+1]||"");const s=process.argv.find(e=>e.startsWith(`${t}=`));return s?String(s.slice(t.length+1)):r},de=async()=>{const e=me("email",process.env.BOT_SCRIPT_EMAIL||"").trim(),t=me("password",process.env.BOT_SCRIPT_PASSWORD||""),s=me("bot-id",process.env.BOT_SCRIPT_BOT_ID||"").trim(),a=me("workflow-file",process.env.BOT_SCRIPT_WORKFLOW_FILE||""),i=me("workflow-json",process.env.BOT_SCRIPT_WORKFLOW_JSON||""),l=me("line-text",process.env.BOT_SCRIPT_LINE_TEXT||"").trim(),c=me("lang",process.env.BOT_SCRIPT_LANG||"zh").trim()||"zh",p=me("run-id",o.randomUUID()).trim()||o.randomUUID();if(!e||!t||!s)throw new Error("missing required args: --email --password --bot-id");const m=await(async(e,t)=>{const o=String(t||"").trim();if(o)return JSON.parse(o);const s=String(e||"").trim();if(!s)throw new Error("missing workflow source: --workflow-file or --workflow-json");const a=await n.readFile(r.resolve(s),"utf8");return JSON.parse(a)})(a,i),d=((e,r="script_workflow")=>Array.isArray(e)?P({workflow_id:`script-${o.randomUUID()}`,name:r,trigger_command:"script",description:"script mode",bot_id:null,is_active:!0,steps_json:e}):P(e||{}))(m,"script_workflow");if(!d?.workflowId||!Array.isArray(d.steps)||0===d.steps.length)throw new Error("invalid workflow input: steps required");const u=await fe(e,t);re={...u,botId:s};try{await Le()}catch{}const w=ue.getLineSession("__script__");w.preferredLanguage=c,w.pendingWorkflow=null;const g=await ue.executeWorkflowBySteps({workflow:d,lineText:l||d.triggerCommand||d.name||"script run",lineSession:w,lineUserId:"",runId:p}),f={ok:!0,run_id:p,workflow_id:d.workflowId,workflow_name:d.name,answer:g.answer||"",image_url:g.imageUrl||"",preview_image_url:g.previewImageUrl||""};process.stdout.write(`${JSON.stringify(f,null,2)}\n`)};let ue;const we=new l({wsUrl:Q.wsUrl,version:Q.version,heartbeatSec:Q.heartbeatSec,reconnectMs:Q.reconnectMs,reconnectMaxAttempts:Q.reconnectMaxAttempts,capabilities:Q.capabilities},{onLog:(e,r)=>{pe(e,r)},onStatus:e=>{ae.emitBotStatus({status:e})},onLineMessage:async e=>ue.handleLineMessage(e),onServerConfigUpdate:async e=>ue.handleServerConfigUpdate(e),onWorkflowExecute:async e=>ue.handleWorkflowExecute(e),onRunTask:async e=>{await ue.handleRunTask(e)}}),ge=M({env:Q,emitLog:pe,getCurrentSession:()=>re}),{signInWithLoginApi:fe,callBotApi:ve,requestBotImageUploadUrl:_e,uploadImageToSignedUrl:he,getBotWorkflowListApiUrl:Se,getBotRuntimeConfigApiUrl:Te}=ge,ye=N({env:Q,emitLog:pe,getCurrentSession:()=>re,setCurrentSession:e=>{re=e},callBotApi:ve,getBotMemoryUploadApiUrl:()=>"",getBotMemoryArchivesApiUrl:()=>"",getBotMemoryDownloadApiUrl:()=>"",getBotRuntimeConfigApiUrl:Te,loadLocalMemories:async()=>{try{const e=await n.readFile(oe(),"utf8"),r=JSON.parse(e);return r&&"object"==typeof r&&Array.isArray(r.items)?r.items:[]}catch(e){if("ENOENT"===K(e))return[];throw e}},saveLocalMemories:async e=>{const t=oe();await(async e=>{await n.mkdir(r.dirname(e),{recursive:!0})})(t),await n.writeFile(t,JSON.stringify({version:1,updatedAt:(new Date).toISOString(),items:Array.isArray(e)?e:[]},null,2),"utf8")},cosineSimilarity:(e,r)=>{if(!Array.isArray(e)||!Array.isArray(r)||0===e.length||0===r.length)return 0;const t=ne(e),o=ne(r);return t&&o?se(e,r)/(t*o):0},parseJsonLoose:O,buildLanguageInstruction:d}),{parseTaskPlan:be,sendChatMessage:Ae,summarizeExecutionResult:Oe,refreshRuntimeConfigFromServer:Le}=ye,Ie=(e,r="artifact.txt")=>String(e||r).replace(/[^a-zA-Z0-9._-]/g,"_")||r,Be=x({env:Q,getHeadless:()=>le,emitLog:pe,emitDebugEvent:ce,notifyBrowserUnavailable:async({channel:e,reason:r,message:t})=>{const o=String(e||"").trim()||("win32"===process.platform?"msedge":"chrome"),s="msedge"===o?"https://www.microsoft.com/edge/download":"https://www.google.com/chrome/",n=["未检测到可用浏览器,自动化暂时无法执行。",`尝试系统通道: ${o}`,r?`错误: ${r}`:"","请先安装浏览器后重启 Bot。"].filter(Boolean);pe("[playwright] browser unavailable, user action required",{channel:o,reason:r||"",message:t||"",download:s});const a=ae.getMainWindow();if(!a||a.isDestroyed())return;const{response:i}=await J.showMessageBox(a,{type:"warning",buttons:["打开下载页","稍后处理"],defaultId:0,cancelId:1,title:"浏览器不可用",message:"未找到可用浏览器",detail:n.join("\n"),noLink:!0});0===i&&await $.openExternal(s).catch(()=>{})},normalizeActionUrl:L,resolveHostFromUrl:I,isValidOpenUrl:B,captureScreenshotAndUpload:async(e,t)=>{const o=await e.screenshot({fullPage:!0,type:"png"}),s=await Pe(),a=Ie(t,`shot-${Date.now()}.png`),i=r.join(s,a.endsWith(".png")?a:`${a}.png`);await n.writeFile(i,o);const l=await _e({filename:r.basename(i),contentType:"image/png"});return await he({uploadUrl:l.upload_url,contentType:"image/png",bytes:o}),{localPath:i,fileUrl:String(l.file_url||l.preview_url||"").trim()}},writeArtifactFile:async({filename:e,content:t})=>{const o=await Pe(),s=r.join(o,Ie(e,`note-${Date.now()}.txt`));return await n.writeFile(s,String(t||""),"utf8"),{localPath:s}}}),ke=Be.executeActionPlan,Ee=Be.closeTaskBrowser,Ce=Be.resolveTaskFallbackUrl;ue=j({getCurrentSession:()=>re,setBotBusyState:(e,r="")=>{we.sendBotState(e?"busy":"online",r)},emitChatEvent:e=>{ae.emitChatEvent(e)},emitLog:pe,emitTaskEvent:(e,r,t)=>we.sendTaskEvent(e,r,t),wsSendLineReply:e=>we.sendJson(e),normalizeIncomingText:p,isNumericChoice:S,detectLanguage:c,isLikelyMojibake:m,buildMojibakeReply:g,buildActionFailedReply:w,buildEmptyReply:u,buildCancelReply:v,buildConfirmPrompt:f,buildLanguageInstruction:d,isConfirmText:T,isCancelText:y,matchWorkflowByCommand:A,isLikelyNewTask:b,buildStepStartReply:_,buildStepFallbackSummary:h,extractFirstUrl:k,extractBasicCredentials:E,isBasicAuthStep:C,parseTaskPlan:be,executeActionPlan:ke,summarizeExecutionResult:Oe,sendChatMessage:Ae,refreshRuntimeConfigFromServer:Le,loadWorkflowsFromServer:async()=>{if(!re?.userId||!re?.botId)return[];const e=await ve(Se(),{bot_id:re.botId});return(Array.isArray(e?.workflows)?e.workflows:[]).map(P).filter(e=>e.workflowId&&e.triggerCommand&&e.steps.length>0&&e.isActive)},normalizeWorkflow:P,resolveTaskFallbackUrl:Ce,taskExecutorResetWorkflowState:()=>Be.resetWorkflowState(),emitDebugEvent:ce});const Pe=async()=>{const e=r.join(D.getPath("userData"),"artifacts");return await n.mkdir(e,{recursive:!0}),e};U({ipcMain:G,env:Q,loadedEnvFiles:X.loadedFiles,wsClient:we,signInWithLoginApi:fe,saveLoginProfile:async({botId:e,email:r,password:t,remember:o})=>{if(o)await n.writeFile(te(),JSON.stringify({botId:e,email:r,password:t,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await n.unlink(te())}catch(e){if("ENOENT"!==K(e))throw e}},clearLineSessions:()=>ue.clearLineSessions(),closeTaskBrowser:Ee,setCurrentSession:e=>{re=e},getCurrentSession:()=>re,emitLog:pe,getWorkflowCount:async()=>ue.getWorkflowCount(),getPlaywrightHeadless:()=>le,setPlaywrightHeadless:e=>{le=Boolean(e)},readSavedLoginProfile:async()=>{try{const e=await n.readFile(te(),"utf8"),r=JSON.parse(e);return{botId:r?.botId||"",email:r?.email||"",password:r?.password||"",remember:Boolean(r?.remember)}}catch(e){if("ENOENT"===K(e))return{botId:"",email:"",password:"",remember:!1};throw e}},sendChatMessage:Ae,closeChatWindow:()=>ae.closeChatWindow()}),D.whenReady().then(()=>{(()=>{if("darwin"!==process.platform)return;if(!s(ie))return;const e=q.createFromPath(ie);e.isEmpty()||D.dock.setIcon(e)})(),ee?(async()=>{try{await de(),await Ee(),D.quit()}catch(e){const r={ok:!1,error:e instanceof Error?e.message:String(e)};process.stderr.write(`${JSON.stringify(r,null,2)}\n`),await Ee(),D.exit(1)}})():(ae.createMainWindow(),D.on("activate",()=>{0===H.getAllWindows().length&&ae.createMainWindow()}))}),D.on("window-all-closed",()=>{we.stop(),Ee(),"darwin"!==process.platform&&D.quit()});
|
|
1
|
+
import e from"node:path";import t from"node:fs/promises";import{existsSync as s}from"node:fs";import{fileURLToPath as i}from"node:url";import{createRequire as o}from"node:module";import{SYSTEM_CONFIG as r}from"./config/systemConfig.js";import{BotWsClient as a}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createAiChatService as m}from"./services/aiChatService.js";import{createWindowManager as d}from"./services/windowManagerService.js";import{createGmoCoinGateway as c}from"./services/trade/gmoCoinGateway.js";import{createTradeConfigService as g}from"./services/trade/tradeConfigService.js";import{createGmoKlineService as p}from"./services/trade/gmoKlineService.js";import{createTradeStatusService as u}from"./services/trade/tradeStatusService.js";const y=o(import.meta.url),{app:w,BrowserWindow:h,ipcMain:P,nativeImage:f}=y("electron"),S=i(import.meta.url),b=e.dirname(S),v=["trade","ai"],A=()=>e.join(w.getPath("userData"),"login-profile.json");let C=null,x=()=>e.join(w.getPath("documents"),"weget-bot-logs");const k=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},O=async({level:s,source:i,message:o,details:r,ts:a})=>{const n=x();await t.mkdir(n,{recursive:!0});const l=new Date,m=l.getFullYear(),d=String(l.getMonth()+1).padStart(2,"0"),c=String(l.getDate()).padStart(2,"0"),g=e.join(n,`${s}-${m}${d}${c}.log`),p=JSON.stringify({ts:a||l.toISOString(),level:s,source:i,message:o,details:r??null},null,0);await t.appendFile(g,`${p}\n`,"utf8")},I=d({BrowserWindow:h,preloadPath:e.join(b,"preload.cjs"),rendererDir:e.join(b,"renderer"),windowIconPath:e.join(b,"renderer","logo.png")}),U=(e,t)=>{const s=(new Date).toISOString();I.emitBotLog({message:e,data:t||null,ts:s}),O({level:k(e),source:"runtime",message:e,details:t,ts:s}).catch(()=>{})},j=e=>{I.emitChatEvent(e)},M=g({configPath:e.join(w.getPath("userData"),"trade-api-config.json"),emitLog:U});M.load(),x=()=>String(M.get().errorLogDir||"").trim()||e.join(w.getPath("documents"),"weget-bot-logs");const B=m({getApiKey:()=>M.get().openAiApiKey,getModel:()=>M.get().aiModel}),L=l({getEnv:()=>({loginApiUrl:r.loginApiUrl,botUploadApiUrl:r.botUploadApiUrl}),emitLog:U,getCurrentSession:()=>C}),F=p({getCoinPublicBaseUrl:()=>r.gmoCryptoPublicApiBaseUrl,getFxPublicBaseUrl:()=>r.gmoFxPublicApiBaseUrl}),T=c({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath});T.setRuntimeConfigProvider(()=>({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath})),T.setCredentialsProvider(M.getCredentials),T.setLogEmitter(U);const E=u({gateway:T,env:{getRiskDailyLossLimitJpy:()=>M.get().riskDailyLossLimitJpy},emitLog:U}),z=new a({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:v,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(e,t)=>U(e,t),onStatus:e=>I.emitBotStatus({status:e}),onLineMessage:async e=>{const t=String(e.line_user_id||""),s=String(e.text||"").trim();if(j({type:"line_user",lineUserId:t,text:s}),!s)return{answer:""};try{const e=await B.sendChat(s);return j({type:"line_assistant",lineUserId:t,text:e}),{answer:e}}catch(e){const s=e instanceof Error?e.message:String(e);U("[ai] line reply failed",{error:s});const i=`AI error: ${s}`;return j({type:"line_assistant",lineUserId:t,text:i}),{answer:i}}}}),G=({market:e,kind:t,symbol:s,side:i,size:o,result:r})=>{const a=`trade-${Date.now()}`,n={category:"trade_history",market:e,kind:t,symbol:s,side:i,size:o,result:r,ts:(new Date).toISOString()};z.sendTaskEvent(a,"trade_history",n),U("[trade] history event sent",n)};n({ipcMain:P,env:{capabilities:v,loginApiUrl:r.loginApiUrl},signInWithLoginApi:L.signInWithLoginApi,saveLoginProfile:async({botId:e,email:s,password:i,remember:o})=>{if(o)await t.writeFile(A(),JSON.stringify({botId:e,email:s,password:i,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await t.unlink(A())}catch(e){if("ENOENT"!==e?.code)throw e}},readSavedLoginProfile:async()=>{try{const e=await t.readFile(A(),"utf8"),s=JSON.parse(e);return{botId:String(s?.botId||""),email:String(s?.email||""),password:String(s?.password||""),remember:Boolean(s?.remember)}}catch(e){if("ENOENT"===e?.code)return{botId:"",email:"",password:"",remember:!1};throw e}},getTradeApiConfig:()=>M.get(),saveTradeApiConfig:e=>M.save(e),setCurrentSession:e=>{C=e||null;const t=C?"connected":"disconnected";I.emitBotStatus({status:t})},getCurrentSession:()=>C,emitLog:U,openGmoKlineWindow:async({symbol:e,interval:t,market:s})=>F.fetchKline({symbol:e,interval:t,market:s}),getGmoMarketQuotes:async({market:e,symbols:t})=>F.fetchQuotes({market:e,symbols:t}),getGmoSymbolRules:async({market:e})=>F.fetchSymbolRules({market:e}),getGmoBuyingPower:async({market:e})=>({market:e,availableJpy:await T.getBuyingPower(e)}),getGmoAccountMetrics:async({market:e})=>T.getAccountMetrics(e),getGmoPositionSummary:async({market:e,symbol:t})=>({market:e,symbol:t,items:await T.getPositionSummary({market:e,symbol:t})}),getGmoOpenPositions:async({market:e,symbol:t,page:s,prevId:i,count:o})=>({market:e,symbol:t||"",items:await T.getOpenPositions({market:"fx"===e?"fx":"crypto",symbol:t||void 0,page:s,prevId:i,count:o})}),placeGmoFxOrder:async({symbol:e,side:t,size:s})=>{const i=await T.placeFxOrder({symbol:e,side:t,size:s,executionType:"MARKET"});return G({market:"fx",kind:"new",symbol:e,side:t,size:s,result:i}),i},placeGmoCoinOrder:async({symbol:e,side:t,size:s})=>{const i=await T.placeCoinOrder({symbol:e,side:t,size:s,executionType:"MARKET"});return G({market:"coin",kind:"new",symbol:e,side:t,size:s,result:i}),i},placeGmoFxCloseOrder:async({symbol:e,side:t,positionId:s,size:i})=>{const o=await T.placeFxCloseOrder({symbol:e,side:t,positionId:s,size:i,executionType:"MARKET"});return G({market:"fx",kind:"close",symbol:e,side:t,size:i,result:{...o,positionId:s}}),o},placeGmoCoinCloseOrder:async({symbol:e,side:t,positionId:s,size:i})=>{const o=await T.placeCoinCloseOrder({symbol:e,side:t,positionId:s,size:i,executionType:"MARKET"});return G({market:"coin",kind:"close",symbol:e,side:t,size:i,result:{...o,positionId:s}}),o},getGmoApiState:()=>E.getApiState(),testGmoApi:()=>E.testApi(),buildTradeStatusSnapshot:()=>E.buildSnapshot(),closeTradeWindows:()=>{},startWsClient:e=>z.start(e),stopWsClient:()=>z.stop(),handleBotChatMessage:async({text:e})=>{j({type:"user",text:String(e||"")});const t=await B.sendChat(e);return j({type:"assistant",text:t}),{answer:t}},writeErrorLog:async({source:e,message:t,details:s})=>{await(async({source:e,message:t,details:s})=>{await O({level:"error",source:e,message:t,details:s})})({source:e,message:t,details:s})},writeRuntimeLog:async({level:e,source:t,message:s,details:i})=>{await O({level:e,source:t,message:s,details:i})}});const D=e.join(b,"renderer","logo.png");w.whenReady().then(()=>{(()=>{if("darwin"!==process.platform)return;if(!s(D))return;const e=f.createFromPath(D);e.isEmpty()||w.dock.setIcon(e)})(),I.createMainWindow(),w.on("activate",()=>{0===h.getAllWindows().length&&I.createMainWindow()})}),w.on("window-all-closed",()=>{z.stop(),"darwin"!==process.platform&&w.quit()});
|
package/dist/src/preload.cjs
CHANGED
|
@@ -4,16 +4,29 @@ contextBridge.exposeInMainWorld('botApi', {
|
|
|
4
4
|
login: (email, password, botId, remember) => ipcRenderer.invoke('auth:login', { email, password, botId, remember }),
|
|
5
5
|
logout: () => ipcRenderer.invoke('auth:logout'),
|
|
6
6
|
getSavedProfile: () => ipcRenderer.invoke('auth:getSavedProfile'),
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
getApiConfig: () => ipcRenderer.invoke('trade:getApiConfig'),
|
|
8
|
+
saveApiConfig: (config) => ipcRenderer.invoke('trade:saveApiConfig', config),
|
|
9
9
|
getRuntimeInfo: () => ipcRenderer.invoke('bot:getRuntimeInfo'),
|
|
10
|
-
|
|
10
|
+
openGmoKlineWindow: ({ symbol, interval, market }) =>
|
|
11
|
+
ipcRenderer.invoke('trade:openGmoKlineWindow', { symbol, interval, market }),
|
|
12
|
+
getMarketQuotes: ({ market, symbols }) => ipcRenderer.invoke('trade:getMarketQuotes', { market, symbols }),
|
|
13
|
+
getSymbolRules: ({ market }) => ipcRenderer.invoke('trade:getSymbolRules', { market }),
|
|
14
|
+
getBuyingPower: ({ market }) => ipcRenderer.invoke('trade:getBuyingPower', { market }),
|
|
15
|
+
getAccountMetrics: ({ market }) => ipcRenderer.invoke('trade:getAccountMetrics', { market }),
|
|
16
|
+
getPositionSummary: ({ market, symbol }) => ipcRenderer.invoke('trade:getPositionSummary', { market, symbol }),
|
|
17
|
+
getOpenPositions: ({ market, symbol, page, prevId, count }) =>
|
|
18
|
+
ipcRenderer.invoke('trade:getOpenPositions', { market, symbol, page, prevId, count }),
|
|
19
|
+
placeCoinOrder: ({ symbol, side, size }) => ipcRenderer.invoke('trade:placeCoinOrder', { symbol, side, size }),
|
|
20
|
+
placeFxOrder: ({ symbol, side, size }) => ipcRenderer.invoke('trade:placeFxOrder', { symbol, side, size }),
|
|
21
|
+
closeCoinPosition: ({ symbol, side, positionId, size }) =>
|
|
22
|
+
ipcRenderer.invoke('trade:closeCoinPosition', { symbol, side, positionId, size }),
|
|
23
|
+
closeFxPosition: ({ symbol, side, positionId, size }) =>
|
|
24
|
+
ipcRenderer.invoke('trade:closeFxPosition', { symbol, side, positionId, size }),
|
|
25
|
+
testGmoApi: () => ipcRenderer.invoke('trade:testGmoApi'),
|
|
26
|
+
getStatusSnapshot: () => ipcRenderer.invoke('trade:getStatusSnapshot'),
|
|
11
27
|
sendChat: (message) => ipcRenderer.invoke('chat:send', { message }),
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
ipcRenderer.on('chat:event', listener);
|
|
15
|
-
return () => ipcRenderer.removeListener('chat:event', listener);
|
|
16
|
-
},
|
|
28
|
+
writeErrorLog: ({ source, message, details }) => ipcRenderer.invoke('log:writeError', { source, message, details }),
|
|
29
|
+
writeLog: ({ level, source, message, details }) => ipcRenderer.invoke('log:write', { level, source, message, details }),
|
|
17
30
|
onLog: (handler) => {
|
|
18
31
|
const listener = (_event, payload) => handler(payload);
|
|
19
32
|
ipcRenderer.on('bot:log', listener);
|
|
@@ -24,4 +37,9 @@ contextBridge.exposeInMainWorld('botApi', {
|
|
|
24
37
|
ipcRenderer.on('bot:status', listener);
|
|
25
38
|
return () => ipcRenderer.removeListener('bot:status', listener);
|
|
26
39
|
},
|
|
40
|
+
onChatEvent: (handler) => {
|
|
41
|
+
const listener = (_event, payload) => handler(payload);
|
|
42
|
+
ipcRenderer.on('chat:event', listener);
|
|
43
|
+
return () => ipcRenderer.removeListener('chat:event', listener);
|
|
44
|
+
},
|
|
27
45
|
});
|
package/dist/src/renderer/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("app-shell"),o=document.getElementById("bot-id"),i=document.getElementById("email"),s=document.getElementById("password"),l=document.getElementById("remember-me"),a=document.getElementById("status"),r=document.getElementById("session"),d=document.getElementById("runtime-info"),c=document.getElementById("logs"),m=document.getElementById("logout-btn"),u=document.getElementById("login-btn"),g=document.getElementById("chat-form"),p=document.getElementById("chat-input"),f=document.getElementById("messages"),w=document.getElementById("send-btn"),b=Array.from(document.querySelectorAll(".tab-btn")),y=Array.from(document.querySelectorAll(".tab-panel"));let E=!1;const v=e=>e&&"object"==typeof e?e:{},h=e=>{for(const t of b)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of y)t.classList.toggle("is-active",t.id===`tab-${e}`)},C=e=>{E=Boolean(e),t.classList.toggle("hidden",E),n.classList.toggle("hidden",!E)};for(const e of b)e.addEventListener("click",()=>h(e.dataset.tab||"config"));const I=(e,t)=>{const n=document.createElement("div"),o=""===e?"":`${e}: `;if(t&&"object"==typeof t){const e=document.createElement("details");e.className="json-node";const i=document.createElement("summary");i.textContent=`${o}${(e=>Array.isArray(e)?`Array(${e.length})`:e&&"object"==typeof e?`Object(${Object.keys(e).length})`:String(e))(t)}`,e.appendChild(i);const s=document.createElement("div");return s.className="json-children",Array.isArray(t)?t.forEach((e,t)=>{s.appendChild(I(String(t),e))}):Object.entries(t).forEach(([e,t])=>{s.appendChild(I(e,t))}),e.appendChild(s),n.appendChild(e),n}const i=document.createElement("div");if(i.className="json-leaf",""!==e){const t=document.createElement("span");t.className="json-key",t.textContent=`${e}: `,i.appendChild(t)}return i.appendChild((e=>{const t=document.createElement("span");if(null===e)return t.className="json-value json-value-null",t.textContent="null",t;const n=typeof e;return"string"===n?(t.className="json-value json-value-string",t.textContent=`"${e}"`,t):"number"===n?(t.className="json-value json-value-number",t.textContent=String(e),t):"boolean"===n?(t.className="json-value json-value-bool",t.textContent=String(e),t):(t.className="json-value",t.textContent=String(e),t)})(t)),n.appendChild(i),n},S=(e,t=null,n=null)=>{const o=document.createElement("div");o.className="log-entry";const i=document.createElement("div");i.className="log-title",i.textContent=((e,t)=>{const n=v(t),o=String(n.workflowName||"").trim(),i=String(n.workflowId||"").trim(),s=String(n.stepTitle||"").trim(),l=Number(n.workflowStepIndex||0),a=Number(n.workflowStepTotal||0),r=o||(i?`workflow:${i}`:"");let d="";return s?d=s:l>0&&a>0?d=`Step ${l}/${a}`:l>0&&(d=`Step ${l}`),r&&d?`${r} / ${d}`:r||d||String(e||"log")})(e,t),o.appendChild(i);const s=document.createElement("div");s.className="log-line";const l=document.createElement("span");if(l.className="log-ts",l.textContent=n||(new Date).toISOString(),s.appendChild(l),s.append(document.createTextNode(e||"")),o.appendChild(s),null!=t&&""!==t)if("object"==typeof t){const e=document.createElement("details");e.className="log-json-root";const n=document.createElement("summary");n.textContent="JSON",e.appendChild(n);const i=document.createElement("div");i.className="json-children",i.appendChild(I("",t)),e.appendChild(i),o.appendChild(e)}else{const e=document.createElement("div");e.className="json-leaf",e.textContent=String(t),o.appendChild(e)}for(c.appendChild(o);c.children.length>300;)c.removeChild(c.firstChild);c.scrollTop=c.scrollHeight},A=(e,t,n="")=>{const o=document.createElement("div");o.className=`msg ${e}`;let i=n||("user"===e?"You":"Assistant");"line-user"===e&&(i=n||"LINE User"),"line-assistant"===e&&(i=n||"AI"),o.textContent=`${i}: ${t}`,f.appendChild(o),f.scrollTop=f.scrollHeight},$=e=>{const t=String(e||"disconnected").trim().toLowerCase();a.textContent=t,a.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?a.classList.add("status-disconnected"):a.classList.add("status-connecting"):a.classList.add("status-connected")},N=e=>{u.disabled=e,u.classList.toggle("is-loading",e),u.textContent=e?"Loading...":"Login"},x=async()=>{if(window.botApi?.getRuntimeInfo)try{const e=await window.botApi.getRuntimeInfo();if(!e?.ok)return;$(e.status||"disconnected"),e.session?(C(!0),r.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(C(!1),r.textContent="not logged in"),(e=>{d.innerHTML="";const t=v(e),n=v(t.runtime),o=t.session?v(t.session):null,i=[["Bot ID",String(o?.botId||"-")],["User ID",String(o?.userId||"-")],["Email",String(o?.email||"-")],["AI Model",String(o?.chatModel||"-")],["Workflow Count",String(n.workflowCount||0)],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["Playwright Visual",n.playwrightHeadless?"off (headless)":"on (visible)"],["WS URL",String(n.wsUrl||"-")],["Login API",String(n.loginApiUrl||"-")],["Workflow API",String(n.workflowListApiUrl||"-")],["Runtime API",String(n.runtimeConfigApiUrl||"-")]];for(const[e,t]of i){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const o=document.createElement("div");o.className="runtime-value",o.textContent=String(t),d.appendChild(n),d.appendChild(o)}})(e)}catch(e){S(`[ui] getRuntimeInfo failed: ${e instanceof Error?e.message:String(e)}`)}};e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return void S("[ui] login blocked: botApi bridge not available");const t=i.value.trim(),n=o.value.trim(),a=s.value;if(n&&t&&a){N(!0),S("[ui] login request");try{const e=await window.botApi.login(t,a,n,l.checked);if(!e.ok)return void S(`[ui] login failed: ${e.error}`);if(!e.session)return void S("[ui] login failed: missing session");C(!0),h("chat"),r.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`,S("[ui] login success"),await x()}catch(e){S(`[ui] login exception: ${e instanceof Error?e.message:String(e)}`)}finally{N(!1)}}}),m.addEventListener("click",async()=>{window.botApi&&(await window.botApi.logout(),C(!1),$("disconnected"),r.textContent="not logged in",S("[ui] logged out"),await x())}),g.addEventListener("submit",async e=>{if(e.preventDefault(),!E)return;const t=p.value.trim();if(!t||!window.botApi)return;A("user",t),p.value="",w.disabled=!0;const n=await window.botApi.sendChat(t);n.ok?A("assistant",String(n.answer||"")):A("assistant",`Error: ${n.error}`),w.disabled=!1}),window.botApi||S("[ui] preload bridge missing: window.botApi is undefined"),window.botApi&&(window.botApi.onLog(e=>{S(String(e?.message||""),e?.data??null,e?.ts||null)}),window.botApi.onStatus(e=>{$(String(e.status||"")),x()}),window.botApi.onChatEvent(e=>{if(!e)return;const t=v(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void A("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";A("line-assistant",String(t.text||""),e)}}),(async()=>{if(window.botApi?.getSavedProfile)try{const e=await window.botApi.getSavedProfile();if(!e?.ok||!e.profile)return;o.value=e.profile.botId||"",i.value=e.profile.email||"",s.value=e.profile.password||"",l.checked=Boolean(e.profile.remember),e.profile.remember&&S("[ui] loaded saved login profile")}catch(e){S(`[ui] load saved profile failed: ${e instanceof Error?e.message:String(e)}`)}})(),x());export{};
|
|
1
|
+
const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("user-notice"),r=document.getElementById("bot-id"),a=document.getElementById("email"),s=document.getElementById("password"),c=document.getElementById("remember-me"),l=document.getElementById("status"),d=document.getElementById("session"),m=document.getElementById("runtime-info"),u=document.getElementById("logs"),g=document.getElementById("messages"),f=document.getElementById("chat-form"),p=document.getElementById("chat-input"),y=document.getElementById("send-btn"),w=document.getElementById("error-log-dir"),b=document.getElementById("risk-daily-loss-limit-jpy"),S=document.getElementById("crypto-api-key"),x=document.getElementById("crypto-api-secret"),v=document.getElementById("fx-api-key"),h=document.getElementById("fx-api-secret"),E=document.getElementById("toggle-crypto-secret-btn"),k=document.getElementById("toggle-fx-secret-btn"),I=document.getElementById("save-api-config-btn"),L=document.getElementById("ai-model"),A=document.getElementById("openai-api-key"),B=document.getElementById("toggle-openai-key-btn"),C=document.getElementById("save-ai-config-btn"),N=document.getElementById("logout-btn"),$=document.getElementById("login-btn"),U=document.getElementById("coin-symbol-select"),M=document.getElementById("fx-symbol-select"),D=document.getElementById("coin-kline-intervals"),F=document.getElementById("fx-kline-intervals"),P=document.getElementById("coin-kline-canvas"),T=document.getElementById("fx-kline-canvas"),z=document.getElementById("coin-market-icon"),O=document.getElementById("fx-market-icon"),q=document.getElementById("coin-order-bid"),j=document.getElementById("coin-order-ask"),J=document.getElementById("coin-order-spread"),Y=document.getElementById("fx-order-bid"),R=document.getElementById("fx-order-ask"),K=document.getElementById("fx-order-spread"),_=document.getElementById("coin-order-qty"),H=document.getElementById("fx-order-qty"),W=document.getElementById("coin-buy-btn"),G=document.getElementById("coin-sell-btn"),Q=document.getElementById("fx-buy-btn"),V=document.getElementById("fx-sell-btn"),X=document.getElementById("coin-required-amount"),Z=document.getElementById("fx-required-amount"),ee=document.getElementById("coin-account-info-refresh-btn"),te=document.getElementById("fx-account-info-refresh-btn"),ne=document.getElementById("coin-account-pnl"),ie=document.getElementById("coin-account-margin"),oe=document.getElementById("coin-account-available"),re=document.getElementById("coin-account-margin-ratio"),ae=document.getElementById("fx-account-pnl"),se=document.getElementById("fx-account-margin"),ce=document.getElementById("fx-account-available"),le=document.getElementById("fx-account-margin-ratio"),de=document.getElementById("coin-position-summary-body"),me=document.getElementById("coin-position-list-body"),ue=document.getElementById("fx-position-summary-body"),ge=document.getElementById("fx-position-list-body"),fe=document.getElementById("coin-position-summary-refresh-btn"),pe=document.getElementById("coin-position-list-refresh-btn"),ye=document.getElementById("fx-position-summary-refresh-btn"),we=document.getElementById("fx-position-list-refresh-btn"),be=Array.from(document.querySelectorAll(".tab-btn")),Se=Array.from(document.querySelectorAll(".tab-panel"));let xe="15m",ve="15m",he=String(U?.value||"BTC_JPY").trim().toUpperCase(),Ee=String(M?.value||"USD_JPY").trim().toUpperCase(),ke=null,Ie=null,Le=null,Ae=null,Be=!1,Ce=!1,Ne=!1,$e=!1,Ue=!1,Me="",De="",Fe=0,Pe=0,Te=null,ze=null;const Oe=new Map,qe={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},je={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let Je=[],Ye=[];const Re=e=>e&&"object"==typeof e?e:{},Ke=e=>{for(const t of be)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of Se)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of be)e.addEventListener("click",()=>Ke(e.dataset.tab||"coin"));const _e=e=>{const n=Be!==e;Be=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&Ct(),e?n&&Tt():(ft(),Ce=!1)},He=e=>{const t=String(e||"disconnected").trim().toLowerCase();l.textContent=t,l.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?l.classList.add("status-disconnected"):l.classList.add("status-connecting"):l.classList.add("status-connected")},We=(e,t=6)=>{if(null===e||!Number.isFinite(e))return"-";const n=Math.abs(e);return n>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:3}):n>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:Math.min(t,5)}):e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:t})},Ge=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,Qe=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,Ve=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),Xe=e=>{const t=String(e||"").trim().replace(/,/g,"");if(!t)return"";if(!/^\d+(\.\d+)?$/.test(t))return"";if(!t.includes("."))return t;return t.replace(/(\.\d*?[1-9])0+$/,"$1").replace(/\.0+$/,"").replace(/\.$/,"")},Ze=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},et=(e,t)=>{const n=String(e||"").trim(),i=n.startsWith("-"),o=i?n.slice(1):n,[r,a=""]=o.split("."),s=(a+"0".repeat(t)).slice(0,t),c=BigInt((r||"0")+s);return i?-c:c},tt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=We(t.bid),a=We(t.ask);n.textContent=r,i.textContent=a,o.textContent=We(t.spread,8)},nt=e=>{if("coin"===e){const e=Number(String(_.value||"").trim()),t=Te;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(X.textContent="- 円"):void(X.textContent=Ge(e*t/2))}const t=Number(String(H.value||"").trim()),n=ze;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?Z.textContent="- 円":Z.textContent=Ge(t*n/20)},it=async(e,t)=>{if(!window.botApi?.getMarketQuotes)return null;const n=String(t||"").trim().toUpperCase(),i=await window.botApi.getMarketQuotes({market:e,symbols:[n]});if(!i?.ok)throw new Error(String(i?.error||"quote api failed"));const o=(i.quotes||[]).find(e=>String(e.symbol||"").toUpperCase()===n)||null;return"coin"===e?(tt({symbol:n,quote:o,orderBidNode:q,orderAskNode:j,orderSpreadNode:J}),Te=o?.ask??null,nt("coin")):(tt({symbol:n,quote:o,orderBidNode:Y,orderAskNode:R,orderSpreadNode:K}),ze=o?.ask??null,nt("fx")),o},ot=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(z.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;O.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},rt=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,at=e=>"coin"===e?qe:je,st=e=>"coin"===e?P:T,ct=e=>"coin"===e?he:Ee,lt=e=>{const t=ct(e),n=("coin"===e?Je:Ye).filter(e=>String(e.symbol||"").trim().toUpperCase()===t),i=[];for(const e of n){const t=String(e.side||"").trim().toUpperCase(),n=Number(e.averagePositionRate||0);if(!Number.isFinite(n)||n<=0)continue;const o="BUY"===t;i.push({price:n,label:o?"BUY Avg":"SELL Avg",color:o?"#1f9d55":"#e03131"})}return i},dt=e=>{const t=at(e);t.candles.length&&$t(st(e),t.candles,lt(e))},mt=(e,t)=>{if(!t)return;const n=at(e);if(!n.candles.length)return;if(n.symbol!==t.symbol)return;const i=n.candles[n.candles.length-1],o=null!==t.bid&&null!==t.ask?(t.bid+t.ask)/2:null!==t.ask?t.ask:t.bid;null!==o&&Number.isFinite(o)&&(i.close=o,i.high=Math.max(i.high,o),i.low=Math.min(i.low,o),$t(st(e),n.candles,lt(e)))},ut=async e=>{const t=at(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==ct(e))return;if(!(Math.floor(Date.now()/rt(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await Dt():await Ft()}finally{t.fetching=!1}}},gt=async e=>{if("coin"!==e){if(!Ue){Ue=!0;try{const e=await it("fx",Ee);mt("fx",e),await ut("fx")}catch(e){const t=kt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==De||n-Pe>15e3)&&(ht("[fx] quote poll failed",{error:t}),De=t,Pe=n)}finally{Ue=!1}}}else{if($e)return;$e=!0;try{const e=await it("coin",he);mt("coin",e),await ut("coin")}catch(e){const t=kt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Me||n-Fe>15e3)&&(ht("[coin] quote poll failed",{error:t}),Me=t,Fe=n)}finally{$e=!1}}},ft=()=>{Le&&(clearInterval(Le),Le=null),Ae&&(clearInterval(Ae),Ae=null)},pt=async e=>{if(window.botApi?.getAccountMetrics){if(!e||"coin"===e){const e=await window.botApi.getAccountMetrics({market:"coin"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);ne.textContent=n>0?Qe(i):Ge(0),ie.textContent=Ge(n),oe.textContent=Ge(t),re.textContent=Ve(Number(e.marginRatio||0))}else ht("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if(!e||"fx"===e){const e=await window.botApi.getAccountMetrics({market:"fx"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);ae.textContent=n>0?Qe(i):Ge(0),se.textContent=Ge(n),ce.textContent=Ge(t),le.textContent=Ve(Number(e.marginRatio||0))}else ht("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},yt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").toUpperCase(),o=n.replace("_","/"),r=String(i.side||"-"),a=String(i.size||"").trim(),s=Number(a||0),c=Number(i.price||0),l=Number(i.lossGain||0),d=Number(i.totalSwap||0),m=String(i.timestamp||"-"),u=Number(i.positionId||0),g=Number.isFinite(u)&&u>0&&a?`data-market="${e}" data-symbol="${n}" data-side="${String(r||"").toUpperCase()}" data-position-id="${u}" data-size="${a}"`:"disabled",f=document.createElement("tr");f.innerHTML=`\n <td><button type="button" class="close-btn" ${g}>決済</button></td>\n <td>${o}<br />${r}</td>\n <td>${s.toLocaleString("ja-JP")}</td>\n <td>${c.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${Qe(l)}<br />${Qe(d)}</td>\n <td>${m.replace("T"," ").slice(0,19)}</td>\n `,t.appendChild(f)}}else t.innerHTML='<tr><td colspan="6" class="empty-cell">対象のお取引はございません。</td></tr>'},wt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").replace("_","/"),o=String(i.side||"-"),r=Number("fx"===e?i.sumPositionSize||0:i.sumPositionQuantity||0),a=Number(i.averagePositionRate||0),s=Number(i.positionLossGain||0),c=Number("fx"===e&&i.sumTotalSwap||0),l=document.createElement("tr");l.innerHTML=`\n <td><button type="button" class="close-btn">決済</button></td>\n <td>${n}<br />${o}</td>\n <td>${r.toLocaleString("ja-JP")}</td>\n <td>${a.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${Qe(s)}<br />${Qe(c)}</td>\n `,t.appendChild(l)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},bt=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if(!e||"coin"===e){const e=await window.botApi.getPositionSummary({market:"coin",symbol:he});e?.ok?(Je=Array.isArray(e.items)?e.items:[],wt("coin",de,Je),dt("coin")):(ht("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),Je=[],wt("coin",de,[]),dt("coin"))}if(!e||"fx"===e){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(Ye=Array.isArray(e.items)?e.items:[],wt("fx",ue,Ye),dt("fx")):(ht("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),Ye=[],wt("fx",ue,[]),dt("fx"))}if(!e||"coin"===e){const e=await window.botApi.getOpenPositions({market:"coin",symbol:he,page:1,count:100});e?.ok?yt("coin",me,Array.isArray(e.items)?e.items:[]):(ht("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),yt("coin",me,[]))}if(!e||"fx"===e){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?yt("fx",ge,Array.isArray(e.items)?e.items:[]):(ht("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),yt("fx",ge,[]))}}},St=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(H.value||"").trim();i?await Nt(t,async()=>{const t=await n.placeFxOrder({symbol:Ee,side:e,size:i});if(!t?.ok){const n=kt(t?.error||"fx order failed");return ht("[fx] order failed",{symbol:Ee,side:e,size:i,error:n}),It("error",n),void await Lt("fx.order",n,t)}ht("[fx] order placed",{symbol:Ee,side:e,size:i,result:t.result||null}),It("success",`${e} 注文を送信しました。`),await pt("fx"),await bt("fx")}):It("error","数量を入力してください。")},xt=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=Xe(String(_.value||""));if(!i)return void It("error","数量の形式が不正です。");const o=((e,t)=>{const n=Oe.get(e);if(!n)return{ok:!0};const i=Xe(n.minOrderSize),o=Xe(n.sizeStep),r=Xe(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(Ze(t),Ze(i),Ze(o),Ze(r)),s=et(t,a),c=et(i,a),l=et(o,a);if(s<c)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(l>0n&&s%l!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>et(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(he,i);o.ok?await Nt(t,async()=>{const t=await n.placeCoinOrder({symbol:he,side:e,size:i});if(!t?.ok){const n=kt(t?.error||"coin order failed");return ht("[coin] order failed",{symbol:he,side:e,size:i,error:n}),It("error",n),void await Lt("coin.order",n,t)}ht("[coin] order placed",{symbol:he,side:e,size:i,result:t.result||null}),It("success",`${e} 注文を送信しました。`),await pt("coin"),await bt("coin")}):It("error",o.reason||"数量が取引ルールに一致しません。")},vt=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},ht=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:vt(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!u)return;const i=document.createElement("div");i.className="log-entry";const o=document.createElement("div");o.className="log-line";const r=document.createElement("span");if(r.className="log-ts",r.textContent=n||(new Date).toISOString(),o.appendChild(r),o.append(document.createTextNode(e||"")),i.appendChild(o),null!=t&&""!==t){const e=document.createElement("pre");e.className="status-output",e.textContent="string"==typeof t?t:JSON.stringify(t,null,2),i.appendChild(e)}for(u.appendChild(i);u.children.length>300;)u.removeChild(u.firstChild);u.scrollTop=u.scrollHeight},Et=(e,t,n="")=>{const i=document.createElement("div");i.className=`msg ${e}`;let o=n||("user"===e?"You":"Assistant");"line-user"===e&&(o=n||"LINE User"),"line-assistant"===e&&(o=n||"AI"),i.textContent=`${o}: ${t}`,g.appendChild(i),g.scrollTop=g.scrollHeight},kt=e=>{const t=String(e||"").trim(),n=t.toLowerCase();return t?n.includes("api key")&&n.includes("missing")?"API Key 未设置,请先在 Config 页面保存。":n.includes("auth")||n.includes("401")||n.includes("403")?"认证失败,请检查 API Key 和 Secret。":n.includes("network")||n.includes("fetch failed")||n.includes("timeout")?"网络连接异常,请检查网络后重试。":n.includes("message is required")?"请输入内容后再发送。":t:"处理失败,请稍后重试。"},It=(e,t)=>{ke&&(clearTimeout(ke),ke=null),o.classList.remove("hidden","notice-error","notice-success"),o.classList.add("error"===e?"notice-error":"notice-success"),o.textContent=t,ke=setTimeout(()=>{At()},"success"===e?2200:4200)},Lt=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},At=()=>{ke&&(clearTimeout(ke),ke=null),o.classList.add("hidden"),o.classList.remove("notice-error","notice-success"),o.textContent=""},Bt=(e,t)=>{Ie&&(clearTimeout(Ie),Ie=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,Ie=setTimeout(()=>{Ct()},"success"===e?2200:4200)},Ct=()=>{Ie&&(clearTimeout(Ie),Ie=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},Nt=async(e,t)=>{if(e.disabled)return;e.disabled=!0,e.classList.add("is-loading");const n=e.textContent||"";try{await t()}catch(e){const t=kt(e instanceof Error?e.message:String(e));ht("[ui] action failed",{error:t}),It("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},$t=(e,t,n=[])=>{const i=e.getContext("2d");if(!i)return;const o=e.width,r=e.height;if(i.clearRect(0,0,o,r),!t.length)return;const a=Math.max(...t.map(e=>e.high)),s=Math.min(...t.map(e=>e.low)),c=n.map(e=>e.price).filter(e=>Number.isFinite(e)),l=c.length?Math.max(a,...c):a,d=c.length?Math.min(s,...c):s,m=Math.max(1e-8,l-d),u=10,g=10,f=o-10-62-8,p=r-10-24-8,y=Math.max(2,f/t.length),w=e=>g+(l-e)/m*p,b=e=>e>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:0}):e>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:3}):e.toLocaleString("en-US",{minimumFractionDigits:5,maximumFractionDigits:5});i.strokeStyle="#c9d4ea",i.strokeRect(.5,.5,o-1,r-1),i.strokeStyle="#e3eaf5",i.lineWidth=1;for(let e=0;e<=4;e+=1){const t=g+p/4*e;i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke()}t.forEach((e,t)=>{const n=u+t*y+.5*y,o=w(e.high),r=w(e.low),a=w(e.open),s=w(e.close),c=e.close>=e.open;i.strokeStyle=c?"#1f9d55":"#d64545",i.fillStyle=c?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,r),i.stroke();const l=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(l,m,d,g)});for(const e of n){const t=w(e.price);i.save(),i.setLineDash([4,4]),i.strokeStyle=e.color,i.lineWidth=1,i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke(),i.restore(),i.font="12px Segoe UI";const n=`${e.label} ${b(e.price)}`,o=Math.ceil(i.measureText(n).width)+10,r=14,a=Math.max(12,Math.min(g+p-18,t-9));i.fillStyle=e.color,i.fillRect(r,a,o,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(n,r+5,a+9)}const S=t[t.length-1],x=S?.close;if(Number.isFinite(x)){const e=w(Number(x));i.save(),i.setLineDash([5,4]),i.strokeStyle="#2b79ff",i.lineWidth=1,i.beginPath(),i.moveTo(u,e),i.lineTo(u+f,e),i.stroke(),i.restore();const t=b(Number(x));i.font="12px Segoe UI";const n=Math.ceil(i.measureText(t).width)+10,o=u+f+6,r=Math.max(12,Math.min(g+p-18,e-9));i.fillStyle="#2b79ff",i.fillRect(o,r,n,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(t,o+5,r+9)}i.fillStyle="#66758d",i.font="12px Segoe UI",i.textBaseline="middle",i.textAlign="left";for(let e=0;e<=4;e+=1){const t=l-m/4*e,n=g+p/4*e;i.fillText(b(t),u+f+8,n)}const v=e=>{const t=new Date(e>1e12?e:1e3*e);if(Number.isNaN(t.getTime()))return"-";const n=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0");return`${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${n}:${i}`};i.textAlign="center",i.textBaseline="top";for(let e=0;e<=4;e+=1){const n=Math.min(t.length-1,Math.floor((t.length-1)*(e/4))),o=u+f*e/4;i.fillText(v(t[n]?.time||0),o,g+p+6)}},Ut=async()=>{if(!window.botApi?.getRuntimeInfo)return;const e=await window.botApi.getRuntimeInfo();e?.ok&&(He(e.status||"disconnected"),e.session?(_e(!0),d.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(_e(!1),d.textContent="local mode (not logged in)"),(e=>{m.innerHTML="";const t=Re(e),n=Re(t.runtime),i=t.session?Re(t.session):null,o=[["Bot ID",String(i?.botId||"-")],["User ID",String(i?.userId||"-")],["Email",String(i?.email||"-")],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["GMO API State",String(n.gmoApiState||"unknown")],["AI Model",String(n.aiModel||"-")],["Login API",String(n.loginApiUrl||"-")]];for(const[e,t]of o){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const i=document.createElement("div");i.className="runtime-value",i.textContent=t,m.appendChild(n),m.appendChild(i)}})(e))};I.addEventListener("click",async()=>{await Nt(I,async()=>{if(!window.botApi?.saveApiConfig)return;const e={riskDailyLossLimitJpy:Number(b.value||"0"),errorLogDir:String(w.value||"").trim(),cryptoApiKey:String(S.value||"").trim(),cryptoApiSecret:String(x.value||"").trim(),fxApiKey:String(v.value||"").trim(),fxApiSecret:String(h.value||"").trim(),aiModel:String(L.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini",openAiApiKey:String(A.value||"").trim()},t=await window.botApi.saveApiConfig(e);if(!t.ok)return ht(`[trade-config] save failed: ${t.error||"unknown"}`),It("error",kt(t.error)),void await Lt("trade-config.save",kt(t.error),t);ht("[trade-config] saved"),It("success","配置已保存到本机。"),await Ut()})}),C.addEventListener("click",async()=>{await Nt(C,async()=>{if(!window.botApi?.saveApiConfig)return;const e={riskDailyLossLimitJpy:Number(b.value||"0"),errorLogDir:String(w.value||"").trim(),cryptoApiKey:String(S.value||"").trim(),cryptoApiSecret:String(x.value||"").trim(),fxApiKey:String(v.value||"").trim(),fxApiSecret:String(h.value||"").trim(),aiModel:String(L.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini",openAiApiKey:String(A.value||"").trim()},t=await window.botApi.saveApiConfig(e);if(!t.ok)return ht(`[ai-config] save failed: ${t.error||"unknown"}`),It("error",kt(t.error)),void await Lt("ai-config.save",kt(t.error),t);ht("[ai-config] saved"),It("success","AI 设置已保存。"),await Ut()})}),B.addEventListener("click",()=>{const e="password"===A.type?"text":"password";A.type=e,B.textContent="password"===e?"显示":"隐藏"}),E.addEventListener("click",()=>{const e="password"===x.type?"text":"password";x.type=e,E.textContent="password"===e?"显示":"隐藏"}),k.addEventListener("click",()=>{const e="password"===h.type?"text":"password";h.type=e,k.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;Ct();const t=a.value.trim(),n=r.value.trim(),i=s.value;if(t&&n&&i){$.disabled=!0,$.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,c.checked);if(!e.ok)return ht(`[ui] login failed: ${e.error||"unknown"}`),void Bt("error",kt(e.error));ht("[ui] login success"),Bt("success","登录成功。"),It("success","登录成功。"),await Ut()}finally{$.disabled=!1,$.textContent="Login"}}}),N.addEventListener("click",async()=>{await Nt(N,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return ht(`[ui] logout failed: ${e.error||"unknown"}`),It("error",kt(e.error)),void await Lt("auth.logout",kt(e.error),e);ht("[ui] logout"),It("success","已登出。"),_e(!1),d.textContent="local mode (not logged in)",await Ut()})});const Mt=async({market:e,symbol:t,interval:n,canvas:i})=>{if(!window.botApi)return;const o=await window.botApi.openGmoKlineWindow({symbol:t,interval:n,market:e});if(!o.ok)return ht(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),It("error",kt(o.error)),void await Lt(`${e}.kline`,kt(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=at(e);a.symbol=String(o.symbol||t||"").toUpperCase(),a.interval=n,a.candles=r.map(e=>({time:Number(e.time||0),open:Number(e.open||0),high:Number(e.high||0),low:Number(e.low||0),close:Number(e.close||0)})),a.lastFetchBucket=Math.floor(Date.now()/rt(n)),$t(i,a.candles,lt(e)),ht("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),At()},Dt=async()=>{await Mt({market:"coin",symbol:he,interval:xe,canvas:P})},Ft=async()=>{await Mt({market:"fx",symbol:Ee,interval:ve,canvas:T})},Pt=({container:e,getCurrent:t,setCurrent:n,onChange:i})=>{const o=Array.from(e.querySelectorAll(".interval-btn"));for(const e of o)e.addEventListener("click",async()=>{const r=String(e.dataset.interval||"").trim();if(r&&r!==t()){n(r);for(const t of o)t.classList.toggle("is-active",t===e);await i()}})};U.addEventListener("change",async()=>{he=String(U.value||"BTC_JPY").trim().toUpperCase(),ot("coin",he);try{await it("coin",he),await Dt(),await bt("coin")}catch(e){const t=kt(e instanceof Error?e.message:String(e));It("error",t)}}),M.addEventListener("change",async()=>{Ee=String(M.value||"USD_JPY").trim().toUpperCase(),ot("fx",Ee);try{await it("fx",Ee),await Ft(),await bt("fx")}catch(e){const t=kt(e instanceof Error?e.message:String(e));It("error",t)}}),Pt({container:D,getCurrent:()=>xe,setCurrent:e=>{xe=e},onChange:Dt}),Pt({container:F,getCurrent:()=>ve,setCurrent:e=>{ve=e},onChange:Ft}),ee.addEventListener("click",async()=>{await Nt(ee,async()=>{await pt("coin")})}),te.addEventListener("click",async()=>{await Nt(te,async()=>{await pt("fx")})}),_.addEventListener("input",()=>{nt("coin")}),H.addEventListener("input",()=>{nt("fx")}),me.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeCoinPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=Xe(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&c?await Nt(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=kt(e?.error||"coin close order failed");return ht("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),It("error",t),void await Lt("coin.closeOrder",t,e)}ht("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),It("success","決済注文を送信しました。"),await pt("coin"),await bt("coin")}):It("error","決済対象データが不正です。")}),ge.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeFxPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&c?await Nt(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=kt(e?.error||"fx close order failed");return ht("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),It("error",t),void await Lt("fx.closeOrder",t,e)}ht("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),It("success","決済注文を送信しました。"),await pt("fx"),await bt("fx")}):It("error","決済対象データが不正です。")}),W.addEventListener("click",async()=>{await xt("BUY",W)}),G.addEventListener("click",async()=>{await xt("SELL",G)}),Q.addEventListener("click",async()=>{await St("BUY",Q)}),V.addEventListener("click",async()=>{await St("SELL",V)}),fe.addEventListener("click",async()=>{await Nt(fe,async()=>{await bt("coin")})}),pe.addEventListener("click",async()=>{await Nt(pe,async()=>{await bt("coin")})}),ye.addEventListener("click",async()=>{await Nt(ye,async()=>{await bt("fx")})}),we.addEventListener("click",async()=>{await Nt(we,async()=>{await bt("fx")})}),f.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(p.value||"").trim();if(t){Et("user",t),p.value="",y.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return Et("assistant",`Error: ${e.error||"unknown"}`),It("error",kt(e.error)),void await Lt("ai.chat",kt(e.error),e);Et("assistant",String(e.answer||"")),At()}catch(e){const t=kt(e instanceof Error?e.message:String(e));Et("assistant",`Error: ${t}`),It("error",t),await Lt("ai.chat.exception",t,e)}finally{y.disabled=!1}}}),window.addEventListener("error",e=>{const t=kt(e.error?.message||e.message||"Unknown UI error");ht("[ui] uncaught error",{error:t}),It("error",t),Lt("ui.error",t,{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason.message:String(e.reason||"Unhandled promise rejection"),n=kt(t);ht("[ui] unhandled rejection",{error:n}),It("error",n),Lt("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{He(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=Re(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void Et("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";Et("line-assistant",String(t.text||""),e)}}));const Tt=async()=>{if(Be&&!Ce&&!Ne){Ne=!0;try{await async function(){ot("coin",he),ot("fx",Ee);try{await it("coin",he)}catch(e){const t=kt(e instanceof Error?e.message:String(e));ht("[coin] quote fetch failed",{error:t}),It("error",t)}try{await it("fx",Ee)}catch(e){const t=kt(e instanceof Error?e.message:String(e));ht("[fx] quote fetch failed",{error:t}),It("error",t)}if(await Dt(),await Ft(),window.botApi?.getSymbolRules)try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){Oe.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&Oe.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){ht("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}}(),await pt(),await bt(),Le||(Le=setInterval(()=>{gt("coin")},1e3)),Ae||(Ae=setInterval(()=>{gt("fx")},1e3)),Ce=!0}finally{Ne=!1}}};_e(!1),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(r.value=e.profile.botId||"",a.value=e.profile.email||"",s.value=e.profile.password||"",c.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getApiConfig)return;const e=await window.botApi.getApiConfig();e?.ok&&e.config&&(w.value=e.config.errorLogDir||"",b.value=String(e.config.riskDailyLossLimitJpy||5e4),S.value=e.config.cryptoApiKey||"",x.value=e.config.cryptoApiSecret||"",v.value=e.config.fxApiKey||"",h.value=e.config.fxApiSecret||"",L.value=e.config.aiModel||"gpt-4.1-mini",A.value=e.config.openAiApiKey||"")})(),Ut();export{};
|