├── .gitignore ├── .prettierrc ├── build └── publish.ts ├── data ├── build.js ├── localPromptDefineMap.json └── src │ ├── dict │ ├── t.js │ ├── 容貌-头发.csv │ ├── 容貌-头饰.csv │ ├── 容貌-眼睛.csv │ ├── 容貌-耳朵.csv │ ├── 容貌-表情.csv │ ├── 构图-形式.csv │ ├── 构图-画面效果.csv │ ├── 构图-视角.csv │ ├── 构图-镜头.csv │ └── 画面效果.csv │ ├── localCommandDesc.js │ └── notion │ ├── fromNotion.js │ └── notionPromptDescMap.json ├── doc ├── CleanShot 2023-04-12 at 16.10.34@2x.png └── assets │ ├── Myintegrations-1@2x.jpeg │ ├── Myintegrations-2@2x.jpeg │ ├── Myintegrations-3@2x.jpeg │ ├── Myintegrations-4@2x.jpg │ ├── notion-demo.jpg │ ├── notion-me-config-1.jpg │ ├── notion-me-config-2.jpg │ ├── notion-me.gif │ ├── 截屏2023-04-02 01.31.05.png │ └── 截屏2023-04-06 15.51.23.png ├── docker ├── docker-compose.yml ├── dockerfile └── readme.md ├── jest.config.js ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── postcss.config.cjs ├── readme.md ├── server ├── index.ts ├── tencentTranslate.ts └── translate.ts ├── src ├── Boot │ ├── index.ts │ └── vue.boot.ts ├── Compoents │ ├── PromptDict │ │ ├── PromptDict.vue │ │ └── getDictData.ts │ └── PromptEditor │ │ ├── Assets │ │ ├── ad.png │ │ ├── ad2.jpg │ │ ├── ad221126.png │ │ ├── ad3.png │ │ ├── ad3b.png │ │ ├── ad3c.png │ │ ├── ad4.png │ │ ├── ad5.jpg │ │ ├── ad6.png │ │ └── ad_moonvy.png │ │ ├── Components │ │ ├── PromptItem │ │ │ ├── PromptItem.vue │ │ │ ├── dnd.ts │ │ │ └── subType.scss │ │ ├── PromptList │ │ │ └── PromptList.vue │ │ ├── PromptMenu │ │ │ └── PromptMenu.vue │ │ └── PromptWork │ │ │ ├── Components │ │ │ └── AddButton.vue │ │ │ └── PromptWork.vue │ │ ├── Lib │ │ ├── DatabaseServer │ │ │ ├── DatabaseServer.ts │ │ │ └── lib │ │ │ │ └── fetchFromNotion.ts │ │ ├── DnD │ │ │ └── index.ts │ │ ├── chinesePercentage.ts │ │ ├── findParentElement.ts │ │ ├── parsePrompts │ │ │ ├── parsePrompts.ts │ │ │ ├── parsers │ │ │ │ ├── Midjourney │ │ │ │ │ └── index.ts │ │ │ │ └── StableDiffusionWebUI │ │ │ │ │ └── index.ts │ │ │ └── test │ │ │ │ └── paresPrompts.test.ts │ │ ├── setMoveable.ts │ │ ├── translate.ts │ │ └── translatePrompts.ts │ │ ├── PromptEditor.vue │ │ ├── PromptEditorClass.ts │ │ └── Sub │ │ ├── PromptItem.ts │ │ ├── PromptList.ts │ │ └── PromptWork.ts ├── Global │ ├── global.d.ts │ ├── vue-global.d.ts │ └── vue-shims.d.ts ├── Lang │ └── tempLang.ts ├── Pages │ ├── Index │ │ ├── Index.vue │ │ └── assets │ │ │ └── logo_full_cn.svg │ ├── Root.vue │ └── index.ts └── Style │ ├── Font │ └── index.ts │ └── var.scss ├── tsconfig.base.json ├── tsconfig.json ├── vite.config.ts └── web ├── index.html ├── index.ts └── public ├── icon.svg └── localPromptDefineMap.json /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .idea 3 | node_modules 4 | __debug__ 5 | yarn-error.log 6 | .DS_Store 7 | RefreshAndPredload.log 8 | secret.json 9 | .env 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 4, 4 | "bracketSpacing":true, 5 | "semi":false 6 | } 7 | 8 | -------------------------------------------------------------------------------- /build/publish.ts: -------------------------------------------------------------------------------- 1 | // Created on 2023/03/26 - 00:39 2 | import { publish, refreshCDN } from "@moonvy/deploy" 3 | 4 | await publish("web", "./dist", "apps/ops") 5 | await refreshCDN(["https://moonvy.com/apps/ops/"]) 6 | -------------------------------------------------------------------------------- /data/build.js: -------------------------------------------------------------------------------- 1 | import csv from "csvtojson" 2 | import fs from "fs" 3 | import { fromNotion } from "./src/notion/fromNotion.js" 4 | import localCommandDesc from "./src/localCommandDesc.js" 5 | 6 | const __dirname = new URL(".", import.meta.url).pathname 7 | 8 | let localPromptDefineMap = {} 9 | 10 | // Add notion database https://www.notion.so/moonvy/5ac19c115d11488f95847c9e2d789dff?v=5ce9b783b4504c23bb7b492aa70c1cfc 11 | let notionPromptDescMap = await fromNotion() 12 | Object.assign(localPromptDefineMap, notionPromptDescMap) 13 | 14 | // Add src/dict/*.csv 15 | let pathLang = `${__dirname}src/dict` 16 | for (let file of fs.readdirSync(pathLang, { withFileTypes: true })) { 17 | if (file.isFile() && file.name.toLowerCase().endsWith(".csv")) { 18 | let re = await csv().fromFile(`${pathLang}/${file.name}`) 19 | re.forEach((item) => addToMap(item)) 20 | console.log(`Add src/dict/${file.name}`) 21 | } 22 | } 23 | console.log(`Add src/dict/*.csv`) 24 | 25 | // src/localCommandDesc.js 26 | localCommandDesc().forEach((item) => addToMap(item)) 27 | console.log(`Add src/localCommandDesc.js`) 28 | 29 | // ------------------------------------ 30 | 31 | Object.values(localPromptDefineMap).forEach((item) => { 32 | if (item?.tags?.length == 0) delete item.tags 33 | }) 34 | 35 | let jsonText = JSON.stringify(localPromptDefineMap, null, 1) 36 | fs.writeFileSync(__dirname + "localPromptDefineMap.json", jsonText) 37 | fs.writeFileSync(__dirname + "../web/public/localPromptDefineMap.json", jsonText) 38 | 39 | let finSize = fs.statSync(__dirname + "/localPromptDefineMap.json").size 40 | let itemsLength = Object.keys(localPromptDefineMap).length 41 | 42 | console.log(`[generated] localPromptDescMap.json ( ${itemsLength} items | ${(finSize / 1024).toFixed(1)}KB )`) 43 | 44 | // -------------------------- 45 | 46 | function addToMap(item) { 47 | const subTypeMap = { 48 | 普通: "normal", 49 | 风格: "style", 50 | 质量: "quality", 51 | 命令: "command", 52 | 负面: "eg", 53 | } 54 | // console.log("item", item) 55 | let key = item.text.toLowerCase() 56 | if (item.subType && subTypeMap[item.subType]) { 57 | item.subType = subTypeMap[item.subType] 58 | } 59 | 60 | if (localPromptDefineMap[key]) { 61 | Object.assign(localPromptDefineMap[key], item) 62 | } else { 63 | localPromptDefineMap[key] = item 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /data/src/dict/t.js: -------------------------------------------------------------------------------- 1 | let data = { 2 | "name": "制图特效", 3 | "category": [ 4 | "构图" 5 | ], 6 | "content": { 7 | "chromatic aberration": { 8 | "image": "ed9212bfc31f8e84315260751b5ffebde059d0748e3c9305d7597752a187fcdf", 9 | "name": "色差", 10 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/chromatic_aberration.html" 11 | }, 12 | "lens flare": { 13 | "alias": [ 14 | "lensflare" 15 | ], 16 | "description": "光线直射镜头而产生的光晕。", 17 | "image": "19fa91640e970b3f3b308ae8271560886129fa28f0e43fec31c07969c80550d7", 18 | "name": "镜头光晕", 19 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/lens_flare.html" 20 | }, 21 | "motion blur": { 22 | "description": "由于高速移动产生的模糊。", 23 | "image": "cac868c0805633e6a08afbf3ad0e58e5a12ead8d8c70752bdbc0c2c1d50bf1f7", 24 | "name": "动态模糊", 25 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/motion_blur.html" 26 | }, 27 | "sparkle": { 28 | "image": "e90b24b2020f41af85f44d60aaa0ac1c41d63f78cff277170c80f40dec190ecb", 29 | "name": "闪耀效果", 30 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/sparkle.html", 31 | "alias": [ 32 | "sparkles", 33 | "sparkling" 34 | ] 35 | }, 36 | "jpeg artifacts": { 37 | "name": "JPEG 压缩失真", 38 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/jpeg_artifacts.html", 39 | "alias": [ 40 | "jpeg artefacts", 41 | "jpg artifacts" 42 | ] 43 | }, 44 | "blurry": { 45 | "name": "模糊的", 46 | "image": "6daa85881f293c2188f7315cc61d41d032dea8af525e51d6293a20cef5e2aec9", 47 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/blurry.html", 48 | "alias": [ 49 | "blurred" 50 | ] 51 | }, 52 | "cinematic lighting": { 53 | "name": "电影光效", 54 | "image": "8029429ec07e77a763ffce45c4f5fa11605a1968c017e8eb94122daefd909135" 55 | }, 56 | "glowing light": { 57 | "name": "荧光", 58 | "image": "3bc6cf378c04b9a02d32110a224e555ca6f83ec75b7016b880aaa9aa8ef0c86f" 59 | }, 60 | "god rays": { 61 | "name": "自上而下的光", 62 | "image": "3616d10a6cf155698fa4265898f5447ee943ad79db63e639870fed926788986f" 63 | }, 64 | "ray tracing": { 65 | "name": "光线追踪", 66 | "image": "32bab9a1ad321971752de2ed337f3822074e5a61593ac9ca9270b81bbeb74d0f" 67 | }, 68 | "reflection light": { 69 | "name": "反射光", 70 | "image": "63587120baf82bb6ce0fa2bd82e6cacd532442e0a735fef4a34c12ca31dc7127" 71 | }, 72 | "overexposure": { 73 | "name": "过曝", 74 | "image": "7294f5926ad976a4c7f65ee5b6554ba59a33661dd45408287cca7a49715965fe", 75 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/overexposure.html" 76 | }, 77 | "backlighting": { 78 | "name": "逆光", 79 | "image": "840f02ef30da0f1d4a32980e9f6205974ede74b1a05d45fec4265ee6a1c67a7d", 80 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/backlighting.html", 81 | "alias": [ 82 | "backlit" 83 | ] 84 | }, 85 | "blending": { 86 | "name": "混合", 87 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/blending.html" 88 | }, 89 | "bloom": { 90 | "name": "盛开", 91 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/bloom.html" 92 | }, 93 | "bokeh": { 94 | "name": "背景散焦", 95 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/bokeh.html", 96 | "image": "43f693f30ecd36322da1d800160989a45b9009bf4f869dc637869a1eb4d54dc4" 97 | }, 98 | "caustics": { 99 | "alias": [ 100 | "caustic lighting" 101 | ], 102 | "name": "焦散", 103 | "description": "光线通过另一个物体反射或折射而投射到表面上的光图案。", 104 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/caustics.html", 105 | "image": "5847768a27d24d8a6c1612f457b28582405529a208527019616bf6839100c3e9" 106 | }, 107 | "chiaroscuro": { 108 | "name": "明暗对比", 109 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/chiaroscuro.html" 110 | }, 111 | "chromatic aberration abuse": { 112 | "name": "色差滥用", 113 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/chromatic_aberration_abuse.html" 114 | }, 115 | "diffraction spikes": { 116 | "name": "衍射十字星", 117 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/diffraction_spikes.html" 118 | }, 119 | "depth of field": { 120 | "name": "背景虚化", 121 | "description": "由于镜头焦距,图片的背景产生了一定程度的虚化。", 122 | "alias": [ 123 | "focus blur" 124 | ], 125 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/depth_of_field.html", 126 | "image": "8b6f9e21dadc3c879eede5f4f939ef4cae8b154b3df98153b4706e12480cde22" 127 | }, 128 | "dithering": { 129 | "name": "抖动", 130 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/dithering.html" 131 | }, 132 | "drop shadow": { 133 | "name": "立绘阴影", 134 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/drop_shadow.html" 135 | }, 136 | "emphasis lines": { 137 | "name": "集中线", 138 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/emphasis_lines.html" 139 | }, 140 | "film grain": { 141 | "name": "胶片颗粒感/老电影滤镜", 142 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/film_grain.html", 143 | "alias": [ 144 | "noise (visual)", 145 | "grainy" 146 | ] 147 | }, 148 | "foreshortening": { 149 | "name": "正前缩距透视法", 150 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/foreshortening.html" 151 | }, 152 | "halftone": { 153 | "name": "一种漫画中常见的网点状的组色组图画法", 154 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/halftone.html" 155 | }, 156 | "image fill": { 157 | "name": "图像填充", 158 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/image_fill.html" 159 | }, 160 | "lens flare abuse": { 161 | "name": "镜头光晕滥用", 162 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/lens_flare_abuse.html", 163 | "image": "3ba21c19d88db5a3ea9e34f0768480949d9f54512c985471cafcec6fb5ff535f" 164 | }, 165 | "motion lines": { 166 | "name": "体现运动的线", 167 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/motion_lines.html", 168 | "image": "9b4bab37adf5e7369350b204cdfe3364403c65515ec9f6abef8baa02ef3eb272" 169 | }, 170 | "multiple monochrome": { 171 | "name": "多个单色结构拼接成的", 172 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/multiple_monochrome.html" 173 | }, 174 | "optical illusion": { 175 | "name": "视错觉", 176 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/optical_illusion.html" 177 | }, 178 | "anaglyph": { 179 | "name": "互补色", 180 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/anaglyph.html" 181 | }, 182 | "stereogram": { 183 | "name": "立体画", 184 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/stereogram.html" 185 | }, 186 | "scanlines": { 187 | "name": "扫描线", 188 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/scanlines.html" 189 | }, 190 | "silhouette": { 191 | "image": "a0f1f26d90b3d6009e8343d8d087b1598e1421614d88b9b9584c03fa2d3e9886", 192 | "name": "剪影", 193 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/silhouette.html" 194 | }, 195 | "speed lines": { 196 | "name": "速度线", 197 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/speed_lines.html", 198 | "image": "fa945044c68a6a6c849af2cea6a249e6c5c7632954b305d541bdc4f4918a519d" 199 | }, 200 | "vignetting": { 201 | "name": "晕影", 202 | "wikiURL": "https://danbooru.donmai.us/wiki_pages/vignetting.html" 203 | } 204 | } 205 | } 206 | 207 | let csvLines = ["text,lang_zh,subType,dir"] 208 | Object.entries(data.content).forEach(function ([key, item]) { 209 | csvLines.push(`${key},${item.name},风格,构图/画面效果`) 210 | }) 211 | 212 | import fs from "fs" 213 | 214 | fs.writeFileSync("./构图-画面效果.csv", csvLines.join("\n")) 215 | -------------------------------------------------------------------------------- /data/src/dict/容貌-头发.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | streaked hair,条染,普通,容貌/头发 3 | multicolored hair,多彩头发,普通,容貌/头发 4 | colored inner hair,内侧染色,普通,容貌/头发 5 | blonde hair,金发,普通,容貌/头发 6 | silver hair,银发,普通,容貌/头发 7 | grey hair,灰发,普通,容貌/头发 8 | white hair,白发,普通,容貌/头发 9 | brown hair,茶发,普通,容貌/头发 10 | light brown hair,浅褐发,普通,容貌/头发 11 | black hair,黑发,普通,容貌/头发 12 | purple hair,紫发,普通,容貌/头发 13 | red hair,红发,普通,容貌/头发 14 | blue hair,蓝发/水色发,普通,容貌/头发 15 | dark blue hair,深蓝发,普通,容貌/头发 16 | light blue hair,浅蓝发,普通,容貌/头发 17 | green hair,绿发,普通,容貌/头发 18 | pink hair,粉发,普通,容貌/头发 19 | gradient hair,渐变发色,普通,容貌/头发 20 | rainbow hair,彩虹发,普通,容貌/头发 21 | ahoge,呆毛,普通,容貌/头发 22 | asymmetrical hair,非对称发型,普通,容貌/头发 23 | bangs,刘海,普通,容貌/头发 24 | blunt bangs,齐刘海,普通,容貌/头发 25 | braid,辫子,普通,容貌/头发 26 | braided ponytail,编织马尾辫,普通,容貌/头发 27 | curly hair,卷发,普通,容貌/头发 28 | curtained hair,窗帘/瀑布发型,普通,容貌/头发 29 | double bun,双团子头,普通,容貌/头发 30 | drill hair,钻头卷/公主卷,普通,容貌/头发 31 | twin drills,双钻头卷,普通,容貌/头发 32 | quad drills,多钻头卷,普通,容貌/头发 33 | side drill,单侧钻头卷,普通,容貌/头发 34 | french braid,法式辫,普通,容貌/头发 35 | hair behind ear,耳后发,普通,容貌/头发 36 | hair between eyes,眼间刘海,普通,容貌/头发 37 | crossed bangs,交错刘海,普通,容貌/头发 38 | hair bun,团子头,普通,容貌/头发 39 | hair intakes,进气口发型,普通,容貌/头发 40 | hair over shoulder,披肩发,普通,容貌/头发 41 | hime cut,姬发式,普通,容貌/头发 42 | long hair,长发,普通,容貌/头发 43 | messy hair,凌乱发型,普通,容貌/头发 44 | parted bangs,分开的刘海,普通,容貌/头发 45 | ponytail,马尾,普通,容貌/头发 46 | short hair,短发,普通,容貌/头发 47 | short ponytail,短马尾,普通,容貌/头发 48 | side swept bangs,朝一个方向的刘海,普通,容貌/头发 49 | side ponytail,侧马尾,普通,容貌/头发 50 | twin braids,双辫子,普通,容貌/头发 51 | twintails,双马尾,普通,容貌/头发 52 | very long hair,很长的头发,普通,容貌/头发 53 | front ponytail,前马尾,普通,容貌/头发 54 | short twintails,短双马尾,普通,容貌/头发 55 | folded ponytail,折叠马尾,普通,容貌/头发 56 | quad tails,四马尾,普通,容貌/头发 57 | single braid,单辫,普通,容貌/头发 58 | low twin braids,低双辫,普通,容貌/头发 59 | side braid,侧辫,普通,容貌/头发 60 | crown braid,冠型织辫,普通,容貌/头发 61 | dreadlocks,脏辫,普通,容貌/头发 62 | cone hair bun,锥形发髻,普通,容貌/头发 63 | braided bun,辫子髻,普通,容貌/头发 64 | doughnut hair bun,圆环发髻,普通,容貌/头发 65 | heart hair bun,心形发髻,普通,容貌/头发 66 | wavy hair,自然卷,普通,容貌/头发 67 | asymmetrical bangs,不对称刘海,普通,容貌/头发 68 | swept bangs,扫浏海,普通,容貌/头发 69 | sidelocks,耳前发,普通,容貌/头发 70 | single sidelock,单耳前发,普通,容貌/头发 71 | hair pulled back,头发后梳,普通,容貌/头发 72 | half updo,侧发后梳,普通,容貌/头发 73 | hair one side up,一侧绑发,普通,容貌/头发 74 | hair two side up,双侧绑发,普通,容貌/头发 75 | hair spread out,散发,普通,容貌/头发 76 | floating hair,漂浮的头发,普通,容貌/头发 77 | straight hair,直发,普通,容貌/头发 78 | big hair,头发很多的,普通,容貌/头发 79 | crystal hair,水晶状的头发,普通,容貌/头发 80 | expressive hair,富有表现力的头发,普通,容貌/头发 81 | hair over eyes,头发遮着双眼,普通,容貌/头发 82 | hair strand,强调一缕一缕感的发型/发丝,普通,容貌/头发 83 | hair over one eye,头发遮住了一只眼睛,普通,容貌/头发 84 | shiny hair,有光泽的头发,普通,容貌/头发 85 | wet hair,湿头发,普通,容貌/头发 86 | hair slicked back,垂下的长鬈发,普通,容貌/头发 87 | high ponytail,披在两侧的两条辫子,普通,容貌/头发 88 | long braid,侧马尾,普通,容貌/头发 89 | low-tied long hair,直发,普通,容貌/头发 90 | low ponytail,低扎马尾,普通,容貌/头发 91 | low twintails,低扎双尾,普通,容貌/头发 92 | medium hair,中等长发,普通,容貌/头发 93 | ringlets,垂下的长鬈发,普通,容貌/头发 94 | side braids,披在两侧的两条辫子,普通,容貌/头发 95 | side bun,披在两侧的发髻,普通,容貌/头发 96 | split ponytail,尾部散开的单马尾发型,普通,容貌/头发 97 | two side up,小型双股辫,普通,容貌/头发 98 | absurdly long hair,超长的头发,普通,容貌/头发 99 | cloud hair,云絮状发型,普通,容貌/头发 100 | flipped hair,外卷发型,普通,容貌/头发 101 | tentacle hair,触手头发,普通,容貌/头发 102 | very short hair,很短的头发,普通,容貌/头发 103 | bangs pinned back,掀起的刘海,普通,容貌/头发 104 | braided bangs,辫子刘海,普通,容貌/头发 105 | diagonal bangs,斜刘海,普通,容貌/头发 106 | single hair intake,单侧进气口发型,普通,容貌/头发 107 | hair ears,耳状头发,普通,容貌/头发 108 | bald,秃头,普通,容貌/头发 109 | bald girl,秃头女孩,普通,容貌/头发 110 | bowl cut,锅盖头,普通,容貌/头发 111 | buzz cut,寸头,普通,容貌/头发 112 | chonmage,丁髷,普通,容貌/头发 113 | crew cut,平头/板寸头,普通,容貌/头发 114 | flattop,平顶,普通,容貌/头发 115 | okappa,河童头,普通,容貌/头发 116 | pixie cut,精灵头,普通,容貌/头发 117 | undercut,帽盔式发型,普通,容貌/头发 118 | bob cut,波波头,普通,容貌/头发 119 | cornrows,玉米垄发型,普通,容貌/头发 120 | mullet,鲻鱼头,普通,容貌/头发 121 | bow-shaped hair,弓形头发,普通,容貌/头发 122 | front braid,前辫,普通,容貌/头发 123 | multiple braids,多股(麻花)辫,普通,容貌/头发 124 | tri braids,三股辫,普通,容貌/头发 125 | quad braids,四股辫,普通,容貌/头发 126 | triple bun,三发髻,普通,容貌/头发 127 | hair rings,发圈,普通,容貌/头发 128 | tied hair,扎头发,普通,容貌/头发 129 | single hair ring,单发圈,普通,容貌/头发 130 | one side up,只扎了一边的头发,普通,容貌/头发 131 | low-braided long hair,低辫长发,普通,容貌/头发 132 | mizura,角发,普通,容貌/头发 133 | multi-tied hair,多扎头发,普通,容貌/头发 134 | nihongami,日本发,普通,容貌/头发 135 | topknot,丸子头,普通,容貌/头发 136 | uneven twintails,两股辫子大小不一,普通,容貌/头发 137 | tri tails,有三股辫子,普通,容貌/头发 138 | quin tails,有五股辫子,普通,容貌/头发 139 | afro,鸟窝头/爆炸头,普通,容貌/头发 140 | huge afro,超大鸟窝头,普通,容貌/头发 141 | beehive hairdo,蜂窝头,普通,容貌/头发 142 | pompadour,蓬帕杜发型,普通,容貌/头发 143 | quiff,蓬松感油头,普通,容貌/头发 144 | hair flaps,在摆动的头发,普通,容貌/头发 145 | pointy hair,带着尖角的发型,普通,容貌/头发 146 | spiked hair,刺刺的头发,普通,容貌/头发 147 | widow's peak,美人尖,普通,容貌/头发 148 | heart ahoge,心形呆毛,普通,容貌/头发 149 | huge ahoge,大呆毛,普通,容貌/头发 150 | antenna hair,多根呆毛,普通,容貌/头发 151 | comb over,遮盖头发稀少部分,普通,容貌/头发 152 | mohawk,莫霍克发型,普通,容貌/头发 153 | lone nape hair,孤颈毛,普通,容貌/头发 154 | hair bikini,头发比基尼,普通,容貌/头发 155 | hair scarf,头发围巾,普通,容貌/头发 156 | -------------------------------------------------------------------------------- /data/src/dict/容貌-头饰.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | halo,头顶光环,普通,容貌/头饰 3 | tokin hat,东金帽子,普通,容貌/头饰 4 | mini top hat,迷你礼帽,普通,容貌/头饰 5 | beret,贝雷帽,普通,容貌/头饰 6 | hood,兜帽,普通,容貌/头饰 7 | nurse cap,护士帽,普通,容貌/头饰 8 | tiara,三重冕,普通,容貌/头饰 9 | crown,皇冠,普通,容貌/头饰 10 | hairpin,发卡,普通,容貌/头饰 11 | hairband,头箍,普通,容貌/头饰 12 | hairclip,发夹,普通,容貌/头饰 13 | hair ribbon,发带,普通,容貌/头饰 14 | hair flower,发花,普通,容貌/头饰 15 | hair ornament,头饰,普通,容貌/头饰 16 | hair bow,蝴蝶结发饰,普通,容貌/头饰 17 | maid headdress,女仆头饰,普通,容貌/头饰 18 | ribbon,丝带,普通,容貌/头饰 19 | sunglasses,太阳镜,普通,容貌/头饰 20 | blindfold,眼罩,普通,容貌/头饰 21 | eyepatch,单眼罩,普通,容貌/头饰 22 | mask,面具/眼罩/口罩,普通,容貌/头饰 23 | jewelry,首饰,普通,容貌/头饰 24 | bell,铃铛,普通,容貌/头饰 25 | facepaint,面纹,普通,容貌/头饰 26 | horns,兽角,普通,容貌/头饰 27 | antlers,鹿角,普通,容貌/头饰 28 | clover hair ornament,三叶草发饰,普通,容貌/头饰 29 | crescent hair ornament,月牙发饰,普通,容貌/头饰 30 | demon horns,恶魔的角,普通,容貌/头饰 31 | jeweled branch of hourai,蓬莱玉枝,普通,容貌/头饰 32 | fish hair ornament,鱼形发饰,普通,容貌/头饰 33 | forehead jewel,额前有宝石,普通,容貌/头饰 34 | forehead mark,额前有图案,普通,容貌/头饰 35 | forehead protector,护额,普通,容貌/头饰 36 | kanzashi,簪子,普通,容貌/头饰 37 | hair bobbles,头绳,普通,容貌/头饰 38 | hairpods,头发上成对的像无线蓝牙的发饰,普通,容貌/头饰 39 | hair bell,头发上系着铃铛,普通,容貌/头饰 40 | heart-shaped eyewear,心形眼镜,普通,容貌/头饰 41 | goggles,护目镜,普通,容貌/头饰 42 | rimless eyewear,无框眼镜,普通,容貌/头饰 43 | over-rim eyewear,下半无框眼镜,普通,容貌/头饰 44 | kamina shades,卡米纳墨镜,普通,容貌/头饰 45 | goggles on head,头上别着护目镜,普通,容貌/头饰 46 | goggles on headwear,帽子上别着护目镜,普通,容貌/头饰 47 | head mounted display,戴着头戴显示设备,普通,容貌/头饰 48 | bandage on,贴有绷带的脸,普通,容貌/头饰 49 | bandage over one eye,缠着绷带的单眼,普通,容貌/头饰 50 | scar across eye,眼睛上的疤痕,普通,容貌/头饰 51 | scar on cheek,脸颊上的疤痕,普通,容貌/头饰 52 | covered eyes,蒙住的眼,普通,容貌/头饰 53 | surgical mask,医用口罩,普通,容貌/头饰 54 | mouth mask,口罩,普通,容貌/头饰 55 | mouth veil,面纱,普通,容貌/头饰 56 | coke-bottle glasses,厚如玻璃瓶底的圆眼镜,普通,容貌/头饰 57 | tengu mask,天狗面具,普通,容貌/头饰 58 | fox mask,狐狸面具,普通,容貌/头饰 59 | mask on head,掀到头上的面具,普通,容貌/头饰 60 | mask pull,拉着口罩,普通,容貌/头饰 61 | mask removed,摘下的面具,普通,容貌/头饰 62 | gas mask,防毒面具,普通,容貌/头饰 63 | anchor choker,锚形项圈,普通,容貌/头饰 64 | bead necklace,珠子项链,普通,容貌/头饰 65 | headphones,耳机,普通,容貌/头饰 66 | behind-the-head headphones,从后脑戴上的耳机,普通,容貌/头饰 67 | whistle around neck,脖子上挂着口哨,普通,容貌/头饰 68 | animal hood,兽耳头罩,普通,容貌/头饰 69 | bespectacled,戴眼镜的,普通,容貌/头饰 70 | fedora,软呢帽,普通,容貌/头饰 71 | witch hat,女巫帽,普通,容貌/头饰 72 | wizard hat,法师帽,普通,容貌/头饰 73 | winged helmet,带翅膀的头盔,普通,容貌/头饰 74 | hood down,放下的兜帽,普通,容貌/头饰 75 | hood up,戴起来的兜帽,普通,容貌/头饰 76 | sailor hat,水手帽,普通,容貌/头饰 77 | santa hat,圣诞帽,普通,容貌/头饰 78 | peaked cap,类似警帽的帽子,普通,容貌/头饰 79 | elbow pads,护肘,普通,容貌/头饰 80 | dragon horns,龙角,普通,容貌/头饰 81 | eyewear on head,眼镜别在头上,普通,容貌/头饰 82 | mole under eye,眼角有痣,普通,容貌/头饰 83 | mole under mouth,嘴角有痣/美人痣,普通,容貌/头饰 84 | x hair ornament,x发饰,普通,容貌/头饰 85 | black hairband,黑色发带,普通,容貌/头饰 86 | hair scrunchie,发箍,普通,容貌/头饰 87 | white hairband,白色发带,普通,容貌/头饰 88 | hair tie,发带,普通,容貌/头饰 89 | frog hair ornament,青蛙发饰,普通,容貌/头饰 90 | food-themed hair ornament,食物发饰,普通,容貌/头饰 91 | star hair ornament,星星发饰,普通,容貌/头饰 92 | heart hair ornament,心形发饰,普通,容貌/头饰 93 | red hairband,红色发带,普通,容貌/头饰 94 | butterfly hair ornament,蝴蝶发饰,普通,容貌/头饰 95 | snake hair ornament,蛇发饰,普通,容貌/头饰 96 | lolita hairband,洛丽塔发带,普通,容貌/头饰 97 | feather hair ornament,羽毛头饰,普通,容貌/头饰 98 | blue hairband,蓝色发带,普通,容貌/头饰 99 | anchor hair ornament,锚发饰,普通,容貌/头饰 100 | leaf hair ornament,叶发饰,普通,容貌/头饰 101 | bunny hair ornament,兔子头饰,普通,容貌/头饰 102 | skull hair ornament,骷髅头饰,普通,容貌/头饰 103 | yellow hairband,黄色发带,普通,容貌/头饰 104 | pink hairband,粉色发带,普通,容貌/头饰 105 | bow hairband,蝴蝶结发带,普通,容貌/头饰 106 | cat hair ornament,猫头饰,普通,容貌/头饰 107 | musical note hair ornament,音符发饰,普通,容貌/头饰 108 | carrot hair ornament,胡萝卜发饰,普通,容貌/头饰 109 | purple hairband,紫色发带,普通,容貌/头饰 110 | hair beads,发珠,普通,容貌/头饰 111 | multiple hair bows,多个蝴蝶结,普通,容貌/头饰 112 | bat hair ornament,蝙蝠发饰,普通,容貌/头饰 113 | bone hair ornament,骨发饰,普通,容貌/头饰 114 | orange hairband,橙色发带,普通,容貌/头饰 115 | snowflake hair ornament,雪花发饰,普通,容貌/头饰 116 | flower on head,头上有花,普通,容貌/头饰 117 | head wreath,头上戴着花冠,普通,容貌/头饰 -------------------------------------------------------------------------------- /data/src/dict/容貌-眼睛.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | heart-shaped pupils,爱心形瞳孔,普通,容貌/眼睛 3 | rolling eyes,眼睛抬头,普通,容貌/眼睛 4 | crazy eyes,疯狂的眼睛,普通,容貌/眼睛 5 | raised eyebrows,挑眉,普通,容貌/眼睛 6 | furrowed brow,下眉毛,普通,容貌/眼睛 7 | dashed eyes,黑眼圈中的水平线,普通,容貌/眼睛 8 | multicolored eyes,多色的黑眼睛,普通,容貌/眼睛 9 | mismatched pupils,左右眼颜色不同,普通,容貌/眼睛 10 | mismatched sclera,左右眼不同颜色的眼白,普通,容貌/眼睛 11 | no pupils,没有眼睛,普通,容貌/眼睛 12 | empty eyes,眼睛没有亮点,普通,容貌/眼睛 13 | blank eyes,白眼球,普通,容貌/眼睛 14 | dilated pupils,瞳孔扩张,普通,容貌/眼睛 15 | hollow eyes,漆黑的眼睛,普通,容貌/眼睛 16 | constricted pupils,瞳孔收缩,普通,容貌/眼睛 17 | symbol-shaped pupils,符号眼,普通,容貌/眼睛 18 | star-shaped pupils,星形眼睛,普通,容貌/眼睛 19 | x-shaped pupils,X 形眼睛,普通,容貌/眼睛 20 | button eyes,纽扣眼,普通,容貌/眼睛 21 | eye reflection,眼睛反射,普通,容貌/眼睛 22 | closed eyes,闭上眼睛,普通,容貌/眼睛 23 | one eye closed,一只眼睛闭着,普通,容貌/眼睛 24 | half-closed eyes,眼睛半闭,普通,容貌/眼睛 25 | eyes closed,闭眼,普通,容貌/眼睛 26 | wince,闭一只眼,普通,容貌/眼睛 27 | tsurime,吊眼角,普通,容貌/眼睛 28 | eyeball,盯着看,普通,容貌/眼睛 29 | tears,眼泪,普通,容貌/眼睛 30 | gradient eyes,渐变瞳色,普通,容貌/眼睛 31 | aqua eyes,吐舌鬼脸,普通,容貌/眼睛 32 | crying with eyes open,睁着眼落泪,普通,容貌/眼睛 33 | glowing eyes,发光的双眼,普通,容貌/眼睛 34 | half-closed eye,半闭的眼睛(单眼),普通,容貌/眼睛 35 | happy tears,开心的眼泪,普通,容貌/眼睛 36 | sparkling eyes,星星眼,普通,容貌/眼睛 37 | glaring,轻蔑/怒视,普通,容貌/眼睛 38 | streaming tears,流泪,普通,容貌/眼睛 39 | eyebrows behind hair,挡在头发下的眉毛,普通,容貌/眼睛 40 | empty eyes,空洞眼睛,普通,容貌/眼睛 41 | wide eyes,睁大眼睛,普通,容貌/眼睛 42 | one eye closed,闭上一只眼,普通,容貌/眼睛 43 | half-closed eyes,半闭眼睛,普通,容貌/眼睛 44 | gradient_eyes,渐变眼,普通,容貌/眼睛 45 | aqua eyes,水汪汪大眼,普通,容貌/眼睛 46 | rolling eyes,翻白眼,普通,容貌/眼睛 47 | cross-eyed,斗鸡眼,普通,容貌/眼睛 48 | slit pupils,猫眼,普通,容貌/眼睛 49 | bloodshot eyes,布满血丝的眼睛,普通,容貌/眼睛 50 | glowing eyes,发光眼睛,普通,容貌/眼睛 51 | tsurime,吊眼角,普通,容貌/眼睛 52 | tareme,垂眼角,普通,容貌/眼睛 53 | devil eyes,恶魔眼,普通,容貌/眼睛 54 | constricted pupils,收缩的瞳孔,普通,容貌/眼睛 55 | devil pupils,魔瞳,普通,容貌/眼睛 56 | snake pupils,蛇瞳,普通,容貌/眼睛 57 | heterochromia,异色瞳,普通,容貌/眼睛 58 | purple eyes,紫眼,普通,容貌/眼睛 59 | red eyes,红眼,普通,容貌/眼睛 60 | slit pupils,竖瞳孔/猫眼,普通,容貌/眼睛 61 | white eyes,白眼,普通,容貌/眼睛 62 | yellow eyes,金眼,普通,容貌/眼睛 63 | tareme,下垂的眼睛,普通,容貌/眼睛 64 | sanpaku,三白眼,普通,容貌/眼睛 65 | upturned eyes,上翘的眼睛,普通,容貌/眼睛 66 | wide-eyed,睁开眼睛,普通,容貌/眼睛 67 | ringed eyes,眼圈,普通,容貌/眼睛 68 | pupils sparkling,闪闪发光瞳,普通,容貌/眼睛 69 | flower-shaped pupils,花形瞳,普通,容貌/眼睛 70 | heart-shaped pupils,爱心瞳,普通,容貌/眼睛 71 | heterochromia,异色瞳,普通,容貌/眼睛 72 | color contact lenses,美瞳,普通,容貌/眼睛 73 | longeyelashes,长睫毛,普通,容貌/眼睛 74 | colored eyelashes,彩色睫毛,普通,容貌/眼睛 75 | mole under eye,眼下痣,普通,容貌/眼睛 76 | solid circle eyes,实心圆眼睛,普通,容貌/眼睛 77 | heart-shaped eyes,心形眼,普通,容貌/眼睛 78 | @ @,晕眼,普通,容貌/眼睛 79 | cross-eyed,斗鸡眼,普通,容貌/眼睛 80 | orange eyes,橙色的眼镜,普通,容貌/眼睛 81 | pink eyes,粉红色的眼睛,普通,容貌/眼睛 82 | amber eyes,琥珀色眼,普通,容貌/眼睛 83 | pac-man eyes,吃豆人形眼,普通,容貌/眼睛 84 | horizontal pupils,一字型瞳孔/蛙眼,普通,容貌/眼睛 85 | diamond-shaped pupils,钻石形瞳孔,普通,容貌/眼睛 86 | flower-shaped pupils,花形瞳孔,普通,容貌/眼睛 -------------------------------------------------------------------------------- /data/src/dict/容貌-耳朵.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | animal ears,动物耳朵,普通,容貌/耳朵 3 | fox ears,狐狸耳朵,普通,容貌/耳朵 4 | cat ears,猫耳,普通,容貌/耳朵 5 | dog ears,狗耳,普通,容貌/耳朵 6 | mouse ears,老鼠耳朵,普通,容貌/耳朵 7 | pointy ears,尖耳,普通,容貌/耳朵 8 | heart earrings,心形耳环,普通,容貌/耳朵 9 | hoop earrings,环状耳环,普通,容貌/耳朵 10 | crystal earrings,水晶耳环,普通,容貌/耳朵 11 | earrings,耳环,普通,容貌/耳朵 12 | crescent earrings,月牙耳环,普通,容貌/耳朵 13 | cat ear headphones,猫耳式耳机,普通,容貌/耳朵 14 | bat ears,蝙蝠耳朵,普通,容貌/耳朵 15 | raccoon ears,浣熊耳朵,普通,容貌/耳朵 16 | long pointy ears,尖尖的长耳朵,普通,容貌/耳朵 17 | covering ears,遮住耳朵,普通,容貌/耳朵 18 | bear ears,熊耳朵,普通,容貌/耳朵 19 | rabbit ears,兔子耳朵,普通,容貌/耳朵 20 | cow ears,牛耳朵,普通,容貌/耳朵 21 | deer ears,鹿耳朵,普通,容貌/耳朵 22 | ferret ears,鼬耳朵,普通,容貌/耳朵 23 | goat ears,山羊耳朵,普通,容貌/耳朵 24 | horse ears,马耳,普通,容貌/耳朵 25 | kemonomimi mode,兽耳萝莉模式,普通,容貌/耳朵 26 | lion ears,狮子耳朵,普通,容貌/耳朵 27 | monkey ears,猴耳,普通,容貌/耳朵 28 | panda ears,熊猫耳朵,普通,容貌/耳朵 29 | pikachu ears,皮卡丘耳朵,普通,容貌/耳朵 30 | pig ears,猪耳朵,普通,容貌/耳朵 31 | sheep ears,羊耳,普通,容貌/耳朵 32 | squirrel ears,松鼠耳朵,普通,容貌/耳朵 33 | tiger ears,虎耳,普通,容貌/耳朵 34 | wolf ears,狼耳朵,普通,容貌/耳朵 35 | fake animal ears,仿制的动物耳朵,普通,容貌/耳朵 -------------------------------------------------------------------------------- /data/src/dict/容貌-表情.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | makeup,浓装,普通,容貌/表情 3 | food on face,食物在脸上,普通,容貌/表情 4 | smelling,闻,普通,容貌/表情 5 | nosebleed,鼻血,普通,容貌/表情 6 | clenched teeth,咬牙,普通,容貌/表情 7 | open mouth,张口,普通,容貌/表情 8 | pout,努嘴,普通,容貌/表情 9 | sigh,叹气,普通,容貌/表情 10 | smile,微笑,普通,容貌/表情 11 | light smile,浅笑,普通,容貌/表情 12 | grin,露齿而笑,普通,容貌/表情 13 | evil smile,坏笑,普通,容貌/表情 14 | angry,生气的,普通,容貌/表情 15 | annoyed,苦恼的,普通,容貌/表情 16 | crazy,疯狂的,普通,容貌/表情 17 | shy,害羞的,普通,容貌/表情 18 | embarrassed,尴尬的,普通,容貌/表情 19 | blush,脸红的,普通,容貌/表情 20 | sleepy,困乏的,普通,容貌/表情 21 | sad,悲伤的,普通,容貌/表情 22 | drunk,喝醉的,普通,容貌/表情 23 | frown,皱眉/蹙额,普通,容貌/表情 24 | fangs,尖牙,普通,容貌/表情 25 | tongue,舌头,普通,容貌/表情 26 | no nose,没有鼻子,普通,容貌/表情 27 | saliva,唾液,普通,容貌/表情 28 | facial hair,胡子,普通,容貌/表情 29 | fingersmile,用手指做出笑脸,普通,容貌/表情 30 | mouth hold,嘴咬住,普通,容貌/表情 31 | parted lips,嘴唇微张,普通,容貌/表情 32 | closed mouth,闭嘴,普通,容貌/表情 33 | tongue out,吐舌头,普通,容貌/表情 34 | licking lips,舔嘴唇,普通,容貌/表情 35 | pain,疼痛,普通,容貌/表情 36 | crying,哭,普通,容貌/表情 37 | fume,气得冒烟(漫画),普通,容貌/表情 38 | grimace,厌恶的怪相,普通,容貌/表情 39 | screaming,尖叫,普通,容貌/表情 40 | v-shaped eyebrows,V形眉(表高傲或愤怒),普通,容貌/表情 41 | scared,害怕的,普通,容貌/表情 42 | scowl,怒视/嫌弃/不满,普通,容貌/表情 43 | serious,严肃的,普通,容貌/表情 44 | tearing up,要哭的表情,普通,容貌/表情 45 | bored,无聊的,普通,容貌/表情 46 | gloom (expression),消沉(表情),普通,容貌/表情 47 | jealous,嫉妒的,普通,容貌/表情 48 | jitome,轻蔑的眼神,普通,容貌/表情 49 | nervous,不安的,普通,容貌/表情 50 | nervous smile,不安地微笑,普通,容貌/表情 51 | shaded,阴沉脸,普通,容貌/表情 52 | turn pale,脸色苍白,普通,容貌/表情 53 | expressionless,无口,普通,容貌/表情 54 | expressions,表情,普通,容貌/表情 55 | unconscious,失神,普通,容貌/表情 56 | bright pupils,明亮的瞳孔,普通,容貌/表情 57 | ear blush,耳红,普通,容貌/表情 58 | holding breath,憋气,普通,容貌/表情 59 | puckered lips,撅起的嘴唇,普通,容貌/表情 60 | seductive smile,诱人的微笑,普通,容貌/表情 61 | smiley,笑脸,普通,容貌/表情 62 | smirk,傻笑/得意的笑,普通,容貌/表情 63 | doyagao,得意脸,普通,容貌/表情 64 | flustered,慌乱的,普通,容貌/表情 65 | full blush,整张脸泛红,普通,容貌/表情 66 | heart in eye,眼里冒爱心,普通,容貌/表情 67 | heavy breathing,喘粗气,普通,容貌/表情 68 | moaning,呻吟,普通,容貌/表情 69 | smug,得意脸,普通,容貌/表情 70 | spit take,惊讶或无语到喷了,普通,容貌/表情 71 | surprised,惊讶,普通,容貌/表情 72 | tsundere,傲娇,普通,容貌/表情 73 | drooling,流口水,普通,容貌/表情 74 | torogao,诱惑的表情,普通,容貌/表情 75 | ahegao,阿嘿颜,普通,容貌/表情 76 | naughty face,下流的表情,普通,容貌/表情 77 | naughty,下流的表情,普通,容貌/表情 78 | endured face,忍耐的表情,普通,容貌/表情 79 | glint,眼中闪现强烈的情感,普通,容貌/表情 80 | happy,快乐/幸福,普通,容貌/表情 81 | laughing,在笑的,普通,容貌/表情 82 | troll,嚣张脸,普通,容貌/表情 83 | yandere,病娇,普通,容貌/表情 84 | saliva trail,唾液拉丝,普通,容貌/表情 85 | red lips,朱唇,普通,容貌/表情 86 | skin fang,虎牙状,普通,容貌/表情 87 | upper teeth,露出上排牙齿,普通,容貌/表情 88 | fang,虎牙,普通,容貌/表情 89 | fang out,露出虎牙/露出尖牙,普通,容貌/表情 90 | long tongue,长舌头,普通,容貌/表情 91 | forehead,额头,普通,容貌/表情 92 | light blush,淡淡的腮红,普通,容貌/表情 93 | cheek-to-cheek,脸贴脸,普通,容貌/表情 94 | cheek bulge,鼓着腮帮,普通,容貌/表情 95 | cheek pinching,捏脸颊,普通,容貌/表情 96 | cheek poking,戳脸颊,普通,容貌/表情 97 | cheek pull,扯脸颊,普通,容貌/表情 98 | chin grab,抬下巴,普通,容貌/表情 99 | covering eyes,遮住眼睛,普通,容貌/表情 100 | covering,挡住脸,普通,容貌/表情 101 | covering mouth,挡住嘴巴,普通,容貌/表情 102 | face-to-face,脸贴脸,普通,容貌/表情 103 | facing another,二人面对面(脸贴得很近),普通,容貌/表情 104 | forehead-to-forehead,额头贴额头,普通,容貌/表情 105 | teeth,牙,普通,容貌/表情 106 | excited,兴奋,普通,容貌/表情 107 | nose blush,害羞,普通,容貌/表情 108 | expressionless eyes,失神,普通,容貌/表情 109 | anger vein,青筋,普通,容貌/表情 110 | blush stickers,表情贴纸,普通,容貌/表情 111 | full-face blush,整张脸泛红,普通,容貌/表情 112 | confused,疑惑,普通,容貌/表情 113 | determined,有决心的,普通,容貌/表情 114 | disappointed,失望的,普通,容貌/表情 115 | disdain,蔑视,普通,容貌/表情 116 | disgust,恶心,普通,容貌/表情 117 | despair,绝望,普通,容貌/表情 118 | envy,嫉妒,普通,容貌/表情 119 | evil,邪恶,普通,容貌/表情 120 | facepalm,以手掩面,普通,容貌/表情 121 | frustrated,沮丧,普通,容貌/表情 122 | guilt,有罪的,普通,容貌/表情 123 | kubrick stare,库布里克凝视,普通,容貌/表情 124 | lonely,孤独的,普通,容貌/表情 125 | raised eyebrow,扬起的眉毛,普通,容貌/表情 126 | rape face,强硬的表情,普通,容貌/表情 127 | depressed,压抑的/郁闷的,普通,容貌/表情 128 | panicking,恐慌的,普通,容貌/表情 129 | worried,担忧的,普通,容貌/表情 130 | tired,累,普通,容貌/表情 131 | sulking,闷闷不乐,普通,容貌/表情 132 | thinking,思考,普通,容貌/表情 133 | pensive,沉思的,普通,容貌/表情 134 | upset,气愤,普通,容貌/表情 135 | crazy smile,疯狂地笑,普通,容貌/表情 136 | forced smile,强迫笑,普通,容貌/表情 137 | glasgow smile,格拉斯哥微笑,普通,容貌/表情 138 | sad smile,苦笑,普通,容貌/表情 139 | stifled laugh,憋笑,普通,容貌/表情 140 | color drain,惊讶到掉色,普通,容貌/表情 141 | horrified,恐惧表情,普通,容貌/表情 142 | sobbing,啜泣,普通,容貌/表情 143 | oral invitation,伸出舌头,普通,容貌/表情 144 | 145 | -------------------------------------------------------------------------------- /data/src/dict/构图-形式.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | afterimage,残像,风格,构图/形式 3 | border,边框,风格,构图/形式 4 | framed,画框,风格,构图/形式 5 | outside border,一部分画到了背景框外面,风格,构图/形式 6 | fading border,褪色边框,风格,构图/形式 7 | rounded corners,背景或画框是圆角,风格,构图/形式 8 | viewfinder,相机取景框,风格,构图/形式 9 | chart,图表,风格,构图/形式 10 | character chart,人设图,风格,构图/形式 11 | reference sheet,设定图,风格,构图/形式 12 | diagram,图表,风格,构图/形式 13 | move chart,动作演示图,风格,构图/形式 14 | relationship graph,关系表,风格,构图/形式 15 | seating chart,座次表,风格,构图/形式 16 | stats,属性栏/状态表,风格,构图/形式 17 | collage,拼贴画,风格,构图/形式 18 | column lineup,小图拼接,风格,构图/形式 19 | bust chart,胸围图,风格,构图/形式 20 | cropped,遭到裁剪,风格,构图/形式 21 | fake scrollbar,假的滚动条,风格,构图/形式 22 | head out of frame,头部脱框,风格,构图/形式 23 | out of frame,脱框,风格,构图/形式 24 | feet out of frame,脚部脱框,风格,构图/形式 25 | isometric,等轴,风格,构图/形式 26 | letterboxed,宽银幕格式,风格,构图/形式 27 | pillarboxed,柱状画布背景,风格,构图/形式 28 | lineup,一排人,风格,构图/形式 29 | mosaic art,马赛克艺术,风格,构图/形式 30 | photomosaic,马赛克拼图,风格,构图/形式 31 | negative space,大量留白,风格,构图/形式 32 | omake,附图,风格,构图/形式 33 | partially underwater shot,部分水下拍摄,风格,构图/形式 34 | social media composition,社交媒体整合,风格,构图/形式 35 | symmetry,左右对称,风格,构图/形式 36 | polar opposites,两极对称,风格,构图/形式 37 | rotational symmetry,对称旋转,风格,构图/形式 38 | tachi-e,立绘样式,风格,构图/形式 39 | trim marks,裁剪标记,风格,构图/形式 40 | zoom layer,人物立绘缩放(剪影)图层,风格,构图/形式 41 | projected inset,类似海报或杂志的插图效果,风格,构图/形式 42 | -------------------------------------------------------------------------------- /data/src/dict/构图-画面效果.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | chromatic aberration,色差,风格,构图/画面效果 3 | lens flare,镜头光晕,风格,构图/画面效果 4 | motion blur,动态模糊,风格,构图/画面效果 5 | sparkle,闪耀效果,风格,构图/画面效果 6 | jpeg artifacts,JPEG 压缩失真,风格,构图/画面效果 7 | blurry,模糊的,风格,构图/画面效果 8 | cinematic lighting,电影光效,风格,构图/画面效果 9 | glowing light,荧光,风格,构图/画面效果 10 | god rays,自上而下的光,风格,构图/画面效果 11 | ray tracing,光线追踪,风格,构图/画面效果 12 | reflection light,反射光,风格,构图/画面效果 13 | overexposure,过曝,风格,构图/画面效果 14 | backlighting,逆光,风格,构图/画面效果 15 | blending,混合,风格,构图/画面效果 16 | bloom,盛开,风格,构图/画面效果 17 | bokeh,背景散焦,风格,构图/画面效果 18 | caustics,焦散,风格,构图/画面效果 19 | chiaroscuro,明暗对比,风格,构图/画面效果 20 | chromatic aberration abuse,色差滥用,风格,构图/画面效果 21 | diffraction spikes,衍射十字星,风格,构图/画面效果 22 | depth of field,背景虚化,风格,构图/画面效果 23 | dithering,抖动,风格,构图/画面效果 24 | drop shadow,立绘阴影,风格,构图/画面效果 25 | emphasis lines,集中线,风格,构图/画面效果 26 | film grain,胶片颗粒感/老电影滤镜,风格,构图/画面效果 27 | foreshortening,正前缩距透视法,风格,构图/画面效果 28 | halftone,一种漫画中常见的网点状的组色组图画法,风格,构图/画面效果 29 | image fill,图像填充,风格,构图/画面效果 30 | lens flare abuse,镜头光晕滥用,风格,构图/画面效果 31 | motion lines,体现运动的线,风格,构图/画面效果 32 | multiple monochrome,多个单色结构拼接成的,风格,构图/画面效果 33 | optical illusion,视错觉,风格,构图/画面效果 34 | anaglyph,互补色,风格,构图/画面效果 35 | stereogram,立体画,风格,构图/画面效果 36 | scanlines,扫描线,风格,构图/画面效果 37 | silhouette,剪影,风格,构图/画面效果 38 | speed lines,速度线,风格,构图/画面效果 39 | vignetting,晕影,风格,构图/画面效果 -------------------------------------------------------------------------------- /data/src/dict/构图-视角.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | first-person view,第一人称视角,风格,构图/视角 3 | pov,主观视角,风格,构图/视角 4 | three sided view,三视图,风格,构图/视角 5 | multiple views,多视图,风格,构图/视角 6 | cut-in,插入画面,风格,构图/视角 7 | blurry foreground,前景模糊,风格,构图/视角 8 | close-up,特写镜头,风格,构图/视角 9 | cowboy shot,七分身镜头,风格,构图/视角 10 | dutch angle,德式倾斜镜头,风格,构图/视角 11 | fisheye,鱼眼镜头,风格,构图/视角 12 | hatching (texture),线影法(纹理),风格,构图/视角 13 | vanishing point,远景透视画法,风格,构图/视角 14 | wide shot,广角镜头,风格,构图/视角 15 | from above,俯视镜头,风格,构图/视角 16 | from behind,背影,风格,构图/视角 17 | from below,仰视镜头,风格,构图/视角 18 | from outside,室外看向室内(的镜头),风格,构图/视角 19 | from side,角色的侧面,风格,构图/视角 20 | atmospheric perspective,大气距离感,风格,构图/视角 21 | panorama,全景,风格,构图/视角 22 | perspective,透视画法,风格,构图/视角 23 | rotated,经过旋转的,风格,构图/视角 24 | sideways,横向显示的,风格,构图/视角 25 | upside-down,倒挂的,风格,构图/视角 26 | -------------------------------------------------------------------------------- /data/src/dict/构图-镜头.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | lens flare,镜头光晕,风格,构图/镜头 3 | lens flare,镜头光晕,风格,构图/镜头 4 | overexposure,过曝,风格,构图/镜头 5 | bokeh,背景散焦,风格,构图/镜头 6 | caustics,焦散,风格,构图/镜头 7 | diffraction spikes,衍射十字星,风格,构图/镜头 8 | foreshortening,正前缩距透视法,风格,构图/镜头 9 | emphasis lines,集中线,风格,构图/镜头 10 | satellite image,卫星鸟瞰,风格,构图/镜头 11 | macro photo,微距照片,风格,构图/镜头 12 | 360 view,360 度视角,风格,构图/镜头 13 | Wide-Angle,广角,风格,构图/镜头 14 | Ultra-Wide Angle,超广角,风格,构图/镜头 15 | Eye-Level Shot,人眼视角拍摄,风格,构图/镜头 16 | Eye-Level Shot,人眼视角拍摄,风格,构图/镜头 17 | f/1.2,光圈 F1.2,风格,构图/镜头 18 | f/1.8,光圈 F1.8,风格,构图/镜头 19 | f/2.8,光圈 F2.8,风格,构图/镜头 20 | f/4.0,光圈 F4.0,风格,构图/镜头 21 | f/16,光圈 F16,风格,构图/镜头 22 | 35mm,焦距 35mm,风格,构图/镜头 23 | 85mm,焦距 85mm,风格,构图/镜头 24 | 135mm,焦距 135mm,风格,构图/镜头 25 | Nikon,尼康,风格,构图/镜头 26 | Canon,佳能,风格,构图/镜头 27 | Fujifilm,富士,风格,构图/镜头 28 | Hasselblad,哈数,风格,构图/镜头 29 | Sony FE,索尼镜头,风格,构图/镜头 30 | Sony FE GM,索尼大师镜头,风格,构图/镜头 31 | Sony FE GM,索尼大师镜头,风格,构图/镜头 32 | 33 | -------------------------------------------------------------------------------- /data/src/dict/画面效果.csv: -------------------------------------------------------------------------------- 1 | text,lang_zh,subType,dir 2 | motion blur,动态模糊,风格,画面效果 3 | chromatic aberration,色差,风格,画面效果 4 | sparkle,闪耀效果,风格,画面效果 5 | jpeg artifacts,JPEG 压缩失真,风格,画面效果 6 | blurry,模糊的,风格,画面效果 7 | cinematic lighting,电影光效,风格,画面效果 8 | glowing light,荧光,风格,画面效果 9 | god rays,神圣感顶光,风格,画面效果 10 | ray tracing,光线追踪,风格,画面效果 11 | reflection light,反射光,风格,画面效果 12 | backlighting,逆光,风格,画面效果 13 | blending,混合,风格,画面效果 14 | bloom,盛开,风格,画面效果 15 | chiaroscuro,明暗对比,风格,画面效果 16 | chromatic aberration abuse,色差滥用,风格,画面效果 17 | depth of field,背景虚化,风格,画面效果 18 | dithering,抖动,风格,画面效果 19 | drop shadow,立绘阴影,风格,画面效果 20 | film grain,胶片颗粒感/老电影滤镜,风格,画面效果 21 | Fujicolor,富士色彩,风格,画面效果 22 | halftone,半调风格,风格,画面效果 23 | image fill,图像填充,风格,画面效果 24 | motion lines,体现运动的线,风格,画面效果 25 | multiple monochrome,多重单色,风格,画面效果 26 | optical illusion,视错觉,风格,画面效果 27 | anaglyph,互补色,风格,画面效果 28 | stereogram,立体画,风格,画面效果 29 | scanlines,扫描线,风格,画面效果 30 | silhouette,剪影,风格,画面效果 31 | speed lines,速度线,风格,画面效果 32 | vignetting,晕影,风格,画面效果 33 | -------------------------------------------------------------------------------- /data/src/localCommandDesc.js: -------------------------------------------------------------------------------- 1 | const Commands = [ 2 | { 3 | keys: ["--version", "--v"], 4 | zh: "版本", 5 | desc: "模型版本", 6 | sampleCmds: ["4", "5", "6", "niji"], 7 | }, 8 | { 9 | keys: ["--aspect", "--ar"], 10 | zh: "宽高比", 11 | sampleCmds: ["2:3", "16:9", "3:2"], 12 | desc: "生成图片的宽高比尺寸", 13 | }, 14 | { 15 | keys: ["--cref"], 16 | zh: "一致性引用源", 17 | desc: "生成与提供的链接一致性的结果(后跟链接)", 18 | }, 19 | { 20 | keys: ["--cw"], 21 | zh: "一致性权重", 22 | sampleCmds: [0, 50, 100], 23 | desc: "<0–100>,数值越大一致性越高", 24 | }, 25 | { 26 | keys: ["--tile"], 27 | zh: "无缝拼贴图案", 28 | desc: "生成无缝拼贴图案", 29 | }, 30 | { 31 | keys: ["--chaos", "--c"], 32 | zh: "多样性", 33 | sampleCmds: [0, 50, 100], 34 | desc: "<0–100>,多样性数值越高,越能产生更多意想不到的结果和组合,越小越能产生更稳定、更可重复的结果", 35 | }, 36 | { 37 | keys: ["--no"], 38 | zh: "负面", 39 | sampleCmds: ["xxx"], 40 | desc: "<0–100> 多样性数值越高,越能产生更多意想不到的结果和组合,越小越能产生更稳定、更可重复的结果。默认值为 0", 41 | }, 42 | { 43 | keys: ["--quality", "--q"], 44 | zh: "质量", 45 | sampleCmds: [".25", ".5", "1"], 46 | desc: "<.25, .5, 1, 2> 要花费多少渲染时间(迭代步数),更高通常会产生更多的细节,但高 --quality 并不总能产生更好的结果。默认值为 1 ", 47 | }, 48 | { 49 | keys: ["--seed"], 50 | zh: "种子", 51 | desc: "<0–4294967295> 指定随机种子,相同的提示词与相同的种子能产生相似的结果", 52 | }, 53 | { 54 | keys: ["--sameseed"], 55 | zh: "相似种子", 56 | desc: "<0–4294967295> 比 --seed 能生成更相似的结果", 57 | }, 58 | { 59 | keys: ["--stop"], 60 | zh: "停止", 61 | desc: "<10–100> 在流程中途结束生成。以较早的百分比停止流程会产生更模糊、更不细致的结果", 62 | }, 63 | { 64 | keys: ["--style"], 65 | zh: "风格", 66 | sampleCmds: ["raw", "4a", "4b", "4c"], 67 | desc: " 模型版本的风格之间切换", 68 | }, 69 | { 70 | keys: ["--stylize", "--s"], 71 | zh: "风格化", 72 | sampleCmds: ["0", "500", "1000"], 73 | desc: "<0–1000> 越高结果的艺术性越高,越低越像真实图片。默认 100", 74 | }, 75 | { 76 | keys: ["--iw"], 77 | zh: "图像权重", 78 | desc: "相对于文本权重的图像提示权重,默认 0.25", 79 | }, 80 | { 81 | keys: ["--uplight"], 82 | zh: "轻量放大器", 83 | desc: "选择 U 按钮时使用替代的“轻量”放大器。结果更接近原始网格图像,放大后的图像细节更少,更平滑", 84 | }, 85 | { 86 | keys: ["--upbeta"], 87 | zh: "测试版放大器", 88 | desc: "选择 U 按钮时使用替代的“beta”放大器。结果更接近原始网格图像。放大后的图像添加的细节明显更少", 89 | }, 90 | { 91 | keys: ["--niji"], 92 | sampleCmds: ["", "5", "6"], 93 | zh: "动漫模型", 94 | desc: "另一种模型专注于动漫风格的图像", 95 | }, 96 | { 97 | keys: ["--test"], 98 | zh: "测试模型", 99 | desc: "特殊测试模型", 100 | }, 101 | { 102 | keys: ["--testp"], 103 | zh: "摄影测试模型", 104 | desc: "特殊的以摄影为重点的测试模型", 105 | }, 106 | { 107 | keys: ["--hd"], 108 | zh: "高清模型", 109 | desc: "使用早期的替代模型来生成更大、更不一致的图像。该算法可能适用于抽象和风景图像", 110 | }, 111 | { 112 | keys: ["--repeat"], 113 | zh: "重复", 114 | sampleCmds: ["1", "3"], 115 | desc: "重复", 116 | }, 117 | ] 118 | 119 | export default function () { 120 | let descs = [] 121 | for (let command of Object.values(Commands)) { 122 | let i = 0 123 | for (let key of command.keys) { 124 | let item = { 125 | text: key, 126 | lang_zh: command.zh, 127 | subType: "command", 128 | desc: command.desc, 129 | } 130 | descs.push(item) 131 | if (i == 0) { 132 | item.sampleCmds = command.sampleCmds 133 | item.dir = `命令/${command.zh}` 134 | } 135 | descs.push({ 136 | text: key.replace("--", "—"), 137 | lang_zh: command.zh, 138 | subType: "command", 139 | desc: command.desc, 140 | }) 141 | i++ 142 | } 143 | } 144 | return descs 145 | } 146 | -------------------------------------------------------------------------------- /data/src/notion/fromNotion.js: -------------------------------------------------------------------------------- 1 | import { Client } from "@notionhq/client" 2 | import secret from "../../../secret.json" assert { type: "json" } 3 | import fs from "fs" 4 | 5 | const notion = new Client({ 6 | auth: secret.notion, 7 | }) 8 | 9 | // database https://www.notion.so/moonvy/5ac19c115d11488f95847c9e2d789dff?v=5ce9b783b4504c23bb7b492aa70c1cfc 10 | let database_id = `5ac19c115d11488f95847c9e2d789dff` 11 | const __dirname = new URL(".", import.meta.url).pathname 12 | 13 | // let items = await fromNotion() 14 | 15 | export async function fromNotion() { 16 | let lines = {} 17 | const subTypeMap = { 18 | 普通: "normal", 19 | 风格: "style", 20 | 质量: "quality", 21 | 命令: "command", 22 | 负面: "eg", 23 | } 24 | 25 | console.log("[notion] get notion database :https://www.notion.so/moonvy/5ac19c115d11488f95847c9e2d789dff") 26 | let i = 0 27 | await once() 28 | async function once(start_cursor) { 29 | let re = await notion.databases.query({ database_id, start_cursor }) 30 | console.log(`[notion] get page${i} :${start_cursor ?? "init"}`) 31 | re.results.forEach((page) => { 32 | let text = page.properties.text.title?.[0]?.text?.content 33 | let desc = page.properties.desc.rich_text?.[0]?.text?.content 34 | let lang_zh = page.properties["lang_zh"].rich_text?.[0]?.text?.content 35 | let tags = page.properties.tags?.multi_select?.map((x) => x.name) 36 | let subType = page.properties.subType?.select?.name 37 | let dir = page.properties.dir?.select?.name 38 | subType = subTypeMap[subType] ?? "normal" 39 | let item = { text, desc, lang_zh, subType, dir, tags } 40 | if (!text) return 41 | // console.log("item",item) 42 | lines[item.text.toLowerCase()] = item 43 | }) 44 | 45 | if (re.has_more) { 46 | await once(re.next_cursor) 47 | } 48 | } 49 | 50 | console.log(`[notion] import ${Object.keys(lines).length} items.`) 51 | fs.writeFileSync(`${__dirname}notionPromptDescMap.json`, JSON.stringify(lines, null, 2)) 52 | return lines 53 | } 54 | -------------------------------------------------------------------------------- /data/src/notion/notionPromptDescMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "1080": { 3 | "text": "1080", 4 | "subType": "quality", 5 | "tags": [] 6 | }, 7 | "cycles render": { 8 | "text": "Cycles Render", 9 | "subType": "style", 10 | "tags": [] 11 | }, 12 | "cyclesrender": { 13 | "text": "CyclesRender", 14 | "subType": "style", 15 | "tags": [] 16 | }, 17 | "blender 3d": { 18 | "text": "blender 3d", 19 | "subType": "style", 20 | "tags": [] 21 | }, 22 | "glassmorphism": { 23 | "text": "Glassmorphism", 24 | "lang_zh": "玻璃形态", 25 | "subType": "style", 26 | "tags": [] 27 | }, 28 | "glass morphism": { 29 | "text": "Glass Morphism", 30 | "lang_zh": "玻璃形态", 31 | "subType": "style", 32 | "tags": [] 33 | }, 34 | "abstract": { 35 | "text": "abstract", 36 | "lang_zh": "抽象", 37 | "subType": "style", 38 | "tags": [] 39 | }, 40 | "uhd": { 41 | "text": "UHD", 42 | "lang_zh": "超高清", 43 | "subType": "quality", 44 | "dir": "质量", 45 | "tags": [] 46 | }, 47 | "1080p": { 48 | "text": "1080P", 49 | "subType": "quality", 50 | "dir": "质量/画质", 51 | "tags": [] 52 | }, 53 | "retina": { 54 | "text": "retina", 55 | "lang_zh": "视网膜屏", 56 | "subType": "quality", 57 | "dir": "质量/画质", 58 | "tags": [] 59 | }, 60 | "hd": { 61 | "text": "HD", 62 | "subType": "quality", 63 | "dir": "质量/画质", 64 | "tags": [] 65 | }, 66 | "modern minimalist illustration": { 67 | "text": "modern minimalist illustration", 68 | "lang_zh": "现代极简主义插画", 69 | "subType": "style", 70 | "tags": [] 71 | }, 72 | "conceptual art": { 73 | "text": "Conceptual art", 74 | "lang_zh": "概念艺术", 75 | "subType": "style", 76 | "dir": "绘画/风格", 77 | "tags": [] 78 | }, 79 | "social realism": { 80 | "text": "Social realism", 81 | "lang_zh": "社会现实主义", 82 | "subType": "style", 83 | "dir": "绘画/风格", 84 | "tags": [] 85 | }, 86 | "interior architecture": { 87 | "text": "interior architecture", 88 | "lang_zh": "室内建筑", 89 | "subType": "style", 90 | "dir": "绘画/风格", 91 | "tags": [] 92 | }, 93 | "modern": { 94 | "text": "modern", 95 | "lang_zh": "现代", 96 | "subType": "style", 97 | "dir": "绘画/风格", 98 | "tags": [] 99 | }, 100 | "anime": { 101 | "text": "anime", 102 | "lang_zh": "动漫", 103 | "subType": "style", 104 | "dir": "绘画/风格", 105 | "tags": [] 106 | }, 107 | "minimalism": { 108 | "text": "Minimalism", 109 | "lang_zh": "极简主义", 110 | "subType": "style", 111 | "dir": "绘画/风格", 112 | "tags": [] 113 | }, 114 | "romanticism": { 115 | "text": "Romanticism", 116 | "lang_zh": "浪漫主义", 117 | "subType": "style", 118 | "dir": "绘画/风格", 119 | "tags": [] 120 | }, 121 | "gothic art": { 122 | "text": "Gothic art", 123 | "lang_zh": "哥特式艺术", 124 | "subType": "style", 125 | "dir": "绘画/风格", 126 | "tags": [] 127 | }, 128 | "american propaganda poster": { 129 | "text": "American propaganda poster", 130 | "lang_zh": "美国宣传海报", 131 | "subType": "style", 132 | "dir": "绘画/风格", 133 | "tags": [] 134 | }, 135 | "c4d": { 136 | "text": "c4d", 137 | "lang_zh": "c4d", 138 | "subType": "style", 139 | "tags": [ 140 | "3D" 141 | ] 142 | }, 143 | "expressionism": { 144 | "text": "Expressionism", 145 | "lang_zh": "表现主义", 146 | "subType": "style", 147 | "dir": "绘画/风格", 148 | "tags": [] 149 | }, 150 | "realism": { 151 | "text": "Realism", 152 | "lang_zh": "现实主义", 153 | "subType": "style", 154 | "dir": "绘画/风格", 155 | "tags": [] 156 | }, 157 | "contemporary art": { 158 | "text": "Contemporary art", 159 | "lang_zh": "当代艺术", 160 | "subType": "style", 161 | "dir": "绘画/风格", 162 | "tags": [] 163 | }, 164 | "dutch golden age painting": { 165 | "text": "Dutch Golden Age painting", 166 | "lang_zh": "荷兰黄金时期绘画", 167 | "subType": "style", 168 | "dir": "绘画/风格", 169 | "tags": [] 170 | }, 171 | "pop art": { 172 | "text": "Pop art", 173 | "lang_zh": "波普艺术", 174 | "subType": "style", 175 | "dir": "绘画/风格", 176 | "tags": [] 177 | }, 178 | "monet": { 179 | "text": "Monet", 180 | "lang_zh": "莫奈", 181 | "subType": "style", 182 | "dir": "绘画/风格", 183 | "tags": [] 184 | }, 185 | "northern renaissance": { 186 | "text": "Northern Renaissance", 187 | "lang_zh": "北方文艺复兴", 188 | "subType": "style", 189 | "dir": "绘画/风格", 190 | "tags": [] 191 | }, 192 | "dadaism": { 193 | "text": "Dadaism", 194 | "lang_zh": "达达主义", 195 | "subType": "style", 196 | "dir": "绘画/风格", 197 | "tags": [] 198 | }, 199 | "luminism": { 200 | "text": "Luminism", 201 | "lang_zh": "明亮主义", 202 | "subType": "style", 203 | "dir": "绘画/风格", 204 | "tags": [] 205 | }, 206 | "hyperdetailed": { 207 | "text": "hyperdetailed", 208 | "lang_zh": "超写实", 209 | "subType": "style", 210 | "tags": [] 211 | }, 212 | "art deco": { 213 | "text": "Art Deco", 214 | "lang_zh": "装饰艺术", 215 | "subType": "style", 216 | "dir": "绘画/风格", 217 | "tags": [] 218 | }, 219 | "rococo style": { 220 | "text": "rococo style", 221 | "lang_zh": "洛可可风格", 222 | "subType": "style", 223 | "dir": "绘画/风格", 224 | "tags": [] 225 | }, 226 | "chiaroscuro": { 227 | "text": "Chiaroscuro", 228 | "lang_zh": "明暗对比法", 229 | "subType": "style", 230 | "dir": "绘画/风格", 231 | "tags": [] 232 | }, 233 | "tonalism": { 234 | "text": "Tonalism", 235 | "lang_zh": "色调主义", 236 | "subType": "style", 237 | "dir": "绘画/风格", 238 | "tags": [] 239 | }, 240 | "3d rendering": { 241 | "text": "3d rendering", 242 | "lang_zh": "3D渲染", 243 | "subType": "style", 244 | "tags": [] 245 | }, 246 | "fauvism": { 247 | "text": "Fauvism", 248 | "lang_zh": "野兽派", 249 | "subType": "style", 250 | "dir": "绘画/风格", 251 | "tags": [] 252 | }, 253 | "punk woman": { 254 | "text": "punk woman", 255 | "lang_zh": "朋克女性", 256 | "subType": "style", 257 | "tags": [] 258 | }, 259 | "carl larsson": { 260 | "text": "Carl Larsson", 261 | "lang_zh": "卡尔·拉尔松风格的画作", 262 | "subType": "style", 263 | "dir": "绘画/风格", 264 | "tags": [] 265 | }, 266 | "mannerism": { 267 | "text": "Mannerism", 268 | "lang_zh": "后期文艺复兴", 269 | "subType": "style", 270 | "dir": "绘画/风格", 271 | "tags": [] 272 | }, 273 | "action painting": { 274 | "text": "Action painting", 275 | "lang_zh": "行动绘画", 276 | "subType": "style", 277 | "dir": "绘画/风格", 278 | "tags": [] 279 | }, 280 | "blender": { 281 | "text": "blender", 282 | "subType": "style", 283 | "tags": [ 284 | "3D" 285 | ] 286 | }, 287 | "anatomically correct": { 288 | "text": "anatomically correct", 289 | "lang_zh": "解剖学正确", 290 | "subType": "quality", 291 | "dir": "质量", 292 | "tags": [] 293 | }, 294 | "cubist futurism": { 295 | "text": "Cubist Futurism", 296 | "lang_zh": "立体派未来主义", 297 | "subType": "style", 298 | "dir": "绘画/风格", 299 | "tags": [] 300 | }, 301 | "futurism": { 302 | "text": "Futurism", 303 | "lang_zh": "未来主义", 304 | "subType": "style", 305 | "dir": "绘画/风格", 306 | "tags": [] 307 | }, 308 | "neoclassicism": { 309 | "text": "Neoclassicism", 310 | "lang_zh": "新古典主义", 311 | "subType": "style", 312 | "dir": "绘画/风格", 313 | "tags": [] 314 | }, 315 | "baroque": { 316 | "text": "Baroque", 317 | "lang_zh": "巴洛克", 318 | "subType": "style", 319 | "dir": "绘画/风格", 320 | "tags": [] 321 | }, 322 | "genre painting": { 323 | "text": "Genre painting", 324 | "lang_zh": "风俗画", 325 | "subType": "style", 326 | "dir": "绘画/风格", 327 | "tags": [] 328 | }, 329 | "constructivism": { 330 | "text": "Constructivism", 331 | "lang_zh": "构成主义", 332 | "subType": "style", 333 | "dir": "绘画/风格", 334 | "tags": [] 335 | }, 336 | "by alfons mucha": { 337 | "text": "by Alfons Mucha", 338 | "lang_zh": "由阿尔方斯·穆卡制作", 339 | "subType": "style", 340 | "dir": "绘画/风格", 341 | "tags": [] 342 | }, 343 | "blind box toy style": { 344 | "text": "blind box toy style", 345 | "lang_zh": "盲盒玩具风格", 346 | "subType": "style", 347 | "dir": "绘画/风格", 348 | "tags": [] 349 | }, 350 | "ukiyo-e": { 351 | "text": "Ukiyo-e", 352 | "lang_zh": "浮世绘", 353 | "subType": "style", 354 | "dir": "绘画/风格", 355 | "tags": [] 356 | }, 357 | "rococo": { 358 | "text": "Rococo", 359 | "lang_zh": "洛可可", 360 | "subType": "style", 361 | "tags": [] 362 | }, 363 | "high detail": { 364 | "text": "high detail", 365 | "lang_zh": "高细节", 366 | "subType": "style", 367 | "dir": "绘画/风格", 368 | "tags": [] 369 | }, 370 | "op art": { 371 | "text": "Op art", 372 | "lang_zh": "视错觉艺术", 373 | "subType": "style", 374 | "dir": "绘画/风格", 375 | "tags": [] 376 | }, 377 | "color field painting": { 378 | "text": "Color Field painting", 379 | "lang_zh": "色块画", 380 | "subType": "style", 381 | "dir": "绘画/风格", 382 | "tags": [] 383 | }, 384 | "renaissance": { 385 | "text": "Renaissance", 386 | "lang_zh": "文艺复兴", 387 | "subType": "style", 388 | "dir": "绘画/风格", 389 | "tags": [] 390 | }, 391 | "surrealism": { 392 | "text": "Surrealism", 393 | "lang_zh": "超现实主义", 394 | "subType": "style", 395 | "dir": "绘画/风格", 396 | "tags": [] 397 | }, 398 | "bauhaus": { 399 | "text": "Bauhaus", 400 | "lang_zh": "包豪斯", 401 | "subType": "style", 402 | "dir": "绘画/风格", 403 | "tags": [] 404 | }, 405 | "abstract expressionism": { 406 | "text": "Abstract expressionism", 407 | "lang_zh": "抽象表现主义", 408 | "subType": "style", 409 | "dir": "绘画/风格", 410 | "tags": [] 411 | }, 412 | "pre-raphaelite brotherhood": { 413 | "text": "Pre-Raphaelite Brotherhood", 414 | "lang_zh": "前拉斐尔派兄弟会", 415 | "subType": "style", 416 | "dir": "绘画/风格", 417 | "tags": [] 418 | }, 419 | "impressionism": { 420 | "text": "Impressionism", 421 | "lang_zh": "印象派", 422 | "subType": "style", 423 | "dir": "绘画/风格", 424 | "tags": [] 425 | }, 426 | "feminist woman": { 427 | "text": "feminist woman", 428 | "lang_zh": "女权主义", 429 | "subType": "style", 430 | "tags": [] 431 | }, 432 | "ccurate": { 433 | "text": "ccurate", 434 | "lang_zh": "准确", 435 | "subType": "quality", 436 | "dir": "质量", 437 | "tags": [] 438 | }, 439 | "classicism": { 440 | "text": "Classicism", 441 | "lang_zh": "古典主义", 442 | "subType": "style", 443 | "dir": "绘画/风格", 444 | "tags": [] 445 | }, 446 | "hyperrealism": { 447 | "text": "Hyperrealism", 448 | "lang_zh": "超现实主义细节画派", 449 | "subType": "style", 450 | "dir": "绘画/风格", 451 | "tags": [] 452 | }, 453 | "ghibli-like colours": { 454 | "text": "Ghibli-like colours", 455 | "lang_zh": "吉卜力色彩", 456 | "subType": "style", 457 | "dir": "绘画/风格", 458 | "tags": [] 459 | }, 460 | "art nouveau": { 461 | "text": "Art Nouveau", 462 | "lang_zh": "新艺术运动", 463 | "subType": "style", 464 | "dir": "绘画/风格", 465 | "tags": [] 466 | }, 467 | "suprematism": { 468 | "text": "Suprematism", 469 | "lang_zh": "至高主义", 470 | "subType": "style", 471 | "dir": "绘画/风格", 472 | "tags": [] 473 | }, 474 | "abstractionism": { 475 | "text": "Abstractionism", 476 | "lang_zh": "抽象主义", 477 | "subType": "style", 478 | "dir": "绘画/风格", 479 | "tags": [] 480 | }, 481 | "pre-rephaëlite painting": { 482 | "text": "pre-rephaëlite painting", 483 | "lang_zh": "前拉斐尔派绘画", 484 | "subType": "style", 485 | "dir": "绘画/风格", 486 | "tags": [] 487 | }, 488 | "textured skin": { 489 | "text": "textured skin", 490 | "lang_zh": "质感皮肤", 491 | "subType": "quality", 492 | "dir": "质量", 493 | "tags": [] 494 | }, 495 | "anime style": { 496 | "text": "anime style", 497 | "lang_zh": "动漫风格", 498 | "subType": "style", 499 | "dir": "绘画/风格", 500 | "tags": [] 501 | }, 502 | "post-impressionism": { 503 | "text": "Post-Impressionism", 504 | "lang_zh": "后印象派", 505 | "subType": "style", 506 | "dir": "绘画/风格", 507 | "tags": [] 508 | }, 509 | "en plein air": { 510 | "text": "En plein air", 511 | "lang_zh": "野外写生", 512 | "subType": "style", 513 | "dir": "绘画/风格", 514 | "tags": [] 515 | }, 516 | "super detail": { 517 | "text": "super detail", 518 | "lang_zh": "非常详细", 519 | "subType": "quality", 520 | "dir": "质量", 521 | "tags": [] 522 | }, 523 | "high details": { 524 | "text": "high details", 525 | "lang_zh": "高细节", 526 | "subType": "quality", 527 | "dir": "质量", 528 | "tags": [] 529 | }, 530 | "8k octane": { 531 | "text": "8k octane", 532 | "lang_zh": "8k八角渲染", 533 | "subType": "style", 534 | "tags": [] 535 | }, 536 | "timeless modern": { 537 | "text": "timeless modern", 538 | "lang_zh": "永恒的现代", 539 | "subType": "style", 540 | "tags": [] 541 | }, 542 | "pointillism": { 543 | "text": "Pointillism", 544 | "lang_zh": "点彩派", 545 | "subType": "style", 546 | "dir": "绘画/风格", 547 | "tags": [] 548 | }, 549 | "verism": { 550 | "text": "Verism", 551 | "lang_zh": "写实主义", 552 | "subType": "style", 553 | "dir": "绘画/风格", 554 | "tags": [] 555 | }, 556 | "raised fist": { 557 | "text": "raised fist", 558 | "lang_zh": "扬起的拳头", 559 | "subType": "style", 560 | "dir": "绘画/风格", 561 | "tags": [] 562 | }, 563 | "ashcan school": { 564 | "text": "Ashcan School", 565 | "lang_zh": "垃圾桶派", 566 | "subType": "style", 567 | "dir": "绘画/风格", 568 | "tags": [] 569 | }, 570 | "soft colors": { 571 | "text": "soft colors", 572 | "lang_zh": "柔和的颜色", 573 | "subType": "style", 574 | "tags": [] 575 | }, 576 | "naïve art": { 577 | "text": "Naïve art", 578 | "lang_zh": "原始艺术", 579 | "subType": "style", 580 | "tags": [] 581 | }, 582 | "careful line drawing": { 583 | "text": "careful line drawing", 584 | "lang_zh": "细心的线条绘画", 585 | "subType": "style", 586 | "tags": [] 587 | }, 588 | "pixar": { 589 | "text": "Pixar", 590 | "lang_zh": "皮克斯", 591 | "subType": "style", 592 | "dir": "绘画/风格", 593 | "tags": [] 594 | }, 595 | "cubism": { 596 | "text": "Cubism", 597 | "lang_zh": "立体主义", 598 | "subType": "style", 599 | "dir": "绘画/风格", 600 | "tags": [] 601 | }, 602 | "soft lighting": { 603 | "text": "soft lighting", 604 | "lang_zh": "柔和的灯光", 605 | "subType": "style", 606 | "tags": [] 607 | }, 608 | "test1": { 609 | "text": "test1", 610 | "subType": "normal", 611 | "tags": [] 612 | }, 613 | "depth of field": { 614 | "text": "Depth of field", 615 | "lang_zh": "景深", 616 | "subType": "style", 617 | "tags": [ 618 | "摄影" 619 | ] 620 | }, 621 | "ultra - wide angle": { 622 | "text": "Ultra - wide angle", 623 | "lang_zh": "超广角", 624 | "subType": "style", 625 | "tags": [ 626 | "摄影" 627 | ] 628 | }, 629 | "portrait photography": { 630 | "text": "Portrait photography", 631 | "lang_zh": "人像摄影", 632 | "subType": "style", 633 | "tags": [ 634 | "摄影" 635 | ] 636 | }, 637 | "color correction": { 638 | "text": "Color correction", 639 | "lang_zh": "色彩校正", 640 | "subType": "style", 641 | "tags": [ 642 | "摄影" 643 | ] 644 | }, 645 | "cinematic": { 646 | "text": "Cinematic", 647 | "lang_zh": "电影", 648 | "subType": "style", 649 | "tags": [ 650 | "摄影", 651 | "平面设计" 652 | ] 653 | }, 654 | "photorealistic": { 655 | "text": "photorealistic", 656 | "lang_zh": "真实感", 657 | "subType": "style", 658 | "tags": [ 659 | "摄影" 660 | ] 661 | }, 662 | "cinematography": { 663 | "text": "cinematography", 664 | "lang_zh": "电影摄影", 665 | "subType": "style", 666 | "tags": [ 667 | "摄影", 668 | "平面设计" 669 | ] 670 | }, 671 | "retrofuturism": { 672 | "text": "retrofuturism", 673 | "lang_zh": "复古未来主义", 674 | "subType": "style", 675 | "tags": [] 676 | }, 677 | "16k": { 678 | "text": "16k", 679 | "subType": "quality", 680 | "dir": "质量/画质", 681 | "tags": [] 682 | }, 683 | "ip": { 684 | "text": "IP", 685 | "subType": "style", 686 | "tags": [ 687 | "3D" 688 | ] 689 | }, 690 | "8k": { 691 | "text": "8k", 692 | "subType": "quality", 693 | "dir": "质量/画质", 694 | "tags": [] 695 | }, 696 | "pixel assets": { 697 | "text": "pixel assets", 698 | "lang_zh": "像素资产", 699 | "subType": "style", 700 | "tags": [] 701 | }, 702 | "character sheet full-length": { 703 | "text": "character sheet full-length", 704 | "lang_zh": "全身角色表", 705 | "subType": "style", 706 | "tags": [] 707 | }, 708 | "pixel art": { 709 | "text": "Pixel Art", 710 | "lang_zh": "像素艺术", 711 | "subType": "style", 712 | "tags": [] 713 | }, 714 | "illustration": { 715 | "text": "illustration", 716 | "lang_zh": "插画", 717 | "subType": "style", 718 | "tags": [] 719 | }, 720 | "japanese illustration style": { 721 | "text": "Japanese illustration style", 722 | "lang_zh": "日本插画风格", 723 | "subType": "style", 724 | "tags": [] 725 | }, 726 | "character design": { 727 | "text": "Character Design", 728 | "lang_zh": "角色设计", 729 | "subType": "style", 730 | "tags": [ 731 | "3D" 732 | ] 733 | }, 734 | "clay render": { 735 | "text": "clay render", 736 | "lang_zh": "粘土渲染", 737 | "subType": "style", 738 | "tags": [ 739 | "3D" 740 | ] 741 | }, 742 | "oc renderer": { 743 | "text": "OC renderer", 744 | "lang_zh": "OC渲染器", 745 | "subType": "style", 746 | "tags": [ 747 | "3D" 748 | ] 749 | }, 750 | "award winning": { 751 | "text": "award winning", 752 | "lang_zh": "屡获殊荣", 753 | "subType": "quality", 754 | "dir": "质量", 755 | "tags": [] 756 | }, 757 | "artistic": { 758 | "text": "artistic", 759 | "lang_zh": "艺术的", 760 | "subType": "style", 761 | "tags": [] 762 | }, 763 | "detail": { 764 | "text": "detail", 765 | "lang_zh": "细节", 766 | "subType": "normal", 767 | "tags": [] 768 | }, 769 | "cinematic lighting": { 770 | "text": "cinematic lighting", 771 | "lang_zh": "电影灯光", 772 | "subType": "style", 773 | "tags": [] 774 | }, 775 | "3d isometric": { 776 | "text": "3D Isometric", 777 | "lang_zh": "3D 等距", 778 | "subType": "style", 779 | "tags": [ 780 | "3D" 781 | ] 782 | }, 783 | "best quality": { 784 | "text": "best quality", 785 | "lang_zh": "最佳质量", 786 | "subType": "quality", 787 | "dir": "质量", 788 | "tags": [] 789 | }, 790 | "detail 8k": { 791 | "text": "detail 8k", 792 | "subType": "style", 793 | "tags": [] 794 | }, 795 | "dribbble": { 796 | "text": "dribbble", 797 | "lang_zh": "Dribbble", 798 | "subType": "style", 799 | "tags": [ 800 | "3D", 801 | "平面设计", 802 | "UI" 803 | ] 804 | }, 805 | "behance": { 806 | "text": "behance", 807 | "lang_zh": "Behance", 808 | "subType": "style", 809 | "tags": [ 810 | "3D", 811 | "UI", 812 | "平面设计" 813 | ] 814 | }, 815 | "icon style": { 816 | "text": "icon style", 817 | "lang_zh": "图标样式", 818 | "subType": "style", 819 | "tags": [] 820 | }, 821 | "minimalist": { 822 | "text": "minimalist", 823 | "lang_zh": "极简主义", 824 | "subType": "style", 825 | "tags": [] 826 | }, 827 | "2d": { 828 | "text": "2D", 829 | "subType": "style", 830 | "tags": [] 831 | }, 832 | "3d": { 833 | "text": "3D", 834 | "subType": "style", 835 | "tags": [ 836 | "3D" 837 | ] 838 | }, 839 | "4k": { 840 | "text": "4K", 841 | "subType": "quality", 842 | "dir": "质量/画质", 843 | "tags": [] 844 | }, 845 | "unreal engine": { 846 | "text": "unreal engine", 847 | "lang_zh": "虚幻引擎", 848 | "subType": "style", 849 | "tags": [] 850 | }, 851 | "hyper realistic": { 852 | "text": "hyper realistic", 853 | "lang_zh": "超现实主义", 854 | "subType": "style", 855 | "tags": [] 856 | }, 857 | "isometric view": { 858 | "text": "isometric view", 859 | "lang_zh": "等距视图", 860 | "subType": "style", 861 | "tags": [] 862 | }, 863 | "damaged": { 864 | "text": "damaged", 865 | "lang_zh": "损坏的", 866 | "subType": "normal", 867 | "tags": [] 868 | }, 869 | "ruined": { 870 | "text": "ruined", 871 | "lang_zh": "破败的", 872 | "subType": "normal", 873 | "tags": [] 874 | }, 875 | "digital art": { 876 | "text": "digital art", 877 | "lang_zh": "数字艺术", 878 | "subType": "style", 879 | "tags": [] 880 | }, 881 | "high quality": { 882 | "text": "high quality", 883 | "lang_zh": "高质量", 884 | "subType": "quality", 885 | "dir": "质量", 886 | "tags": [] 887 | }, 888 | "trumbledown": { 889 | "text": "trumbledown", 890 | "lang_zh": "摇摇欲坠", 891 | "subType": "normal", 892 | "tags": [] 893 | }, 894 | "apocalypse": { 895 | "text": "apocalypse", 896 | "lang_zh": "启示录", 897 | "subType": "normal", 898 | "tags": [] 899 | }, 900 | "isometric projection": { 901 | "text": "isometric projection", 902 | "lang_zh": "等距投影", 903 | "subType": "style", 904 | "tags": [] 905 | } 906 | } -------------------------------------------------------------------------------- /doc/CleanShot 2023-04-12 at 16.10.34@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/CleanShot 2023-04-12 at 16.10.34@2x.png -------------------------------------------------------------------------------- /doc/assets/Myintegrations-1@2x.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/Myintegrations-1@2x.jpeg -------------------------------------------------------------------------------- /doc/assets/Myintegrations-2@2x.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/Myintegrations-2@2x.jpeg -------------------------------------------------------------------------------- /doc/assets/Myintegrations-3@2x.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/Myintegrations-3@2x.jpeg -------------------------------------------------------------------------------- /doc/assets/Myintegrations-4@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/Myintegrations-4@2x.jpg -------------------------------------------------------------------------------- /doc/assets/notion-demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/notion-demo.jpg -------------------------------------------------------------------------------- /doc/assets/notion-me-config-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/notion-me-config-1.jpg -------------------------------------------------------------------------------- /doc/assets/notion-me-config-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/notion-me-config-2.jpg -------------------------------------------------------------------------------- /doc/assets/notion-me.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/notion-me.gif -------------------------------------------------------------------------------- /doc/assets/截屏2023-04-02 01.31.05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/截屏2023-04-02 01.31.05.png -------------------------------------------------------------------------------- /doc/assets/截屏2023-04-06 15.51.23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/doc/assets/截屏2023-04-06 15.51.23.png -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | # open-prompt-studio start 4 | open-prompt-studio: 5 | container_name: "open-prompt-studio" 6 | image: "open-prompt-studio:latest" # build by https://github.com/Moonvy/OpenPromptStudio 7 | environment: 8 | - TENCENT_SECRET_ID=AKIDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 9 | - TENCENT_SECRET_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 10 | - LOCAL_TRANSLATE_HOST=localhost:39011 # 自定义翻译服务地址,设置为你服务器最终访问地址 11 | ports: 12 | - "12833:12833" # 宿主 Web 页端口:容器端口 13 | - "39011:39011" # 宿主翻译服务端口:容器端口 14 | restart: on-failure:3 # always on-failure:3 or unless-stopped default "no" 15 | # open-prompt-studio end 16 | -------------------------------------------------------------------------------- /docker/dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine3.16 2 | WORKDIR /app 3 | COPY ["package.json", "package-lock.json*", "./"] 4 | RUN npm install --registry http://registry.npmmirror.com --force 5 | COPY ./src ./src 6 | COPY ./web ./web 7 | COPY ./server ./server 8 | COPY ./data/localPromptDefineMap.json ./data/localPromptDefineMap.json 9 | COPY package.json tsconfig.json tsconfig.base.json vite.config.ts ./ 10 | EXPOSE 12833 39011 11 | CMD ["npm", "run", "start"] 12 | -------------------------------------------------------------------------------- /docker/readme.md: -------------------------------------------------------------------------------- 1 | # Docker 说明 2 | 3 | ## 使用 docker-compose 4 | 5 | 最简单的部署方法是使用 docker-compose :下载 [docker-compose.yml](https://github.com/Moonvy/OpenPromptStudio/blob/master/docker/docker-compose.yml) 文件,在所在目录下执行 `docker compose up -d` 6 | 7 | 8 | ```yml 9 | version: "3.7" 10 | services: 11 | # open-prompt-studio start 12 | open-prompt-studio: 13 | container_name: "open-prompt-studio" 14 | image: "open-prompt-studio:latest" # build by https://github.com/Moonvy/OpenPromptStudio 15 | environment: 16 | - TENCENT_SECRET_ID=AKIDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 17 | - TENCENT_SECRET_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 18 | - LOCAL_TRANSLATE_HOST=localhost:39011 # 自定义翻译服务地址,设置为你服务器最终访问地址 19 | ports: 20 | - "12833:12833" # 宿主 Web 页端口:容器端口 21 | - "39011:39011" # 宿主翻译服务端口:容器端口 22 | restart: on-failure:3 # always on-failure:3 or unless-stopped default "no" 23 | # open-prompt-studio end 24 | ``` 25 | 26 | 27 | ## 构建 28 | 29 | 在没有 `node` 环境的情况下, 可以使用当前构建脚本。 30 | 31 | ### 构建命令 32 | 33 | ``` 34 | docker build -t open-prompt-studio:latest -f docker/dockerfile . 35 | ``` 36 | 37 | ### 运行容器 38 | 39 | ``` 40 | docker run -d -p 12833:12833 -p 39011:39011 --name open-prompt-studio open-prompt-studio:latest 41 | ``` 42 | 43 | 访问 [http://localhost:12833](http://localhost:12833) 就可以了 44 | 45 | 重新映射端口 46 | 默认使用以下端口,如有需要可以在 docker 中重新映射 47 | 48 | - Web 访问端口:12833 49 | - 翻译服务端口:39011 50 | 51 | ```bash 52 | # 在宿主使用 80 和 3000 替换默认的 12833, 39011: 53 | $ docker run -d -p 80:12833 -p 3000:19212 --name open-prompt-studio open-prompt-studio:latest 54 | ``` 55 | 56 | ### 配置 57 | 58 | #### `.env` 文件内配置 59 | 60 | ```env 61 | # 腾讯翻译配置 https://bobtranslate.com/service/translate/tencent.html 62 | TENCENT_SECRET_ID="AKIDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 63 | TENCENT_SECRET_KEY="a5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 64 | 65 | # 自定义翻译服务地址 [可选] (如果你部署在服务器上,通过此配置指定 Web 端访问翻译服务的地址) 66 | # LOCAL_TRANSLATE_HOST="192.168.50.222:3000" 67 | ``` 68 | 69 | 70 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | transform: { 3 | "^.+\\.(t|j)sx?$": [ 4 | "@swc-node/jest", 5 | { 6 | dynamicImport: true, 7 | experimentalDecorators: true, 8 | emitDecoratorMetadata: true, 9 | }, 10 | ], 11 | }, 12 | extensionsToTreatAsEsm: [".ts"], 13 | transformIgnorePatterns: ["/!node_modules\\/lodash-es/"], 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@moonvy/open-prompt-studio", 3 | "version": "1.4.4", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "start": "npm-run-all --parallel dev serve", 8 | "dev": "vite --host 0.0.0.0", 9 | "build": "vite build", 10 | "preview": "vite serve", 11 | "serve": "jiti ./server/index.ts", 12 | "fetch": "node ./data/build.js", 13 | "up": "tsx ./build/publish.ts", 14 | "publish": "npm run build && npm run up" 15 | }, 16 | "devDependencies": { 17 | "@iconify/vue2": "2.1.0", 18 | "@notionhq/client": "2.2.4", 19 | "@types/express": "4.17.17", 20 | "@types/jest": "29.5.1", 21 | "@types/lodash": "4.14.189", 22 | "@types/node": "20", 23 | "@vitejs/plugin-legacy": "5.2.0", 24 | "@vitejs/plugin-vue2": "2.3.1", 25 | "autoprefixer": "10.4.14", 26 | "csvtojson": "2.0.10", 27 | "sass": "1.59.2", 28 | "typescript": "4.9", 29 | "vite": "5.0.11", 30 | "vite-plugin-html": "3.2.0", 31 | "vite-plugin-progress": "^0.0.6" 32 | }, 33 | "dependencies": { 34 | "@fontsource/fira-code": "4.5.13", 35 | "@fontsource/jetbrains-mono": "4.5.12", 36 | "@moonvy/app-core": "*", 37 | "@vueuse/core": "10.1.0", 38 | "axios": "1.3.4", 39 | "copy-image-clipboard": "^2.1.2", 40 | "cors": "2.8.5", 41 | "crypto-js": "4.1.1", 42 | "dotenv": "16.0.3", 43 | "downloadjs": "^1.4.7", 44 | "express": "4.18.2", 45 | "floating-vue": "1.0.0-beta.19", 46 | "fzz": "*", 47 | "html-to-image": "1.11.11", 48 | "jiti": "1.16.0", 49 | "lodash": "4.17.21", 50 | "npm-run-all": "4.1.5", 51 | "postcss": "8.4.21", 52 | "tencentcloud-sdk-nodejs": "4.0.578", 53 | "v-tooltip": "2.1.3", 54 | "vue": "2.7.16", 55 | "vue-router": "3.6.5", 56 | "vue-smooth-dnd": "0.8.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("autoprefixer")], 3 | } 4 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 🥣 OPS/OpenPromptStudio 2 | 3 | ## 提示词工作室 | 可视化编辑提示词 4 | 5 |

6 | OPS-cover 7 |

8 | 9 | [**🥣 立即试试** moonvy.com/apps/ops/](https://moonvy.com/apps/ops/) 10 | 11 | 这是一个旨在把 AIGC 提示词(现在支持 Midjourney)可视化并提供编辑功能的工具,有以下特性 12 | 13 | - 显示英文提示词的中文翻译 14 | - 翻译输入的中文提示词到英文(因为 Midjourney 仅支持英文提示词) 15 | - 为提示词进行分类(普通、样式、质量、命令) 16 | - 轻松的排序、隐藏提示词 17 | - 把提示词可视化结果导出为图片 18 | - 常用提示词词典 19 | - 通过 Notion 管理提示词词典 20 | 21 | ## 使用教程 22 | 23 | 24 | OPS-cover 25 | 26 | 27 | [📺 B 站视频教程](https://www.bilibili.com/video/BV15N411P7D3/?spm_id_from=333.337.search-card.all.click&vd_source=1f6edbc8e03c44932da52d02c0c11c1c) 28 | 29 | ## 如何连接的我的 Notion 来管理自己的词典 30 | 31 | OPS 支持使用 [Notion](https://www.notion.so/) 来管理自己的词典,使用 Notion 管理相对简单,可自定义程度也很高。 32 | 33 | ![ ](./doc/assets/notion-me.gif) 34 | 35 | ### 1. 复制「演示-AIGC 提示词库」 36 | 37 | 复制我们的演示文档的自己的 Notion 工作区中 38 | 39 | [**📕 演示-AIGC 提示词库**](https://moonvy.notion.site/b768c5c1852f4e2fbaee1b4a99f26d49?v=346e91e8114648c59079eeea2d9d56c7) 40 | 41 |

42 | 43 |

44 | 45 | 保持表头定义: `text`, `subType`、`dir`、`lang_zh` 不要变(或者你可以新建一个 Notion 数据库,只要有这些表头 OPS 就能连接的这个数据库) 46 | 47 | #### Notion 表头定义 48 | 49 | | 表头 | 作用 | 50 | | ------- | ------------------------------------------------------- | 51 | | text | 提示词原文(不区分大小写) | 52 | | lang_zh | 对应的中文翻译 | 53 | | subType | 提示词在 OPS 中的分类(`普通`、`风格`、`质量`、`命令`) | 54 | | dir | 词典中的分类,子分类用`/`分隔如:`风格/绘画风格` | 55 | | alias | 别名,可以有多个,用`,` 分隔 | 56 | 57 | ### 2. 创建自己的 Notion 集成插件(integrations) 58 | 59 | 要让 OPS 连接到自己的 Notion 数据库,需要创建一个自己的集成(integrations)。OPS 会通过此集成的权限连接到你的数据库。 60 | 61 | #### 2.1 打开集成开发页面 62 | 63 | 打开 Notion 的集成开发页面 [🔗 www.notion.so/my-integrations](https://www.notion.so/my-integrations) 64 | 点击 「+ new integrations」按钮创建一个新集成插件 65 | 66 |

67 | 68 |

69 | 70 | #### 2.2 创建集成插件 71 | 72 | 在集成插件页面中选择允许访问的 Notion 工作区(Workspace),你的 Notion 数据库需要创建在此工作区下,OPS 才能通过集成插件访问。 73 | 74 |

75 | 76 |

77 | 78 | #### 2.3 获取集成插件 Token 密钥 79 | 80 | 集成插件创建完毕后,复制 Token 秘钥保存下来,你将使用此 Token 作为访问凭证,请妥善保管不要在公开场合泄露。 81 | 82 |

83 | 84 |

85 | 86 | #### 2.4 在数据库页面链接到你的集成 87 | 88 | 集成插件创建后,还需要在你的 Notion 数据库的菜单中连接到你的集成插件: 89 | 90 |

91 | 92 |

93 | 94 | ### 3. 在 OPS 中配置 Notion 95 | 96 | 在 OPS 右上角打开提示词词典,鼠标放在「连接我的 Notion」按钮上,展开设置面板 97 | 98 | - 「Integrations Token」 里面填入前面我们生成的集成 Token 秘钥(秘钥只会保存在浏览器本地(localStorage),不会被上传到任何地方) 99 | 100 | - 「Database ID」里粘贴你 Notion 数据库的访问地址 101 | 102 | - 然后点击「载入」按钮 103 | 104 |

105 | 106 |

107 | 108 | #### 获取 Notion 数据库的访问地址(`DatabaseID`) 109 | 110 | 在 Notion 数据库菜单中点击 「Copy link to view」 就可以了,粘贴 Notion 数据库地址到 OPS 的配置输入框后会自动提取 `DatabaseID` 111 | 112 |

113 | 114 |

115 | 116 | ## 更好的体验 117 | 118 | 你可以在 [zeroG 浏览器](https://moonvy.com/zeroG/) 里让 OPS 119 | 与 Discord 在一个无限画布中使用,获得更好的体验 120 | 121 | ![截屏2023-04-06 15.51.23.png](./doc%2Fassets%2F%E6%88%AA%E5%B1%8F2023-04-06%2015.51.23.png) 122 | 123 | ## 开发者 124 | 125 | 本地运行需要 NodeJS 环境 126 | 127 | 使用 `npm run start` 运行 128 | 129 | 运行打开后访问 `localhost:12833/apps/ops/` 130 | 131 | ### Docker 132 | 133 | 如果你不想安装 NodeJS 环境,可以使用 Docker 运行,参考 [./docker](https://github.com/Moonvy/OpenPromptStudio/tree/master/docker/) 134 | 135 | ### 如何修改默认提示词词典 136 | 137 | 1. 在 [./data/src](https://github.com/Moonvy/OpenPromptStudio/tree/master/data/src) 中编辑 `.csv` 文件,你可以用 Excel、Numbers 或者纯文本编辑器编辑。 138 | 139 | 2. 在 [Notion](https://www.notion.so/) 中编辑([./data/src/notion/fromNotion.js](https://github.com/Moonvy/OpenPromptStudio/tree/master/data/src/notion/fromNotion.js) ) 140 | 141 | ### 翻译服务 142 | 143 | 在 `./server` 文件夹中有一个翻译服务的简单实现,调用腾讯翻译 144 | 你需要申请一个[腾讯机器翻译的账号](https://bobtranslate.com/service/translate/tencent.html)(每月免费额度 500 万字) 145 | 然后在项目根目录创建一个 `.env` 文件写入你的的 `SECRET_ID` 与 `SECRET_KEY` 146 | 147 | `.env`: 148 | 149 | ```env 150 | # 翻译机配置 https://bobtranslate.com/service/translate/tencent.html 151 | TENCENT_SECRET_ID="AKIDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 152 | TENCENT_SECRET_KEY="a5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 153 | 154 | # 自定义翻译服务地址 [可选] (如果你部署在服务器上,通过此配置指定 Web 端访问翻译服务的地址) 155 | # LOCAL_TRANSLATE_HOST="192.168.50.222:3000" 156 | ``` 157 | 158 | 然后运行 `npm run serve` 启动 `OPS 服务` 和 `本地翻译服务` 159 | 160 | #### 自部署 161 | 162 | 如果要部署到自己的服务器,请在 `.env` 文件中配置翻译服务的访问地址: `LOCAL_TRANSLATE_HOST`,如 `192.168.50.222:3000`或者`https://mySite.com`,在 Web 页中会根据此地址发起请求,请根据你部署后实际访问地址来配置。 163 | -------------------------------------------------------------------------------- /server/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import express from "express" 3 | // @ts-ignore 4 | import cors from "cors" 5 | import * as dotenv from "dotenv" 6 | import { translate } from "./translate" 7 | dotenv.config() 8 | const app = express() 9 | 10 | app.use(express.json()) 11 | app.use(cors()) 12 | 13 | app.post("/prompt-studio/translate/prompts", async (req: any, res: any) => { 14 | let input: { words: string[]; to: string } = req.body 15 | let orgText = input.words.join("\n") 16 | const finText = await translate({ text: orgText, to: input.to ?? "zh-cn", server: "tencent" }) 17 | 18 | if (finText) { 19 | let words = finText.split("\n") 20 | res.json(words) 21 | } else { 22 | res.json([]) 23 | } 24 | }) 25 | 26 | const port = 39011 27 | app.listen(port, () => { 28 | console.log(`translate server started on port ${port}`) 29 | }) 30 | -------------------------------------------------------------------------------- /server/tencentTranslate.ts: -------------------------------------------------------------------------------- 1 | import * as tencentcloud from "tencentcloud-sdk-nodejs" 2 | import { useOf } from "fzz" 3 | const TmtClient = tencentcloud.tmt.v20180321.Client 4 | 5 | let useTencentClient = useOf(() => { 6 | const clientConfig = { 7 | credential: { 8 | secretId: process.env.TENCENT_SECRET_ID, 9 | secretKey: process.env.TENCENT_SECRET_KEY, 10 | }, 11 | region: "ap-beijing", 12 | profile: { 13 | httpProfile: { 14 | endpoint: "tmt.tencentcloudapi.com", 15 | }, 16 | }, 17 | } 18 | // 实例化要请求产品的client对象,clientProfile是可选的 19 | // 腾讯云 API 文档 20 | // https://cloud.tencent.com/document/api/551/15619 21 | // 22 | return new TmtClient(clientConfig) 23 | }) 24 | 25 | export async function tencentTranslate(input: { text: string; from?: string; to?: string }) { 26 | let params: any = { 27 | SourceText: input.text, 28 | Source: langCodeFilter(input?.from ?? "auto"), 29 | Target: langCodeFilter(input?.to ?? "zh"), 30 | ProjectId: 0, 31 | } 32 | let re = await useTencentClient().TextTranslate(params) 33 | if (re.TargetText) return re.TargetText 34 | } 35 | 36 | function langCodeFilter(lang: string) { 37 | if (lang == "zh-cn") return "zh" 38 | return lang 39 | } 40 | -------------------------------------------------------------------------------- /server/translate.ts: -------------------------------------------------------------------------------- 1 | // Created on 2023/04/12 - 11:40 2 | import { tencentTranslate } from "./tencentTranslate" 3 | 4 | export async function translate(input: { 5 | text: string 6 | from?: string 7 | to?: string 8 | server?: string 9 | }): Promise { 10 | if (input.text == "") return "" 11 | let re 12 | 13 | try { 14 | if (input?.server === "google") { 15 | // todo 16 | } else if (input?.server === "tencent") { 17 | re = await tencentTranslate({ text: input.text, from: input?.from, to: input?.to }) 18 | console.log("[translate]", input.text.length + "words.", { input, re }) 19 | } 20 | } catch (e) { 21 | console.error('[translate] translate failed.', input, e) 22 | } 23 | return re 24 | } 25 | -------------------------------------------------------------------------------- /src/Boot/index.ts: -------------------------------------------------------------------------------- 1 | import { bootVue } from "./vue.boot" 2 | import { I18n } from "@moonvy/app-core" 3 | 4 | export function boot() { 5 | ;(globalThis).I18n = I18n 6 | ;(globalThis).t = I18n.t 7 | 8 | bootVue((Vue) => { 9 | Vue.prototype.t = I18n.t 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/Boot/vue.boot.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import FloatingVue from 'floating-vue' 3 | import 'floating-vue/dist/style.css' 4 | 5 | // ----------------- 全局组件 -----–––––––––––––––- 6 | import { Icon as vIcon } from "@iconify/vue2" 7 | // ----------------------–––––––––––––––- 8 | import vRoot from "../Pages/Root.vue" 9 | import { getPagesRouter } from "../Pages" 10 | 11 | export function bootVue(setVueHandler?: (VueConstructor: typeof Vue) => any) { 12 | // 设置 Vue 13 | if (setVueHandler) setVueHandler(Vue) 14 | // --------–––––––––––––––––––––––––––––– 15 | Vue.component("Icon", vIcon) 16 | Vue.use(FloatingVue) 17 | // --------–––––––––––––––––––––––––––––– 18 | let router = getPagesRouter(Vue) 19 | let vueIns = new Vue({ 20 | el: "#app-root", 21 | router, 22 | render: (h) => h(vRoot), 23 | }) 24 | 25 | // 注册变量 26 | Object.assign(window, { 27 | vueIns, 28 | _Vue: Vue, 29 | }) 30 | return { Vue, vueIns } 31 | } 32 | -------------------------------------------------------------------------------- /src/Compoents/PromptDict/PromptDict.vue: -------------------------------------------------------------------------------- 1 | 2 | 71 | 263 | 402 | -------------------------------------------------------------------------------- /src/Compoents/PromptDict/getDictData.ts: -------------------------------------------------------------------------------- 1 | import { PromptItem } from "../PromptEditor/Sub/PromptItem" 2 | 3 | import { useDatabaseServer } from "../PromptEditor/Lib/DatabaseServer/DatabaseServer" 4 | 5 | export async function getDictData(onlyMyNotion?: boolean): Promise { 6 | let database = useDatabaseServer() 7 | let define = await database.getPromptsDefine({ onlyMyNotion }) 8 | 9 | let dirMap: any = {} 10 | for (let key in define) { 11 | let item = define[key] 12 | let dirPath = item.dir 13 | if (item.isAlias) continue 14 | if (dirPath) { 15 | let newWords 16 | if (item.subType == "command") { 17 | let sampleCmds = item.sampleCmds ?? [""] 18 | newWords = sampleCmds.map((cmd) => ({ 19 | text: item.text, 20 | langText: `${item.lang_zh}`, 21 | subType: item.subType, 22 | rawText: `${item.text} ${cmd}`, 23 | desc: item.desc, 24 | args: [cmd], 25 | })) 26 | } else { 27 | newWords = [ 28 | { 29 | text: item.text, 30 | langText: item.lang_zh, 31 | subType: item.subType, 32 | desc: item.desc, 33 | }, 34 | ] 35 | } 36 | 37 | let dirNames = dirPath.split("/") 38 | let [dirName, subDirName] = dirNames 39 | if (!dirMap[dirName]) { 40 | dirMap[dirName] = { 41 | name: dirName, 42 | children: [], 43 | words: [], 44 | } 45 | } 46 | if (subDirName) { 47 | let subDir = dirMap[dirName].children.find((subDir: any) => subDir.name === subDirName) 48 | if (!subDir) { 49 | subDir = { 50 | name: subDirName, 51 | children: [], 52 | words: [], 53 | } 54 | dirMap[dirName].children.push(subDir) 55 | } 56 | subDir.words.push(...newWords) 57 | } else { 58 | dirMap[dirName].words.push(...newWords) 59 | } 60 | } 61 | } 62 | 63 | let dictDirs = Object.values(dirMap) 64 | 65 | dictDirs.forEach((dir: any) => { 66 | dir.words = lists(dir.words) 67 | dir.children.forEach((subDir: any) => { 68 | subDir.words = lists(subDir.words) 69 | }) 70 | }) 71 | 72 | function lists(words: any[]) { 73 | return words.map((word) => { 74 | let item = PromptItem.fromWord(word) 75 | item.state.isDict = true 76 | return item 77 | }) 78 | } 79 | 80 | return dictDirs 81 | } 82 | 83 | export interface IDictDir { 84 | name: string 85 | children: IDictDir[] 86 | words: string[] 87 | } 88 | 89 | export interface IWordDesc { 90 | text: string 91 | lang_zh: string 92 | subType?: string 93 | tags?: string[] 94 | } 95 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad2.jpg -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad221126.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad221126.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad3.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad3b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad3b.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad3c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad3c.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad4.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad5.jpg -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad6.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Assets/ad_moonvy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moonvy/OpenPromptStudio/710d14e6969f060b3b944ff07bde14795f45da28/src/Compoents/PromptEditor/Assets/ad_moonvy.png -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Components/PromptItem/PromptItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 51 | 233 | 347 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Components/PromptItem/dnd.ts: -------------------------------------------------------------------------------- 1 | import setMoveable from "../../Lib/setMoveable" 2 | import { findParentElement } from "../../Lib/findParentElement" 3 | 4 | export default function initDnd(el: HTMLElement, vueIns: any) { 5 | let ghost: HTMLElement | null = null 6 | let isStart = false 7 | let elStartX = 0, 8 | elStartY = 0 9 | 10 | setMoveable(el, { 11 | callback_start: (e) => { 12 | let rect = (e.startEvent!.target as any).getBoundingClientRect() 13 | elStartX = rect.x 14 | elStartY = rect.y 15 | return false 16 | }, 17 | 18 | callback_move: (e) => { 19 | // console.log("[dnd:move]", e) 20 | if (!isStart && Math.abs(e.startOffsetX) + Math.abs(e.startOffsetY) > 10) { 21 | dndStart() 22 | } 23 | if (ghost) { 24 | ghost.style.left = ` ${elStartX + e.startOffsetX}px` 25 | ghost.style.top = `${elStartY + e.startOffsetY}px` 26 | // console.log("[dnd:move]", { startEvent: e.startEvent, elStartX, elStartY }) 27 | } 28 | return false 29 | }, 30 | callback_end: (e) => { 31 | console.log("[dnd:end]", e) 32 | 33 | // 词典拖拽到工作区 34 | if (vueIns.$el.classList.contains("dict-word")) { 35 | } 36 | //工作区拖拽到工作区 37 | else { 38 | } 39 | 40 | let dropEl = e.event?.target 41 | if (dropEl.classList.contains("dnd-slot-pre") || dropEl.classList.contains("dnd-slot-next")) { 42 | let itemEl = findParentElement(dropEl, (el) => el.classList.contains("PromptItem")) 43 | if (itemEl) { 44 | let itemVueIns = (itemEl).__vue__ 45 | let item = itemVueIns.item 46 | let list = itemVueIns.list 47 | 48 | let orgItem = vueIns.item 49 | let orgList = vueIns.list 50 | 51 | orgList?.removePrompt(orgItem) 52 | let offset = dropEl.classList.contains("dnd-slot-next") ? 1 : 0 53 | 54 | // console.log("[dnd:slot]", { item, list, orgItem, orgList }, offset, { itemVueIns, itemEl }) 55 | orgItem = list.insertPromptOf(item, orgItem, offset) 56 | 57 | orgItem.data.word.subType = item.data.word.subType 58 | orgItem.data.word.lv = item.data.word.lv 59 | itemVueIns.$emit("update") 60 | } 61 | } else if (dropEl.classList.contains("PromptList-list")) { 62 | let itemEl = findParentElement(dropEl, (el) => el.classList.contains("PromptList")) 63 | console.log("[dnd:itemEl]", { dropEl, itemEl }) 64 | if (itemEl) { 65 | let itemVueIns = (itemEl).__vue__ 66 | let list = itemVueIns.list 67 | let orgItem = vueIns.item 68 | let orgList = vueIns.list 69 | console.log("[dnd:slot]", { list, orgItem, orgList }, { itemVueIns, itemEl }) 70 | orgList?.removePrompt(orgItem) 71 | orgItem = list.insertPromptLast(orgItem) 72 | 73 | orgItem.data.word.subType = list.data.type 74 | itemVueIns.$emit("update") 75 | } 76 | } 77 | 78 | dndEnd() 79 | }, 80 | }) 81 | 82 | function dndStart() { 83 | document.body.classList.toggle("dnd-ing", true) 84 | isStart = true 85 | ghost = el.cloneNode(true) 86 | ghost.classList.toggle("dnd-ghost") 87 | document.body.appendChild(ghost) 88 | el.classList.toggle("dnd-org", true) 89 | } 90 | function dndEnd() { 91 | ghost?.remove() 92 | ghost = null 93 | isStart = false 94 | el.classList.toggle("dnd-org", false) 95 | document.body.classList.toggle("dnd-ing", false) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Components/PromptItem/subType.scss: -------------------------------------------------------------------------------- 1 | .PromptItem { 2 | &.subType-quality { 3 | --bk: linear-gradient(#45507a, #69728b); 4 | --bk-desc: linear-gradient(#6f80b2, #5475f6); 5 | } 6 | &.subType-command { 7 | --bk: linear-gradient(#584589, #7774a0); 8 | --bk-desc: linear-gradient(#8d79c0, #7a78dc); 9 | } 10 | &.subType-style { 11 | --bk: linear-gradient(#406e6d, #749b98); 12 | --bk-desc: linear-gradient(#75a19f, #31aaa3); 13 | } 14 | &.subType-eg { 15 | --bk: linear-gradient(#844444, #7c6c6c); 16 | --bk-desc: linear-gradient(#da4927, #c78a6e); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Components/PromptList/PromptList.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 22 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Components/PromptMenu/PromptMenu.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 60 | 128 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Components/PromptWork/Components/AddButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 18 | 21 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/DatabaseServer/DatabaseServer.ts: -------------------------------------------------------------------------------- 1 | import { fetchFromNotion } from "./lib/fetchFromNotion" 2 | 3 | export interface IPromptDefineItem { 4 | text: string 5 | subType?: string 6 | desc?: string 7 | dir?: string 8 | lang_zh?: string 9 | sampleCmds?: string[] 10 | isAlias?: boolean 11 | tags?: string[] 12 | } 13 | 14 | export class DatabaseServer { 15 | localPromptDefineMap: { [key: string]: IPromptDefineItem } = {} 16 | notionPromptDefineMap: { [key: string]: IPromptDefineItem } = {} 17 | isReady: null | Promise = null 18 | constructor() {} 19 | async ready() { 20 | if (this.isReady != null) return this.isReady 21 | this.isReady = this.init() 22 | return this.isReady 23 | } 24 | async init() { 25 | // localJson 26 | let localPromptDescMap = await (await fetch("./localPromptDefineMap.json")).json() 27 | // console.log('localPromptDescMap',localPromptDescMap) 28 | this.localPromptDefineMap = localPromptDescMap 29 | return true 30 | } 31 | async queryPromptsDefine(prompts: string[]): Promise { 32 | await this.ready() 33 | let reuslt = [] 34 | for (let prompt of prompts) { 35 | let re = this.localPromptDefineMap[prompt?.toLowerCase()] 36 | if (re) { 37 | reuslt.push(re) 38 | } else { 39 | reuslt.push(null) 40 | } 41 | } 42 | return reuslt 43 | } 44 | 45 | async getPromptsDefine(options?: { onlyMyNotion?: boolean }) { 46 | await this.ready() 47 | if (options?.onlyMyNotion) { 48 | return this.notionPromptDefineMap 49 | } else { 50 | return this.localPromptDefineMap 51 | } 52 | } 53 | 54 | async fetchNotion(options: { apiKey: string; databaseId: string }) { 55 | console.log("fetchNotion options", options) 56 | let { defineMap, me } = await fetchFromNotion(options) 57 | this.notionPromptDefineMap = defineMap 58 | Object.assign(this.localPromptDefineMap, defineMap) 59 | return { defineMap, me } 60 | } 61 | } 62 | 63 | let databaseServer: DatabaseServer 64 | export function useDatabaseServer() { 65 | if (!databaseServer) databaseServer = new DatabaseServer() 66 | return databaseServer 67 | } 68 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/DatabaseServer/lib/fetchFromNotion.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "@notionhq/client" 2 | import { cloneDeep } from "lodash" 3 | 4 | export async function fetchFromNotion(options: { apiKey: string; databaseId: string }) { 5 | let { databaseId: database_id, apiKey } = options 6 | 7 | let defineMap: any = {} 8 | const subTypeMap: any = { 9 | 普通: "normal", 10 | 风格: "style", 11 | 质量: "quality", 12 | 命令: "command", 13 | 负面: "eg", 14 | } 15 | 16 | const notion = new Client({ 17 | auth: apiKey, 18 | baseUrl: `https://worker-cors.yarna-moonvy.workers.dev/https://api.notion.com`, 19 | }) 20 | 21 | let i = 0 22 | await once() 23 | async function once(start_cursor?: string) { 24 | let re = await notion.databases.query({ database_id, start_cursor }) 25 | console.log(`[notion] get page${i} :${start_cursor ?? "init"}`, { re }) 26 | re.results.forEach((page: any, index) => { 27 | // console.log(`[notion] page${index}`, { page }) 28 | let text = page.properties?.text?.title?.[0]?.text?.content 29 | let desc = page.properties?.desc?.rich_text?.[0]?.text?.content 30 | let lang_zh = page.properties?.["lang_zh"].rich_text?.[0]?.text?.content 31 | let tags = page.properties?.tags?.multi_select?.map((x: any) => x.name) 32 | let subType = page.properties?.subType?.select?.name 33 | let dir = page.properties?.dir?.select?.name 34 | let alias = page.properties?.alias?.rich_text?.[0]?.text?.content 35 | subType = subTypeMap[subType] ?? "normal" 36 | let item = { text, desc, lang_zh, subType, dir, tags, alias } 37 | if (!text) return 38 | defineMap[item?.text?.toLowerCase()] = item 39 | if (typeof alias === "string") { 40 | alias.split(/[,,]/).forEach((text) => { 41 | text = text.trim() 42 | if (text != "") { 43 | let cloneItem = cloneDeep(item) 44 | cloneItem.text = text 45 | ;(cloneItem as any).isAlias = true 46 | defineMap[text.toLowerCase()] = cloneItem 47 | } 48 | }) 49 | } 50 | }) 51 | if (re.has_more) { 52 | await once(re.next_cursor!) 53 | } 54 | } 55 | console.log(`[notion] import ${Object.keys(defineMap).length} items.`) 56 | 57 | let databaseInfo: any = await notion.databases.retrieve({ database_id }) 58 | let me = { 59 | name: databaseInfo?.title?.[0]?.text?.content, 60 | url: databaseInfo?.url, 61 | } 62 | return { defineMap, me } 63 | } 64 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/DnD/index.ts: -------------------------------------------------------------------------------- 1 | export function dndInit() { 2 | document.body.addEventListener("mousedown", mousedown) 3 | } 4 | 5 | function mousedown(e: MouseEvent) {} 6 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/chinesePercentage.ts: -------------------------------------------------------------------------------- 1 | export function chinesePercentage(text: string) { 2 | var chineseCount = 0 3 | for (var i = 0; i < text.length; i++) { 4 | var charCode = text.charCodeAt(i) 5 | if (charCode >= 0x4e00 && charCode <= 0x9fa5) { 6 | // console.log("c",text[i]) 7 | chineseCount++ 8 | } 9 | } 10 | return (chineseCount / text.length) * 100 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/findParentElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在元素的父级链上找元素 3 | * @param el 4 | * @param findFunc 5 | */ 6 | export function findParentElement( 7 | el: HTMLElement, 8 | findFunc: (el: HTMLElement, deep: number) => boolean, 9 | deep = 0 10 | ): HTMLElement | undefined { 11 | if (el.parentElement) { 12 | if (findFunc(el.parentElement, deep)) { 13 | return el.parentElement 14 | } else { 15 | return findParentElement(el.parentElement, findFunc, deep + 1) 16 | } 17 | } else { 18 | return undefined 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/parsePrompts/parsePrompts.ts: -------------------------------------------------------------------------------- 1 | import { midjourneyParse, midjourneyStringify } from "./parsers/Midjourney" 2 | import {stableDiffusionWebUIParse, stableDiffusionWebUIStringify} from "./parsers/StableDiffusionWebUI" 3 | import { useDatabaseServer } from "../DatabaseServer/DatabaseServer" 4 | import { IPromptGroup } from "../../Sub/PromptWork" 5 | 6 | export interface IPromptWord { 7 | id: string 8 | text: string 9 | rawText: string 10 | type: PromptWordType 11 | group?: string 12 | langText?: string 13 | subType?: string 14 | desc?: string 15 | link?: string 16 | args?: string[] 17 | dir?: string 18 | lv?: number 19 | alv?: number 20 | isEg?: boolean 21 | } 22 | export enum PromptWordType { 23 | Word = "word", 24 | } 25 | export interface IPromptParseResult { 26 | words: IPromptWord[] 27 | } 28 | 29 | export async function parsePrompts( 30 | text: string, 31 | options: { parser: string; minify?: boolean; zh2en?: boolean } = { 32 | parser: "midjourney", 33 | } 34 | ): Promise { 35 | let words: IPromptWord[] 36 | if (options.parser === "midjourney") { 37 | words = await midjourneyParse(text, options) 38 | } else if (options.parser === "stable-diffusion-webui") { 39 | words = await stableDiffusionWebUIParse(text, options) 40 | } else { 41 | throw new Error(`err ParsePrompts not support this parser:${options.parser}`) 42 | } 43 | if (options.minify) { 44 | words = wordsDeduplicat(words) 45 | } 46 | 47 | let dataserver = useDatabaseServer() 48 | let promptsDefine = await dataserver.queryPromptsDefine(words.map((w) => w.text)) 49 | let localLang = "zh-cn" // todo: 根据环境和设置选择语言 50 | let langKey = `lang_${localLang}` 51 | 52 | let result = { words: [], egWords: [] } 53 | 54 | let indexMap: { [id: string]: number } = {} 55 | words.forEach((word, i) => { 56 | if (indexMap[word.rawText] !== undefined) { 57 | indexMap[word.rawText] = indexMap[word.rawText] + 1 58 | } else { 59 | indexMap[word.rawText] = 0 60 | } 61 | word.id = word.rawText + "__" + indexMap[word.rawText] 62 | 63 | let define = promptsDefine[i] 64 | if (define) { 65 | if (define.lang_zh) word.langText = define.lang_zh 66 | if (define.subType) word.subType = define.subType 67 | if (define.desc) word.desc = define.desc 68 | if (define.dir) word.dir = define.dir 69 | } 70 | result.words.push(word) 71 | }) 72 | // console.log("parsePrompts words", JSON.stringify(result, null, 2)) 73 | return result 74 | } 75 | 76 | export function stringifyPrompts(groups: IPromptGroup[] = [], options: { parser: string }) { 77 | let prompts: string 78 | if (options.parser === "midjourney") { 79 | prompts = midjourneyStringify(groups) 80 | } else if (options.parser === "stable-diffusion-webui") { 81 | prompts = stableDiffusionWebUIStringify(groups) 82 | } else { 83 | throw new Error(`err ParsePrompts not support this parser:${options.parser}`) 84 | } 85 | return prompts 86 | } 87 | 88 | function wordsDeduplicat(words: IPromptWord[]): IPromptWord[] { 89 | let map: any = {} 90 | words.forEach((word) => (map[word.rawText] = word)) 91 | return Object.values(map) 92 | } 93 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/parsePrompts/parsers/Midjourney/index.ts: -------------------------------------------------------------------------------- 1 | import { IPromptWord, PromptWordType } from "../../parsePrompts" 2 | import { translateZh2En } from "../../../translatePrompts" 3 | import { IPromptGroup } from "../../../../Sub/PromptWork" 4 | 5 | export async function midjourneyParse(text: string, options?: { zh2en?: boolean }): Promise { 6 | let re = paresCommands(text) 7 | text = re.text 8 | 9 | // 按权重划分权重 10 | let textListByWeight = text.split("::") 11 | let words: IPromptWord[] = [] 12 | 13 | for (let i = 0; i < textListByWeight.length; i++) { 14 | let groupIndex = i 15 | let text = textListByWeight[i] 16 | 17 | let lv = 1 18 | let nextText = textListByWeight[groupIndex + 1] 19 | if (nextText) { 20 | const REG_Weight = /^[\.\-0-9]+/ 21 | let weightText = REG_Weight.exec(nextText)?.[0] 22 | if (weightText) { 23 | textListByWeight[groupIndex + 1] = nextText.slice(weightText.length) 24 | lv = Number.parseFloat(weightText) 25 | } 26 | } 27 | 28 | let texts = split(text).filter((text) => text != "") 29 | 30 | if (options?.zh2en) texts = await translateZh2En(texts) 31 | 32 | texts.forEach((text, i) => { 33 | let word = { text, type: PromptWordType.Word, rawText: text, group: `${groupIndex}::${lv}`, lv } 34 | words.push(word) 35 | }) 36 | } 37 | 38 | let lastGroup = words[words.length - 1]?.group 39 | 40 | re.commands.forEach((command) => { 41 | words.push({ 42 | id: command.command, 43 | text: command.command, 44 | rawText: command.rawText, 45 | type: PromptWordType.Word, 46 | subType: "command", 47 | group: lastGroup, 48 | args: command.args, 49 | }) 50 | }) 51 | // console.log("midjourneyParse words", JSON.stringify(words)) 52 | return words 53 | } 54 | 55 | export function midjourneyStringify(groups: IPromptGroup[] = []) { 56 | let finText = "" 57 | let commands: string[] = [] 58 | let i = 0, 59 | len = groups.length 60 | for (let group of groups) { 61 | let chars: string[] = [] 62 | for (let list of group.lists) { 63 | for (let item of list.items) { 64 | if (item.data.disabled) continue 65 | if (item.data.word.subType === "command") { 66 | commands.push(item.data.word.rawText!) 67 | } else { 68 | chars.push(item.data.word.rawText!) 69 | } 70 | } 71 | } 72 | finText += chars.filter((x) => x != "").join(", ") 73 | if (len > 0) { 74 | if (i < len - 1) { 75 | finText += ` ::${group.groupLv == 1 ? "" : group.groupLv} ` 76 | } else { 77 | if (group.groupLv && group.groupLv != 1) { 78 | finText += ` ::${group.groupLv} ` 79 | } 80 | } 81 | } 82 | i++ 83 | } 84 | if (commands.length > 0) finText += ` ${commands.join(" ")}` 85 | 86 | // console.log("exportPrompts") 87 | return finText.trim() 88 | } 89 | 90 | function split(text: string) { 91 | // 使用正则表达式匹配被大括号包含的内容以及其他以符号分隔的内容 92 | const REG_SPLIT = /{[^}]*}|[^{,,|\[\]\(\)\n]+/g; 93 | let matches = text.match(REG_SPLIT) || []; 94 | return matches.map(word => word.trim()); 95 | } 96 | 97 | /** 解析命令 */ 98 | function paresCommands(text: string) { 99 | // 匹配带一个参数的命令 100 | const REG_COMMAND_ARG = 101 | /(--|—)(version|v|aspect|ar|quality|q|chaos|c|seed|sameseed|stop|style|stylize|s|no|niji|repeat|iw|width|w|height|h+) ([\w\.:]+)/g 102 | // 匹配任意无命令 103 | const REG_ALL = /(--|—)([a-zA-Z0-9]+)/g 104 | 105 | let commands: { command: string; args: string[]; rawText: string }[] = [] 106 | 107 | text = text.replace(REG_COMMAND_ARG, (substring: string, ...args: any[]) => { 108 | commands.push({ command: args[0] + args[1], args: [args[2]], rawText: substring }) 109 | return "" 110 | }) 111 | text = text.replace(REG_ALL, (substring: string, ...args: any[]) => { 112 | commands.push({ command: args[0] + args[1], args: [], rawText: substring }) 113 | return "" 114 | }) 115 | return { text, commands } 116 | } 117 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/parsePrompts/parsers/StableDiffusionWebUI/index.ts: -------------------------------------------------------------------------------- 1 | import { IPromptWord, PromptWordType } from "../../parsePrompts" 2 | import { translateZh2En } from "../../../translatePrompts" 3 | import { round } from "lodash" 4 | import {IPromptGroup} from "../../../../Sub/PromptWork"; 5 | /** 解析命令 */ 6 | export async function stableDiffusionWebUIParse(text: string, options?: { zh2en?: boolean }): Promise { 7 | // 因为使用较少,暂不支持 Prompt matrix https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#prompt-matrix 8 | // let textListByMatrix = text.split("|") 9 | let words: IPromptWord[] = [] 10 | 11 | let texts = split(text).filter((t) => t != "") 12 | 13 | console.log("[stableDiffusionWebUIParse]", { texts }) 14 | if (options?.zh2en) texts = await translateZh2En(texts) 15 | 16 | texts.forEach((text, i) => { 17 | let { word } = paresWord(text) 18 | words.push(word) 19 | }) 20 | 21 | return words 22 | } 23 | 24 | function split(text: string) { 25 | return text.split(/[,,\n]/).map((word) => word.trim()) 26 | } 27 | 28 | /** 解析单个字符 */ 29 | function paresWord(text: string) { 30 | const REG_Attention_number = /^\(([^:]+?):([0-9\.]+?)\)$/ // (xxx:2) 31 | const REG_Attention_adds = /^(\(+)(.+?)(\)+)$/ // ((((xxx))) 32 | const REG_Attention_subs = /^(\[+)(.+?)(\]+)$/ // [[xxx]] 33 | const REG_ExtraNetworks = /^<(.+?):(.+?):(.*?)>$/ // 34 | const Editing_from_to_when = /^\[(.*?):(.*?):(.*?)\]$/ // [from:to:when] 35 | const Editing_to_when = /^\[(.*?):(.*?)\]$/ // [from:to:when] 36 | 37 | let lv, alv, displayText 38 | let word 39 | 40 | let re: any 41 | // [from:to:when] https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#prompt-editing 42 | if (mactch(Editing_from_to_when.test(text))) { 43 | displayText = text 44 | word = { 45 | text: displayText, 46 | type: PromptWordType.Word, 47 | rawText: text, 48 | ...Commands.Editing_from_to_when, 49 | } 50 | } 51 | // [from:when] 52 | else if (mactch(Editing_to_when.test(text))) { 53 | displayText = text 54 | word = { 55 | text: displayText, 56 | type: PromptWordType.Word, 57 | rawText: text, 58 | ...Commands.Editing_to_when, 59 | } 60 | } 61 | // (xxx:2) 62 | else if (mactch(REG_Attention_number.exec(text))) { 63 | displayText = re[1] 64 | lv = re[2] 65 | word = { text: displayText, lv, type: PromptWordType.Word, rawText: text } 66 | } 67 | // ((((xxx))) 68 | else if (mactch(REG_Attention_adds.exec(text))) { 69 | displayText = re[2] 70 | alv = re[1].length 71 | lv = round(Math.pow(1.1, alv), 2) 72 | word = { text: displayText, lv, alv, type: PromptWordType.Word, rawText: text } 73 | } 74 | // [[[xxx]]] 75 | else if (mactch(REG_Attention_subs.exec(text))) { 76 | displayText = re[2] 77 | alv = -re[1].length 78 | lv = round(1 / Math.pow(1.1, Math.abs(alv)), 2) 79 | word = { text: displayText, lv, alv, type: PromptWordType.Word, rawText: text } 80 | } 81 | // 82 | else if (mactch(REG_ExtraNetworks.exec(text))) { 83 | word = { 84 | text: text, 85 | type: PromptWordType.Word, 86 | rawText: text, 87 | langText: `${re[1]}`, 88 | subType: "command", 89 | link: `https://www.google.com/search?q=${encodeURIComponent(re[2])}%20 civitai.com `, 90 | } 91 | } else { 92 | displayText = text 93 | word = { text: displayText, type: PromptWordType.Word, rawText: text } 94 | } 95 | 96 | // console.log("[paresWordInfo]", word) 97 | return { displayText, lv, word } 98 | 99 | function mactch(v: any) { 100 | re = v 101 | return v 102 | } 103 | } 104 | 105 | const Commands = { 106 | Editing_from_to_when: { 107 | subType: "command", 108 | langText: "剪辑 (Editing) ", 109 | desc: `[起始词:结束词:切换时机] \n在生成采样的过程中,从一个关键词切换到另一个关键词。\n切换时机:大于 1 的整数代表指定步数,如果小于 1 的小数代表百分比时间。\n结束词: 如果为空表示切换时删除起始词`, 110 | link: `https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#prompt-editing`, 111 | }, 112 | Editing_to_when: { 113 | subType: "command", 114 | langText: "剪辑 (Editing) ", 115 | desc: `[起始词:切换时机] \n在生成采样的过程中,从一个关键词切换到另一个关键词。\n切换时机:大于 1 的整数代表指定步数,如果小于 1 的小数代表百分比时间。`, 116 | link: `https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#prompt-editing`, 117 | }, 118 | } 119 | 120 | 121 | export function stableDiffusionWebUIStringify(groups: IPromptGroup[] = []) { 122 | let finText = "" 123 | let commands: string[] = [] 124 | let i = 0, 125 | len = groups.length 126 | for (let group of groups) { 127 | let chars: string[] = [] 128 | for (let list of group.lists) { 129 | for (let item of list.items) { 130 | if (item.data.disabled) continue 131 | if (item.data.word.subType === "command") { 132 | commands.push(item.data.word.rawText!) 133 | } else { 134 | chars.push(item.data.word.rawText!) 135 | } 136 | } 137 | } 138 | finText += chars.filter((x) => x != "").join(", ") 139 | finText += ` ` 140 | i++ 141 | } 142 | if (commands.length > 0) finText += ` ${commands.join(" ")}` 143 | 144 | // console.log("exportPrompts") 145 | return finText.trim() 146 | } 147 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/parsePrompts/test/paresPrompts.test.ts: -------------------------------------------------------------------------------- 1 | import { parsePrompts } from "../ParsePrompts" 2 | 3 | test("parsePrompts", () => { 4 | let re = parsePrompts( 5 | "Portrait Of a Dragon Princess, Makeup, Explosion Of Liquid Splash, pastel colors, Intricate, Highly Detailed, Fantasy Background, Illustration, Sharp Focus, Dramatic Lighting, Trending On Artstation, Cinematic, 8k, Concept Art, Elegant, Reflections" 6 | ) 7 | 8 | console.log(re) 9 | }) 10 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/setMoveable.ts: -------------------------------------------------------------------------------- 1 | export interface IMoveableOptions { 2 | /** 开始回调,返回 True 提前终止移动*/ 3 | callback_start?: (e: IMoveInfo) => boolean 4 | /** 移动回调,返回 True 提前终止移动*/ 5 | callback_move?: (e: IMoveInfo) => boolean 6 | /** 开始回调,关闭*/ 7 | callback_end?: (e: IMoveInfo) => void 8 | /** 传递 passive 参数给 move 事件, 默认为 ture */ 9 | passive?: boolean 10 | /** 元素移动 */ 11 | setMove?: boolean 12 | /** 只允许指定类名的元素移动 */ 13 | allowClass?: string 14 | /** Vue 事件如果给定,会在其销毁时自动取消监听*/ 15 | vueIns?: any 16 | } 17 | 18 | export interface IMoveInfo { 19 | startEvent: MouseEvent | TouchEvent | null 20 | event: MouseEvent | TouchEvent | null 21 | startX: number 22 | startY: number 23 | x: number 24 | y: number 25 | offsetX: number 26 | offsetY: number 27 | startOffsetX: number 28 | startOffsetY: number 29 | startStyleX: number 30 | startStyleY: number 31 | 32 | lastStartOffsetX: number 33 | lastStartOffsetY: number 34 | fixStartOffsetX: number 35 | fixStartOffsetY: number 36 | } 37 | 38 | /** 39 | * 创建基于元素的移动事件监听 40 | * @param el 41 | * @param options 42 | */ 43 | export default function setMoveable(el: HTMLElement, options: IMoveableOptions) { 44 | const doc = document.body 45 | let moveInfo: IMoveInfo = {} 46 | 47 | let eventOptions = options.passive ?? true 48 | el.addEventListener("mousedown", start, false) 49 | // el.addEventListener("contextmenu", start, false) 50 | el.addEventListener("touchstart", start, false) 51 | 52 | function start(e: MouseEvent | TouchEvent) { 53 | if (options.allowClass) { 54 | if (!(e)?.target?.classList.contains(options.allowClass)) return 55 | } 56 | 57 | let clientX, clientY 58 | if (typeof TouchEvent != "undefined" && e instanceof TouchEvent) { 59 | clientX = e.touches[0].clientX 60 | clientY = e.touches[0].clientY 61 | } else { 62 | clientX = (e).clientX 63 | clientY = (e).clientY 64 | } 65 | 66 | moveInfo.event = e 67 | moveInfo.x = clientX 68 | moveInfo.y = clientY 69 | moveInfo.offsetX = 0 70 | moveInfo.offsetY = 0 71 | moveInfo.startOffsetX = 0 72 | moveInfo.startOffsetY = 0 73 | moveInfo.lastStartOffsetX = 0 74 | moveInfo.lastStartOffsetY = 0 75 | moveInfo.fixStartOffsetX = 0 76 | moveInfo.fixStartOffsetY = 0 77 | moveInfo.startX = clientX 78 | moveInfo.startY = clientY 79 | moveInfo.startStyleX = el.style.left ? parseFloat(el.style.left) : el.getBoundingClientRect().left 80 | moveInfo.startStyleY = el.style.top ? parseFloat(el.style.top) : el.getBoundingClientRect().top 81 | moveInfo.startEvent = e 82 | 83 | // console.log("[setMoveable]", moveInfo, e) 84 | if (options.callback_start) { 85 | let stop = options.callback_start(moveInfo) 86 | if (stop) return 87 | } 88 | 89 | // 设置 class 90 | el.classList.toggle("moveable-moving", true) 91 | doc.classList.toggle("moveable-doc-moving", true) 92 | registEvent() 93 | } 94 | 95 | function move(e: MouseEvent | TouchEvent) { 96 | let clientX, clientY 97 | if (typeof TouchEvent != "undefined" && e instanceof TouchEvent) { 98 | clientX = e.touches[0].clientX 99 | clientY = e.touches[0].clientY 100 | } else { 101 | if ((e).buttons === 0) { 102 | end() 103 | return 104 | } 105 | clientX = (e).clientX 106 | clientY = (e).clientY 107 | } 108 | 109 | moveInfo.event = e 110 | moveInfo.offsetX = clientX - moveInfo.x 111 | moveInfo.offsetY = clientY - moveInfo.y 112 | moveInfo.startOffsetX = clientX - moveInfo.startX 113 | moveInfo.startOffsetY = clientY - moveInfo.startY 114 | moveInfo.fixStartOffsetX = moveInfo.startOffsetX - moveInfo.lastStartOffsetX 115 | moveInfo.fixStartOffsetY = moveInfo.startOffsetY - moveInfo.lastStartOffsetY 116 | moveInfo.lastStartOffsetX = moveInfo.startOffsetX 117 | moveInfo.lastStartOffsetY = moveInfo.startOffsetY 118 | moveInfo.x = clientX 119 | moveInfo.y = clientY 120 | 121 | 122 | if (options.callback_move) { 123 | let stop = options.callback_move(moveInfo) 124 | if (stop) return 125 | } 126 | 127 | if (options.setMove) { 128 | el.style.left = moveInfo.startStyleX + moveInfo.startOffsetX + "px" 129 | el.style.top = moveInfo.startStyleY + moveInfo.startOffsetY + "px" 130 | 131 | // el.style.transform = `translate(${moveInfo.startOffsetX}px, ${moveInfo.startOffsetY }px)` 132 | } 133 | 134 | e.preventDefault() 135 | } 136 | 137 | function end() { 138 | moveInfo.startEvent = null 139 | unRegistEvent() 140 | if (options.callback_end) options.callback_end(moveInfo) 141 | 142 | // 设置 class 143 | el.classList.toggle("moveable-moving", false) 144 | doc.classList.toggle("moveable-doc-moving", false) 145 | 146 | // 如果最近移动过(距离大于1),设置一个临时 class 标记 147 | if (moveInfo.startOffsetX + moveInfo.startOffsetY > 1) { 148 | el.classList.toggle("moveable-moved-recent", true) 149 | setTimeout(() => { 150 | el.classList.toggle("moveable-moved-recent", false) 151 | }, 300) 152 | } 153 | } 154 | 155 | /** 156 | * 注册事件 157 | */ 158 | function registEvent() { 159 | doc.addEventListener("mousemove", move, eventOptions) 160 | doc.addEventListener("touchmove", move, eventOptions) 161 | doc.addEventListener("mouseup", end, false) 162 | doc.addEventListener("touchend", end, false) 163 | } 164 | 165 | /** 166 | * 解绑事件 167 | */ 168 | function unRegistEvent() { 169 | doc.removeEventListener("mousemove", move, eventOptions) 170 | doc.removeEventListener("touchmove", move, eventOptions) 171 | doc.removeEventListener("mouseup", end, false) 172 | doc.removeEventListener("touchend", end, false) 173 | } 174 | 175 | let destroyListener = () => { 176 | el.removeEventListener("mousedown", start, false) 177 | el.removeEventListener("contextmenu", start, false) 178 | el.removeEventListener("touchstart", start, false) 179 | 180 | el.removeEventListener("touchmove", move, eventOptions) 181 | el.removeEventListener("mousemove", move, eventOptions) 182 | el.removeEventListener("mouseup", end, false) 183 | el.removeEventListener("touchend", end, false) 184 | } 185 | if (options.vueIns) { 186 | options.vueIns.$once("hook:beforeDestroy", () => { 187 | destroyListener() 188 | }) 189 | } 190 | 191 | return destroyListener 192 | } 193 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/translate.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | 4 | (window)._translate = translate 5 | 6 | let cache: any = {} 7 | 8 | export async function translate( 9 | testList: string[], 10 | options: { server: string } = { server: "http://localhost:19212/prompt-studio/translate" } 11 | ) { 12 | let resultList: string[][] = [] 13 | let reqList: [string, number][] = [] 14 | testList.forEach((text, i) => { 15 | if (cache[text]) { 16 | let t = cache[text] 17 | resultList.push([text, t]) 18 | } else { 19 | resultList.push([text]) 20 | reqList.push([text, i]) 21 | } 22 | }) 23 | 24 | let rawText = reqList.map((req) => req[0]).join("\n") 25 | let re = await axios.post(`${options.server}`, { text: rawText, to: "zh-cn" }) 26 | 27 | if (re && re.data) { 28 | let list = re.data.split("\n") 29 | list.forEach((text: string, i: number) => { 30 | if (reqList[i]) { 31 | let raw = reqList[i][0] 32 | let index = reqList[i][1] 33 | resultList[index].push(text) 34 | cache[raw] = text 35 | } 36 | }) 37 | 38 | return resultList.map((x) => x[1]) 39 | } 40 | } 41 | translate.cache = cache 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Lib/translatePrompts.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import { chinesePercentage } from "./chinesePercentage" 3 | ;(window)._translatePrompts = translatePrompts 4 | 5 | let cache: any = {} 6 | export async function translatePrompts(testList: string[], options?: { server?: string; to?: string }) { 7 | try { 8 | let resultList: string[][] = [] 9 | let reqList: [string, number][] = [] 10 | 11 | testList.forEach((text, i) => { 12 | if (cache[text]) { 13 | let t = cache[text] 14 | resultList.push([text, t]) 15 | } else { 16 | resultList.push([text]) 17 | reqList.push([text, i]) 18 | } 19 | }) 20 | 21 | let host = (globalThis).__OPS_SERVER 22 | let orgWords = reqList.map((req) => req[0]) 23 | if (orgWords.length == 0) return resultList.map((x) => x[1]) 24 | let re = await axios.post(`${options?.server ?? `${host}/translate/prompts`}`, { 25 | words: orgWords, 26 | to: options?.to ?? "zh", 27 | }) 28 | 29 | if (re && re.data) { 30 | let list = re.data 31 | list.forEach((text: string, i: number) => { 32 | if (reqList[i]) { 33 | let raw = reqList[i][0] 34 | let index = reqList[i][1] 35 | resultList[index].push(text) 36 | cache[raw] = text 37 | if (!cache[text]) cache[text] = raw 38 | } 39 | }) 40 | return resultList.map((x) => x[1]) 41 | } 42 | } catch (e) { 43 | console.error(e) 44 | } 45 | } 46 | 47 | export async function translateZh2En(texts: string[]) { 48 | let finTexts = texts.slice() 49 | let zhWords: [string, number][] = [] 50 | texts.forEach((text, i) => { 51 | if (chinesePercentage(text)) { 52 | zhWords.push([text, i]) 53 | } 54 | }) 55 | let prompts = zhWords.map((x) => { 56 | return x[0] 57 | }) 58 | let re = await translatePrompts(prompts, { to: "en" }) 59 | // console.log("[translateZh2En]", prompts, "=>", re) 60 | if (re) { 61 | re.forEach((en, i) => { 62 | let orgIndex = zhWords[i][1] 63 | finTexts[orgIndex] = en 64 | }) 65 | } 66 | return finTexts 67 | } 68 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/PromptEditor.vue: -------------------------------------------------------------------------------- 1 | 157 | 200 | 259 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/PromptEditorClass.ts: -------------------------------------------------------------------------------- 1 | import { PromptWork } from "./Sub/PromptWork" 2 | 3 | export const LOCAL_TRANSLATE_SERVER = process.env.LOCAL_TRANSLATE_HOST 4 | ? `${ 5 | process.env.LOCAL_TRANSLATE_HOST.startsWith("http") 6 | ? process.env.LOCAL_TRANSLATE_HOST 7 | : "//" + process.env.LOCAL_TRANSLATE_HOST 8 | }/prompt-studio` 9 | : "http://localhost:39011/prompt-studio" 10 | 11 | 12 | 13 | export class PromptEditorClass { 14 | data = { 15 | server: location.host.startsWith("moonvy.com") 16 | ? "https://indexfs.moonvy.com:19213/prompt-studio" 17 | : LOCAL_TRANSLATE_SERVER, 18 | enablePngExportFixed: false, 19 | enablePngExportCopy: false, 20 | } 21 | 22 | works: PromptWork[] 23 | addWorkspace() { 24 | this.works.push(new PromptWork()) 25 | } 26 | removeWorkspace(promptWork: PromptWork) { 27 | this.works = this.works.filter((item) => item !== promptWork) 28 | } 29 | 30 | constructor(options?: { initPrompts?: string[] }) { 31 | if (options?.initPrompts) { 32 | this.works = options.initPrompts.map((initText) => new PromptWork({ initText })) 33 | } else { 34 | this.works = [ 35 | new PromptWork({ 36 | initText: `apple, forest ::-1 big bad wolf, wood ::2 unreal engine, cinematic lighting, UHD, super detail --aspect 2:3`, 37 | }), 38 | new PromptWork({ 39 | // initText: `symmetrical,(PureErosFace_V1:0.8), [:(highly detail face: 1.2):0.1],[to:when],[from::when], [[df]], (((twintails))), `, 40 | // parser: "stable-diffusion-webui", 41 | }), 42 | ] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Sub/PromptItem.ts: -------------------------------------------------------------------------------- 1 | import { IPromptWord, PromptWordType } from "../Lib/parsePrompts/parsePrompts" 2 | import { useDatabaseServer } from "../Lib/DatabaseServer/DatabaseServer" 3 | import { chinesePercentage } from "../Lib/chinesePercentage" 4 | import { translatePrompts } from "../Lib/translatePrompts" 5 | 6 | export interface IPromptItemData { 7 | word: IPromptWord 8 | // 被禁用的 9 | disabled?: boolean 10 | } 11 | 12 | export class PromptItem { 13 | static fromWord(word: IPromptWord) { 14 | return new PromptItem({ 15 | word: Object.assign(emptyWord(), word), 16 | }) 17 | } 18 | static createEmpty(data: { text?: string; group?: string; subType?: string }) { 19 | return new PromptItem({ 20 | word: { 21 | ...emptyWord(), 22 | id: `${data.text},${Date.now()}`, 23 | text: data.text ?? "", 24 | rawText: data.text ?? "", 25 | type: PromptWordType.Word, 26 | group: data.group, 27 | subType: data.subType, 28 | desc: "", 29 | args: [], 30 | lv: 1, 31 | isEg: false, 32 | }, 33 | }) 34 | } 35 | 36 | data!: IPromptItemData 37 | state = { 38 | isEdit: false, 39 | isDict: false, 40 | } 41 | constructor(data?: IPromptItemData) { 42 | this.data = Object.assign({ disabled: false }, data) 43 | } 44 | 45 | /** 更新内容(检查分类、翻译) */ 46 | async updateContent(text: string) { 47 | let dataserver = useDatabaseServer() 48 | let rawText = text 49 | // 命令 50 | let isCommand 51 | if (text.startsWith("--") || text.startsWith("—")) { 52 | text = text.split(" ")[0] 53 | isCommand = true 54 | } 55 | 56 | // 重置旧数据 57 | this.data.word.desc = undefined 58 | this.data.word.langText = undefined 59 | this.data.word.rawText = "" 60 | 61 | // 「中翻英」中文翻译成英文 62 | let cp = chinesePercentage(text) 63 | let isZhToEn 64 | if (!isCommand && cp > 50) { 65 | let re = await translatePrompts([text], { to: "en" }) 66 | if (re?.[0]) { 67 | isZhToEn = true 68 | this.data.word.langText = text 69 | text = re[0] 70 | rawText = text 71 | } 72 | } 73 | 74 | this.data.word.text = text 75 | this.data.word.rawText = rawText 76 | 77 | let pDesc = (await dataserver.queryPromptsDefine([text]))?.[0] 78 | 79 | if (pDesc) { 80 | if (pDesc["lang_zh"]) this.data.word.langText = pDesc["lang_zh"] 81 | if (pDesc.desc) this.data.word.desc = pDesc.desc 82 | if (pDesc.subType) this.data.word.subType = pDesc.subType 83 | } 84 | 85 | console.log("[updateContent]", this.data.word.langText, cp) 86 | // 如果且没有进行中翻英,且文本是英文而且没有译文,进行「英翻中」 87 | if (!isZhToEn && !isCommand && !this.data.word.langText && cp < 5) { 88 | let re = await translatePrompts([text]) 89 | if (re && re[0]) { 90 | this.data.word.langText = re[0] 91 | } 92 | } 93 | } 94 | } 95 | 96 | function emptyWord() { 97 | return { 98 | id: null, 99 | text: null, 100 | rawText: null, 101 | langText: null, 102 | type: PromptWordType.Word, 103 | group: null, 104 | subType: null, 105 | desc: null, 106 | args: [], 107 | lv: null, 108 | isEg: null, 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Sub/PromptList.ts: -------------------------------------------------------------------------------- 1 | import { PromptItem } from "./PromptItem" 2 | import { remove } from "lodash" 3 | import { IPromptWord } from "../Lib/parsePrompts/parsePrompts" 4 | 5 | export interface IPromptListData { 6 | id: string 7 | type?: string 8 | index?: number 9 | name?: string 10 | desc?: string 11 | } 12 | 13 | export class PromptList { 14 | data!: IPromptListData 15 | state!: {} 16 | items: PromptItem[] = [] 17 | constructor(data: IPromptListData) { 18 | if (data) this.data = data 19 | } 20 | applyWords(words: IPromptWord[]) { 21 | this.items = words.map((word) => PromptItem.fromWord(word)) 22 | } 23 | /** 添加提示词 */ 24 | pushPrompt(text: string, data?: { lv?: number; group?: string; subType?: string }) { 25 | let item = PromptItem.createEmpty({ text, ...data }) 26 | this.items.push(item) 27 | return item 28 | } 29 | /** 删除一个提示词 */ 30 | removePrompt(item: PromptItem) { 31 | this.items = this.items.filter((x) => x !== item) 32 | } 33 | /** 插入一个提示词到某个原有词的位置 */ 34 | insertPromptOf(targetItem: PromptItem, newItem: PromptItem, offset: number = 0) { 35 | if (newItem.state.isDict) { 36 | newItem = PromptItem.fromWord({ ...newItem.data.word }) 37 | } 38 | 39 | let targetIndex = this.items.indexOf(targetItem) 40 | let newIndex = targetIndex + offset 41 | this.items.splice(newIndex, 0, newItem) 42 | this.items = this.items.slice() 43 | return newItem 44 | } 45 | insertPromptLast(newItem: PromptItem) { 46 | if (newItem.state.isDict) { 47 | newItem = PromptItem.fromWord({ ...newItem.data.word }) 48 | } 49 | 50 | this.items = [...this.items, newItem] 51 | 52 | return newItem 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Compoents/PromptEditor/Sub/PromptWork.ts: -------------------------------------------------------------------------------- 1 | import { PromptList } from "./PromptList" 2 | import { uuid } from "fzz" 3 | import { IPromptWord, parsePrompts, stringifyPrompts } from "../Lib/parsePrompts/parsePrompts" 4 | import { SubTypeDisplayMap } from "../../../Lang/tempLang" 5 | import { chinesePercentage } from "../Lib/chinesePercentage" 6 | import { PromptItem } from "./PromptItem" 7 | import { translatePrompts } from "../Lib/translatePrompts" 8 | 9 | export interface IPromptWorkData { 10 | name: string 11 | id: string 12 | initText?: string 13 | parser?: string 14 | } 15 | 16 | export interface IPromptGroup { 17 | id: string 18 | name?: string 19 | groupLv?: number 20 | lists: PromptList[] 21 | } 22 | 23 | export class PromptWork { 24 | data!: IPromptWorkData 25 | state!: {} 26 | groups: IPromptGroup[] = [] 27 | 28 | get id() { 29 | return this.data.id 30 | } 31 | constructor(data?: Partial) { 32 | this.data = Object.assign( 33 | { 34 | id: uuid.v4(), 35 | name: "untitled", 36 | }, 37 | data 38 | ) 39 | } 40 | 41 | /** 导入提示词 */ 42 | async importPrompts(text: string, options: { parser: string }) { 43 | let { words } = await parsePrompts(text, { zh2en: true, ...options }) 44 | this.data.parser = options.parser 45 | // 分组 46 | let groupList: { [group: string]: IPromptWord[] } = {} 47 | let noGroup: IPromptWord[] = [] 48 | words.forEach((word) => { 49 | if (word.group) { 50 | if (!groupList[word.group]) groupList[word.group] = [] 51 | groupList[word.group].push(word) 52 | } else { 53 | noGroup.push(word) 54 | } 55 | }) 56 | // 分组 57 | let finGroups = [] 58 | for (let key in groupList) { 59 | let wordList = groupList[key] 60 | let listMap = createListMap(wordList) 61 | let lv = wordList[0]?.lv 62 | finGroups.push({ 63 | id: `group-${key}`, 64 | groupLv: Number.parseFloat(lv), 65 | lists: sortPromptMap(listMap), 66 | }) 67 | } 68 | // 未分组 69 | if (noGroup.length > 0) { 70 | finGroups.push({ 71 | id: `group-noGoutp`, 72 | lists: sortPromptMap(createListMap(noGroup)), 73 | }) 74 | } 75 | 76 | this.groups = finGroups 77 | 78 | // 翻译 79 | this.translate() 80 | 81 | function createListMap(words: IPromptWord[]) { 82 | let subTypeMap: { [subType: string]: IPromptWord[] } = {} 83 | words.forEach((word) => { 84 | let subType = word.subType ?? "normal" 85 | if (word.isEg) subType = "eg" 86 | if (subType in subTypeMap) { 87 | subTypeMap[subType].push(word) 88 | } else { 89 | subTypeMap[subType] = [word] 90 | } 91 | }) 92 | let listMap = {} 93 | for (let subType in subTypeMap) { 94 | listMap[subType] = new PromptList({ 95 | id: subType, 96 | type: subType, 97 | name: SubTypeDisplayMap[subType] ?? subType, 98 | index: ListSortMap[subType] ?? 0, 99 | }) 100 | listMap[subType].applyWords(subTypeMap[subType]) 101 | } 102 | return listMap 103 | } 104 | } 105 | 106 | /** 导出提示词 */ 107 | exportPrompts() { 108 | return stringifyPrompts(this.groups, { parser: this.data.parser ?? "midjourney" }) 109 | } 110 | 111 | async reflowPrompts(addPrompt?: string) { 112 | let prompt = this.exportPrompts() 113 | if (addPrompt) { 114 | prompt += " , " + addPrompt 115 | } 116 | await this.importPrompts(prompt, { parser: this.data.parser ?? "midjourney" }) 117 | } 118 | 119 | /** 翻译全部提示词 */ 120 | async translate() { 121 | let needTranslateItems: PromptItem[] = [] 122 | for (let group of this.groups) { 123 | for (let list of group.lists) { 124 | if (list.data.type !== "normal") continue 125 | for (let item of list.items) { 126 | if (item.data.word.langText) continue 127 | let cp = chinesePercentage(item.data.word.text) 128 | // 中文含量低,需要翻译 129 | if (cp < 5) needTranslateItems.push(item) 130 | } 131 | } 132 | } 133 | let rawTexts = needTranslateItems.map((item) => item.data.word.text) 134 | let re = await translatePrompts(rawTexts) 135 | if (re) { 136 | re.forEach((langText, i) => { 137 | needTranslateItems[i].data.word.langText = langText 138 | }) 139 | } 140 | } 141 | 142 | /** 禁用全部提示词 */ 143 | disableAll() { 144 | let isAllDisabled = true 145 | this.groups.forEach((group) => { 146 | group.lists.forEach((list) => 147 | list.items.forEach((item) => { 148 | if (!item.data.disabled) { 149 | isAllDisabled = false 150 | } 151 | }) 152 | ) 153 | }) 154 | 155 | this.groups.forEach((group) => { 156 | group.lists.forEach((list) => 157 | list.items.forEach((item) => (item.data.disabled = isAllDisabled ? false : true)) 158 | ) 159 | }) 160 | } 161 | } 162 | const ListSortMap: any = { 163 | normal: 6, 164 | style: 7, 165 | quality: 8, 166 | command: 9, 167 | eg: 10, 168 | } 169 | 170 | function sortPromptMap(listMap: { [key: string]: PromptList }) { 171 | return Object.values(listMap).sort((a, b) => { 172 | return (a.data.index ?? 0) - (b.data.index ?? 0) 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /src/Global/global.d.ts: -------------------------------------------------------------------------------- 1 | import type { VueConstructor } from "vue" 2 | 3 | declare global { 4 | declare var vueIns: VueConstructor 5 | } 6 | -------------------------------------------------------------------------------- /src/Global/vue-global.d.ts: -------------------------------------------------------------------------------- 1 | import type VueRouter, { Route } from "vue-router" 2 | import type { I18n } from "@moonvy/app-core" 3 | // 在 types/vue.d.ts 里 Vue 有构造函数类型 4 | declare module "vue/types/vue" { 5 | // 声明为 Vue 补充的东西 6 | interface Vue { 7 | $route: Route 8 | $router: VueRouter 9 | t: typeof I18n.t 10 | } 11 | interface VueConstructor { 12 | $route: Route 13 | $router: VueRouter 14 | t: typeof I18n.t 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Global/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue" 3 | export default Vue 4 | } 5 | 6 | declare module "*.png" { 7 | const value: string 8 | export default value 9 | } 10 | -------------------------------------------------------------------------------- /src/Lang/tempLang.ts: -------------------------------------------------------------------------------- 1 | export const SubTypeDisplayMap: any = { 2 | normal: "普通", 3 | style: "风格", 4 | quality: "质量", 5 | command: "命令", 6 | eg: "负面", 7 | } 8 | -------------------------------------------------------------------------------- /src/Pages/Index/Index.vue: -------------------------------------------------------------------------------- 1 | 36 | 176 | 222 | -------------------------------------------------------------------------------- /src/Pages/Index/assets/logo_full_cn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Pages/Root.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/Pages/index.ts: -------------------------------------------------------------------------------- 1 | import VueRouter from "vue-router" 2 | import vIndex from "../Pages/Index/Index.vue" 3 | import type { VueConstructor } from "vue" 4 | 5 | export function getRoutes() { 6 | return [ 7 | { 8 | path: "/", 9 | redirect: "/apps/ops/", 10 | }, 11 | { 12 | path: "/apps/ops/", 13 | name: "Index", 14 | component: vIndex, 15 | }, 16 | ] 17 | } 18 | 19 | export function getPagesRouter(Vue: VueConstructor) { 20 | let routes = getRoutes() 21 | Vue.use(VueRouter) 22 | let router = new VueRouter({ mode: "history", routes }) 23 | return router 24 | } 25 | -------------------------------------------------------------------------------- /src/Style/Font/index.ts: -------------------------------------------------------------------------------- 1 | import "@fontsource/jetbrains-mono" 2 | import "@fontsource/jetbrains-mono/300.css" 3 | import "@fontsource/jetbrains-mono/200.css" 4 | import "@fontsource/jetbrains-mono/800.css" 5 | import "@fontsource/fira-code" 6 | import "@fontsource/fira-code/300.css" 7 | -------------------------------------------------------------------------------- /src/Style/var.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: #f7f7f7; 3 | } 4 | body, 5 | html, 6 | button, 7 | input, 8 | optgroup, 9 | select, 10 | textarea { 11 | font-family: "SF Pro SC", "HanHei SC", "SF Pro Text", "Myriad Set Pro", "SF Pro Icons", "Apple Legacy Chevron", 12 | "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif; 13 | } 14 | 15 | select { 16 | --bk-color: #e9e9e9; 17 | background: var(--bk-color); 18 | border: none; 19 | padding: 6px 10px; 20 | border-radius: 4px; 21 | font-size: var(--font-size-1); 22 | text-shadow: 0 1px 1px #ffffff29; 23 | color: #484644; 24 | appearance: none; 25 | -webkit-appearance: none; 26 | background: url("data:image/svg+xml;utf8,") 27 | no-repeat; 28 | background-size: 9px; 29 | background-position: calc(100% - 14px) 14px; 30 | background-repeat: no-repeat; 31 | background-color: var(--bk-color); 32 | transition: all 0.15s ease; 33 | cursor: pointer; 34 | &:hover { 35 | --bk-color: #e0e0e0; 36 | } 37 | &:active { 38 | --bk-color: #d7d7d7; 39 | } 40 | ::before { 41 | content: "dd"; 42 | } 43 | 44 | &:focus-visible { 45 | outline: none; 46 | box-shadow: 0 0 0 2px rgb(182 179 179); 47 | } 48 | } 49 | 50 | button { 51 | --bk-color: #e9e9e9; 52 | background: var(--bk-color); 53 | border: none; 54 | padding: var(--padding-2) var(--padding-3); 55 | border-radius: 3px; 56 | color: #484644; 57 | font-size: var(--font-size-1); 58 | text-shadow: 0 1px 1px #ffffff29; 59 | transition: all 0.15s ease; 60 | cursor: pointer; 61 | display: flex; 62 | place-items: center; 63 | height: 2.3em; 64 | &:hover { 65 | --bk-color: #e0e0e0; 66 | } 67 | &:active { 68 | --bk-color: #d7d7d7; 69 | } 70 | &:focus-visible { 71 | outline: none; 72 | box-shadow: 0 0 0 2px rgb(182 179 179); 73 | } 74 | .iconify { 75 | margin-right: 10px; 76 | font-size: 1.2em; 77 | } 78 | &.icon { 79 | height: 2.3em; 80 | width: 2.3em; 81 | padding: 0; 82 | place-items: center; 83 | place-content: center; 84 | .iconify { 85 | margin-right: 0; 86 | } 87 | } 88 | } 89 | 90 | input, 91 | .input { 92 | border: none; 93 | background: transparent; 94 | font-size: var(--font-size-1); 95 | padding: var(--padding-1) var(--padding-2); 96 | font-family: "JetBrains Mono", -apple-system; 97 | border-radius: 3px; 98 | color: #5f5c5c; 99 | flex: none; 100 | &:focus-visible { 101 | outline: none; 102 | box-shadow: 0 0 0 2px rgb(182 179 179); 103 | } 104 | } 105 | 106 | .checkbox { 107 | display: flex; 108 | font-size: var(--font-size-1); 109 | place-items: center; 110 | user-select: none; 111 | color: #5f5c5c; 112 | input { 113 | margin-right: 6px; 114 | } 115 | } 116 | 117 | ::selection { 118 | background: #3126a1; 119 | color: #f9faff; 120 | } 121 | 122 | :root { 123 | --padding-4: 22px; 124 | --padding-3: 16px; 125 | --padding-2: 8px; 126 | --padding-1: 4px; 127 | 128 | --font-size-05: 12px; 129 | --font-size-1: 14px; 130 | --font-size-2: 16px; 131 | } 132 | 133 | .v-popper--theme-tooltip { 134 | .v-popper__inner { 135 | background: rgba(19, 22, 26, 0.84); 136 | backdrop-filter: blur(20px); 137 | font-size: 13px; 138 | white-space: pre-wrap; 139 | } 140 | 141 | .v-popper__arrow-outer { 142 | border-color: rgba(19, 22, 26, 0.84); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "Node", 6 | "useDefineForClassFields": false, 7 | "strict": true, 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "lib": ["ES2022", "DOM"], 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "sourceMap": true, 15 | "declaration": true, 16 | "declarationMap": true, 17 | "useUnknownInCatchVariables": false, 18 | "noUnusedLocals": false, 19 | "noUnusedParameters": false, 20 | "paths": {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "noImplicitThis": true 6 | }, 7 | "include": ["src", "web"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import vue from "@vitejs/plugin-vue2" 3 | import legacy from "@vitejs/plugin-legacy" 4 | import path from "path" 5 | import { resolve } from "path" 6 | import progress from "vite-plugin-progress" 7 | import * as process from "process" 8 | import * as dotenv from "dotenv" 9 | dotenv.config() 10 | 11 | // https://vitejs.dev/config/ 12 | let config = { 13 | root: "./web", 14 | base: "/apps/ops/", 15 | server: { 16 | port: 12833, 17 | host: "0.0.0.0", 18 | }, 19 | worker: { 20 | format: "es", 21 | }, 22 | plugins: [vue(), progress()], 23 | css: {}, 24 | build: { 25 | // minify: "terser", 26 | assetsInlineLimit: 1024 * 10 /* 10kb */, 27 | emptyOutDir: true, 28 | outDir: resolve(__dirname, "dist"), 29 | assetsDir: `version/2023-03/`, 30 | publicDir: resolve(__dirname, "web/public"), 31 | rollupOptions: { 32 | input: { 33 | main: resolve(__dirname, "web/index.html"), 34 | }, 35 | }, 36 | reportCompressedSize: false, 37 | }, 38 | resolve: {}, 39 | define: { 40 | "process.env.LOCAL_TRANSLATE_HOST": process.env.LOCAL_TRANSLATE_HOST 41 | ? `"${process.env.LOCAL_TRANSLATE_HOST}"` 42 | : "false", 43 | }, 44 | } 45 | // ------------- [vite build] ------------ 46 | if (process.env.NODE_ENV == "production") { 47 | // @ts-ignore 48 | // config.build.minify = "terser" 49 | config.plugins.push(legacy({ targets: ["defaults", "not IE 11"] })) 50 | } 51 | export default defineConfig(config) 52 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OPS 提示词工作室 | 可视化编辑提示词 | 一键翻译 AIGC 提示词 | Midjourney 提示词 | OpenPromptStudio made by 7 | Moonvy 月维 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /web/index.ts: -------------------------------------------------------------------------------- 1 | import { boot } from "../src/Boot" 2 | console.log("[boot]") 3 | boot() 4 | -------------------------------------------------------------------------------- /web/public/icon.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------