@actalk/inkos-studio 1.2.0 → 1.3.0
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/dist/api/book-create.js +1 -1
- package/dist/api/book-create.js.map +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +748 -87
- package/dist/api/server.js.map +1 -1
- package/dist/assets/_baseUniq-C-g5htb0.js +1 -0
- package/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-D8_7TLub.js +1 -0
- package/dist/assets/apl-dKokRX4l.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/arc-B13N8XoZ.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-COxJdWXS.js +36 -0
- package/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/astro-CbQHKStN.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/bat-BkioyH1T.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/bird2-DPOp833l.js +1 -0
- package/dist/assets/blade-D4QpJJKB.js +1 -0
- package/dist/assets/blockDiagram-DXYQGD6D-DS_ZpHJ5.js +132 -0
- package/dist/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/c3-eo99z4R2.js +1 -0
- package/dist/assets/c4Diagram-AHTNJAMY-BxSij0o0.js +10 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/assets/channel-C2r273Z1.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-DFB21W8n.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-BnJ77uxv.js +206 -0
- package/dist/assets/chunk-55IACEB6-CQ_glRcB.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-Dyd-Q8PI.js +1 -0
- package/dist/assets/chunk-FMBD7UC4-BDiYToLm.js +15 -0
- package/dist/assets/chunk-OYMX7WX6-DEzfeklY.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-CJPYUr7I.js +1 -0
- package/dist/assets/chunk-YZCP3GAM-CBxzODC9.js +1 -0
- package/dist/assets/clarity-D53aC0YG.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-CSUo3NoK.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CSUo3NoK.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/clone-DJaLNYqk.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cobol-nwyudZeR.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/coq-DkFqJrB1.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-BDcwl962.js +1 -0
- package/dist/assets/cpp-CofmeUqb.js +1 -0
- package/dist/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/assets/css-DPfMkruS.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/cytoscape.esm-DxGcaOPV.js +331 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/dagre-KV5264BT-q3iXqwp5.js +4 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dart-CF10PKvl.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-CSyQp4XD.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-CyO2u0j-.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-0Rum0AK-.js +43 -0
- package/dist/assets/diagram-TYMM5635-CpFFDrQG.js +24 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/edge-BkV0erSs.js +1 -0
- package/dist/assets/elixir-CDX3lj18.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/assets/erDiagram-SMLLAGMA-DgPF_t0E.js +85 -0
- package/dist/assets/erb-B12qg9BL.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/flowDiagram-DWJPFMVM-D1sVXEs4.js +162 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-Cah2BN0H.js +292 -0
- package/dist/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-Bb32ArKL.js +106 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gn-n2N0HUVH.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-CxLEBnE3.js +1 -0
- package/dist/assets/graph-DPYgoB8M.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-CaT9iCJl.js +1 -0
- package/dist/assets/haml-B8DHNrY2.js +1 -0
- package/dist/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/highlighted-body-OFNGDK62-C5OR7F5H.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/assets/horizon-bright-Cn-bp-IR.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/html-GMplVEZG.js +1 -0
- package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-CdSd4xAM.js +1267 -0
- package/dist/assets/index-hHRX6lZN.css +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-BRPlXDRX.js +2 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-DIMNZ5LJ.js +70 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-CQ9Buybm.js +139 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-CxzCAyBv.js +1 -0
- package/dist/assets/just-Cw27pwNe.js +1 -0
- package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/assets/kanban-definition-6JOO6SKY-CsZuyfP0.js +89 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-DZf3V79B.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/assets/layout-wiYzOkdn.js +1 -0
- package/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/linear-D2SNljoC.js +1 -0
- package/dist/assets/liquid-DYVedYrR.js +1 -0
- package/dist/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/lua-BaeVxFsk.js +1 -0
- package/dist/assets/luau-C-HG3fhB.js +1 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/marko-CnJfTvn9.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/mdc-BMNejdWA.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/assets/min-NQHw0HnA.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-CLFneoyC.js +96 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/assets/move-IF9eRakj.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/nextflow-Zz6hmt5N.js +1 -0
- package/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
- package/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/assets/nim-CVrawwO9.js +1 -0
- package/dist/assets/nix-CwoSXNpI.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/odin-BBf5iR-q.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/perl-C0TMdlhV.js +1 -0
- package/dist/assets/php-Dhbhpdrm.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-CGetk0td.js +30 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/assets/pug-CGlum2m_.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-DGvGQ55s.js +7 -0
- package/dist/assets/r-Dspwwk_N.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-Uh8Bk_45.js +1 -0
- package/dist/assets/red-bN70gL4F.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/requirementDiagram-MS252O5E-BtWYKfId.js +84 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/ron-D8l8udqQ.js +1 -0
- package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rst-BrH8l1NY.js +1 -0
- package/dist/assets/ruby-Dw2BHqvy.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-B7dHCFEb.js +10 -0
- package/dist/assets/sas-cz2c8ADy.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/scala-C151Ov-r.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scss-OYdSNvt2.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-SCghmIaN.js +157 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/assets/solidity-rGO070M0.js +1 -0
- package/dist/assets/soy-Brmx7dQM.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/sql-BLtJtn59.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-BH5u7GGu.js +1 -0
- package/dist/assets/stateDiagram-FHFEXIEX-Da-qWGV4.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-EC5r6wTi.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/assets/svelte-C_ipcX3V.js +1 -0
- package/dist/assets/swift-D82vCrfD.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-P3uqSqPl.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/tex-idrVyKtj.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-DXzhFcp9.js +120 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/twig-DNn4PbVi.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/assets/typst-DHCkPAjA.js +1 -0
- package/dist/assets/v-BcVCzyr7.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vb-D17OF-Vu.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-CHwAxJ-d.js +34 -0
- package/dist/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/assets/vesper-DU1UobuO.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vue-DN_0RTcg.js +1 -0
- package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/assets/vue-vine-CQOfvN7w.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/wardley-RL74JXVD-DOtmpHBm.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DqzDdtmB.js +20 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-B5UU8au-.js +7 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/index.html +2 -2
- package/package.json +22 -4
- package/dist/assets/index-CrV75dEH.css +0 -1
- package/dist/assets/index-tV8pf10O.js +0 -395
package/dist/api/server.js
CHANGED
|
@@ -2,12 +2,68 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
3
|
import { streamSSE } from "hono/streaming";
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
|
-
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession,
|
|
6
|
-
import { access, readFile, readdir } from "node:fs/promises";
|
|
5
|
+
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, findOrCreateBookSession, listBookSessions, loadBookSession, persistBookSession, appendBookSessionMessage, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceModel, loadSecrets, saveSecrets, getServiceApiKey, listModelsForService, chatCompletion, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
|
|
6
|
+
import { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { isSafeBookId } from "./safety.js";
|
|
9
9
|
import { ApiError } from "./errors.js";
|
|
10
10
|
import { buildStudioBookConfig } from "./book-create.js";
|
|
11
|
+
// -- Pipeline stage definitions per agent type --
|
|
12
|
+
const PIPELINE_STAGES = {
|
|
13
|
+
writer: [
|
|
14
|
+
"准备章节输入", "撰写章节草稿", "落盘最终章节",
|
|
15
|
+
"生成最终真相文件", "校验真相文件变更", "同步记忆索引",
|
|
16
|
+
"更新章节索引与快照",
|
|
17
|
+
],
|
|
18
|
+
architect: [
|
|
19
|
+
"生成基础设定", "保存书籍配置", "写入基础设定文件",
|
|
20
|
+
"初始化控制文档", "创建初始快照",
|
|
21
|
+
],
|
|
22
|
+
reviser: [
|
|
23
|
+
"加载修订上下文", "修订章节", "落盘修订结果",
|
|
24
|
+
"更新索引与快照",
|
|
25
|
+
],
|
|
26
|
+
auditor: ["审计章节"],
|
|
27
|
+
};
|
|
28
|
+
const AGENT_LABELS = {
|
|
29
|
+
architect: "建书", writer: "写作", auditor: "审计",
|
|
30
|
+
reviser: "修订", exporter: "导出",
|
|
31
|
+
};
|
|
32
|
+
const TOOL_LABELS = {
|
|
33
|
+
read: "读取文件", edit: "编辑文件", grep: "搜索", ls: "列目录",
|
|
34
|
+
};
|
|
35
|
+
function resolveToolLabel(tool, agent) {
|
|
36
|
+
if (tool === "sub_agent" && agent)
|
|
37
|
+
return AGENT_LABELS[agent] ?? agent;
|
|
38
|
+
return TOOL_LABELS[tool] ?? tool;
|
|
39
|
+
}
|
|
40
|
+
function summarizeResult(result) {
|
|
41
|
+
if (typeof result === "string")
|
|
42
|
+
return result.slice(0, 200);
|
|
43
|
+
if (result && typeof result === "object") {
|
|
44
|
+
const r = result;
|
|
45
|
+
if (typeof r.content === "string")
|
|
46
|
+
return r.content.slice(0, 200);
|
|
47
|
+
if (typeof r.text === "string")
|
|
48
|
+
return r.text.slice(0, 200);
|
|
49
|
+
}
|
|
50
|
+
return String(result).slice(0, 200);
|
|
51
|
+
}
|
|
52
|
+
function extractToolError(result) {
|
|
53
|
+
if (typeof result === "string")
|
|
54
|
+
return result.slice(0, 500);
|
|
55
|
+
if (result && typeof result === "object") {
|
|
56
|
+
const r = result;
|
|
57
|
+
if (typeof r.content === "string")
|
|
58
|
+
return r.content.slice(0, 500);
|
|
59
|
+
if (r.content && Array.isArray(r.content)) {
|
|
60
|
+
const textPart = r.content.find((c) => c.type === "text");
|
|
61
|
+
if (textPart)
|
|
62
|
+
return textPart.text?.slice(0, 500) ?? "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return String(result).slice(0, 500);
|
|
66
|
+
}
|
|
11
67
|
const subscribers = new Set();
|
|
12
68
|
const bookCreateStatus = new Map();
|
|
13
69
|
function broadcast(event, data) {
|
|
@@ -15,6 +71,154 @@ function broadcast(event, data) {
|
|
|
15
71
|
handler(event, data);
|
|
16
72
|
}
|
|
17
73
|
}
|
|
74
|
+
function isCustomServiceId(serviceId) {
|
|
75
|
+
return serviceId === "custom" || serviceId.startsWith("custom:");
|
|
76
|
+
}
|
|
77
|
+
function serviceConfigKey(entry) {
|
|
78
|
+
return entry.service === "custom" ? `custom:${entry.name ?? "Custom"}` : entry.service;
|
|
79
|
+
}
|
|
80
|
+
function normalizeServiceEntry(serviceId, value) {
|
|
81
|
+
if (serviceId.startsWith("custom:")) {
|
|
82
|
+
return {
|
|
83
|
+
service: "custom",
|
|
84
|
+
name: decodeURIComponent(serviceId.slice("custom:".length)),
|
|
85
|
+
...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
|
|
86
|
+
...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
|
|
87
|
+
...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
|
|
88
|
+
...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
|
|
89
|
+
...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (serviceId === "custom") {
|
|
93
|
+
return {
|
|
94
|
+
service: "custom",
|
|
95
|
+
...(typeof value.name === "string" && value.name.length > 0 ? { name: value.name } : {}),
|
|
96
|
+
...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
|
|
97
|
+
...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
|
|
98
|
+
...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
|
|
99
|
+
...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
|
|
100
|
+
...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
service: serviceId,
|
|
105
|
+
...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
|
|
106
|
+
...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
|
|
107
|
+
...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
|
|
108
|
+
...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function normalizeConfigSource(value) {
|
|
112
|
+
return value === "studio" ? "studio" : "env";
|
|
113
|
+
}
|
|
114
|
+
function normalizeServiceConfig(raw) {
|
|
115
|
+
if (Array.isArray(raw)) {
|
|
116
|
+
return raw
|
|
117
|
+
.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
118
|
+
.map((entry) => ({
|
|
119
|
+
service: typeof entry.service === "string" && entry.service.length > 0 ? entry.service : "custom",
|
|
120
|
+
...(typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {}),
|
|
121
|
+
...(typeof entry.baseUrl === "string" && entry.baseUrl.length > 0 ? { baseUrl: entry.baseUrl } : {}),
|
|
122
|
+
...(typeof entry.temperature === "number" ? { temperature: entry.temperature } : {}),
|
|
123
|
+
...(typeof entry.maxTokens === "number" ? { maxTokens: entry.maxTokens } : {}),
|
|
124
|
+
...(entry.apiFormat === "chat" || entry.apiFormat === "responses" ? { apiFormat: entry.apiFormat } : {}),
|
|
125
|
+
...(typeof entry.stream === "boolean" ? { stream: entry.stream } : {}),
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
if (raw && typeof raw === "object") {
|
|
129
|
+
return Object.entries(raw)
|
|
130
|
+
.filter(([, value]) => value && typeof value === "object")
|
|
131
|
+
.map(([serviceId, value]) => normalizeServiceEntry(serviceId, value));
|
|
132
|
+
}
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
function mergeServiceConfig(existing, updates) {
|
|
136
|
+
const merged = new Map(existing.map((entry) => [serviceConfigKey(entry), entry]));
|
|
137
|
+
for (const update of updates) {
|
|
138
|
+
merged.set(serviceConfigKey(update), update);
|
|
139
|
+
}
|
|
140
|
+
return [...merged.values()];
|
|
141
|
+
}
|
|
142
|
+
async function loadRawConfig(root) {
|
|
143
|
+
const configPath = join(root, "inkos.json");
|
|
144
|
+
const raw = await readFile(configPath, "utf-8");
|
|
145
|
+
return JSON.parse(raw);
|
|
146
|
+
}
|
|
147
|
+
async function saveRawConfig(root, config) {
|
|
148
|
+
await writeFile(join(root, "inkos.json"), JSON.stringify(config, null, 2), "utf-8");
|
|
149
|
+
}
|
|
150
|
+
async function readEnvConfigSummary(path) {
|
|
151
|
+
try {
|
|
152
|
+
const raw = await readFile(path, "utf-8");
|
|
153
|
+
const values = new Map();
|
|
154
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
155
|
+
const trimmed = line.trim();
|
|
156
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
157
|
+
continue;
|
|
158
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
159
|
+
if (!match)
|
|
160
|
+
continue;
|
|
161
|
+
const [, key, value] = match;
|
|
162
|
+
values.set(key, value.trim());
|
|
163
|
+
}
|
|
164
|
+
const provider = values.get("INKOS_LLM_PROVIDER") ?? null;
|
|
165
|
+
const baseUrl = values.get("INKOS_LLM_BASE_URL") ?? null;
|
|
166
|
+
const model = values.get("INKOS_LLM_MODEL") ?? null;
|
|
167
|
+
const apiKey = values.get("INKOS_LLM_API_KEY") ?? "";
|
|
168
|
+
const detected = Boolean(provider || baseUrl || model || apiKey);
|
|
169
|
+
return {
|
|
170
|
+
detected,
|
|
171
|
+
provider,
|
|
172
|
+
baseUrl,
|
|
173
|
+
model,
|
|
174
|
+
hasApiKey: apiKey.length > 0,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return {
|
|
179
|
+
detected: false,
|
|
180
|
+
provider: null,
|
|
181
|
+
baseUrl: null,
|
|
182
|
+
model: null,
|
|
183
|
+
hasApiKey: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function readEnvConfigStatus(root) {
|
|
188
|
+
const project = await readEnvConfigSummary(join(root, ".env"));
|
|
189
|
+
const global = await readEnvConfigSummary(GLOBAL_ENV_PATH);
|
|
190
|
+
return {
|
|
191
|
+
project,
|
|
192
|
+
global,
|
|
193
|
+
effectiveSource: project.detected ? "project" : global.detected ? "global" : null,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function resolveConfiguredServiceBaseUrl(root, serviceId, inlineBaseUrl) {
|
|
197
|
+
if (inlineBaseUrl?.trim())
|
|
198
|
+
return inlineBaseUrl.trim();
|
|
199
|
+
if (!isCustomServiceId(serviceId)) {
|
|
200
|
+
return resolveServicePreset(serviceId)?.baseUrl;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const config = await loadRawConfig(root);
|
|
204
|
+
const services = normalizeServiceConfig(config.llm?.services);
|
|
205
|
+
const matched = services.find((entry) => serviceConfigKey(entry) === serviceId);
|
|
206
|
+
return matched?.baseUrl;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function resolveConfiguredServiceEntry(root, serviceId) {
|
|
213
|
+
try {
|
|
214
|
+
const config = await loadRawConfig(root);
|
|
215
|
+
const services = normalizeServiceConfig(config.llm?.services);
|
|
216
|
+
return services.find((entry) => serviceConfigKey(entry) === serviceId);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
18
222
|
// --- Server factory ---
|
|
19
223
|
export function createStudioServer(initialConfig, root) {
|
|
20
224
|
const app = new Hono();
|
|
@@ -29,14 +233,14 @@ export function createStudioServer(initialConfig, root) {
|
|
|
29
233
|
return c.json({ error: { code: "INTERNAL_ERROR", message: "Unexpected server error." } }, 500);
|
|
30
234
|
});
|
|
31
235
|
// BookId validation middleware — blocks path traversal on all book routes
|
|
32
|
-
app.use("/api/books/:id/*", async (c, next) => {
|
|
236
|
+
app.use("/api/v1/books/:id/*", async (c, next) => {
|
|
33
237
|
const bookId = c.req.param("id");
|
|
34
238
|
if (!isSafeBookId(bookId)) {
|
|
35
239
|
throw new ApiError(400, "INVALID_BOOK_ID", `Invalid book ID: "${bookId}"`);
|
|
36
240
|
}
|
|
37
241
|
await next();
|
|
38
242
|
});
|
|
39
|
-
app.use("/api/books/:id", async (c, next) => {
|
|
243
|
+
app.use("/api/v1/books/:id", async (c, next) => {
|
|
40
244
|
const bookId = c.req.param("id");
|
|
41
245
|
if (!isSafeBookId(bookId)) {
|
|
42
246
|
throw new ApiError(400, "INVALID_BOOK_ID", `Invalid book ID: "${bookId}"`);
|
|
@@ -49,36 +253,47 @@ export function createStudioServer(initialConfig, root) {
|
|
|
49
253
|
broadcast("log", { level: entry.level, tag: entry.tag, message: entry.message });
|
|
50
254
|
},
|
|
51
255
|
};
|
|
256
|
+
// Logger sink that prints to server terminal
|
|
257
|
+
const consoleSink = {
|
|
258
|
+
write(entry) {
|
|
259
|
+
const prefix = `[${entry.tag}]`;
|
|
260
|
+
if (entry.level === "warn")
|
|
261
|
+
console.warn(prefix, entry.message);
|
|
262
|
+
else if (entry.level === "error")
|
|
263
|
+
console.error(prefix, entry.message);
|
|
264
|
+
else
|
|
265
|
+
console.log(prefix, entry.message);
|
|
266
|
+
},
|
|
267
|
+
};
|
|
52
268
|
async function loadCurrentProjectConfig(options) {
|
|
53
269
|
const freshConfig = await loadProjectConfig(root, options);
|
|
54
270
|
cachedConfig = freshConfig;
|
|
55
271
|
return freshConfig;
|
|
56
272
|
}
|
|
57
273
|
async function buildPipelineConfig(overrides) {
|
|
58
|
-
const currentConfig = await loadCurrentProjectConfig();
|
|
59
|
-
const logger = createLogger({ tag: "studio", sinks: [sseSink] });
|
|
274
|
+
const currentConfig = overrides?.currentConfig ?? await loadCurrentProjectConfig();
|
|
275
|
+
const logger = createLogger({ tag: "studio", sinks: [sseSink, consoleSink] });
|
|
60
276
|
return {
|
|
61
|
-
client: createLLMClient(currentConfig.llm),
|
|
62
|
-
model: currentConfig.llm.model,
|
|
277
|
+
client: overrides?.client ?? createLLMClient(currentConfig.llm),
|
|
278
|
+
model: overrides?.model ?? currentConfig.llm.model,
|
|
63
279
|
projectRoot: root,
|
|
64
280
|
defaultLLMConfig: currentConfig.llm,
|
|
65
281
|
modelOverrides: currentConfig.modelOverrides,
|
|
66
282
|
notifyChannels: currentConfig.notify,
|
|
67
283
|
logger,
|
|
68
284
|
onStreamProgress: (progress) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
285
|
+
broadcast("llm:progress", {
|
|
286
|
+
status: progress.status,
|
|
287
|
+
elapsedMs: progress.elapsedMs,
|
|
288
|
+
totalChars: progress.totalChars,
|
|
289
|
+
chineseChars: progress.chineseChars,
|
|
290
|
+
});
|
|
76
291
|
},
|
|
77
292
|
externalContext: overrides?.externalContext,
|
|
78
293
|
};
|
|
79
294
|
}
|
|
80
295
|
// --- Books ---
|
|
81
|
-
app.get("/api/books", async (c) => {
|
|
296
|
+
app.get("/api/v1/books", async (c) => {
|
|
82
297
|
const bookIds = await state.listBooks();
|
|
83
298
|
const books = await Promise.all(bookIds.map(async (id) => {
|
|
84
299
|
const book = await state.loadBookConfig(id);
|
|
@@ -87,7 +302,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
87
302
|
}));
|
|
88
303
|
return c.json({ books });
|
|
89
304
|
});
|
|
90
|
-
app.get("/api/books/:id", async (c) => {
|
|
305
|
+
app.get("/api/v1/books/:id", async (c) => {
|
|
91
306
|
const id = c.req.param("id");
|
|
92
307
|
try {
|
|
93
308
|
const book = await state.loadBookConfig(id);
|
|
@@ -100,7 +315,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
100
315
|
}
|
|
101
316
|
});
|
|
102
317
|
// --- Genres ---
|
|
103
|
-
app.get("/api/genres", async (c) => {
|
|
318
|
+
app.get("/api/v1/genres", async (c) => {
|
|
104
319
|
const { listAvailableGenres, readGenreProfile } = await import("@actalk/inkos-core");
|
|
105
320
|
const rawGenres = await listAvailableGenres(root);
|
|
106
321
|
const genres = await Promise.all(rawGenres.map(async (g) => {
|
|
@@ -115,7 +330,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
115
330
|
return c.json({ genres });
|
|
116
331
|
});
|
|
117
332
|
// --- Book Create ---
|
|
118
|
-
app.post("/api/books/create", async (c) => {
|
|
333
|
+
app.post("/api/v1/books/create", async (c) => {
|
|
119
334
|
const body = await c.req.json();
|
|
120
335
|
const now = new Date().toISOString();
|
|
121
336
|
const bookConfig = buildStudioBookConfig(body, now);
|
|
@@ -156,7 +371,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
156
371
|
});
|
|
157
372
|
return c.json({ status: "creating", bookId });
|
|
158
373
|
});
|
|
159
|
-
app.get("/api/books/:id/create-status", async (c) => {
|
|
374
|
+
app.get("/api/v1/books/:id/create-status", async (c) => {
|
|
160
375
|
const id = c.req.param("id");
|
|
161
376
|
const status = bookCreateStatus.get(id);
|
|
162
377
|
if (!status) {
|
|
@@ -165,7 +380,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
165
380
|
return c.json(status);
|
|
166
381
|
});
|
|
167
382
|
// --- Chapters ---
|
|
168
|
-
app.get("/api/books/:id/chapters/:num", async (c) => {
|
|
383
|
+
app.get("/api/v1/books/:id/chapters/:num", async (c) => {
|
|
169
384
|
const id = c.req.param("id");
|
|
170
385
|
const num = parseInt(c.req.param("num"), 10);
|
|
171
386
|
const bookDir = state.bookDir(id);
|
|
@@ -184,7 +399,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
184
399
|
}
|
|
185
400
|
});
|
|
186
401
|
// --- Chapter Save ---
|
|
187
|
-
app.put("/api/books/:id/chapters/:num", async (c) => {
|
|
402
|
+
app.put("/api/v1/books/:id/chapters/:num", async (c) => {
|
|
188
403
|
const id = c.req.param("id");
|
|
189
404
|
const num = parseInt(c.req.param("num"), 10);
|
|
190
405
|
const bookDir = state.bookDir(id);
|
|
@@ -206,12 +421,13 @@ export function createStudioServer(initialConfig, root) {
|
|
|
206
421
|
});
|
|
207
422
|
// --- Truth files ---
|
|
208
423
|
const TRUTH_FILES = [
|
|
424
|
+
"author_intent.md", "current_focus.md",
|
|
209
425
|
"story_bible.md", "volume_outline.md", "current_state.md",
|
|
210
426
|
"particle_ledger.md", "pending_hooks.md", "chapter_summaries.md",
|
|
211
427
|
"subplot_board.md", "emotional_arcs.md", "character_matrix.md",
|
|
212
428
|
"style_guide.md", "parent_canon.md", "fanfic_canon.md", "book_rules.md",
|
|
213
429
|
];
|
|
214
|
-
app.get("/api/books/:id/truth/:file", async (c) => {
|
|
430
|
+
app.get("/api/v1/books/:id/truth/:file", async (c) => {
|
|
215
431
|
const id = c.req.param("id");
|
|
216
432
|
const file = c.req.param("file");
|
|
217
433
|
if (!TRUTH_FILES.includes(file)) {
|
|
@@ -227,7 +443,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
227
443
|
}
|
|
228
444
|
});
|
|
229
445
|
// --- Analytics ---
|
|
230
|
-
app.get("/api/books/:id/analytics", async (c) => {
|
|
446
|
+
app.get("/api/v1/books/:id/analytics", async (c) => {
|
|
231
447
|
const id = c.req.param("id");
|
|
232
448
|
try {
|
|
233
449
|
const chapters = await state.loadChapterIndex(id);
|
|
@@ -238,7 +454,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
238
454
|
}
|
|
239
455
|
});
|
|
240
456
|
// --- Actions ---
|
|
241
|
-
app.post("/api/books/:id/write-next", async (c) => {
|
|
457
|
+
app.post("/api/v1/books/:id/write-next", async (c) => {
|
|
242
458
|
const id = c.req.param("id");
|
|
243
459
|
const body = await c.req.json().catch(() => ({ wordCount: undefined }));
|
|
244
460
|
broadcast("write:start", { bookId: id });
|
|
@@ -251,7 +467,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
251
467
|
});
|
|
252
468
|
return c.json({ status: "writing", bookId: id });
|
|
253
469
|
});
|
|
254
|
-
app.post("/api/books/:id/draft", async (c) => {
|
|
470
|
+
app.post("/api/v1/books/:id/draft", async (c) => {
|
|
255
471
|
const id = c.req.param("id");
|
|
256
472
|
const body = await c.req.json().catch(() => ({ wordCount: undefined, context: undefined }));
|
|
257
473
|
broadcast("draft:start", { bookId: id });
|
|
@@ -263,7 +479,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
263
479
|
});
|
|
264
480
|
return c.json({ status: "drafting", bookId: id });
|
|
265
481
|
});
|
|
266
|
-
app.post("/api/books/:id/chapters/:num/approve", async (c) => {
|
|
482
|
+
app.post("/api/v1/books/:id/chapters/:num/approve", async (c) => {
|
|
267
483
|
const id = c.req.param("id");
|
|
268
484
|
const num = parseInt(c.req.param("num"), 10);
|
|
269
485
|
try {
|
|
@@ -276,7 +492,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
276
492
|
return c.json({ error: String(e) }, 500);
|
|
277
493
|
}
|
|
278
494
|
});
|
|
279
|
-
app.post("/api/books/:id/chapters/:num/reject", async (c) => {
|
|
495
|
+
app.post("/api/v1/books/:id/chapters/:num/reject", async (c) => {
|
|
280
496
|
const id = c.req.param("id");
|
|
281
497
|
const num = parseInt(c.req.param("num"), 10);
|
|
282
498
|
try {
|
|
@@ -300,7 +516,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
300
516
|
}
|
|
301
517
|
});
|
|
302
518
|
// --- SSE ---
|
|
303
|
-
app.get("/api/events", (c) => {
|
|
519
|
+
app.get("/api/v1/events", (c) => {
|
|
304
520
|
return streamSSE(c, async (stream) => {
|
|
305
521
|
const handler = (event, data) => {
|
|
306
522
|
stream.writeSSE({ event, data: JSON.stringify(data) });
|
|
@@ -318,8 +534,184 @@ export function createStudioServer(initialConfig, root) {
|
|
|
318
534
|
await new Promise(() => { });
|
|
319
535
|
});
|
|
320
536
|
});
|
|
537
|
+
// --- Model discovery ---
|
|
538
|
+
app.get("/api/v1/services", async (c) => {
|
|
539
|
+
const secrets = await loadSecrets(root);
|
|
540
|
+
const SERVICE_KEYS = [
|
|
541
|
+
"openai", "anthropic", "deepseek", "moonshot", "minimax",
|
|
542
|
+
"bailian", "zhipu", "siliconflow", "ppio", "openrouter", "ollama",
|
|
543
|
+
];
|
|
544
|
+
// Fast: only check connection status from secrets, no external API calls
|
|
545
|
+
const services = SERVICE_KEYS.map((key) => {
|
|
546
|
+
const preset = resolveServicePreset(key);
|
|
547
|
+
return {
|
|
548
|
+
service: key,
|
|
549
|
+
label: preset?.label ?? key,
|
|
550
|
+
connected: Boolean(secrets.services[key]?.apiKey),
|
|
551
|
+
};
|
|
552
|
+
});
|
|
553
|
+
// Add custom services from inkos.json
|
|
554
|
+
try {
|
|
555
|
+
const config = await loadRawConfig(root);
|
|
556
|
+
for (const svc of normalizeServiceConfig(config.llm?.services)) {
|
|
557
|
+
if (svc.service === "custom") {
|
|
558
|
+
const secretKey = `custom:${svc.name}`;
|
|
559
|
+
services.push({
|
|
560
|
+
service: secretKey,
|
|
561
|
+
label: svc.name ?? "Custom",
|
|
562
|
+
connected: Boolean(secrets.services[secretKey]?.apiKey),
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
catch { /* no config file */ }
|
|
568
|
+
return c.json({ services });
|
|
569
|
+
});
|
|
570
|
+
app.get("/api/v1/services/config", async (c) => {
|
|
571
|
+
const config = await loadRawConfig(root);
|
|
572
|
+
const llm = config.llm ?? {};
|
|
573
|
+
const services = normalizeServiceConfig(llm.services);
|
|
574
|
+
const envConfig = await readEnvConfigStatus(root);
|
|
575
|
+
return c.json({
|
|
576
|
+
services,
|
|
577
|
+
defaultModel: llm.defaultModel ?? null,
|
|
578
|
+
configSource: normalizeConfigSource(llm.configSource),
|
|
579
|
+
envConfig,
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
app.put("/api/v1/services/config", async (c) => {
|
|
583
|
+
const body = await c.req.json();
|
|
584
|
+
const config = await loadRawConfig(root);
|
|
585
|
+
config.llm = config.llm ?? {};
|
|
586
|
+
const llm = config.llm;
|
|
587
|
+
if (body.services !== undefined) {
|
|
588
|
+
const existingServices = normalizeServiceConfig(llm.services);
|
|
589
|
+
const incomingServices = normalizeServiceConfig(body.services);
|
|
590
|
+
llm.services = mergeServiceConfig(existingServices, incomingServices);
|
|
591
|
+
}
|
|
592
|
+
if (body.defaultModel !== undefined) {
|
|
593
|
+
llm.defaultModel = body.defaultModel;
|
|
594
|
+
}
|
|
595
|
+
if (body.configSource !== undefined) {
|
|
596
|
+
llm.configSource = normalizeConfigSource(body.configSource);
|
|
597
|
+
}
|
|
598
|
+
await saveRawConfig(root, config);
|
|
599
|
+
return c.json({ ok: true });
|
|
600
|
+
});
|
|
601
|
+
app.post("/api/v1/services/:service/test", async (c) => {
|
|
602
|
+
const service = c.req.param("service");
|
|
603
|
+
const { apiKey, baseUrl, apiFormat, stream } = await c.req.json();
|
|
604
|
+
if (!apiKey?.trim()) {
|
|
605
|
+
return c.json({ ok: false, error: "API Key 不能为空" }, 400);
|
|
606
|
+
}
|
|
607
|
+
const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service, baseUrl);
|
|
608
|
+
if (!resolvedBaseUrl) {
|
|
609
|
+
return c.json({ ok: false, error: `未知服务商: ${service}` }, 400);
|
|
610
|
+
}
|
|
611
|
+
// Call the real /models API — no fallback
|
|
612
|
+
const modelsUrl = resolvedBaseUrl.replace(/\/$/, "") + "/models";
|
|
613
|
+
try {
|
|
614
|
+
const res = await fetch(modelsUrl, {
|
|
615
|
+
headers: { Authorization: `Bearer ${apiKey.trim()}` },
|
|
616
|
+
signal: AbortSignal.timeout(10_000),
|
|
617
|
+
});
|
|
618
|
+
if (!res.ok) {
|
|
619
|
+
const body = await res.text().catch(() => "");
|
|
620
|
+
if (res.status === 401 || res.status === 403) {
|
|
621
|
+
return c.json({ ok: false, error: "API Key 无效,请检查后重试" }, 400);
|
|
622
|
+
}
|
|
623
|
+
return c.json({ ok: false, error: `服务商返回 ${res.status}: ${body.slice(0, 200)}` }, 400);
|
|
624
|
+
}
|
|
625
|
+
const json = await res.json();
|
|
626
|
+
const models = (json.data ?? []).map((m) => ({ id: m.id, name: m.id }));
|
|
627
|
+
if (models.length === 0) {
|
|
628
|
+
return c.json({ ok: false, error: "连接成功但未返回可用模型" }, 400);
|
|
629
|
+
}
|
|
630
|
+
const rawConfig = await loadRawConfig(root).catch(() => ({}));
|
|
631
|
+
const preferredModel = typeof rawConfig.llm?.defaultModel === "string"
|
|
632
|
+
? String(rawConfig.llm.defaultModel)
|
|
633
|
+
: undefined;
|
|
634
|
+
const sampleModel = models.find((m) => m.id === preferredModel)?.id
|
|
635
|
+
?? models.find((m) => m.id === "gpt-5.4")?.id
|
|
636
|
+
?? models[0]?.id;
|
|
637
|
+
if (sampleModel) {
|
|
638
|
+
const client = createLLMClient({
|
|
639
|
+
provider: service === "anthropic" ? "anthropic" : "openai",
|
|
640
|
+
service: isCustomServiceId(service) ? "custom" : service,
|
|
641
|
+
configSource: "studio",
|
|
642
|
+
baseUrl: resolvedBaseUrl,
|
|
643
|
+
apiKey: apiKey.trim(),
|
|
644
|
+
model: sampleModel,
|
|
645
|
+
temperature: 0.7,
|
|
646
|
+
maxTokens: 64,
|
|
647
|
+
thinkingBudget: 0,
|
|
648
|
+
apiFormat: apiFormat ?? "chat",
|
|
649
|
+
stream: stream ?? true,
|
|
650
|
+
});
|
|
651
|
+
try {
|
|
652
|
+
await chatCompletion(client, sampleModel, [{ role: "user", content: "ping" }], { maxTokens: 5 });
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
656
|
+
return c.json({ ok: false, error: `模型列表可读,但生成测试失败:${message}` }, 400);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return c.json({ ok: true, modelCount: models.length, models: models.slice(0, 50) });
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
663
|
+
return c.json({ ok: false, error: `连接超时:无法访问 ${modelsUrl}` }, 400);
|
|
664
|
+
}
|
|
665
|
+
return c.json({ ok: false, error: `连接失败: ${err?.message ?? String(err)}` }, 400);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
app.put("/api/v1/services/:service/secret", async (c) => {
|
|
669
|
+
const service = c.req.param("service");
|
|
670
|
+
const { apiKey } = await c.req.json();
|
|
671
|
+
const secrets = await loadSecrets(root);
|
|
672
|
+
if (apiKey?.trim()) {
|
|
673
|
+
secrets.services[service] = { apiKey: apiKey.trim() };
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
delete secrets.services[service];
|
|
677
|
+
}
|
|
678
|
+
await saveSecrets(root, secrets);
|
|
679
|
+
return c.json({ ok: true });
|
|
680
|
+
});
|
|
681
|
+
app.get("/api/v1/services/:service/secret", async (c) => {
|
|
682
|
+
const service = c.req.param("service");
|
|
683
|
+
const secrets = await loadSecrets(root);
|
|
684
|
+
return c.json({
|
|
685
|
+
apiKey: secrets.services[service]?.apiKey ?? "",
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
app.get("/api/v1/services/:service/models", async (c) => {
|
|
689
|
+
const service = c.req.param("service");
|
|
690
|
+
const apiKey = c.req.query("apiKey") || await getServiceApiKey(root, service);
|
|
691
|
+
// No key = no models (don't fallback to built-in list)
|
|
692
|
+
if (!apiKey)
|
|
693
|
+
return c.json({ models: [] });
|
|
694
|
+
const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service);
|
|
695
|
+
if (!resolvedBaseUrl)
|
|
696
|
+
return c.json({ models: [] });
|
|
697
|
+
// Call real API only
|
|
698
|
+
let models = [];
|
|
699
|
+
try {
|
|
700
|
+
const modelsUrl = resolvedBaseUrl.replace(/\/$/, "") + "/models";
|
|
701
|
+
const res = await fetch(modelsUrl, {
|
|
702
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
703
|
+
signal: AbortSignal.timeout(10_000),
|
|
704
|
+
});
|
|
705
|
+
if (res.ok) {
|
|
706
|
+
const json = await res.json();
|
|
707
|
+
models = (json.data ?? []).map((m) => ({ id: m.id, name: m.id }));
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch { /* timeout or network error — return empty */ }
|
|
711
|
+
return c.json({ models });
|
|
712
|
+
});
|
|
321
713
|
// --- Project info ---
|
|
322
|
-
app.get("/api/project", async (c) => {
|
|
714
|
+
app.get("/api/v1/project", async (c) => {
|
|
323
715
|
const currentConfig = await loadCurrentProjectConfig({ requireApiKey: false });
|
|
324
716
|
// Check if language was explicitly set in inkos.json (not just the schema default)
|
|
325
717
|
const raw = JSON.parse(await readFile(join(root, "inkos.json"), "utf-8"));
|
|
@@ -337,7 +729,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
337
729
|
});
|
|
338
730
|
});
|
|
339
731
|
// --- Config editing ---
|
|
340
|
-
app.put("/api/project", async (c) => {
|
|
732
|
+
app.put("/api/v1/project", async (c) => {
|
|
341
733
|
const updates = await c.req.json();
|
|
342
734
|
const configPath = join(root, "inkos.json");
|
|
343
735
|
try {
|
|
@@ -365,7 +757,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
365
757
|
}
|
|
366
758
|
});
|
|
367
759
|
// --- Truth files browser ---
|
|
368
|
-
app.get("/api/books/:id/truth", async (c) => {
|
|
760
|
+
app.get("/api/v1/books/:id/truth", async (c) => {
|
|
369
761
|
const id = c.req.param("id");
|
|
370
762
|
const bookDir = state.bookDir(id);
|
|
371
763
|
const storyDir = join(bookDir, "story");
|
|
@@ -384,12 +776,12 @@ export function createStudioServer(initialConfig, root) {
|
|
|
384
776
|
});
|
|
385
777
|
// --- Daemon control ---
|
|
386
778
|
let schedulerInstance = null;
|
|
387
|
-
app.get("/api/daemon", (c) => {
|
|
779
|
+
app.get("/api/v1/daemon", (c) => {
|
|
388
780
|
return c.json({
|
|
389
781
|
running: schedulerInstance?.isRunning ?? false,
|
|
390
782
|
});
|
|
391
783
|
});
|
|
392
|
-
app.post("/api/daemon/start", async (c) => {
|
|
784
|
+
app.post("/api/v1/daemon/start", async (c) => {
|
|
393
785
|
if (schedulerInstance?.isRunning) {
|
|
394
786
|
return c.json({ error: "Daemon already running" }, 400);
|
|
395
787
|
}
|
|
@@ -429,7 +821,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
429
821
|
return c.json({ error: String(e) }, 500);
|
|
430
822
|
}
|
|
431
823
|
});
|
|
432
|
-
app.post("/api/daemon/stop", (c) => {
|
|
824
|
+
app.post("/api/v1/daemon/stop", (c) => {
|
|
433
825
|
if (!schedulerInstance?.isRunning) {
|
|
434
826
|
return c.json({ error: "Daemon not running" }, 400);
|
|
435
827
|
}
|
|
@@ -439,7 +831,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
439
831
|
return c.json({ ok: true, running: false });
|
|
440
832
|
});
|
|
441
833
|
// --- Logs ---
|
|
442
|
-
app.get("/api/logs", async (c) => {
|
|
834
|
+
app.get("/api/v1/logs", async (c) => {
|
|
443
835
|
const logPath = join(root, "inkos.log");
|
|
444
836
|
try {
|
|
445
837
|
const content = await readFile(logPath, "utf-8");
|
|
@@ -459,7 +851,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
459
851
|
}
|
|
460
852
|
});
|
|
461
853
|
// --- Agent chat ---
|
|
462
|
-
app.get("/api/interaction/session", async (c) => {
|
|
854
|
+
app.get("/api/v1/interaction/session", async (c) => {
|
|
463
855
|
const session = await loadProjectSession(root);
|
|
464
856
|
const activeBookId = await resolveSessionActiveBook(root, session);
|
|
465
857
|
return c.json({
|
|
@@ -469,38 +861,307 @@ export function createStudioServer(initialConfig, root) {
|
|
|
469
861
|
activeBookId,
|
|
470
862
|
});
|
|
471
863
|
});
|
|
472
|
-
|
|
473
|
-
|
|
864
|
+
// -- Per-book session endpoints --
|
|
865
|
+
app.get("/api/v1/sessions", async (c) => {
|
|
866
|
+
const bookId = c.req.query("bookId");
|
|
867
|
+
const sessions = await listBookSessions(root, bookId === undefined ? null : bookId === "null" ? null : bookId);
|
|
868
|
+
return c.json({ sessions: sessions.map((s) => ({
|
|
869
|
+
sessionId: s.sessionId,
|
|
870
|
+
bookId: s.bookId,
|
|
871
|
+
messageCount: s.messages.length,
|
|
872
|
+
createdAt: s.createdAt,
|
|
873
|
+
updatedAt: s.updatedAt,
|
|
874
|
+
})) });
|
|
875
|
+
});
|
|
876
|
+
app.get("/api/v1/sessions/:sessionId", async (c) => {
|
|
877
|
+
const session = await loadBookSession(root, c.req.param("sessionId"));
|
|
878
|
+
if (!session)
|
|
879
|
+
return c.json({ error: "Session not found" }, 404);
|
|
880
|
+
return c.json({ session });
|
|
881
|
+
});
|
|
882
|
+
app.post("/api/v1/sessions", async (c) => {
|
|
883
|
+
const body = await c.req.json().catch(() => ({}));
|
|
884
|
+
const bookId = body.bookId ?? null;
|
|
885
|
+
const session = await findOrCreateBookSession(root, bookId);
|
|
886
|
+
return c.json({ session });
|
|
887
|
+
});
|
|
888
|
+
app.post("/api/v1/agent", async (c) => {
|
|
889
|
+
const { instruction, activeBookId, sessionId: reqSessionId, model: reqModel, service: reqService } = await c.req.json();
|
|
890
|
+
const sessionId = reqSessionId;
|
|
474
891
|
if (!instruction?.trim()) {
|
|
475
892
|
return c.json({ error: "No instruction provided" }, 400);
|
|
476
893
|
}
|
|
477
894
|
broadcast("agent:start", { instruction, activeBookId });
|
|
478
895
|
try {
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
const
|
|
896
|
+
// Load config + create LLM client (pipeline created after model resolution)
|
|
897
|
+
const config = await loadCurrentProjectConfig({ requireApiKey: false });
|
|
898
|
+
const client = createLLMClient(config.llm);
|
|
899
|
+
// Resolve or create BookSession for history
|
|
900
|
+
let bookSession;
|
|
901
|
+
if (sessionId) {
|
|
902
|
+
bookSession =
|
|
903
|
+
(await loadBookSession(root, sessionId)) ??
|
|
904
|
+
(await findOrCreateBookSession(root, activeBookId ?? null));
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
bookSession = await findOrCreateBookSession(root, activeBookId ?? null);
|
|
908
|
+
}
|
|
909
|
+
// Build initial message context from persisted session
|
|
910
|
+
const initialMessages = bookSession.messages
|
|
911
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
912
|
+
.map((m) => ({ role: m.role, content: m.content }));
|
|
913
|
+
// Resolve model — multi-service resolution
|
|
914
|
+
let resolvedModel;
|
|
915
|
+
let resolvedApiKey;
|
|
916
|
+
if (reqService && reqModel) {
|
|
917
|
+
// 1. Frontend explicitly selected a service+model — fail loudly if no key
|
|
918
|
+
try {
|
|
919
|
+
const configuredEntry = await resolveConfiguredServiceEntry(root, reqService);
|
|
920
|
+
const resolved = await resolveServiceModel(reqService, reqModel, root, await resolveConfiguredServiceBaseUrl(root, reqService), configuredEntry?.apiFormat);
|
|
921
|
+
resolvedModel = resolved.model;
|
|
922
|
+
resolvedApiKey = resolved.apiKey;
|
|
923
|
+
}
|
|
924
|
+
catch (e) {
|
|
925
|
+
const msg = e?.message ?? String(e);
|
|
926
|
+
if (/API key/i.test(msg)) {
|
|
927
|
+
return c.json({
|
|
928
|
+
error: `请先为 ${reqService} 配置 API Key`,
|
|
929
|
+
response: `请先在模型配置中为 ${reqService} 填写 API Key,然后再试。`,
|
|
930
|
+
}, 400);
|
|
931
|
+
}
|
|
932
|
+
throw e;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (!resolvedModel) {
|
|
936
|
+
// 2. Try defaultModel from new config format
|
|
937
|
+
const rawConfig = config.llm;
|
|
938
|
+
const defaultModel = rawConfig.defaultModel;
|
|
939
|
+
const servicesArr = normalizeServiceConfig(rawConfig.services);
|
|
940
|
+
const firstService = servicesArr[0];
|
|
941
|
+
if (firstService?.service && defaultModel) {
|
|
942
|
+
try {
|
|
943
|
+
const resolved = await resolveServiceModel(serviceConfigKey(firstService), defaultModel, root, firstService.baseUrl, firstService.apiFormat);
|
|
944
|
+
resolvedModel = resolved.model;
|
|
945
|
+
resolvedApiKey = resolved.apiKey;
|
|
946
|
+
}
|
|
947
|
+
catch { /* fall through */ }
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (!resolvedModel) {
|
|
951
|
+
// 3. Try first connected service from secrets
|
|
952
|
+
const secrets = await loadSecrets(root);
|
|
953
|
+
for (const [svcName, svcData] of Object.entries(secrets.services)) {
|
|
954
|
+
if (svcData?.apiKey) {
|
|
955
|
+
try {
|
|
956
|
+
const models = await listModelsForService(svcName, svcData.apiKey);
|
|
957
|
+
if (models.length > 0) {
|
|
958
|
+
const configuredEntry = await resolveConfiguredServiceEntry(root, svcName);
|
|
959
|
+
const resolved = await resolveServiceModel(svcName, models[0].id, root, await resolveConfiguredServiceBaseUrl(root, svcName), configuredEntry?.apiFormat);
|
|
960
|
+
resolvedModel = resolved.model;
|
|
961
|
+
resolvedApiKey = resolved.apiKey;
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
catch { /* try next */ }
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (!resolvedModel) {
|
|
970
|
+
// 4. Legacy fallback: use createLLMClient
|
|
971
|
+
resolvedModel = client._piModel
|
|
972
|
+
? client._piModel
|
|
973
|
+
: { provider: config.llm.provider ?? "anthropic", modelId: config.llm.model };
|
|
974
|
+
resolvedApiKey = client._apiKey;
|
|
975
|
+
}
|
|
976
|
+
const model = resolvedModel;
|
|
977
|
+
const agentApiKey = resolvedApiKey;
|
|
978
|
+
const configuredEntry = reqService ? await resolveConfiguredServiceEntry(root, reqService) : undefined;
|
|
979
|
+
// Create pipeline with resolved model (so sub_agent tools use the frontend-selected model)
|
|
980
|
+
// Don't spread config.llm — its baseUrl/provider belong to the old service.
|
|
981
|
+
// Let createLLMClient resolve baseUrl from the service preset.
|
|
982
|
+
const pipelineClient = (reqService && reqModel && resolvedApiKey)
|
|
983
|
+
? createLLMClient({
|
|
984
|
+
...config.llm,
|
|
985
|
+
service: configuredEntry?.service ?? reqService,
|
|
986
|
+
model: reqModel,
|
|
987
|
+
apiKey: resolvedApiKey,
|
|
988
|
+
...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
|
|
989
|
+
...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
|
|
990
|
+
baseUrl: configuredEntry?.baseUrl ?? "",
|
|
991
|
+
})
|
|
992
|
+
: client;
|
|
993
|
+
const pipeline = new PipelineRunner(await buildPipelineConfig({
|
|
994
|
+
client: pipelineClient,
|
|
995
|
+
model: reqModel ?? config.llm.model,
|
|
996
|
+
currentConfig: config,
|
|
997
|
+
}));
|
|
998
|
+
// Run pi-agent session
|
|
999
|
+
const collectedToolExecs = [];
|
|
1000
|
+
const result = await runAgentSession({
|
|
1001
|
+
model,
|
|
1002
|
+
apiKey: agentApiKey,
|
|
1003
|
+
pipeline,
|
|
482
1004
|
projectRoot: root,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
1005
|
+
bookId: activeBookId ?? null,
|
|
1006
|
+
sessionId: bookSession.sessionId,
|
|
1007
|
+
language: config.language ?? "zh",
|
|
1008
|
+
onEvent: (event) => {
|
|
1009
|
+
if (event.type === "message_update") {
|
|
1010
|
+
const ame = event.assistantMessageEvent;
|
|
1011
|
+
if (ame.type === "text_delta") {
|
|
1012
|
+
broadcast("draft:delta", { text: ame.delta });
|
|
1013
|
+
}
|
|
1014
|
+
else if (ame.type === "thinking_delta") {
|
|
1015
|
+
broadcast("thinking:delta", { text: ame.delta });
|
|
1016
|
+
}
|
|
1017
|
+
else if (ame.type === "thinking_start") {
|
|
1018
|
+
broadcast("thinking:start", {});
|
|
1019
|
+
}
|
|
1020
|
+
else if (ame.type === "thinking_end") {
|
|
1021
|
+
broadcast("thinking:end", {});
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (event.type === "tool_execution_start") {
|
|
1025
|
+
const args = event.args;
|
|
1026
|
+
const agent = event.toolName === "sub_agent" ? args?.agent : undefined;
|
|
1027
|
+
const stages = agent ? (PIPELINE_STAGES[agent] ?? []) : [];
|
|
1028
|
+
collectedToolExecs.push({
|
|
1029
|
+
id: event.toolCallId,
|
|
1030
|
+
tool: event.toolName,
|
|
1031
|
+
agent,
|
|
1032
|
+
label: resolveToolLabel(event.toolName, agent),
|
|
1033
|
+
status: "running",
|
|
1034
|
+
args,
|
|
1035
|
+
stages: stages.length > 0
|
|
1036
|
+
? stages.map(l => ({ label: l, status: "pending" }))
|
|
1037
|
+
: undefined,
|
|
1038
|
+
startedAt: Date.now(),
|
|
1039
|
+
});
|
|
1040
|
+
broadcast("tool:start", {
|
|
1041
|
+
id: event.toolCallId,
|
|
1042
|
+
tool: event.toolName,
|
|
1043
|
+
args,
|
|
1044
|
+
stages,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
if (event.type === "tool_execution_update") {
|
|
1048
|
+
broadcast("tool:update", { tool: event.toolName, partialResult: event.partialResult });
|
|
1049
|
+
}
|
|
1050
|
+
if (event.type === "tool_execution_end") {
|
|
1051
|
+
const exec = collectedToolExecs.find(t => t.id === event.toolCallId);
|
|
1052
|
+
if (exec) {
|
|
1053
|
+
exec.status = event.isError ? "error" : "completed";
|
|
1054
|
+
exec.completedAt = Date.now();
|
|
1055
|
+
exec.stages = exec.stages?.map(s => ({ ...s, status: "completed" }));
|
|
1056
|
+
if (event.isError)
|
|
1057
|
+
exec.error = extractToolError(event.result);
|
|
1058
|
+
else
|
|
1059
|
+
exec.result = summarizeResult(event.result);
|
|
1060
|
+
}
|
|
1061
|
+
broadcast("tool:end", {
|
|
1062
|
+
id: event.toolCallId,
|
|
1063
|
+
tool: event.toolName,
|
|
1064
|
+
result: event.result,
|
|
1065
|
+
isError: event.isError,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
}, instruction, initialMessages);
|
|
1070
|
+
// Persist user + assistant messages to BookSession
|
|
1071
|
+
bookSession = appendBookSessionMessage(bookSession, {
|
|
1072
|
+
role: "user",
|
|
1073
|
+
content: instruction,
|
|
1074
|
+
timestamp: Date.now(),
|
|
1075
|
+
});
|
|
1076
|
+
if (result.responseText) {
|
|
1077
|
+
const lastAssistant = result.messages?.filter((m) => m.role === "assistant").pop();
|
|
1078
|
+
const thinking = lastAssistant?.thinking;
|
|
1079
|
+
bookSession = appendBookSessionMessage(bookSession, {
|
|
1080
|
+
role: "assistant",
|
|
1081
|
+
content: result.responseText,
|
|
1082
|
+
...(thinking ? { thinking } : {}),
|
|
1083
|
+
...(collectedToolExecs.length > 0 ? { toolExecutions: collectedToolExecs } : {}),
|
|
1084
|
+
timestamp: Date.now() + 1,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (!result.responseText) {
|
|
1088
|
+
try {
|
|
1089
|
+
const fallbackClient = createLLMClient({
|
|
1090
|
+
...config.llm,
|
|
1091
|
+
service: configuredEntry?.service ?? reqService ?? config.llm.service,
|
|
1092
|
+
model: reqModel ?? config.llm.model,
|
|
1093
|
+
apiKey: agentApiKey ?? config.llm.apiKey,
|
|
1094
|
+
baseUrl: configuredEntry?.baseUrl ?? config.llm.baseUrl,
|
|
1095
|
+
...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
|
|
1096
|
+
...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
|
|
1097
|
+
});
|
|
1098
|
+
const fallback = await chatCompletion(fallbackClient, reqModel ?? config.llm.model, [
|
|
1099
|
+
{ role: "system", content: buildAgentSystemPrompt(activeBookId ?? null, config.language ?? "zh") },
|
|
1100
|
+
{ role: "user", content: instruction },
|
|
1101
|
+
], { maxTokens: 256 });
|
|
1102
|
+
if (fallback.content?.trim()) {
|
|
1103
|
+
bookSession = appendBookSessionMessage(bookSession, {
|
|
1104
|
+
role: "assistant",
|
|
1105
|
+
content: fallback.content,
|
|
1106
|
+
timestamp: Date.now() + 1,
|
|
1107
|
+
});
|
|
1108
|
+
await persistBookSession(root, bookSession);
|
|
1109
|
+
return c.json({
|
|
1110
|
+
response: fallback.content,
|
|
1111
|
+
session: { sessionId: bookSession.sessionId },
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
// fall through to probe-based diagnosis below
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
const probeClient = createLLMClient({
|
|
1120
|
+
...config.llm,
|
|
1121
|
+
service: configuredEntry?.service ?? reqService ?? config.llm.service,
|
|
1122
|
+
model: reqModel ?? config.llm.model,
|
|
1123
|
+
apiKey: agentApiKey ?? config.llm.apiKey,
|
|
1124
|
+
baseUrl: configuredEntry?.baseUrl ?? config.llm.baseUrl,
|
|
1125
|
+
...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
|
|
1126
|
+
...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
|
|
1127
|
+
});
|
|
1128
|
+
await chatCompletion(probeClient, reqModel ?? config.llm.model, [{ role: "user", content: "ping" }], { maxTokens: 5 });
|
|
1129
|
+
}
|
|
1130
|
+
catch (probeError) {
|
|
1131
|
+
const probeMessage = probeError instanceof Error ? probeError.message : String(probeError);
|
|
1132
|
+
return c.json({
|
|
1133
|
+
error: { code: "AGENT_EMPTY_RESPONSE", message: probeMessage },
|
|
1134
|
+
response: probeMessage,
|
|
1135
|
+
}, 502);
|
|
1136
|
+
}
|
|
1137
|
+
const emptyMessage = "模型未返回文本内容。请检查协议类型(chat/responses)、流式开关或上游服务兼容性。";
|
|
1138
|
+
return c.json({
|
|
1139
|
+
error: { code: "AGENT_EMPTY_RESPONSE", message: emptyMessage },
|
|
1140
|
+
response: emptyMessage,
|
|
1141
|
+
}, 502);
|
|
1142
|
+
}
|
|
1143
|
+
await persistBookSession(root, bookSession);
|
|
1144
|
+
broadcast("agent:complete", { instruction, activeBookId });
|
|
1145
|
+
return c.json({
|
|
1146
|
+
response: result.responseText,
|
|
1147
|
+
session: { sessionId: bookSession.sessionId },
|
|
486
1148
|
});
|
|
487
|
-
const response = result.responseText ?? "Acknowledged.";
|
|
488
|
-
broadcast("agent:complete", { instruction, activeBookId, response });
|
|
489
|
-
return c.json({ response, session: result.session, request: result.request });
|
|
490
1149
|
}
|
|
491
1150
|
catch (e) {
|
|
492
1151
|
const msg = e instanceof Error ? e.message : String(e);
|
|
493
1152
|
broadcast("agent:error", { instruction, activeBookId, error: msg });
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
message:
|
|
498
|
-
|
|
499
|
-
|
|
1153
|
+
// Agent busy — return 429 with user-friendly message
|
|
1154
|
+
if (/already processing|prompt.*queue/i.test(msg)) {
|
|
1155
|
+
return c.json({
|
|
1156
|
+
error: { code: "AGENT_BUSY", message: "正在处理中,请等待当前操作完成" },
|
|
1157
|
+
response: "正在处理中,请等待当前操作完成后再发送。",
|
|
1158
|
+
}, 429);
|
|
1159
|
+
}
|
|
1160
|
+
return c.json({ error: { code: "AGENT_ERROR", message: msg } }, 500);
|
|
500
1161
|
}
|
|
501
1162
|
});
|
|
502
1163
|
// --- Language setup ---
|
|
503
|
-
app.post("/api/project/language", async (c) => {
|
|
1164
|
+
app.post("/api/v1/project/language", async (c) => {
|
|
504
1165
|
const { language } = await c.req.json();
|
|
505
1166
|
const configPath = join(root, "inkos.json");
|
|
506
1167
|
try {
|
|
@@ -516,7 +1177,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
516
1177
|
}
|
|
517
1178
|
});
|
|
518
1179
|
// --- Audit ---
|
|
519
|
-
app.post("/api/books/:id/audit/:chapter", async (c) => {
|
|
1180
|
+
app.post("/api/v1/books/:id/audit/:chapter", async (c) => {
|
|
520
1181
|
const id = c.req.param("id");
|
|
521
1182
|
const chapterNum = parseInt(c.req.param("chapter"), 10);
|
|
522
1183
|
const bookDir = state.bookDir(id);
|
|
@@ -548,7 +1209,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
548
1209
|
}
|
|
549
1210
|
});
|
|
550
1211
|
// --- Revise ---
|
|
551
|
-
app.post("/api/books/:id/revise/:chapter", async (c) => {
|
|
1212
|
+
app.post("/api/v1/books/:id/revise/:chapter", async (c) => {
|
|
552
1213
|
const id = c.req.param("id");
|
|
553
1214
|
const chapterNum = parseInt(c.req.param("chapter"), 10);
|
|
554
1215
|
const bookDir = state.bookDir(id);
|
|
@@ -578,7 +1239,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
578
1239
|
}
|
|
579
1240
|
});
|
|
580
1241
|
// --- Export ---
|
|
581
|
-
app.get("/api/books/:id/export", async (c) => {
|
|
1242
|
+
app.get("/api/v1/books/:id/export", async (c) => {
|
|
582
1243
|
const id = c.req.param("id");
|
|
583
1244
|
const format = (c.req.query("format") ?? "txt");
|
|
584
1245
|
const approvedOnly = c.req.query("approvedOnly") === "true";
|
|
@@ -634,7 +1295,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
634
1295
|
}
|
|
635
1296
|
});
|
|
636
1297
|
// --- Export to file (save to project dir) ---
|
|
637
|
-
app.post("/api/books/:id/export-save", async (c) => {
|
|
1298
|
+
app.post("/api/v1/books/:id/export-save", async (c) => {
|
|
638
1299
|
const id = c.req.param("id");
|
|
639
1300
|
const { format, approvedOnly } = await c.req.json().catch(() => ({ format: "txt", approvedOnly: false }));
|
|
640
1301
|
const fmt = format ?? "txt";
|
|
@@ -667,7 +1328,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
667
1328
|
}
|
|
668
1329
|
});
|
|
669
1330
|
// --- Genre detail + copy ---
|
|
670
|
-
app.get("/api/genres/:id", async (c) => {
|
|
1331
|
+
app.get("/api/v1/genres/:id", async (c) => {
|
|
671
1332
|
const genreId = c.req.param("id");
|
|
672
1333
|
try {
|
|
673
1334
|
const { readGenreProfile } = await import("@actalk/inkos-core");
|
|
@@ -678,7 +1339,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
678
1339
|
return c.json({ error: String(e) }, 404);
|
|
679
1340
|
}
|
|
680
1341
|
});
|
|
681
|
-
app.post("/api/genres/:id/copy", async (c) => {
|
|
1342
|
+
app.post("/api/v1/genres/:id/copy", async (c) => {
|
|
682
1343
|
const genreId = c.req.param("id");
|
|
683
1344
|
if (/[/\\\0]/.test(genreId) || genreId.includes("..")) {
|
|
684
1345
|
throw new ApiError(400, "INVALID_GENRE_ID", `Invalid genre ID: "${genreId}"`);
|
|
@@ -697,11 +1358,11 @@ export function createStudioServer(initialConfig, root) {
|
|
|
697
1358
|
}
|
|
698
1359
|
});
|
|
699
1360
|
// --- Model overrides ---
|
|
700
|
-
app.get("/api/project/model-overrides", async (c) => {
|
|
1361
|
+
app.get("/api/v1/project/model-overrides", async (c) => {
|
|
701
1362
|
const raw = JSON.parse(await readFile(join(root, "inkos.json"), "utf-8"));
|
|
702
1363
|
return c.json({ overrides: raw.modelOverrides ?? {} });
|
|
703
1364
|
});
|
|
704
|
-
app.put("/api/project/model-overrides", async (c) => {
|
|
1365
|
+
app.put("/api/v1/project/model-overrides", async (c) => {
|
|
705
1366
|
const { overrides } = await c.req.json();
|
|
706
1367
|
const configPath = join(root, "inkos.json");
|
|
707
1368
|
const raw = JSON.parse(await readFile(configPath, "utf-8"));
|
|
@@ -711,11 +1372,11 @@ export function createStudioServer(initialConfig, root) {
|
|
|
711
1372
|
return c.json({ ok: true });
|
|
712
1373
|
});
|
|
713
1374
|
// --- Notify channels ---
|
|
714
|
-
app.get("/api/project/notify", async (c) => {
|
|
1375
|
+
app.get("/api/v1/project/notify", async (c) => {
|
|
715
1376
|
const raw = JSON.parse(await readFile(join(root, "inkos.json"), "utf-8"));
|
|
716
1377
|
return c.json({ channels: raw.notify ?? [] });
|
|
717
1378
|
});
|
|
718
|
-
app.put("/api/project/notify", async (c) => {
|
|
1379
|
+
app.put("/api/v1/project/notify", async (c) => {
|
|
719
1380
|
const { channels } = await c.req.json();
|
|
720
1381
|
const configPath = join(root, "inkos.json");
|
|
721
1382
|
const raw = JSON.parse(await readFile(configPath, "utf-8"));
|
|
@@ -725,7 +1386,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
725
1386
|
return c.json({ ok: true });
|
|
726
1387
|
});
|
|
727
1388
|
// --- AIGC Detection ---
|
|
728
|
-
app.post("/api/books/:id/detect/:chapter", async (c) => {
|
|
1389
|
+
app.post("/api/v1/books/:id/detect/:chapter", async (c) => {
|
|
729
1390
|
const id = c.req.param("id");
|
|
730
1391
|
const chapterNum = parseInt(c.req.param("chapter"), 10);
|
|
731
1392
|
const bookDir = state.bookDir(id);
|
|
@@ -746,7 +1407,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
746
1407
|
}
|
|
747
1408
|
});
|
|
748
1409
|
// --- Truth file edit ---
|
|
749
|
-
app.put("/api/books/:id/truth/:file", async (c) => {
|
|
1410
|
+
app.put("/api/v1/books/:id/truth/:file", async (c) => {
|
|
750
1411
|
const id = c.req.param("id");
|
|
751
1412
|
const file = c.req.param("file");
|
|
752
1413
|
if (!TRUTH_FILES.includes(file)) {
|
|
@@ -763,7 +1424,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
763
1424
|
// NEW ENDPOINTS — CLI parity
|
|
764
1425
|
// =============================================
|
|
765
1426
|
// --- Book Delete ---
|
|
766
|
-
app.delete("/api/books/:id", async (c) => {
|
|
1427
|
+
app.delete("/api/v1/books/:id", async (c) => {
|
|
767
1428
|
const id = c.req.param("id");
|
|
768
1429
|
const bookDir = state.bookDir(id);
|
|
769
1430
|
try {
|
|
@@ -777,7 +1438,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
777
1438
|
}
|
|
778
1439
|
});
|
|
779
1440
|
// --- Book Update ---
|
|
780
|
-
app.put("/api/books/:id", async (c) => {
|
|
1441
|
+
app.put("/api/v1/books/:id", async (c) => {
|
|
781
1442
|
const id = c.req.param("id");
|
|
782
1443
|
const updates = await c.req.json();
|
|
783
1444
|
try {
|
|
@@ -798,7 +1459,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
798
1459
|
}
|
|
799
1460
|
});
|
|
800
1461
|
// --- Write Rewrite (specific chapter) ---
|
|
801
|
-
app.post("/api/books/:id/rewrite/:chapter", async (c) => {
|
|
1462
|
+
app.post("/api/v1/books/:id/rewrite/:chapter", async (c) => {
|
|
802
1463
|
const id = c.req.param("id");
|
|
803
1464
|
const chapterNum = parseInt(c.req.param("chapter"), 10);
|
|
804
1465
|
const body = await c.req
|
|
@@ -819,7 +1480,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
819
1480
|
return c.json({ error: String(e) }, 500);
|
|
820
1481
|
}
|
|
821
1482
|
});
|
|
822
|
-
app.post("/api/books/:id/resync/:chapter", async (c) => {
|
|
1483
|
+
app.post("/api/v1/books/:id/resync/:chapter", async (c) => {
|
|
823
1484
|
const id = c.req.param("id");
|
|
824
1485
|
const chapterNum = parseInt(c.req.param("chapter"), 10);
|
|
825
1486
|
const body = await c.req
|
|
@@ -837,7 +1498,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
837
1498
|
}
|
|
838
1499
|
});
|
|
839
1500
|
// --- Detect All chapters ---
|
|
840
|
-
app.post("/api/books/:id/detect-all", async (c) => {
|
|
1501
|
+
app.post("/api/v1/books/:id/detect-all", async (c) => {
|
|
841
1502
|
const id = c.req.param("id");
|
|
842
1503
|
const bookDir = state.bookDir(id);
|
|
843
1504
|
try {
|
|
@@ -858,7 +1519,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
858
1519
|
}
|
|
859
1520
|
});
|
|
860
1521
|
// --- Detect Stats ---
|
|
861
|
-
app.get("/api/books/:id/detect/stats", async (c) => {
|
|
1522
|
+
app.get("/api/v1/books/:id/detect/stats", async (c) => {
|
|
862
1523
|
const id = c.req.param("id");
|
|
863
1524
|
try {
|
|
864
1525
|
const { loadDetectionHistory, analyzeDetectionInsights } = await import("@actalk/inkos-core");
|
|
@@ -872,7 +1533,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
872
1533
|
}
|
|
873
1534
|
});
|
|
874
1535
|
// --- Genre Create ---
|
|
875
|
-
app.post("/api/genres/create", async (c) => {
|
|
1536
|
+
app.post("/api/v1/genres/create", async (c) => {
|
|
876
1537
|
const body = await c.req.json();
|
|
877
1538
|
if (!body.id || !body.name) {
|
|
878
1539
|
return c.json({ error: "id and name are required" }, 400);
|
|
@@ -904,7 +1565,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
904
1565
|
return c.json({ ok: true, id: body.id });
|
|
905
1566
|
});
|
|
906
1567
|
// --- Genre Edit ---
|
|
907
|
-
app.put("/api/genres/:id", async (c) => {
|
|
1568
|
+
app.put("/api/v1/genres/:id", async (c) => {
|
|
908
1569
|
const genreId = c.req.param("id");
|
|
909
1570
|
if (/[/\\\0]/.test(genreId) || genreId.includes("..")) {
|
|
910
1571
|
throw new ApiError(400, "INVALID_GENRE_ID", `Invalid genre ID: "${genreId}"`);
|
|
@@ -935,7 +1596,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
935
1596
|
return c.json({ ok: true, id: genreId });
|
|
936
1597
|
});
|
|
937
1598
|
// --- Genre Delete (project-level only) ---
|
|
938
|
-
app.delete("/api/genres/:id", async (c) => {
|
|
1599
|
+
app.delete("/api/v1/genres/:id", async (c) => {
|
|
939
1600
|
const genreId = c.req.param("id");
|
|
940
1601
|
if (/[/\\\0]/.test(genreId) || genreId.includes("..")) {
|
|
941
1602
|
throw new ApiError(400, "INVALID_GENRE_ID", `Invalid genre ID: "${genreId}"`);
|
|
@@ -951,7 +1612,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
951
1612
|
}
|
|
952
1613
|
});
|
|
953
1614
|
// --- Style Analyze ---
|
|
954
|
-
app.post("/api/style/analyze", async (c) => {
|
|
1615
|
+
app.post("/api/v1/style/analyze", async (c) => {
|
|
955
1616
|
const { text, sourceName } = await c.req.json();
|
|
956
1617
|
if (!text?.trim())
|
|
957
1618
|
return c.json({ error: "text is required" }, 400);
|
|
@@ -965,7 +1626,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
965
1626
|
}
|
|
966
1627
|
});
|
|
967
1628
|
// --- Style Import to Book ---
|
|
968
|
-
app.post("/api/books/:id/style/import", async (c) => {
|
|
1629
|
+
app.post("/api/v1/books/:id/style/import", async (c) => {
|
|
969
1630
|
const id = c.req.param("id");
|
|
970
1631
|
const { text, sourceName } = await c.req.json();
|
|
971
1632
|
broadcast("style:start", { bookId: id });
|
|
@@ -981,7 +1642,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
981
1642
|
}
|
|
982
1643
|
});
|
|
983
1644
|
// --- Import Chapters ---
|
|
984
|
-
app.post("/api/books/:id/import/chapters", async (c) => {
|
|
1645
|
+
app.post("/api/v1/books/:id/import/chapters", async (c) => {
|
|
985
1646
|
const id = c.req.param("id");
|
|
986
1647
|
const { text, splitRegex } = await c.req.json();
|
|
987
1648
|
if (!text?.trim())
|
|
@@ -1001,7 +1662,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1001
1662
|
}
|
|
1002
1663
|
});
|
|
1003
1664
|
// --- Import Canon ---
|
|
1004
|
-
app.post("/api/books/:id/import/canon", async (c) => {
|
|
1665
|
+
app.post("/api/v1/books/:id/import/canon", async (c) => {
|
|
1005
1666
|
const id = c.req.param("id");
|
|
1006
1667
|
const { fromBookId } = await c.req.json();
|
|
1007
1668
|
if (!fromBookId)
|
|
@@ -1019,7 +1680,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1019
1680
|
}
|
|
1020
1681
|
});
|
|
1021
1682
|
// --- Fanfic Init ---
|
|
1022
|
-
app.post("/api/fanfic/init", async (c) => {
|
|
1683
|
+
app.post("/api/v1/fanfic/init", async (c) => {
|
|
1023
1684
|
const body = await c.req.json();
|
|
1024
1685
|
if (!body.title || !body.sourceText) {
|
|
1025
1686
|
return c.json({ error: "title and sourceText are required" }, 400);
|
|
@@ -1052,7 +1713,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1052
1713
|
}
|
|
1053
1714
|
});
|
|
1054
1715
|
// --- Fanfic Show (read canon) ---
|
|
1055
|
-
app.get("/api/books/:id/fanfic", async (c) => {
|
|
1716
|
+
app.get("/api/v1/books/:id/fanfic", async (c) => {
|
|
1056
1717
|
const id = c.req.param("id");
|
|
1057
1718
|
const bookDir = state.bookDir(id);
|
|
1058
1719
|
try {
|
|
@@ -1064,7 +1725,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1064
1725
|
}
|
|
1065
1726
|
});
|
|
1066
1727
|
// --- Fanfic Refresh ---
|
|
1067
|
-
app.post("/api/books/:id/fanfic/refresh", async (c) => {
|
|
1728
|
+
app.post("/api/v1/books/:id/fanfic/refresh", async (c) => {
|
|
1068
1729
|
const id = c.req.param("id");
|
|
1069
1730
|
const { sourceText, sourceName } = await c.req.json();
|
|
1070
1731
|
if (!sourceText?.trim())
|
|
@@ -1083,7 +1744,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1083
1744
|
}
|
|
1084
1745
|
});
|
|
1085
1746
|
// --- Radar Scan ---
|
|
1086
|
-
app.post("/api/radar/scan", async (c) => {
|
|
1747
|
+
app.post("/api/v1/radar/scan", async (c) => {
|
|
1087
1748
|
broadcast("radar:start", {});
|
|
1088
1749
|
try {
|
|
1089
1750
|
const pipeline = new PipelineRunner(await buildPipelineConfig());
|
|
@@ -1097,7 +1758,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1097
1758
|
}
|
|
1098
1759
|
});
|
|
1099
1760
|
// --- Doctor (environment health check) ---
|
|
1100
|
-
app.get("/api/doctor", async (c) => {
|
|
1761
|
+
app.get("/api/v1/doctor", async (c) => {
|
|
1101
1762
|
const { existsSync } = await import("node:fs");
|
|
1102
1763
|
const { GLOBAL_ENV_PATH } = await import("@actalk/inkos-core");
|
|
1103
1764
|
const checks = {
|
|
@@ -1127,7 +1788,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1127
1788
|
}
|
|
1128
1789
|
// --- Standalone runner ---
|
|
1129
1790
|
export async function startStudioServer(root, port = 4567, options) {
|
|
1130
|
-
const config = await loadProjectConfig(root);
|
|
1791
|
+
const config = await loadProjectConfig(root, { requireApiKey: false });
|
|
1131
1792
|
const app = createStudioServer(config, root);
|
|
1132
1793
|
// Serve frontend static files — single process for API + frontend
|
|
1133
1794
|
if (options?.staticDir) {
|
|
@@ -1161,7 +1822,7 @@ export async function startStudioServer(root, port = 4567, options) {
|
|
|
1161
1822
|
if (existsSync(indexPath)) {
|
|
1162
1823
|
const indexHtml = await readFileFs(indexPath, "utf-8");
|
|
1163
1824
|
app.get("*", (c) => {
|
|
1164
|
-
if (c.req.path.startsWith("/api/"))
|
|
1825
|
+
if (c.req.path.startsWith("/api/v1/"))
|
|
1165
1826
|
return c.notFound();
|
|
1166
1827
|
return c.html(indexHtml);
|
|
1167
1828
|
});
|