research-copilot 0.1.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/LICENSE +21 -0
- package/README.md +190 -0
- package/app/build/icon.icns +0 -0
- package/app/build/icon.ico +0 -0
- package/app/build/icon.png +0 -0
- package/app/out/main/index.mjs +6719 -0
- package/app/out/preload/index.js +141 -0
- package/app/out/renderer/assets/Inter-Variable-Latin-8kRkwJBP.woff2 +0 -0
- package/app/out/renderer/assets/Inter-Variable-LatinExt-B_-bZUTo.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/app/out/renderer/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/app/out/renderer/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/app/out/renderer/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/app/out/renderer/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/app/out/renderer/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/app/out/renderer/assets/MilkdownMarkdownEditor-bLPxrCVb.js +89821 -0
- package/app/out/renderer/assets/MilkdownMarkdownEditor-tTNRIB2K.css +2555 -0
- package/app/out/renderer/assets/Tableau10-BqnYsPR6.js +9 -0
- package/app/out/renderer/assets/apl-fqmucPXA.js +140 -0
- package/app/out/renderer/assets/arc-J47ePHZ2.js +132 -0
- package/app/out/renderer/assets/array-DgktLKBx.js +6 -0
- package/app/out/renderer/assets/asciiarmor-DucZyvP0.js +56 -0
- package/app/out/renderer/assets/asn1-BnOEsgAm.js +144 -0
- package/app/out/renderer/assets/asterisk-QAlztEwS.js +345 -0
- package/app/out/renderer/assets/blockDiagram-c4efeb88-5uRQXgQJ.js +1817 -0
- package/app/out/renderer/assets/brainfuck-DZVCuF_t.js +53 -0
- package/app/out/renderer/assets/c4Diagram-c83219d4-C4iCTPEL.js +2464 -0
- package/app/out/renderer/assets/channel-ZAmhHE3g.js +7 -0
- package/app/out/renderer/assets/classDiagram-beda092f-7NOZxq_W.js +357 -0
- package/app/out/renderer/assets/classDiagram-v2-2358418a-5fPT-cUH.js +291 -0
- package/app/out/renderer/assets/clike-xqXYL6ge.js +805 -0
- package/app/out/renderer/assets/clojure-BhXMqnxz.js +849 -0
- package/app/out/renderer/assets/clone-CsnzsYXQ.js +8 -0
- package/app/out/renderer/assets/cmake-BGaNd9E7.js +71 -0
- package/app/out/renderer/assets/cobol-4yqQntpt.js +120 -0
- package/app/out/renderer/assets/coffeescript-D2dXvhEc.js +308 -0
- package/app/out/renderer/assets/commonlisp-CF_VNHQR.js +130 -0
- package/app/out/renderer/assets/createText-1719965b-Cji7KN4K.js +4904 -0
- package/app/out/renderer/assets/crystal-DyuLTqLs.js +398 -0
- package/app/out/renderer/assets/css-c-jst79C.js +1783 -0
- package/app/out/renderer/assets/cypher-Dlu_3r4V.js +121 -0
- package/app/out/renderer/assets/d-UURgV0Ux.js +179 -0
- package/app/out/renderer/assets/diff-B_Bi2Crb.js +25 -0
- package/app/out/renderer/assets/dockerfile-Bvk733Ga.js +201 -0
- package/app/out/renderer/assets/dtd-Dy74G54E.js +114 -0
- package/app/out/renderer/assets/dylan-TSb-Nfix.js +314 -0
- package/app/out/renderer/assets/ebnf-DAomQUbD.js +139 -0
- package/app/out/renderer/assets/ecl-B59qGGVg.js +178 -0
- package/app/out/renderer/assets/edges-96097737-CD0EvAZQ.js +1844 -0
- package/app/out/renderer/assets/eiffel-Dze7nlu3.js +134 -0
- package/app/out/renderer/assets/elm-DG7jkhNZ.js +176 -0
- package/app/out/renderer/assets/erDiagram-0228fc6a-DRYXBpi7.js +1321 -0
- package/app/out/renderer/assets/erlang-BO6gOnGA.js +674 -0
- package/app/out/renderer/assets/factor-CMxFHDqz.js +65 -0
- package/app/out/renderer/assets/fcl-CDDUNjTj.js +141 -0
- package/app/out/renderer/assets/flowDb-c6c81e3f-CuoIN-Cy.js +1713 -0
- package/app/out/renderer/assets/flowDiagram-50d868cf-CPWPLOml.js +1272 -0
- package/app/out/renderer/assets/flowDiagram-v2-4f6560a1-C_R12s4S.js +33 -0
- package/app/out/renderer/assets/flowchart-elk-definition-6af322e1-BdKUSFpi.js +92921 -0
- package/app/out/renderer/assets/forth-B9D2JCeE.js +116 -0
- package/app/out/renderer/assets/fortran-CAG2BFbe.js +467 -0
- package/app/out/renderer/assets/ganttDiagram-a2739b55-ygqT5HlG.js +3399 -0
- package/app/out/renderer/assets/gas-d3KEcW3x.js +294 -0
- package/app/out/renderer/assets/gherkin-DhZlEZiy.js +115 -0
- package/app/out/renderer/assets/gitGraphDiagram-82fe8481-D97GT4iA.js +1791 -0
- package/app/out/renderer/assets/graph-DpC13d95.js +1237 -0
- package/app/out/renderer/assets/groovy-CpwJiBl7.js +223 -0
- package/app/out/renderer/assets/haskell-BlGBCCe3.js +459 -0
- package/app/out/renderer/assets/haxe-7MlzfeYV.js +514 -0
- package/app/out/renderer/assets/http-BqypyemW.js +79 -0
- package/app/out/renderer/assets/idl-4HIGJlDI.js +985 -0
- package/app/out/renderer/assets/index-4-ziknCv.js +292 -0
- package/app/out/renderer/assets/index-5325376f-Bbs7Fbqr.js +663 -0
- package/app/out/renderer/assets/index-B2jip-rk.js +2489 -0
- package/app/out/renderer/assets/index-BKTVfokE.js +312 -0
- package/app/out/renderer/assets/index-BiJbFgVG.js +118 -0
- package/app/out/renderer/assets/index-Bn433Fat.js +83 -0
- package/app/out/renderer/assets/index-BqDyyRCx.js +39679 -0
- package/app/out/renderer/assets/index-BzFMeMPn.js +158 -0
- package/app/out/renderer/assets/index-C-_uCjZJ.css +2701 -0
- package/app/out/renderer/assets/index-C1ithNW1.js +1765 -0
- package/app/out/renderer/assets/index-CAJkRYkO.js +407 -0
- package/app/out/renderer/assets/index-CleO0-yj.js +690 -0
- package/app/out/renderer/assets/index-Cq4MH3sY.js +1020 -0
- package/app/out/renderer/assets/index-CtA0Xj22.js +705 -0
- package/app/out/renderer/assets/index-CvAZkqBZ.js +83 -0
- package/app/out/renderer/assets/index-D3UDN-5c.js +152 -0
- package/app/out/renderer/assets/index-D4F9R5ao.js +179 -0
- package/app/out/renderer/assets/index-D6RguhZ5.js +328 -0
- package/app/out/renderer/assets/index-DnEowqXv.js +386 -0
- package/app/out/renderer/assets/index-K8c8Mqdy.js +98 -0
- package/app/out/renderer/assets/index-Kh14gO6K.js +62 -0
- package/app/out/renderer/assets/index-WFd2jRnA.js +333 -0
- package/app/out/renderer/assets/index-WgMfkRFp.js +313 -0
- package/app/out/renderer/assets/index-Y4lKyF6t.js +1042 -0
- package/app/out/renderer/assets/index-fx307_f1.js +643 -0
- package/app/out/renderer/assets/infoDiagram-8eee0895-ptaVSwzq.js +511 -0
- package/app/out/renderer/assets/init-ZxktEp_H.js +16 -0
- package/app/out/renderer/assets/javascript-C3MnDRiU.js +994 -0
- package/app/out/renderer/assets/journeyDiagram-c64418c1-aloEGOQp.js +1184 -0
- package/app/out/renderer/assets/julia-Bs6JJhYG.js +407 -0
- package/app/out/renderer/assets/layout-ZeuHE_aY.js +2217 -0
- package/app/out/renderer/assets/line-CAgaGl-S.js +45 -0
- package/app/out/renderer/assets/linear-DIg7lTe1.js +539 -0
- package/app/out/renderer/assets/livescript-DmzgM3Yt.js +296 -0
- package/app/out/renderer/assets/lua-8cJgIlqe.js +256 -0
- package/app/out/renderer/assets/mathematica-DNLOL9PQ.js +110 -0
- package/app/out/renderer/assets/mbox-Ga7d4MMN.js +117 -0
- package/app/out/renderer/assets/mindmap-definition-8da855dc-B8XVoUxz.js +36054 -0
- package/app/out/renderer/assets/mirc-Dma3B8rS.js +107 -0
- package/app/out/renderer/assets/mllike-DHn7xckP.js +334 -0
- package/app/out/renderer/assets/modelica-0d55jYY0.js +147 -0
- package/app/out/renderer/assets/mscgen-DdqZYINH.js +135 -0
- package/app/out/renderer/assets/mumps-Btr8VblO.js +93 -0
- package/app/out/renderer/assets/nginx-DTDtBDVN.js +141 -0
- package/app/out/renderer/assets/nsis-3zG7tgur.js +62 -0
- package/app/out/renderer/assets/ntriples-CvgOYMpL.js +153 -0
- package/app/out/renderer/assets/octave-DYBj3-tl.js +200 -0
- package/app/out/renderer/assets/ordinal-DSZU4PqD.js +76 -0
- package/app/out/renderer/assets/oz-R_e8WMIi.js +231 -0
- package/app/out/renderer/assets/pascal-GD8iposT.js +105 -0
- package/app/out/renderer/assets/path-Cp2qmpkd.js +109 -0
- package/app/out/renderer/assets/perl-DL9mHpoi.js +1105 -0
- package/app/out/renderer/assets/pieDiagram-a8764435-DlwoeBU2.js +770 -0
- package/app/out/renderer/assets/pig-C_4T4YIV.js +101 -0
- package/app/out/renderer/assets/powershell-B0suO7Vd.js +328 -0
- package/app/out/renderer/assets/properties-BR-vP1aU.js +58 -0
- package/app/out/renderer/assets/protobuf-BxgpyhoW.js +77 -0
- package/app/out/renderer/assets/pug-By0kVCfm.js +405 -0
- package/app/out/renderer/assets/puppet-Bdao66PW.js +137 -0
- package/app/out/renderer/assets/python-CvWbmiX4.js +427 -0
- package/app/out/renderer/assets/q-CrbCVq4a.js +131 -0
- package/app/out/renderer/assets/quadrantDiagram-1e28029f-BaSi1XB4.js +1200 -0
- package/app/out/renderer/assets/r-V7nswm59.js +170 -0
- package/app/out/renderer/assets/requirementDiagram-08caed73-D3EFyegZ.js +1092 -0
- package/app/out/renderer/assets/rpm-C-DLY-If.js +109 -0
- package/app/out/renderer/assets/ruby-JDKLJNK0.js +330 -0
- package/app/out/renderer/assets/sankeyDiagram-a04cb91d-Cv44AsnM.js +1174 -0
- package/app/out/renderer/assets/sas-D2UG-yhZ.js +207 -0
- package/app/out/renderer/assets/scheme-BKzrkGJD.js +222 -0
- package/app/out/renderer/assets/sequenceDiagram-c5b8d532-CuUBu-x4.js +3337 -0
- package/app/out/renderer/assets/shell-BlsXDxCn.js +222 -0
- package/app/out/renderer/assets/sieve-CjwBwOY5.js +135 -0
- package/app/out/renderer/assets/simple-mode-DMneyfDu.js +130 -0
- package/app/out/renderer/assets/smalltalk-BOIGQuhN.js +121 -0
- package/app/out/renderer/assets/solr-CwD7U71z.js +69 -0
- package/app/out/renderer/assets/sparql-DYskk2vE.js +249 -0
- package/app/out/renderer/assets/spreadsheet-Bgtt3oLP.js +87 -0
- package/app/out/renderer/assets/sql-BSrOzCRI.js +354 -0
- package/app/out/renderer/assets/stateDiagram-1ecb1508-BOU34Zp4.js +454 -0
- package/app/out/renderer/assets/stateDiagram-v2-c2b004d7-BgRoffou.js +326 -0
- package/app/out/renderer/assets/stex-B6LNC55o.js +231 -0
- package/app/out/renderer/assets/styles-b4e223ce-BMr9TPuj.js +1483 -0
- package/app/out/renderer/assets/styles-ca3715f6-DgbNw99p.js +1363 -0
- package/app/out/renderer/assets/styles-d45a18b0-DtRYKYKf.js +574 -0
- package/app/out/renderer/assets/stylus-BkS-boTH.js +565 -0
- package/app/out/renderer/assets/svgDrawCommon-b86b1483-Bein03PD.js +100 -0
- package/app/out/renderer/assets/swift-FRZi1uvB.js +291 -0
- package/app/out/renderer/assets/tcl-CUcaCdmq.js +114 -0
- package/app/out/renderer/assets/textile-BnFpjsrl.js +414 -0
- package/app/out/renderer/assets/tiddlywiki-CjprD-Qp.js +218 -0
- package/app/out/renderer/assets/tiki-B4EPSQ1G.js +265 -0
- package/app/out/renderer/assets/timeline-definition-faaaa080-BlWpLE_4.js +1212 -0
- package/app/out/renderer/assets/toml-BOuWGMcf.js +76 -0
- package/app/out/renderer/assets/troff-E1bJ0PPL.js +61 -0
- package/app/out/renderer/assets/ttcn-cfg-Dc39-fIP.js +133 -0
- package/app/out/renderer/assets/ttcn-tKd4HLu4.js +192 -0
- package/app/out/renderer/assets/turtle-Dq7-1WAf.js +124 -0
- package/app/out/renderer/assets/vb-Dp90gtsv.js +196 -0
- package/app/out/renderer/assets/vbscript-CI6_mxxU.js +479 -0
- package/app/out/renderer/assets/velocity-BwIZK1TH.js +149 -0
- package/app/out/renderer/assets/verilog-DDCYnHN8.js +430 -0
- package/app/out/renderer/assets/vhdl-DCkMIyT9.js +158 -0
- package/app/out/renderer/assets/webidl-BTLTThCm.js +204 -0
- package/app/out/renderer/assets/xquery-BgiOC5Ce.js +525 -0
- package/app/out/renderer/assets/xychartDiagram-f5964ef8-Bhga-YXm.js +1799 -0
- package/app/out/renderer/assets/yacas-b5lAVEIl.js +130 -0
- package/app/out/renderer/assets/z80-BZV19vqv.js +93 -0
- package/app/out/renderer/index.html +13 -0
- package/app/out/skills/community-builtin/README.md +29 -0
- package/app/out/skills/community-builtin/document-docx/SKILL.md +44 -0
- package/app/out/skills/community-builtin/document-docx/scripts/docx-to-markdown.sh +20 -0
- package/app/out/skills/community-builtin/document-docx/scripts/extract-docx-text.sh +28 -0
- package/app/out/skills/community-builtin/document-docx/scripts/init-docx-template.sh +32 -0
- package/app/out/skills/community-builtin/document-docx/scripts/setup-docx-tools.sh +10 -0
- package/app/out/skills/community-builtin/markitdown/SKILL.md +105 -0
- package/app/out/skills/community-builtin/markitdown/scripts/batch-convert.sh +40 -0
- package/app/out/skills/community-builtin/markitdown/scripts/convert-file.sh +24 -0
- package/app/out/skills/community-builtin/markitdown/scripts/setup-markitdown.sh +10 -0
- package/app/out/skills/community-builtin/repo-quick-audit/SKILL.md +27 -0
- package/app/out/skills/community-builtin/repo-quick-audit/scripts/audit-basics.sh +19 -0
- package/app/out/skills/research-pilot-default-project-skills/README.md +23 -0
- package/app/out/skills/research-pilot-default-project-skills/citation-management/SKILL.md +39 -0
- package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/doi-to-bibtex.sh +25 -0
- package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/normalize-bibtex-keys.sh +51 -0
- package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/setup-citation-tools.sh +10 -0
- package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/validate-bib.sh +31 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/SKILL.md +34 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/api_reference.md +412 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/common_issues.md +563 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/plot_types.md +476 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/styling_guide.md +589 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/subagent_quickstart.md +30 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/scripts/plot_template.py +401 -0
- package/app/out/skills/research-pilot-default-project-skills/matplotlib/scripts/style_configurator.py +409 -0
- package/app/out/skills/research-pilot-default-project-skills/research-grants/SKILL.md +38 -0
- package/app/out/skills/research-pilot-default-project-skills/research-grants/scripts/check-grant-compliance.sh +40 -0
- package/app/out/skills/research-pilot-default-project-skills/research-grants/scripts/grant-summary-card.sh +32 -0
- package/app/out/skills/research-pilot-default-project-skills/research-grants/scripts/init-grant-structure.sh +70 -0
- package/app/package.json +77 -0
- package/bin/cli.mjs +56 -0
- package/lib/README.md +145 -0
- package/lib/skills/_generated.ts +13 -0
- package/lib/skills/builtin/brainstorming-research-ideas/SKILL.md +280 -0
- package/lib/skills/builtin/coding/SKILL.md +114 -0
- package/lib/skills/builtin/creative-thinking-for-research/SKILL.md +273 -0
- package/lib/skills/builtin/matplotlib/SKILL.md +361 -0
- package/lib/skills/builtin/matplotlib/references/api_reference.md +412 -0
- package/lib/skills/builtin/matplotlib/references/common_issues.md +563 -0
- package/lib/skills/builtin/matplotlib/references/plot_types.md +476 -0
- package/lib/skills/builtin/matplotlib/references/styling_guide.md +589 -0
- package/lib/skills/builtin/matplotlib/scripts/plot_template.py +401 -0
- package/lib/skills/builtin/matplotlib/scripts/style_configurator.py +409 -0
- package/lib/skills/builtin/paper-writing/SKILL.md +554 -0
- package/lib/skills/builtin/paper-writing/references/checklists.md +524 -0
- package/lib/skills/builtin/paper-writing/references/citation-workflow.md +562 -0
- package/lib/skills/builtin/paper-writing/references/reviewer-guidelines.md +462 -0
- package/lib/skills/builtin/paper-writing/references/sources.md +189 -0
- package/lib/skills/builtin/paper-writing/references/systems-conferences.md +260 -0
- package/lib/skills/builtin/paper-writing/references/writing-guide.md +476 -0
- package/lib/skills/builtin/paper-writing/templates/README.md +408 -0
- package/lib/skills/builtin/paper-writing/templates/aaai2026/README.md +534 -0
- package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026.bib +111 -0
- package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026.bst +1493 -0
- package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026.sty +315 -0
- package/lib/skills/builtin/paper-writing/templates/acl/README.md +50 -0
- package/lib/skills/builtin/paper-writing/templates/acl/acl.sty +312 -0
- package/lib/skills/builtin/paper-writing/templates/acl/acl_latex.tex +377 -0
- package/lib/skills/builtin/paper-writing/templates/acl/acl_lualatex.tex +101 -0
- package/lib/skills/builtin/paper-writing/templates/acl/acl_natbib.bst +1940 -0
- package/lib/skills/builtin/paper-writing/templates/acl/anthology.bib.txt +26 -0
- package/lib/skills/builtin/paper-writing/templates/acl/custom.bib +70 -0
- package/lib/skills/builtin/paper-writing/templates/acl/formatting.md +326 -0
- package/lib/skills/builtin/paper-writing/templates/asplos2027/main.tex +459 -0
- package/lib/skills/builtin/paper-writing/templates/asplos2027/references.bib +135 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/README.md +3 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.bib +11 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.bst +1440 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.sty +218 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.tex +305 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/fancyhdr.sty +485 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/math_commands.tex +508 -0
- package/lib/skills/builtin/paper-writing/templates/colm2025/natbib.sty +1246 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/fancyhdr.sty +485 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/math_commands.tex +508 -0
- package/lib/skills/builtin/paper-writing/templates/iclr2026/natbib.sty +1246 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/algorithm.sty +79 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/algorithmic.sty +201 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/example_paper.bib +75 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/example_paper.pdf +0 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/example_paper.tex +662 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/fancyhdr.sty +864 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/icml2026.bst +1443 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/icml2026.sty +767 -0
- package/lib/skills/builtin/paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
- package/lib/skills/builtin/paper-writing/templates/neurips2025/Makefile +36 -0
- package/lib/skills/builtin/paper-writing/templates/neurips2025/extra_pkgs.tex +53 -0
- package/lib/skills/builtin/paper-writing/templates/neurips2025/main.tex +38 -0
- package/lib/skills/builtin/paper-writing/templates/neurips2025/neurips.sty +382 -0
- package/lib/skills/builtin/paper-writing/templates/nsdi2027/main.tex +426 -0
- package/lib/skills/builtin/paper-writing/templates/nsdi2027/references.bib +151 -0
- package/lib/skills/builtin/paper-writing/templates/nsdi2027/usenix-2020-09.sty +83 -0
- package/lib/skills/builtin/paper-writing/templates/osdi2026/main.tex +429 -0
- package/lib/skills/builtin/paper-writing/templates/osdi2026/references.bib +150 -0
- package/lib/skills/builtin/paper-writing/templates/osdi2026/usenix-2020-09.sty +83 -0
- package/lib/skills/builtin/paper-writing/templates/sosp2026/main.tex +532 -0
- package/lib/skills/builtin/paper-writing/templates/sosp2026/references.bib +148 -0
- package/lib/skills/builtin/research-grants/SKILL.md +958 -0
- package/lib/skills/builtin/research-grants/assets/budget_justification_template.md +453 -0
- package/lib/skills/builtin/research-grants/assets/nih_specific_aims_template.md +166 -0
- package/lib/skills/builtin/research-grants/assets/nsf_project_summary_template.md +92 -0
- package/lib/skills/builtin/research-grants/references/README.md +285 -0
- package/lib/skills/builtin/research-grants/references/broader_impacts.md +392 -0
- package/lib/skills/builtin/research-grants/references/darpa_guidelines.md +636 -0
- package/lib/skills/builtin/research-grants/references/doe_guidelines.md +586 -0
- package/lib/skills/builtin/research-grants/references/nih_guidelines.md +851 -0
- package/lib/skills/builtin/research-grants/references/nsf_guidelines.md +570 -0
- package/lib/skills/builtin/research-grants/references/nstc_guidelines.md +733 -0
- package/lib/skills/builtin/research-grants/references/specific_aims_guide.md +458 -0
- package/lib/skills/builtin/rewrite-humanize/SKILL.md +116 -0
- package/lib/skills/builtin/rewrite-humanize/references/cs-venue-tone.md +57 -0
- package/lib/skills/builtin/rewrite-humanize/references/lexicon.md +50 -0
- package/lib/skills/builtin/scholar-evaluation/SKILL.md +300 -0
- package/lib/skills/builtin/scholar-evaluation/references/evaluation_framework.md +663 -0
- package/lib/skills/builtin/scholar-evaluation/scripts/calculate_scores.py +379 -0
- package/lib/skills/builtin/scientific-schematics/SKILL.md +603 -0
- package/lib/skills/builtin/scientific-schematics/references/QUICK_REFERENCE.md +182 -0
- package/lib/skills/builtin/scientific-schematics/references/README.md +292 -0
- package/lib/skills/builtin/scientific-schematics/references/best_practices.md +560 -0
- package/lib/skills/builtin/scientific-schematics/scripts/__pycache__/generate_schematic.cpython-312.pyc +0 -0
- package/lib/skills/builtin/scientific-schematics/scripts/__pycache__/generate_schematic_ai.cpython-312.pyc +0 -0
- package/lib/skills/builtin/scientific-schematics/scripts/example_usage.sh +85 -0
- package/lib/skills/builtin/scientific-schematics/scripts/generate_schematic.py +141 -0
- package/lib/skills/builtin/scientific-schematics/scripts/generate_schematic_ai.py +910 -0
- package/lib/skills/builtin/scientific-visualization/SKILL.md +749 -0
- package/lib/skills/builtin/scientific-visualization/assets/color_palettes.py +197 -0
- package/lib/skills/builtin/scientific-visualization/assets/nature.mplstyle +63 -0
- package/lib/skills/builtin/scientific-visualization/assets/presentation.mplstyle +61 -0
- package/lib/skills/builtin/scientific-visualization/assets/publication.mplstyle +68 -0
- package/lib/skills/builtin/scientific-visualization/references/color_palettes.md +348 -0
- package/lib/skills/builtin/scientific-visualization/references/journal_requirements.md +320 -0
- package/lib/skills/builtin/scientific-visualization/references/matplotlib_examples.md +620 -0
- package/lib/skills/builtin/scientific-visualization/references/publication_guidelines.md +205 -0
- package/lib/skills/builtin/scientific-visualization/scripts/figure_export.py +343 -0
- package/lib/skills/builtin/scientific-visualization/scripts/style_presets.py +416 -0
- package/lib/skills/builtin/scientific-writing/SKILL.md +745 -0
- package/lib/skills/builtin/scientific-writing/assets/REPORT_FORMATTING_GUIDE.md +574 -0
- package/lib/skills/builtin/scientific-writing/assets/scientific_report.sty +606 -0
- package/lib/skills/builtin/scientific-writing/assets/scientific_report_template.tex +449 -0
- package/lib/skills/builtin/scientific-writing/references/citation_styles.md +720 -0
- package/lib/skills/builtin/scientific-writing/references/figures_tables.md +806 -0
- package/lib/skills/builtin/scientific-writing/references/imrad_structure.md +686 -0
- package/lib/skills/builtin/scientific-writing/references/professional_report_formatting.md +664 -0
- package/lib/skills/builtin/scientific-writing/references/reporting_guidelines.md +748 -0
- package/lib/skills/builtin/scientific-writing/references/writing_principles.md +824 -0
- package/lib/skills/builtin/seaborn/SKILL.md +674 -0
- package/lib/skills/builtin/seaborn/references/examples.md +822 -0
- package/lib/skills/builtin/seaborn/references/function_reference.md +770 -0
- package/lib/skills/builtin/seaborn/references/objects_interface.md +964 -0
- package/lib/skills/data-analysis/SKILL.md +285 -0
- package/lib/skills/generate-skill-content.mjs +58 -0
- package/lib/skills/index.ts +34 -0
- package/lib/skills/loader.ts +452 -0
- package/package.json +62 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AI-powered scientific schematic generation using OpenRouter API.
|
|
4
|
+
|
|
5
|
+
This script uses a smart iterative refinement approach:
|
|
6
|
+
1. Generate initial image with Gemini image model via OpenRouter
|
|
7
|
+
2. AI quality review using Gemini for scientific critique
|
|
8
|
+
3. Only regenerate if quality is below threshold for document type
|
|
9
|
+
4. Repeat until quality meets standards (max iterations)
|
|
10
|
+
|
|
11
|
+
Requirements:
|
|
12
|
+
- OPENROUTER_API_KEY environment variable
|
|
13
|
+
- requests library
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
python generate_schematic_ai.py "Create a flowchart showing CONSORT participant flow" -o flowchart.png
|
|
17
|
+
python generate_schematic_ai.py "Neural network architecture diagram" -o architecture.png --iterations 2
|
|
18
|
+
python generate_schematic_ai.py "Simple block diagram" -o diagram.png --doc-type poster
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import base64
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
import time
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import requests
|
|
33
|
+
except ImportError:
|
|
34
|
+
print("Error: requests library not found. Install with: pip install requests")
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Try to load .env file from multiple potential locations
|
|
39
|
+
def _load_env_file():
|
|
40
|
+
"""Load .env file from current directory, parent directories, or package directory.
|
|
41
|
+
|
|
42
|
+
Returns True if a .env file was found and loaded, False otherwise.
|
|
43
|
+
Note: This does NOT override existing environment variables.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
from dotenv import load_dotenv
|
|
47
|
+
except ImportError:
|
|
48
|
+
return False # python-dotenv not installed
|
|
49
|
+
|
|
50
|
+
# Try current working directory first
|
|
51
|
+
env_path = Path.cwd() / ".env"
|
|
52
|
+
if env_path.exists():
|
|
53
|
+
load_dotenv(dotenv_path=env_path, override=False)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
# Try parent directories (up to 5 levels)
|
|
57
|
+
cwd = Path.cwd()
|
|
58
|
+
for _ in range(5):
|
|
59
|
+
env_path = cwd / ".env"
|
|
60
|
+
if env_path.exists():
|
|
61
|
+
load_dotenv(dotenv_path=env_path, override=False)
|
|
62
|
+
return True
|
|
63
|
+
cwd = cwd.parent
|
|
64
|
+
if cwd == cwd.parent: # Reached root
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
# Try the package's parent directory (project root)
|
|
68
|
+
script_dir = Path(__file__).resolve().parent
|
|
69
|
+
for _ in range(5):
|
|
70
|
+
env_path = script_dir / ".env"
|
|
71
|
+
if env_path.exists():
|
|
72
|
+
load_dotenv(dotenv_path=env_path, override=False)
|
|
73
|
+
return True
|
|
74
|
+
script_dir = script_dir.parent
|
|
75
|
+
if script_dir == script_dir.parent:
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ScientificSchematicGenerator:
|
|
85
|
+
"""Generate scientific schematics using AI with smart iterative refinement.
|
|
86
|
+
|
|
87
|
+
Uses Gemini review to determine if regeneration is needed.
|
|
88
|
+
Multiple passes only occur if the generated schematic doesn't meet the
|
|
89
|
+
quality threshold for the target document type.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# Quality thresholds by document type (score out of 10)
|
|
93
|
+
QUALITY_THRESHOLDS = {
|
|
94
|
+
"journal": 8.5,
|
|
95
|
+
"conference": 8.0,
|
|
96
|
+
"poster": 7.0,
|
|
97
|
+
"presentation": 6.5,
|
|
98
|
+
"report": 7.5,
|
|
99
|
+
"grant": 8.0,
|
|
100
|
+
"thesis": 8.0,
|
|
101
|
+
"preprint": 7.5,
|
|
102
|
+
"default": 7.5,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
DEFAULT_IMAGE_MODEL = "google/gemini-3-pro-image-preview"
|
|
106
|
+
DEFAULT_REVIEW_MODEL = "google/gemini-3-pro-preview"
|
|
107
|
+
DEFAULT_REQUEST_TIMEOUT_SECONDS = 300
|
|
108
|
+
|
|
109
|
+
# Scientific diagram best practices prompt template
|
|
110
|
+
SCIENTIFIC_DIAGRAM_GUIDELINES = """
|
|
111
|
+
Create a high-quality scientific diagram with these requirements:
|
|
112
|
+
|
|
113
|
+
VISUAL QUALITY:
|
|
114
|
+
- Clean white or light background (no textures or gradients). Professional pastel tones.
|
|
115
|
+
- Flat vector illustration, academic aesthetic. Similar to figures in DeepMind or OpenAI papers.
|
|
116
|
+
- High contrast for readability and printing
|
|
117
|
+
- Professional, publication-ready appearance
|
|
118
|
+
- Sharp, clear lines and text
|
|
119
|
+
- Adequate spacing between elements to prevent crowding
|
|
120
|
+
|
|
121
|
+
TYPOGRAPHY:
|
|
122
|
+
- Clear, readable sans-serif fonts (Arial, Helvetica style)
|
|
123
|
+
- Minimum 10pt font size for all labels
|
|
124
|
+
- Consistent font sizes throughout
|
|
125
|
+
- All text horizontal or clearly readable
|
|
126
|
+
- No overlapping text
|
|
127
|
+
|
|
128
|
+
SCIENTIFIC STANDARDS:
|
|
129
|
+
- Accurate representation of concepts
|
|
130
|
+
- Clear labels for all components
|
|
131
|
+
- Include scale bars, legends, or axes where appropriate
|
|
132
|
+
- Use standard scientific notation and symbols
|
|
133
|
+
- Include units where applicable
|
|
134
|
+
|
|
135
|
+
ACCESSIBILITY:
|
|
136
|
+
- Colorblind-friendly color palette (use Okabe-Ito colors if using color)
|
|
137
|
+
- High contrast between elements
|
|
138
|
+
- Redundant encoding (shapes + colors, not just colors)
|
|
139
|
+
- Works well in grayscale
|
|
140
|
+
|
|
141
|
+
LAYOUT:
|
|
142
|
+
- Logical flow (left-to-right, top-to-bottom, circular and other shapes). Group related components logically.
|
|
143
|
+
- Clear visual hierarchy
|
|
144
|
+
- Balanced composition
|
|
145
|
+
- Appropriate use of whitespace
|
|
146
|
+
- No clutter or unnecessary decorative elements
|
|
147
|
+
|
|
148
|
+
IMPORTANT - NO FIGURE NUMBERS:
|
|
149
|
+
- Do NOT include "Figure 1:", "Fig. 1", or any figure numbering in the image
|
|
150
|
+
- Do NOT add captions or titles like "Figure: ..." at the top or bottom
|
|
151
|
+
- Figure numbers and captions are added separately in the document/LaTeX
|
|
152
|
+
- The diagram should contain only the visual content itself
|
|
153
|
+
|
|
154
|
+
Negative Constraints:
|
|
155
|
+
- NO photorealistic photos, NO messy sketches, NO unreadable text, NO 3D shading artifacts.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
api_key: Optional[str] = None,
|
|
161
|
+
image_model: Optional[str] = None,
|
|
162
|
+
review_model: Optional[str] = None,
|
|
163
|
+
request_timeout_seconds: Optional[int] = None,
|
|
164
|
+
verbose: bool = False,
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Initialize the generator.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
api_key: OpenRouter API key
|
|
171
|
+
image_model: Model ID for image generation (OpenRouter format)
|
|
172
|
+
review_model: Model ID for quality review (OpenRouter format)
|
|
173
|
+
request_timeout_seconds: Request timeout for API calls
|
|
174
|
+
verbose: Print detailed progress information
|
|
175
|
+
"""
|
|
176
|
+
_load_env_file()
|
|
177
|
+
self.verbose = verbose
|
|
178
|
+
self._last_error = None
|
|
179
|
+
|
|
180
|
+
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
|
181
|
+
if not self.api_key:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
"OPENROUTER_API_KEY is not set.\n"
|
|
184
|
+
"Set OPENROUTER_API_KEY environment variable or pass --api-key."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self.image_model = (
|
|
188
|
+
image_model
|
|
189
|
+
or os.getenv("SCHEMATIC_IMAGE_MODEL")
|
|
190
|
+
or self.DEFAULT_IMAGE_MODEL
|
|
191
|
+
)
|
|
192
|
+
self.review_model = (
|
|
193
|
+
review_model
|
|
194
|
+
or os.getenv("SCHEMATIC_REVIEW_MODEL")
|
|
195
|
+
or self.DEFAULT_REVIEW_MODEL
|
|
196
|
+
)
|
|
197
|
+
self.request_timeout_seconds = self._parse_timeout_seconds(
|
|
198
|
+
request_timeout_seconds
|
|
199
|
+
or os.getenv("SCHEMATIC_REQUEST_TIMEOUT_SECONDS")
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def _parse_timeout_seconds(self, value: Optional[Any]) -> int:
|
|
203
|
+
try:
|
|
204
|
+
parsed = int(str(value).strip()) if value is not None else 0
|
|
205
|
+
except (TypeError, ValueError):
|
|
206
|
+
parsed = 0
|
|
207
|
+
return max(parsed, 30) if parsed else self.DEFAULT_REQUEST_TIMEOUT_SECONDS
|
|
208
|
+
|
|
209
|
+
def _log(self, message: str):
|
|
210
|
+
"""Log message if verbose mode is enabled."""
|
|
211
|
+
if self.verbose:
|
|
212
|
+
print(f"[{time.strftime('%H:%M:%S')}] {message}")
|
|
213
|
+
|
|
214
|
+
def _post_openrouter(
|
|
215
|
+
self,
|
|
216
|
+
model: str,
|
|
217
|
+
messages: List[Dict[str, Any]],
|
|
218
|
+
timeout_seconds: Optional[int] = None,
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
"""
|
|
221
|
+
Make a request to OpenRouter chat completions API.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
model: Model identifier (OpenRouter format, e.g. google/gemini-3-pro-image-preview)
|
|
225
|
+
messages: List of message dictionaries in OpenAI format
|
|
226
|
+
timeout_seconds: Optional timeout override
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
API response as dictionary
|
|
230
|
+
"""
|
|
231
|
+
timeout = timeout_seconds or self.request_timeout_seconds
|
|
232
|
+
headers = {
|
|
233
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
234
|
+
"Content-Type": "application/json",
|
|
235
|
+
"HTTP-Referer": "https://github.com/research-copilot",
|
|
236
|
+
"X-Title": "Research Copilot Scientific Schematics",
|
|
237
|
+
}
|
|
238
|
+
payload: Dict[str, Any] = {
|
|
239
|
+
"model": model,
|
|
240
|
+
"messages": messages,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
self._log(f"Making request to OpenRouter with model {model}...")
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
response = requests.post(
|
|
247
|
+
OPENROUTER_API_URL,
|
|
248
|
+
headers=headers,
|
|
249
|
+
json=payload,
|
|
250
|
+
timeout=timeout,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
response_json = response.json()
|
|
255
|
+
except json.JSONDecodeError:
|
|
256
|
+
response_json = {"raw_text": response.text[:500]}
|
|
257
|
+
|
|
258
|
+
if response.status_code != 200:
|
|
259
|
+
error_detail = response_json.get("error", response_json)
|
|
260
|
+
self._log(f"HTTP {response.status_code}: {error_detail}")
|
|
261
|
+
raise RuntimeError(
|
|
262
|
+
f"API request failed (HTTP {response.status_code}): {error_detail}"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return response_json
|
|
266
|
+
except requests.exceptions.Timeout:
|
|
267
|
+
raise RuntimeError(f"API request timed out after {timeout} seconds")
|
|
268
|
+
except requests.exceptions.RequestException as e:
|
|
269
|
+
raise RuntimeError(f"API request failed: {str(e)}")
|
|
270
|
+
|
|
271
|
+
def _extract_image_from_response(self, response: Dict[str, Any]) -> Optional[bytes]:
|
|
272
|
+
"""
|
|
273
|
+
Extract image bytes from an OpenRouter response.
|
|
274
|
+
|
|
275
|
+
Handles multiple response formats:
|
|
276
|
+
1. message.images array with image_url data URIs (Gemini via OpenRouter)
|
|
277
|
+
2. Multipart content array with inline_data (Gemini native)
|
|
278
|
+
3. Content array with image_url data URIs
|
|
279
|
+
4. Base64 image data in content string
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Image bytes or None if not found
|
|
283
|
+
"""
|
|
284
|
+
try:
|
|
285
|
+
choices = response.get("choices", [])
|
|
286
|
+
if not choices:
|
|
287
|
+
self._log("No choices in response")
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
message = choices[0].get("message", {})
|
|
291
|
+
|
|
292
|
+
# Case 0: message.images array (OpenRouter Gemini image generation format)
|
|
293
|
+
# The API returns content=null but images=[{type: "image_url", image_url: {url: "data:..."}}]
|
|
294
|
+
images = message.get("images")
|
|
295
|
+
if isinstance(images, list):
|
|
296
|
+
for index, img in enumerate(images):
|
|
297
|
+
if not isinstance(img, dict):
|
|
298
|
+
continue
|
|
299
|
+
# Handle {type: "image_url", image_url: {url: "data:image/png;base64,..."}}
|
|
300
|
+
if img.get("type") == "image_url":
|
|
301
|
+
image_url = img.get("image_url", {})
|
|
302
|
+
url = image_url.get("url", "") if isinstance(image_url, dict) else str(image_url)
|
|
303
|
+
if url.startswith("data:") and "," in url:
|
|
304
|
+
_, b64data = url.split(",", 1)
|
|
305
|
+
self._log(f"Found image in message.images[{index}]")
|
|
306
|
+
return base64.b64decode(b64data)
|
|
307
|
+
# Handle direct {url: "data:..."} format
|
|
308
|
+
url = img.get("url", "")
|
|
309
|
+
if url.startswith("data:") and "," in url:
|
|
310
|
+
_, b64data = url.split(",", 1)
|
|
311
|
+
self._log(f"Found image in message.images[{index}] (direct url)")
|
|
312
|
+
return base64.b64decode(b64data)
|
|
313
|
+
|
|
314
|
+
content = message.get("content", "")
|
|
315
|
+
|
|
316
|
+
# Case 1: content is a list of parts (multimodal response)
|
|
317
|
+
if isinstance(content, list):
|
|
318
|
+
for index, part in enumerate(content):
|
|
319
|
+
if not isinstance(part, dict):
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
# Check for inline_data (Gemini native format passed through)
|
|
323
|
+
inline_data = part.get("inline_data", {})
|
|
324
|
+
if isinstance(inline_data, dict) and inline_data.get("data"):
|
|
325
|
+
data = str(inline_data["data"]).replace("\n", "").replace("\r", "").replace(" ", "")
|
|
326
|
+
self._log(f"Found image in inline_data part {index}")
|
|
327
|
+
return base64.b64decode(data)
|
|
328
|
+
|
|
329
|
+
# Check for image_url with data URI
|
|
330
|
+
if part.get("type") == "image_url":
|
|
331
|
+
image_url = part.get("image_url", {})
|
|
332
|
+
url = image_url.get("url", "") if isinstance(image_url, dict) else str(image_url)
|
|
333
|
+
if url.startswith("data:") and "," in url:
|
|
334
|
+
_, b64data = url.split(",", 1)
|
|
335
|
+
self._log(f"Found image in image_url part {index}")
|
|
336
|
+
return base64.b64decode(b64data)
|
|
337
|
+
|
|
338
|
+
self._log("No image data found in content parts")
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
# Case 2: content is a string — might contain base64 image in markdown
|
|
342
|
+
if isinstance(content, str) and content:
|
|
343
|
+
# Look for markdown image with base64
|
|
344
|
+
match = re.search(r'!\[.*?\]\(data:image/[^;]+;base64,([A-Za-z0-9+/=\s]+)\)', content)
|
|
345
|
+
if match:
|
|
346
|
+
b64data = match.group(1).replace("\n", "").replace("\r", "").replace(" ", "")
|
|
347
|
+
self._log("Found base64 image in markdown content")
|
|
348
|
+
return base64.b64decode(b64data)
|
|
349
|
+
|
|
350
|
+
# Look for raw base64 block (some models return just the data)
|
|
351
|
+
stripped = content.strip()
|
|
352
|
+
if len(stripped) > 1000 and re.match(r'^[A-Za-z0-9+/=\n\r]+$', stripped):
|
|
353
|
+
try:
|
|
354
|
+
data = base64.b64decode(stripped.replace("\n", "").replace("\r", ""))
|
|
355
|
+
# Verify it's actually an image (PNG or JPEG magic bytes)
|
|
356
|
+
if data[:4] == b'\x89PNG' or data[:2] == b'\xff\xd8':
|
|
357
|
+
self._log("Found raw base64 image in content string")
|
|
358
|
+
return data
|
|
359
|
+
except Exception:
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
self._log("No image data found in response")
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
except Exception as e:
|
|
366
|
+
self._log(f"Error extracting image: {str(e)}")
|
|
367
|
+
if self.verbose:
|
|
368
|
+
import traceback
|
|
369
|
+
traceback.print_exc()
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
def _image_to_base64_url(self, image_path: str) -> str:
|
|
373
|
+
"""
|
|
374
|
+
Convert image file to a data URI for use in OpenAI-compatible messages.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
image_path: Path to image file
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Data URI string (data:image/png;base64,...)
|
|
381
|
+
"""
|
|
382
|
+
with open(image_path, "rb") as f:
|
|
383
|
+
image_data = f.read()
|
|
384
|
+
|
|
385
|
+
ext = Path(image_path).suffix.lower()
|
|
386
|
+
mime_type = {
|
|
387
|
+
".png": "image/png",
|
|
388
|
+
".jpg": "image/jpeg",
|
|
389
|
+
".jpeg": "image/jpeg",
|
|
390
|
+
".gif": "image/gif",
|
|
391
|
+
".webp": "image/webp",
|
|
392
|
+
}.get(ext, "image/png")
|
|
393
|
+
|
|
394
|
+
b64 = base64.b64encode(image_data).decode("utf-8")
|
|
395
|
+
return f"data:{mime_type};base64,{b64}"
|
|
396
|
+
|
|
397
|
+
def generate_image(self, prompt: str) -> Optional[bytes]:
|
|
398
|
+
"""
|
|
399
|
+
Generate an image using the image model via OpenRouter.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
prompt: Description of the diagram to generate
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Image bytes or None if generation failed
|
|
406
|
+
"""
|
|
407
|
+
self._last_error = None
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
messages = [{"role": "user", "content": prompt}]
|
|
411
|
+
response = self._post_openrouter(
|
|
412
|
+
model=self.image_model,
|
|
413
|
+
messages=messages,
|
|
414
|
+
)
|
|
415
|
+
image_data = self._extract_image_from_response(response)
|
|
416
|
+
|
|
417
|
+
if self.verbose:
|
|
418
|
+
self._log(f"Response keys: {response.keys()}")
|
|
419
|
+
if "error" in response:
|
|
420
|
+
self._log(f"API Error: {response['error']}")
|
|
421
|
+
choices = response.get("choices", [])
|
|
422
|
+
if choices:
|
|
423
|
+
msg = choices[0].get("message", {})
|
|
424
|
+
content = msg.get("content", "")
|
|
425
|
+
if isinstance(content, list):
|
|
426
|
+
self._log(f"Content part count: {len(content)}")
|
|
427
|
+
for i, part in enumerate(content[:3]):
|
|
428
|
+
if isinstance(part, dict):
|
|
429
|
+
self._log(f" Part {i}: keys={list(part.keys())}")
|
|
430
|
+
elif isinstance(content, str):
|
|
431
|
+
self._log(f"Content is string, length={len(content)}")
|
|
432
|
+
|
|
433
|
+
if "error" in response:
|
|
434
|
+
error_msg = response["error"]
|
|
435
|
+
if isinstance(error_msg, dict):
|
|
436
|
+
error_msg = error_msg.get("message", str(error_msg))
|
|
437
|
+
self._last_error = f"API Error: {error_msg}"
|
|
438
|
+
print(f"✗ {self._last_error}")
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
if image_data:
|
|
442
|
+
self._log(f"✓ Generated image ({len(image_data)} bytes)")
|
|
443
|
+
else:
|
|
444
|
+
self._last_error = (
|
|
445
|
+
f"No image data in response for model {self.image_model}"
|
|
446
|
+
)
|
|
447
|
+
self._log(f"✗ {self._last_error}")
|
|
448
|
+
|
|
449
|
+
return image_data
|
|
450
|
+
except RuntimeError as e:
|
|
451
|
+
self._last_error = str(e)
|
|
452
|
+
self._log(f"✗ Generation failed: {self._last_error}")
|
|
453
|
+
return None
|
|
454
|
+
except Exception as e:
|
|
455
|
+
self._last_error = f"Unexpected error: {str(e)}"
|
|
456
|
+
self._log(f"✗ Generation failed: {self._last_error}")
|
|
457
|
+
if self.verbose:
|
|
458
|
+
import traceback
|
|
459
|
+
traceback.print_exc()
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
def review_image(
|
|
463
|
+
self,
|
|
464
|
+
image_path: str,
|
|
465
|
+
original_prompt: str,
|
|
466
|
+
iteration: int,
|
|
467
|
+
doc_type: str = "default",
|
|
468
|
+
max_iterations: int = 2,
|
|
469
|
+
) -> Tuple[str, float, bool]:
|
|
470
|
+
"""
|
|
471
|
+
Review generated image using a multimodal model for quality analysis.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
image_path: Path to the generated image
|
|
475
|
+
original_prompt: Original user prompt
|
|
476
|
+
iteration: Current iteration number
|
|
477
|
+
doc_type: Document type (journal, poster, presentation, etc.)
|
|
478
|
+
max_iterations: Maximum iterations allowed
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Tuple of (critique text, quality score 0-10, needs_improvement bool)
|
|
482
|
+
"""
|
|
483
|
+
image_data_url = self._image_to_base64_url(image_path)
|
|
484
|
+
|
|
485
|
+
threshold = self.QUALITY_THRESHOLDS.get(
|
|
486
|
+
doc_type.lower(), self.QUALITY_THRESHOLDS["default"]
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
review_prompt = f"""You are an expert reviewer evaluating a scientific diagram for publication quality.
|
|
490
|
+
|
|
491
|
+
ORIGINAL REQUEST: {original_prompt}
|
|
492
|
+
|
|
493
|
+
DOCUMENT TYPE: {doc_type} (quality threshold: {threshold}/10)
|
|
494
|
+
ITERATION: {iteration}/{max_iterations}
|
|
495
|
+
|
|
496
|
+
Carefully evaluate this diagram on these criteria:
|
|
497
|
+
|
|
498
|
+
1. **Scientific Accuracy** (0-2 points)
|
|
499
|
+
- Correct representation of concepts
|
|
500
|
+
- Proper notation and symbols
|
|
501
|
+
- Accurate relationships shown
|
|
502
|
+
|
|
503
|
+
2. **Clarity and Readability** (0-2 points)
|
|
504
|
+
- Easy to understand at a glance
|
|
505
|
+
- Clear visual hierarchy
|
|
506
|
+
- No ambiguous elements
|
|
507
|
+
|
|
508
|
+
3. **Label Quality** (0-2 points)
|
|
509
|
+
- All important elements labeled
|
|
510
|
+
- Labels are readable (appropriate font size)
|
|
511
|
+
- Consistent labeling style
|
|
512
|
+
|
|
513
|
+
4. **Layout and Composition** (0-2 points)
|
|
514
|
+
- Logical flow (top-to-bottom or left-to-right)
|
|
515
|
+
- Balanced use of space
|
|
516
|
+
- No overlapping elements
|
|
517
|
+
|
|
518
|
+
5. **Professional Appearance** (0-2 points)
|
|
519
|
+
- Publication-ready quality
|
|
520
|
+
- Clean, crisp lines and shapes
|
|
521
|
+
- Appropriate colors/contrast
|
|
522
|
+
|
|
523
|
+
RESPOND IN THIS EXACT FORMAT:
|
|
524
|
+
SCORE: [total score 0-10]
|
|
525
|
+
|
|
526
|
+
STRENGTHS:
|
|
527
|
+
- [strength 1]
|
|
528
|
+
- [strength 2]
|
|
529
|
+
|
|
530
|
+
ISSUES:
|
|
531
|
+
- [issue 1 if any]
|
|
532
|
+
- [issue 2 if any]
|
|
533
|
+
|
|
534
|
+
VERDICT: [ACCEPTABLE or NEEDS_IMPROVEMENT]
|
|
535
|
+
|
|
536
|
+
If score >= {threshold}, the diagram is ACCEPTABLE for {doc_type} publication.
|
|
537
|
+
If score < {threshold}, mark as NEEDS_IMPROVEMENT with specific suggestions."""
|
|
538
|
+
|
|
539
|
+
messages = [
|
|
540
|
+
{
|
|
541
|
+
"role": "user",
|
|
542
|
+
"content": [
|
|
543
|
+
{"type": "text", "text": review_prompt},
|
|
544
|
+
{
|
|
545
|
+
"type": "image_url",
|
|
546
|
+
"image_url": {"url": image_data_url},
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
}
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
try:
|
|
553
|
+
response = self._post_openrouter(model=self.review_model, messages=messages)
|
|
554
|
+
|
|
555
|
+
choices = response.get("choices", [])
|
|
556
|
+
if not choices:
|
|
557
|
+
return "Image generated successfully", 8.0, False
|
|
558
|
+
|
|
559
|
+
message = choices[0].get("message", {})
|
|
560
|
+
content = message.get("content", "")
|
|
561
|
+
|
|
562
|
+
# Handle content as string or list of parts
|
|
563
|
+
if isinstance(content, list):
|
|
564
|
+
text_parts = []
|
|
565
|
+
for block in content:
|
|
566
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
567
|
+
text_parts.append(str(block.get("text", "")))
|
|
568
|
+
elif isinstance(block, str):
|
|
569
|
+
text_parts.append(block)
|
|
570
|
+
content = "\n".join(text_parts)
|
|
571
|
+
|
|
572
|
+
if not isinstance(content, str):
|
|
573
|
+
content = str(content)
|
|
574
|
+
|
|
575
|
+
# Extract score
|
|
576
|
+
score = 7.5 # Default
|
|
577
|
+
|
|
578
|
+
score_match = re.search(r"SCORE:\s*(\d+(?:\.\d+)?)", content, re.IGNORECASE)
|
|
579
|
+
if score_match:
|
|
580
|
+
score = float(score_match.group(1))
|
|
581
|
+
else:
|
|
582
|
+
score_match = re.search(
|
|
583
|
+
r"(?:score|rating|quality)[:\s]+(\d+(?:\.\d+)?)\s*(?:/\s*10)?",
|
|
584
|
+
content,
|
|
585
|
+
re.IGNORECASE,
|
|
586
|
+
)
|
|
587
|
+
if score_match:
|
|
588
|
+
score = float(score_match.group(1))
|
|
589
|
+
|
|
590
|
+
# Determine if improvement is needed
|
|
591
|
+
needs_improvement = False
|
|
592
|
+
if "NEEDS_IMPROVEMENT" in content.upper():
|
|
593
|
+
needs_improvement = True
|
|
594
|
+
elif score < threshold:
|
|
595
|
+
needs_improvement = True
|
|
596
|
+
|
|
597
|
+
self._log(
|
|
598
|
+
f"✓ Review complete (Score: {score}/10, Threshold: {threshold}/10)"
|
|
599
|
+
)
|
|
600
|
+
self._log(
|
|
601
|
+
f" Verdict: {'Needs improvement' if needs_improvement else 'Acceptable'}"
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
content if content else "Image generated successfully",
|
|
606
|
+
score,
|
|
607
|
+
needs_improvement,
|
|
608
|
+
)
|
|
609
|
+
except Exception as e:
|
|
610
|
+
self._log(f"Review skipped: {str(e)}")
|
|
611
|
+
return "Image generated successfully (review skipped)", 7.5, False
|
|
612
|
+
|
|
613
|
+
def improve_prompt(
|
|
614
|
+
self, original_prompt: str, critique: str, iteration: int
|
|
615
|
+
) -> str:
|
|
616
|
+
"""
|
|
617
|
+
Improve the generation prompt based on critique.
|
|
618
|
+
"""
|
|
619
|
+
improved_prompt = f"""{self.SCIENTIFIC_DIAGRAM_GUIDELINES}
|
|
620
|
+
|
|
621
|
+
USER REQUEST: {original_prompt}
|
|
622
|
+
|
|
623
|
+
ITERATION {iteration}: Based on previous feedback, address these specific improvements:
|
|
624
|
+
{critique}
|
|
625
|
+
|
|
626
|
+
Generate an improved version that addresses all the critique points while maintaining scientific accuracy and professional quality."""
|
|
627
|
+
|
|
628
|
+
return improved_prompt
|
|
629
|
+
|
|
630
|
+
def generate_iterative(
|
|
631
|
+
self,
|
|
632
|
+
user_prompt: str,
|
|
633
|
+
output_path: str,
|
|
634
|
+
iterations: int = 2,
|
|
635
|
+
doc_type: str = "default",
|
|
636
|
+
) -> Dict[str, Any]:
|
|
637
|
+
"""
|
|
638
|
+
Generate scientific schematic with smart iterative refinement.
|
|
639
|
+
|
|
640
|
+
Only regenerates if the quality score is below the threshold for the
|
|
641
|
+
specified document type.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
user_prompt: User's description of desired diagram
|
|
645
|
+
output_path: Path to save final image
|
|
646
|
+
iterations: Maximum refinement iterations (default: 2, max: 2)
|
|
647
|
+
doc_type: Document type for quality threshold
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
Dictionary with generation results and metadata
|
|
651
|
+
"""
|
|
652
|
+
output_path = Path(output_path)
|
|
653
|
+
output_dir = output_path.parent
|
|
654
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
655
|
+
|
|
656
|
+
base_name = output_path.stem
|
|
657
|
+
extension = output_path.suffix or ".png"
|
|
658
|
+
|
|
659
|
+
threshold = self.QUALITY_THRESHOLDS.get(
|
|
660
|
+
doc_type.lower(), self.QUALITY_THRESHOLDS["default"]
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
results = {
|
|
664
|
+
"user_prompt": user_prompt,
|
|
665
|
+
"doc_type": doc_type,
|
|
666
|
+
"quality_threshold": threshold,
|
|
667
|
+
"iterations": [],
|
|
668
|
+
"final_image": None,
|
|
669
|
+
"final_score": 0.0,
|
|
670
|
+
"success": False,
|
|
671
|
+
"early_stop": False,
|
|
672
|
+
"early_stop_reason": None,
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
current_prompt = f"""{self.SCIENTIFIC_DIAGRAM_GUIDELINES}
|
|
676
|
+
|
|
677
|
+
USER REQUEST: {user_prompt}
|
|
678
|
+
|
|
679
|
+
Generate a publication-quality scientific diagram that meets all the guidelines above."""
|
|
680
|
+
|
|
681
|
+
print(f"\n{'=' * 60}")
|
|
682
|
+
print(f"Generating Scientific Schematic")
|
|
683
|
+
print(f"{'=' * 60}")
|
|
684
|
+
print(f"Description: {user_prompt}")
|
|
685
|
+
print(f"Document Type: {doc_type}")
|
|
686
|
+
print(f"Quality Threshold: {threshold}/10")
|
|
687
|
+
print(f"Max Iterations: {iterations}")
|
|
688
|
+
print(f"Image Model: {self.image_model}")
|
|
689
|
+
print(f"Review Model: {self.review_model}")
|
|
690
|
+
print(f"Output: {output_path}")
|
|
691
|
+
print(f"{'=' * 60}\n")
|
|
692
|
+
|
|
693
|
+
for i in range(1, iterations + 1):
|
|
694
|
+
print(f"\n[Iteration {i}/{iterations}]")
|
|
695
|
+
print("-" * 40)
|
|
696
|
+
|
|
697
|
+
# Generate image
|
|
698
|
+
print(f"Generating image...")
|
|
699
|
+
image_data = self.generate_image(current_prompt)
|
|
700
|
+
|
|
701
|
+
if not image_data:
|
|
702
|
+
error_msg = self._last_error or "Image generation failed - no image data returned"
|
|
703
|
+
print(f"✗ Generation failed: {error_msg}")
|
|
704
|
+
results["iterations"].append(
|
|
705
|
+
{"iteration": i, "success": False, "error": error_msg}
|
|
706
|
+
)
|
|
707
|
+
continue
|
|
708
|
+
|
|
709
|
+
# Save iteration image
|
|
710
|
+
iter_path = output_dir / f"{base_name}_v{i}{extension}"
|
|
711
|
+
with open(iter_path, "wb") as f:
|
|
712
|
+
f.write(image_data)
|
|
713
|
+
print(f"✓ Saved: {iter_path}")
|
|
714
|
+
|
|
715
|
+
# Review image
|
|
716
|
+
print(f"Reviewing image...")
|
|
717
|
+
critique, score, needs_improvement = self.review_image(
|
|
718
|
+
str(iter_path), user_prompt, i, doc_type, iterations
|
|
719
|
+
)
|
|
720
|
+
print(f"✓ Score: {score}/10 (threshold: {threshold}/10)")
|
|
721
|
+
|
|
722
|
+
iteration_result = {
|
|
723
|
+
"iteration": i,
|
|
724
|
+
"image_path": str(iter_path),
|
|
725
|
+
"prompt": current_prompt,
|
|
726
|
+
"critique": critique,
|
|
727
|
+
"score": score,
|
|
728
|
+
"needs_improvement": needs_improvement,
|
|
729
|
+
"success": True,
|
|
730
|
+
}
|
|
731
|
+
results["iterations"].append(iteration_result)
|
|
732
|
+
|
|
733
|
+
# Check if quality is acceptable
|
|
734
|
+
if not needs_improvement:
|
|
735
|
+
print(
|
|
736
|
+
f"\n✓ Quality meets {doc_type} threshold ({score} >= {threshold})"
|
|
737
|
+
)
|
|
738
|
+
print(f" No further iterations needed!")
|
|
739
|
+
results["final_image"] = str(iter_path)
|
|
740
|
+
results["final_score"] = score
|
|
741
|
+
results["success"] = True
|
|
742
|
+
results["early_stop"] = True
|
|
743
|
+
results["early_stop_reason"] = (
|
|
744
|
+
f"Quality score {score} meets threshold {threshold} for {doc_type}"
|
|
745
|
+
)
|
|
746
|
+
break
|
|
747
|
+
|
|
748
|
+
if i == iterations:
|
|
749
|
+
print(f"\n⚠ Maximum iterations reached")
|
|
750
|
+
results["final_image"] = str(iter_path)
|
|
751
|
+
results["final_score"] = score
|
|
752
|
+
results["success"] = True
|
|
753
|
+
break
|
|
754
|
+
|
|
755
|
+
# Quality below threshold — improve prompt
|
|
756
|
+
print(f"\n⚠ Quality below threshold ({score} < {threshold})")
|
|
757
|
+
print(f"Improving prompt based on feedback...")
|
|
758
|
+
current_prompt = self.improve_prompt(user_prompt, critique, i + 1)
|
|
759
|
+
|
|
760
|
+
# Copy final version to output path
|
|
761
|
+
if results["success"] and results["final_image"]:
|
|
762
|
+
final_iter_path = Path(results["final_image"])
|
|
763
|
+
if final_iter_path != output_path:
|
|
764
|
+
import shutil
|
|
765
|
+
shutil.copy(final_iter_path, output_path)
|
|
766
|
+
print(f"\n✓ Final image: {output_path}")
|
|
767
|
+
|
|
768
|
+
# Save review log
|
|
769
|
+
log_path = output_dir / f"{base_name}_review_log.json"
|
|
770
|
+
with open(log_path, "w") as f:
|
|
771
|
+
json.dump(results, f, indent=2)
|
|
772
|
+
print(f"✓ Review log: {log_path}")
|
|
773
|
+
|
|
774
|
+
print(f"\n{'=' * 60}")
|
|
775
|
+
print(f"Generation Complete!")
|
|
776
|
+
print(f"Final Score: {results['final_score']}/10")
|
|
777
|
+
if results["early_stop"]:
|
|
778
|
+
print(
|
|
779
|
+
f"Iterations Used: {len([r for r in results['iterations'] if r.get('success')])}/{iterations} (early stop)"
|
|
780
|
+
)
|
|
781
|
+
print(f"{'=' * 60}\n")
|
|
782
|
+
|
|
783
|
+
return results
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def main():
|
|
787
|
+
"""Command-line interface."""
|
|
788
|
+
parser = argparse.ArgumentParser(
|
|
789
|
+
description="Generate scientific schematics using AI with smart iterative refinement",
|
|
790
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
791
|
+
epilog="""
|
|
792
|
+
Examples:
|
|
793
|
+
# Generate a flowchart for a journal paper
|
|
794
|
+
python generate_schematic_ai.py "CONSORT participant flow diagram" -o flowchart.png --doc-type journal
|
|
795
|
+
|
|
796
|
+
# Generate neural network architecture for presentation (lower threshold)
|
|
797
|
+
python generate_schematic_ai.py "Transformer encoder-decoder architecture" -o transformer.png --doc-type presentation
|
|
798
|
+
|
|
799
|
+
# Generate with custom max iterations for poster
|
|
800
|
+
python generate_schematic_ai.py "Biological signaling pathway" -o pathway.png --iterations 2 --doc-type poster
|
|
801
|
+
|
|
802
|
+
# Verbose output
|
|
803
|
+
python generate_schematic_ai.py "Circuit diagram" -o circuit.png -v
|
|
804
|
+
|
|
805
|
+
Document Types (quality thresholds):
|
|
806
|
+
journal 8.5/10 - Nature, Science, peer-reviewed journals
|
|
807
|
+
conference 8.0/10 - Conference papers
|
|
808
|
+
thesis 8.0/10 - Dissertations, theses
|
|
809
|
+
grant 8.0/10 - Grant proposals
|
|
810
|
+
preprint 7.5/10 - arXiv, bioRxiv, etc.
|
|
811
|
+
report 7.5/10 - Technical reports
|
|
812
|
+
poster 7.0/10 - Academic posters
|
|
813
|
+
presentation 6.5/10 - Slides, talks
|
|
814
|
+
default 7.5/10 - General purpose
|
|
815
|
+
|
|
816
|
+
Note: Multiple iterations only occur if quality is BELOW the threshold.
|
|
817
|
+
If the first generation meets the threshold, no extra API calls are made.
|
|
818
|
+
|
|
819
|
+
Environment:
|
|
820
|
+
OPENROUTER_API_KEY Required. Your OpenRouter API key.
|
|
821
|
+
SCHEMATIC_IMAGE_MODEL Optional. Override image generation model (default: google/gemini-3-pro-image-preview)
|
|
822
|
+
SCHEMATIC_REVIEW_MODEL Optional. Override review model (default: google/gemini-3-pro-preview)
|
|
823
|
+
SCHEMATIC_REQUEST_TIMEOUT_SECONDS Optional. Request timeout in seconds.
|
|
824
|
+
""",
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
parser.add_argument("prompt", help="Description of the diagram to generate")
|
|
828
|
+
parser.add_argument(
|
|
829
|
+
"-o", "--output", required=True, help="Output image path (e.g., diagram.png)"
|
|
830
|
+
)
|
|
831
|
+
parser.add_argument(
|
|
832
|
+
"--iterations",
|
|
833
|
+
type=int,
|
|
834
|
+
default=2,
|
|
835
|
+
help="Maximum refinement iterations (default: 2, max: 2)",
|
|
836
|
+
)
|
|
837
|
+
parser.add_argument(
|
|
838
|
+
"--doc-type",
|
|
839
|
+
default="default",
|
|
840
|
+
choices=[
|
|
841
|
+
"journal",
|
|
842
|
+
"conference",
|
|
843
|
+
"poster",
|
|
844
|
+
"presentation",
|
|
845
|
+
"report",
|
|
846
|
+
"grant",
|
|
847
|
+
"thesis",
|
|
848
|
+
"preprint",
|
|
849
|
+
"default",
|
|
850
|
+
],
|
|
851
|
+
help="Document type for quality threshold (default: default)",
|
|
852
|
+
)
|
|
853
|
+
parser.add_argument("--api-key", help="OpenRouter API key (or use OPENROUTER_API_KEY)")
|
|
854
|
+
parser.add_argument(
|
|
855
|
+
"--image-model",
|
|
856
|
+
default=None,
|
|
857
|
+
help="Image generation model (default: google/gemini-3-pro-image-preview)",
|
|
858
|
+
)
|
|
859
|
+
parser.add_argument(
|
|
860
|
+
"--review-model",
|
|
861
|
+
default=None,
|
|
862
|
+
help="Review model (default: google/gemini-3-pro-preview)",
|
|
863
|
+
)
|
|
864
|
+
parser.add_argument(
|
|
865
|
+
"--timeout-seconds",
|
|
866
|
+
type=int,
|
|
867
|
+
default=None,
|
|
868
|
+
help=argparse.SUPPRESS,
|
|
869
|
+
)
|
|
870
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
|
871
|
+
|
|
872
|
+
args = parser.parse_args()
|
|
873
|
+
|
|
874
|
+
# Validate iterations
|
|
875
|
+
if args.iterations < 1 or args.iterations > 2:
|
|
876
|
+
print("Error: Iterations must be between 1 and 2")
|
|
877
|
+
sys.exit(1)
|
|
878
|
+
|
|
879
|
+
try:
|
|
880
|
+
generator = ScientificSchematicGenerator(
|
|
881
|
+
api_key=args.api_key,
|
|
882
|
+
image_model=args.image_model,
|
|
883
|
+
review_model=args.review_model,
|
|
884
|
+
request_timeout_seconds=args.timeout_seconds,
|
|
885
|
+
verbose=args.verbose,
|
|
886
|
+
)
|
|
887
|
+
results = generator.generate_iterative(
|
|
888
|
+
user_prompt=args.prompt,
|
|
889
|
+
output_path=args.output,
|
|
890
|
+
iterations=args.iterations,
|
|
891
|
+
doc_type=args.doc_type,
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
if results["success"]:
|
|
895
|
+
print(f"\n✓ Success! Image saved to: {args.output}")
|
|
896
|
+
if results.get("early_stop"):
|
|
897
|
+
print(
|
|
898
|
+
f" (Completed in {len([r for r in results['iterations'] if r.get('success')])} iteration(s) - quality threshold met)"
|
|
899
|
+
)
|
|
900
|
+
sys.exit(0)
|
|
901
|
+
else:
|
|
902
|
+
print(f"\n✗ Generation failed. Check review log for details.")
|
|
903
|
+
sys.exit(1)
|
|
904
|
+
except Exception as e:
|
|
905
|
+
print(f"\n✗ Error: {str(e)}")
|
|
906
|
+
sys.exit(1)
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
if __name__ == "__main__":
|
|
910
|
+
main()
|