├── .gitignore
├── manifest.json
├── widget-src
├── tsconfig.json
├── MessageRow.tsx
└── code.tsx
├── .prettierrc
├── package.json
├── README.md
├── ui.html
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node
2 | *.log
3 | *.log.*
4 | node_modules
5 |
6 | out/
7 | dist/
8 | code.js
9 |
10 | # macOS
11 | .DS_Store
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FigChat",
3 | "id": "1224228637658454666",
4 | "api": "1.0.0",
5 | "main": "dist/code.js",
6 | "capabilities": [],
7 | "enableProposedApi": false,
8 | "editorType": ["figjam", "figma"],
9 | "containsWidget": true,
10 | "widgetApi": "1.0.0",
11 | "ui": "ui.html"
12 | }
13 |
--------------------------------------------------------------------------------
/widget-src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react",
4 | "jsxFactory": "figma.widget.h",
5 | "jsxFragmentFactory": "figma.widget.Fragment",
6 | "target": "es6",
7 | "lib": ["es2016"],
8 | "strict": true,
9 | "typeRoots": ["../node_modules/@types", "../node_modules/@figma"],
10 | "moduleResolution": "nodenext",
11 | "allowSyntheticDefaultImports": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": false,
4 | "embeddedLanguageFormatting": "auto",
5 | "htmlWhitespaceSensitivity": "css",
6 | "insertPragma": false,
7 | "jsxBracketSameLine": false,
8 | "jsxSingleQuote": false,
9 | "printWidth": 80,
10 | "proseWrap": "preserve",
11 | "quoteProps": "as-needed",
12 | "requirePragma": false,
13 | "semi": false,
14 | "singleQuote": true,
15 | "tabWidth": 4,
16 | "trailingComma": "none",
17 | "useTabs": true,
18 | "vueIndentScriptAndStyle": false,
19 | "jsdocParser": true
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fable-chat",
3 | "version": "1.0.0",
4 | "description": "ChatGPT on a multiplayer canvas",
5 | "scripts": {
6 | "build": "esbuild widget-src/code.tsx --bundle --outfile=dist/code.js --target=es6",
7 | "tsc": "tsc --noEmit -p widget-src",
8 | "watch": "npm run build -- --watch",
9 | "dev": "npm run build -- --watch"
10 | },
11 | "author": "",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "@figma/plugin-typings": "*",
15 | "@figma/widget-typings": "*",
16 | "esbuild": "*",
17 | "typescript": "*"
18 | },
19 | "dependencies": {
20 | "axios": "^1.3.4",
21 | "nanoid": "^4.0.2",
22 | "openai": "^3.2.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Below are the steps to get your widget running. You can also find instructions at:
2 |
3 | https://www.figma.com/widget-docs/setup-guide/
4 |
5 | This widget template uses Typescript and NPM, two standard tools in creating JavaScript applications.
6 |
7 | First, download Node.js which comes with NPM. This will allow you to install TypeScript and other
8 | libraries. You can find the download link here:
9 |
10 | https://nodejs.org/en/download/
11 |
12 | Next, install TypeScript, esbuild and the latest type definitions by running:
13 |
14 | npm install
15 |
16 | If you are familiar with JavaScript, TypeScript will look very familiar. In fact, valid JavaScript code
17 | is already valid Typescript code.
18 |
19 | TypeScript adds type annotations to variables. This allows code editors such as Visual Studio Code
20 | to provide information about the Figma API while you are writing code, as well as help catch bugs
21 | you previously didn't notice.
22 |
23 | For more information, visit https://www.typescriptlang.org/
24 |
25 | Using TypeScript requires a compiler to convert TypeScript (widget-src/code.tsx) into JavaScript (dist/code.js)
26 | for the browser to run. We use esbuild to do this for us.
27 |
28 | We recommend writing TypeScript code using Visual Studio code:
29 |
30 | 1. Download Visual Studio Code if you haven't already: https://code.visualstudio.com/.
31 | 2. Open this directory in Visual Studio Code.
32 | 3. Compile TypeScript to JavaScript: Run the "Terminal > Run Build Task..." menu item,
33 | then select "npm: watch". You will have to do this again every time
34 | you reopen Visual Studio Code.
35 |
36 | That's it! Visual Studio Code will regenerate the JavaScript file every time you save.
37 |
--------------------------------------------------------------------------------
/widget-src/MessageRow.tsx:
--------------------------------------------------------------------------------
1 | const {widget} = figma
2 | const {AutoLayout, Text, Input, Line, SVG} = widget
3 |
4 | import {ChatMessage} from './code'
5 |
6 | type MessageRowProps = {
7 | message: ChatMessage
8 | index?: number
9 | expandable?: boolean
10 | deleteable?: boolean
11 | placeholder?: string
12 | widened?: boolean
13 | roleWidth?: number
14 | roleColor?: string
15 | monospace?: boolean
16 |
17 | onDelete?: () => void
18 | onExpandCollapse?: (expanded: boolean) => void
19 | onToggleRole?: (role: 'user' | 'assistant') => void
20 | onUpdateContent?: (content: string) => void
21 | }
22 |
23 | export const MessageRow = ({
24 | message,
25 | index,
26 | expandable,
27 | deleteable,
28 | placeholder,
29 | widened,
30 | roleWidth,
31 | roleColor = '#000000',
32 | monospace = false,
33 |
34 | onDelete,
35 | onExpandCollapse,
36 | onToggleRole,
37 | onUpdateContent
38 | }: MessageRowProps) => {
39 | return (
40 |
41 |
47 |
48 | {expandable && (
49 |
63 | onExpandCollapse?.(!message.collapsed)
64 | }
65 | >
66 | `
70 | : ``
71 | }
72 | opacity={0.2}
73 | />
74 |
75 | )}
76 |
89 | onToggleRole?.(
90 | message.role === 'assistant'
91 | ? 'user'
92 | : 'assistant'
93 | )
94 | : undefined
95 | }
96 | cornerRadius={4}
97 | width="hug-contents"
98 | >
99 |
111 | {message.role}
112 |
113 |
114 |
115 |
123 | {message.collapsed && (
124 | onExpandCollapse?.(true)}
131 | fill={{r: 0, g: 0, b: 0, a: 0.5}}
132 | fontFamily={monospace ? 'Roboto Mono' : 'Inter'}
133 | >
134 | {message.content
135 | .split('')
136 | .slice(0, widened ? 70 : 30)
137 | .join('') + '...'}
138 |
139 | )}
140 | {!message.collapsed && (
141 | {
147 | onUpdateContent?.(e.characters)
148 | }}
149 | placeholder={placeholder}
150 | height={!message.collapsed ? 'hug-contents' : 23}
151 | inputBehavior="multiline"
152 | paragraphSpacing={8}
153 | fontFamily={monospace ? 'Roboto Mono' : 'Inter'}
154 | />
155 | )}
156 |
157 | {deleteable && (
158 |
173 | `}
175 | opacity={0.2}
176 | />
177 |
178 | )}
179 |
180 |
186 |
187 | )
188 | }
189 |
--------------------------------------------------------------------------------
/ui.html:
--------------------------------------------------------------------------------
1 |
230 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@esbuild/android-arm64@0.17.15":
6 | version "0.17.15"
7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.15.tgz#893ad71f3920ccb919e1757c387756a9bca2ef42"
8 | integrity sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==
9 |
10 | "@esbuild/android-arm@0.17.15":
11 | version "0.17.15"
12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.15.tgz#143e0d4e4c08c786ea410b9a7739779a9a1315d8"
13 | integrity sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==
14 |
15 | "@esbuild/android-x64@0.17.15":
16 | version "0.17.15"
17 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.15.tgz#d2d12a7676b2589864281b2274355200916540bc"
18 | integrity sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==
19 |
20 | "@esbuild/darwin-arm64@0.17.15":
21 | version "0.17.15"
22 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.15.tgz#2e88e79f1d327a2a7d9d06397e5232eb0a473d61"
23 | integrity sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==
24 |
25 | "@esbuild/darwin-x64@0.17.15":
26 | version "0.17.15"
27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.15.tgz#9384e64c0be91388c57be6d3a5eaf1c32a99c91d"
28 | integrity sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==
29 |
30 | "@esbuild/freebsd-arm64@0.17.15":
31 | version "0.17.15"
32 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.15.tgz#2ad5a35bc52ebd9ca6b845dbc59ba39647a93c1a"
33 | integrity sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==
34 |
35 | "@esbuild/freebsd-x64@0.17.15":
36 | version "0.17.15"
37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.15.tgz#b513a48446f96c75fda5bef470e64d342d4379cd"
38 | integrity sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==
39 |
40 | "@esbuild/linux-arm64@0.17.15":
41 | version "0.17.15"
42 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.15.tgz#9697b168175bfd41fa9cc4a72dd0d48f24715f31"
43 | integrity sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==
44 |
45 | "@esbuild/linux-arm@0.17.15":
46 | version "0.17.15"
47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.15.tgz#5b22062c54f48cd92fab9ffd993732a52db70cd3"
48 | integrity sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==
49 |
50 | "@esbuild/linux-ia32@0.17.15":
51 | version "0.17.15"
52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.15.tgz#eb28a13f9b60b5189fcc9e98e1024f6b657ba54c"
53 | integrity sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==
54 |
55 | "@esbuild/linux-loong64@0.17.15":
56 | version "0.17.15"
57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.15.tgz#32454bdfe144cf74b77895a8ad21a15cb81cfbe5"
58 | integrity sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==
59 |
60 | "@esbuild/linux-mips64el@0.17.15":
61 | version "0.17.15"
62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.15.tgz#af12bde0d775a318fad90eb13a0455229a63987c"
63 | integrity sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==
64 |
65 | "@esbuild/linux-ppc64@0.17.15":
66 | version "0.17.15"
67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.15.tgz#34c5ed145b2dfc493d3e652abac8bd3baa3865a5"
68 | integrity sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==
69 |
70 | "@esbuild/linux-riscv64@0.17.15":
71 | version "0.17.15"
72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.15.tgz#87bd515e837f2eb004b45f9e6a94dc5b93f22b92"
73 | integrity sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==
74 |
75 | "@esbuild/linux-s390x@0.17.15":
76 | version "0.17.15"
77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.15.tgz#20bf7947197f199ddac2ec412029a414ceae3aa3"
78 | integrity sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==
79 |
80 | "@esbuild/linux-x64@0.17.15":
81 | version "0.17.15"
82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.15.tgz#31b93f9c94c195e852c20cd3d1914a68aa619124"
83 | integrity sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==
84 |
85 | "@esbuild/netbsd-x64@0.17.15":
86 | version "0.17.15"
87 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.15.tgz#8da299b3ac6875836ca8cdc1925826498069ac65"
88 | integrity sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==
89 |
90 | "@esbuild/openbsd-x64@0.17.15":
91 | version "0.17.15"
92 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.15.tgz#04a1ec3d4e919714dba68dcf09eeb1228ad0d20c"
93 | integrity sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==
94 |
95 | "@esbuild/sunos-x64@0.17.15":
96 | version "0.17.15"
97 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.15.tgz#6694ebe4e16e5cd7dab6505ff7c28f9c1c695ce5"
98 | integrity sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==
99 |
100 | "@esbuild/win32-arm64@0.17.15":
101 | version "0.17.15"
102 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.15.tgz#1f95b2564193c8d1fee8f8129a0609728171d500"
103 | integrity sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==
104 |
105 | "@esbuild/win32-ia32@0.17.15":
106 | version "0.17.15"
107 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.15.tgz#c362b88b3df21916ed7bcf75c6d09c6bf3ae354a"
108 | integrity sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==
109 |
110 | "@esbuild/win32-x64@0.17.15":
111 | version "0.17.15"
112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz#c2e737f3a201ebff8e2ac2b8e9f246b397ad19b8"
113 | integrity sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==
114 |
115 | "@figma/plugin-typings@*":
116 | version "1.62.0"
117 | resolved "https://registry.yarnpkg.com/@figma/plugin-typings/-/plugin-typings-1.62.0.tgz#b3d416ce0a8df6c8b611e091b960ab79fbaeaf66"
118 | integrity sha512-Pv1DZqsuJJbzVsqgCz1fPPxEdQ1o521jbcMw1MDyM9oerw7QltM8TluR5BrGgBi6Zc8zXE6J6ZuUbzz/x7Bh6A==
119 |
120 | "@figma/widget-typings@*":
121 | version "1.5.0"
122 | resolved "https://registry.yarnpkg.com/@figma/widget-typings/-/widget-typings-1.5.0.tgz#ac56637b4cab229c4f19aa1a4ceb63cbf60373f0"
123 | integrity sha512-gktog9TSdFpFIu3mEK3e8EsAUhBOmfVzHVMACR/0DRCneXUNaFyQnr/0Layg6BvAUyYzcYv8jT7Oe2e7GHIQ4g==
124 |
125 | asynckit@^0.4.0:
126 | version "0.4.0"
127 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
128 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
129 |
130 | axios@^0.26.0:
131 | version "0.26.1"
132 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
133 | integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
134 | dependencies:
135 | follow-redirects "^1.14.8"
136 |
137 | axios@^1.3.4:
138 | version "1.3.4"
139 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024"
140 | integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==
141 | dependencies:
142 | follow-redirects "^1.15.0"
143 | form-data "^4.0.0"
144 | proxy-from-env "^1.1.0"
145 |
146 | combined-stream@^1.0.8:
147 | version "1.0.8"
148 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
149 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
150 | dependencies:
151 | delayed-stream "~1.0.0"
152 |
153 | delayed-stream@~1.0.0:
154 | version "1.0.0"
155 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
156 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
157 |
158 | esbuild@*:
159 | version "0.17.15"
160 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.15.tgz#209ebc87cb671ffb79574db93494b10ffaf43cbc"
161 | integrity sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==
162 | optionalDependencies:
163 | "@esbuild/android-arm" "0.17.15"
164 | "@esbuild/android-arm64" "0.17.15"
165 | "@esbuild/android-x64" "0.17.15"
166 | "@esbuild/darwin-arm64" "0.17.15"
167 | "@esbuild/darwin-x64" "0.17.15"
168 | "@esbuild/freebsd-arm64" "0.17.15"
169 | "@esbuild/freebsd-x64" "0.17.15"
170 | "@esbuild/linux-arm" "0.17.15"
171 | "@esbuild/linux-arm64" "0.17.15"
172 | "@esbuild/linux-ia32" "0.17.15"
173 | "@esbuild/linux-loong64" "0.17.15"
174 | "@esbuild/linux-mips64el" "0.17.15"
175 | "@esbuild/linux-ppc64" "0.17.15"
176 | "@esbuild/linux-riscv64" "0.17.15"
177 | "@esbuild/linux-s390x" "0.17.15"
178 | "@esbuild/linux-x64" "0.17.15"
179 | "@esbuild/netbsd-x64" "0.17.15"
180 | "@esbuild/openbsd-x64" "0.17.15"
181 | "@esbuild/sunos-x64" "0.17.15"
182 | "@esbuild/win32-arm64" "0.17.15"
183 | "@esbuild/win32-ia32" "0.17.15"
184 | "@esbuild/win32-x64" "0.17.15"
185 |
186 | follow-redirects@^1.14.8, follow-redirects@^1.15.0:
187 | version "1.15.2"
188 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
189 | integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
190 |
191 | form-data@^4.0.0:
192 | version "4.0.0"
193 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
194 | integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
195 | dependencies:
196 | asynckit "^0.4.0"
197 | combined-stream "^1.0.8"
198 | mime-types "^2.1.12"
199 |
200 | mime-db@1.52.0:
201 | version "1.52.0"
202 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
203 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
204 |
205 | mime-types@^2.1.12:
206 | version "2.1.35"
207 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
208 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
209 | dependencies:
210 | mime-db "1.52.0"
211 |
212 | nanoid@^4.0.2:
213 | version "4.0.2"
214 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
215 | integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
216 |
217 | openai@^3.2.1:
218 | version "3.2.1"
219 | resolved "https://registry.yarnpkg.com/openai/-/openai-3.2.1.tgz#1fa35bdf979cbde8453b43f2dd3a7d401ee40866"
220 | integrity sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==
221 | dependencies:
222 | axios "^0.26.0"
223 | form-data "^4.0.0"
224 |
225 | proxy-from-env@^1.1.0:
226 | version "1.1.0"
227 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
228 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
229 |
230 | typescript@*:
231 | version "5.0.3"
232 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.3.tgz#fe976f0c826a88d0a382007681cbb2da44afdedf"
233 | integrity sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==
234 |
--------------------------------------------------------------------------------
/widget-src/code.tsx:
--------------------------------------------------------------------------------
1 | import {MessageRow} from './MessageRow'
2 |
3 | const {widget} = figma
4 | const {
5 | AutoLayout,
6 | Text,
7 | Input,
8 | Line,
9 | SVG,
10 | usePropertyMenu,
11 | useEffect,
12 | waitForTask,
13 | useSyncedState
14 | } = widget
15 |
16 | export type ChatMessage = {
17 | role: string
18 | content: string
19 | collapsed: boolean
20 | }
21 |
22 | const SampleMessages: ChatMessage[] = [
23 | {role: 'user', content: 'Hi', collapsed: false},
24 | {role: 'assistant', content: 'Hello!', collapsed: false}
25 | ]
26 |
27 | function FigChat() {
28 | const [messages, setMessages] = useSyncedState(
29 | 'messages',
30 | SampleMessages
31 | )
32 |
33 | const [titleVisible, setTitleVisible] = useSyncedState(
34 | 'titleVisible',
35 | false
36 | )
37 | const [title, setTitle] = useSyncedState('title', '')
38 |
39 | const [systemMessageVisible, setSystemMessageVisible] =
40 | useSyncedState('systemMessageVisible', false)
41 | const [systemMessage, setSystemMessage] = useSyncedState<
42 | ChatMessage & {
43 | role: 'system'
44 | }
45 | >('systemMessage', {role: 'system', content: '', collapsed: false})
46 |
47 | const [keyVisible, setKeyVisible] = useSyncedState(
48 | 'keyVisible',
49 | false
50 | )
51 | const [openAIKeyMessage, setOpenAIKeyMessage] = useSyncedState<
52 | ChatMessage & {
53 | role: 'OpenAI Key'
54 | }
55 | >('key', {role: 'OpenAI Key', content: '', collapsed: false})
56 | const [anthropicKeyMessage, setAnthropicKeyMessage] = useSyncedState<
57 | ChatMessage & {
58 | role: 'Anthropic Key'
59 | }
60 | >('anthropicKey', {role: 'Anthropic Key', content: '', collapsed: false})
61 | const [anthropicProxyURLMessage, setAnthropicProxyURLMessage] =
62 | useSyncedState('anthropicProxyURL', {
63 | role: 'Proxy URL',
64 | content: 'http://localhost:5555',
65 | collapsed: false
66 | })
67 |
68 | const [loadState, setLoadState] = useSyncedState<
69 | 'ready' | 'loading' | 'error'
70 | >('loadState', 'ready')
71 | const [error, setError] = useSyncedState>(
72 | 'error',
73 | {}
74 | )
75 | const widgetId = figma.widget.useWidgetId()
76 |
77 | // This is used to detect if this widget node is new or not
78 | // If the node ID does not match the one in the synced state, then this is a new node, and we should fetch keys
79 | const [storedWidgetId, setStoredWidgetId] = useSyncedState(
80 | 'storedWidgetId',
81 | ''
82 | )
83 |
84 | const [model, setModel] = useSyncedState('model', 'gpt-4')
85 | const [temp, setTemp] = useSyncedState('temp', '0.7')
86 | const [topP, setTopP] = useSyncedState('top_p', '1')
87 | const [widened, setWidened] = useSyncedState('widened', false)
88 |
89 | // Do not use colorState directly. Use color() instead.
90 | // #ffffff is used as a special value to indicate "no color".
91 | const [colorState, setColorState] = useSyncedState('color', '#ffffff')
92 |
93 | const isGPT = () => model.startsWith('gpt')
94 | const color = () => (colorState === '#ffffff' ? undefined : colorState)
95 |
96 | // Key Fetch
97 | // Fetch OpenAI & Anthropic keys from existing widgets on widget mount
98 | const keyFetch = () => {
99 | // Detect OpenAI Key
100 | let openAIKey = openAIKeyMessage.content
101 | let anthropicKey = anthropicKeyMessage.content
102 | let anthropicProxyURL = anthropicProxyURLMessage.content
103 |
104 | // Fetch keys if needed
105 | if (!openAIKey || !anthropicKey || !anthropicProxyURL) {
106 | let existingOpenAIKey: string | undefined = undefined
107 | let existingAnthropicKey: string | undefined = undefined
108 | let existingAnthropicProxyURL: string | undefined = undefined
109 |
110 | figma.currentPage.children.forEach((node) => {
111 | if (
112 | node.type === 'WIDGET' &&
113 | node.widgetId === figma.widgetId
114 | ) {
115 | const nodeOpenAIKey = node.widgetSyncedState.key?.content
116 | if (nodeOpenAIKey) existingOpenAIKey = nodeOpenAIKey
117 |
118 | const nodeAnthropicKey =
119 | node.widgetSyncedState.anthropicKey?.content
120 | if (nodeAnthropicKey)
121 | existingAnthropicKey = nodeAnthropicKey
122 |
123 | const nodeAnthropicProxyURL =
124 | node.widgetSyncedState.anthropicProxyURL?.content
125 | if (nodeAnthropicProxyURL)
126 | existingAnthropicProxyURL = nodeAnthropicProxyURL
127 | }
128 | })
129 |
130 | // Set OpenAI key if found
131 | if (!openAIKey && existingOpenAIKey) {
132 | setOpenAIKeyMessage({
133 | role: 'OpenAI Key',
134 | content: existingOpenAIKey,
135 | collapsed: false
136 | })
137 | openAIKey = existingOpenAIKey
138 | }
139 |
140 | // Set Anthropic key if found
141 | if (!anthropicKey && existingAnthropicKey) {
142 | setAnthropicKeyMessage({
143 | role: 'Anthropic Key',
144 | content: existingAnthropicKey,
145 | collapsed: false
146 | })
147 | anthropicKey = existingAnthropicKey
148 | }
149 |
150 | // Set Anthropic proxy URL if found
151 | if (!anthropicProxyURL && existingAnthropicProxyURL) {
152 | console.log('setting proxy url')
153 | debugger
154 | setAnthropicProxyURLMessage({
155 | role: 'Proxy URL',
156 | content: existingAnthropicProxyURL,
157 | collapsed: false
158 | })
159 | anthropicProxyURL = existingAnthropicProxyURL
160 | }
161 | }
162 |
163 | // Return keys
164 | return {
165 | openAIKey,
166 | anthropicKey,
167 | anthropicProxyURL
168 | }
169 | }
170 |
171 | // Key Fetch on Mount Effect
172 | // Run Key Fetch on widget mount
173 | useEffect(() => {
174 | if (widgetId !== storedWidgetId) {
175 | // New widget node, fetch keys
176 | setStoredWidgetId(widgetId)
177 | keyFetch()
178 | }
179 | })
180 |
181 | usePropertyMenu(
182 | [
183 | {
184 | propertyName: 'model',
185 | itemType: 'dropdown',
186 | tooltip: 'Model',
187 | options: [
188 | {
189 | option: 'gpt-4',
190 | label: 'GPT-4'
191 | },
192 | {
193 | option: 'gpt-3.5-turbo-16k',
194 | label: 'GPT-3.5'
195 | },
196 | {
197 | option: 'claude-v1-100k',
198 | label: 'Claude v1'
199 | },
200 | {
201 | option: 'claude-instant-v1-100k',
202 | label: 'Claude Instant v1'
203 | },
204 | {
205 | option: 'claude-2',
206 | label: 'Claude v2'
207 | }
208 | ],
209 | selectedOption: model
210 | },
211 | {itemType: 'separator'},
212 | {
213 | propertyName: 'temp',
214 | itemType: 'dropdown',
215 | tooltip: 'Temp',
216 | options: [
217 | {
218 | option: '0',
219 | label: '0'
220 | },
221 | {
222 | option: '0.1',
223 | label: '0.1'
224 | },
225 | {
226 | option: '0.2',
227 | label: '0.2'
228 | },
229 | {
230 | option: '0.3',
231 | label: '0.3'
232 | },
233 | {
234 | option: '0.4',
235 | label: '0.4'
236 | },
237 | {
238 | option: '0.5',
239 | label: '0.5'
240 | },
241 | {
242 | option: '0.6',
243 | label: '0.6'
244 | },
245 |
246 | {
247 | option: '0.7',
248 | label: '0.7'
249 | },
250 | {
251 | option: '0.8',
252 | label: '0.8'
253 | },
254 | {
255 | option: '0.9',
256 |
257 | label: '0.9'
258 | },
259 | {
260 | option: '1',
261 | label: '1'
262 | }
263 | ],
264 | selectedOption: temp
265 | },
266 | {
267 | propertyName: 'top_p',
268 | itemType: 'dropdown',
269 | tooltip: 'Top P',
270 | options: [
271 | {
272 | option: '0',
273 | label: '0'
274 | },
275 | {
276 | option: '0.1',
277 | label: '0.1'
278 | },
279 | {
280 | option: '0.2',
281 | label: '0.2'
282 | },
283 | {
284 | option: '0.3',
285 | label: '0.3'
286 | },
287 | {
288 | option: '0.4',
289 | label: '0.4'
290 | },
291 | {
292 | option: '0.5',
293 | label: '0.5'
294 | },
295 | {
296 | option: '0.6',
297 | label: '0.6'
298 | },
299 |
300 | {
301 | option: '0.7',
302 | label: '0.7'
303 | },
304 | {
305 | option: '0.8',
306 | label: '0.8'
307 | },
308 | {
309 | option: '0.9',
310 |
311 | label: '0.9'
312 | },
313 | {
314 | option: '1',
315 | label: '1'
316 | }
317 | ],
318 | selectedOption: topP
319 | },
320 | {itemType: 'separator'},
321 | {
322 | propertyName: 'toggleTitle',
323 | itemType: 'toggle',
324 | tooltip: 'Toggle Title',
325 | isToggled: titleVisible,
326 | icon: ``
327 | },
328 | {
329 | propertyName: 'toggleSystem',
330 | itemType: 'toggle',
331 | tooltip: 'Toggle System Message',
332 | isToggled: systemMessageVisible,
333 | icon: ``
334 | },
335 | {
336 | propertyName: 'toggleKey',
337 | itemType: 'toggle',
338 | tooltip: `Toggle ${
339 | isGPT() ? 'OpenAI Key' : 'Anthropic Key & Proxy'
340 | }`,
341 | isToggled: keyVisible,
342 | icon: ``
343 | },
344 | {itemType: 'separator'},
345 | {
346 | propertyName: 'expandCollapse',
347 | itemType: 'action',
348 | tooltip: [systemMessage, ...messages].some(
349 | (msg) => !msg.collapsed
350 | )
351 | ? 'Collapse All Messages'
352 | : 'Expand All Messages',
353 | icon: [systemMessage, ...messages].some((msg) => !msg.collapsed)
354 | ? ``
355 | : ``
356 | },
357 | {
358 | propertyName: 'widenNarrow',
359 | itemType: 'action',
360 | tooltip: widened ? 'Narrow Chat' : 'Widen Chat',
361 | icon: widened
362 | ? ``
363 | : ``
364 | },
365 | {
366 | itemType: 'color-selector',
367 | propertyName: 'color',
368 | tooltip: 'Chat Color',
369 | options: [
370 | {tooltip: 'Red', option: '#F24822'},
371 | {tooltip: 'Yellow', option: '#FFCD29'},
372 | {tooltip: 'Green', option: '#14AE5C'},
373 | {tooltip: 'Blue', option: '#0D99FF'},
374 | {tooltip: 'Purple', option: '#9747FF'},
375 | {tooltip: 'Orange', option: '#FFA629'},
376 | {tooltip: 'Gray', option: '#B3B3B3'},
377 | {tooltip: 'Default', option: '#ffffff'}
378 | ],
379 | selectedOption: colorState
380 | },
381 | {itemType: 'separator'},
382 | {
383 | propertyName: 'resetChat',
384 | itemType: 'action',
385 | tooltip: 'Reset Chat',
386 | icon: ``
387 | }
388 | ],
389 | async ({propertyName, propertyValue}) => {
390 | switch (propertyName) {
391 | case 'toggleTitle':
392 | setTitleVisible((v) => !v)
393 | break
394 | case 'toggleSystem':
395 | setSystemMessageVisible((v) => !v)
396 | break
397 | case 'toggleKey':
398 | setKeyVisible((v) => !v)
399 | break
400 | case 'resetChat':
401 | setSystemMessage({
402 | role: 'system',
403 | content: '',
404 | collapsed: false
405 | })
406 | setMessages([
407 | {
408 | role: 'user',
409 | content: '',
410 | collapsed: false
411 | }
412 | ])
413 | break
414 | case 'expandCollapse':
415 | // If any messages are expanded, collapse them all
416 | // Otherwise, expand them all
417 | setSystemMessage((msg) => ({
418 | ...msg,
419 | collapsed: messages.some((msg) => !msg.collapsed)
420 | }))
421 |
422 | setMessages((messages) =>
423 | messages.map((msg) => ({
424 | ...msg,
425 | collapsed: messages.some((msg) => !msg.collapsed)
426 | }))
427 | )
428 | break
429 | case 'widenNarrow':
430 | setWidened((v) => !v)
431 | break
432 | case 'color':
433 | if (propertyValue !== undefined)
434 | setColorState(propertyValue)
435 | break
436 | case 'model':
437 | if (propertyValue !== undefined) {
438 | // Update model
439 | setModel(propertyValue)
440 |
441 | // If we're switching between GPT and Claude with default temps set, update the temp
442 | if (
443 | model.includes('gpt') &&
444 | !propertyValue.includes('gpt')
445 | ) {
446 | // GPT to Claude
447 | if (temp === '0.7') setTemp('1')
448 | } else if (
449 | // Claude to GPT
450 | !model.includes('gpt') &&
451 | propertyValue.includes('gpt')
452 | ) {
453 | if (temp === '1') setTemp('0.7')
454 | }
455 | }
456 | break
457 | case 'temp':
458 | if (propertyValue !== undefined) setTemp(propertyValue)
459 | break
460 | case 'topP':
461 | if (propertyValue !== undefined) setTopP(propertyValue)
462 | break
463 | }
464 | }
465 | )
466 |
467 | const addMessage = () => {
468 | setMessages([
469 | ...messages,
470 | {
471 | role:
472 | messages[messages.length - 1]?.role === 'user'
473 | ? 'assistant'
474 | : 'user',
475 | content: '',
476 | collapsed: false
477 | }
478 | ])
479 | }
480 |
481 | const cancel = async () => {
482 | setLoadState('ready')
483 | figma.ui.postMessage({type: 'cancel'})
484 | }
485 |
486 | const submit = async (messagesOverride?: ChatMessage[]) => {
487 | let messagesToSubmit = messagesOverride ?? messages
488 | let openAIKey = openAIKeyMessage.content
489 | let anthropicKey = anthropicKeyMessage.content
490 | let anthropicProxyURL = anthropicProxyURLMessage.content
491 |
492 | // Run Key Fetch if model key not set
493 | if (
494 | (isGPT() && !openAIKey) ||
495 | (!isGPT() && (!anthropicKey || !anthropicProxyURL))
496 | ) {
497 | console.log('running key fetch')
498 | // Model key not set, run key fetch
499 | const fetchedKeys = keyFetch()
500 | openAIKey = fetchedKeys.openAIKey
501 | anthropicKey = fetchedKeys.anthropicKey
502 | anthropicProxyURL = fetchedKeys.anthropicProxyURL
503 |
504 | // If still no keys, return
505 | if ((isGPT() && !openAIKey) || (!isGPT() && !anthropicKey)) {
506 | setLoadState('error')
507 | setError({
508 | message: `No ${
509 | isGPT() ? 'OpenAI' : 'Anthropic'
510 | } key set.\nClick the key icon in the FigChat toolbar to set one.`
511 | })
512 | return
513 | } else if (!isGPT() && !anthropicProxyURL) {
514 | setLoadState('error')
515 | setError({
516 | message: `No Proxy URL set.\nFigChat requires a proxy to call Anthropic Claude.\nClick the key icon in the FigChat toolbar to set one.`
517 | })
518 | return
519 | }
520 | }
521 |
522 | // Submit
523 | const stream = true
524 | setLoadState('loading')
525 | waitForTask(
526 | new Promise((resolve) => {
527 | figma.showUI(__html__, {visible: false})
528 | figma.ui.postMessage({
529 | type: stream ? 'submit-stream' : 'submit',
530 | messages: systemMessage.content
531 | ? [systemMessage, ...messagesToSubmit]
532 | : messagesToSubmit,
533 | key: isGPT() ? openAIKey : anthropicKey,
534 | temp: +temp,
535 | topP: +topP,
536 | model,
537 | anthropicProxyURL: isGPT()
538 | ? undefined
539 | : anthropicProxyURLMessage.content
540 | })
541 |
542 | if (stream) {
543 | // STREAMING
544 | figma.ui.onmessage = async (response: {
545 | messages?: ChatMessage[]
546 | error?: Record
547 | state?: 'streaming' | 'complete'
548 | }) => {
549 | if (response.error) {
550 | setLoadState('error')
551 | setError(response.error)
552 | // @ts-ignore
553 | resolve()
554 | } else if (response.messages) {
555 | setMessages(response.messages)
556 | if (response.state === 'complete') {
557 | setLoadState('ready')
558 | // @ts-ignore
559 | resolve()
560 | }
561 | }
562 | }
563 | }
564 |
565 | if (!stream) {
566 | // NON-STREAMING
567 | figma.ui.onmessage = async (response: {
568 | assistantMessage?: string
569 | error?: Record
570 | }) => {
571 | if (response.error) {
572 | setLoadState('error')
573 | setError(response.error)
574 | } else if (response.assistantMessage) {
575 | const newMessages = [
576 | ...messagesToSubmit,
577 | {
578 | role: 'assistant',
579 | content: response.assistantMessage,
580 | collapsed: false
581 | }
582 | ]
583 | setMessages(newMessages)
584 | setLoadState('ready')
585 | }
586 | // @ts-ignore
587 | resolve()
588 | }
589 | }
590 | })
591 | )
592 | }
593 |
594 | return (
595 |
613 | {titleVisible && (
614 |
620 |
626 | setTitle(e.characters)}
630 | placeholder="Chat"
631 | horizontalAlignText="center"
632 | fontSize={35}
633 | fontWeight={600}
634 | lineHeight={46}
635 | fill={color() ?? '#000000'}
636 | />
637 | {
649 | const oldModel = model
650 | let newModel = ''
651 |
652 | // Rotate model
653 | switch (model) {
654 | case 'gpt-3.5-turbo-16k':
655 | newModel = 'gpt-4'
656 | break
657 | case 'gpt-4':
658 | newModel = 'claude-v1-100k'
659 | break
660 | case 'claude-v1-100k':
661 | newModel = 'claude-instant-v1-100k'
662 | break
663 | case 'claude-instant-v1-100k':
664 | newModel = 'gpt-3.5-turbo-16k'
665 | break
666 | }
667 | setModel(newModel)
668 |
669 | // If we're switching between GPT and Claude with default temps set, update the temp
670 | if (
671 | oldModel.includes('gpt') &&
672 | !newModel.includes('gpt')
673 | ) {
674 | // GPT to Claude
675 | if (temp === '0.7') setTemp('1')
676 | } else if (
677 | // Claude to GPT
678 | !oldModel.includes('gpt') &&
679 | newModel.includes('gpt')
680 | ) {
681 | if (temp === '1') setTemp('0.7')
682 | }
683 | }}
684 | >
685 |
695 | {{
696 | 'gpt-3.5-turbo-16k': 'GPT-3.5',
697 | 'gpt-4': 'GPT-4',
698 | 'claude-v1-100k': 'Claude v1',
699 | 'claude-instant-v1-100k':
700 | 'Claude Instant v1',
701 | 'claude-2': 'Claude v2'
702 | }[model] ?? 'Unknown'}
703 |
704 |
705 |
706 |
712 |
713 | )}
714 | {keyVisible && (
715 | {
725 | isGPT()
726 | ? setOpenAIKeyMessage({
727 | ...openAIKeyMessage,
728 | content
729 | })
730 | : setAnthropicKeyMessage({
731 | ...anthropicKeyMessage,
732 | content
733 | })
734 | }}
735 | onExpandCollapse={(collapsed: boolean) => {
736 | isGPT()
737 | ? setOpenAIKeyMessage({
738 | ...openAIKeyMessage,
739 | collapsed
740 | })
741 | : setAnthropicKeyMessage({
742 | ...anthropicKeyMessage,
743 | collapsed
744 | })
745 | }}
746 | />
747 | )}
748 | {keyVisible && !isGPT() && (
749 | {
759 | setAnthropicProxyURLMessage({
760 | ...anthropicProxyURLMessage,
761 | content
762 | })
763 | }}
764 | onExpandCollapse={(collapsed: boolean) => {
765 | setAnthropicProxyURLMessage({
766 | ...anthropicProxyURLMessage,
767 | collapsed
768 | })
769 | }}
770 | />
771 | )}
772 | {systemMessageVisible && (
773 | {
782 | setSystemMessage({...systemMessage, content})
783 | }}
784 | onExpandCollapse={(collapsed: boolean) => {
785 | setSystemMessage({...systemMessage, collapsed})
786 | }}
787 | />
788 | )}
789 | {messages.map((message, index) => (
790 | {
799 | const newMessages = [...messages]
800 | newMessages[index].content = content
801 | setMessages(newMessages)
802 | }}
803 | onDelete={() => {
804 | const newMessages = [...messages]
805 | newMessages.splice(index, 1)
806 | setMessages(newMessages)
807 | }}
808 | onExpandCollapse={(collapsed: boolean) => {
809 | const newMessages = [...messages]
810 | newMessages[index].collapsed = collapsed
811 | setMessages(newMessages)
812 | }}
813 | onToggleRole={(role: string) => {
814 | const newMessages = [...messages]
815 | newMessages[index].role = role
816 | setMessages(newMessages)
817 | }}
818 | />
819 | ))}
820 |
821 |
827 |
837 | `}
839 | />
840 |
850 | Add message
851 |
852 |
853 | {
866 | const oldModel = model
867 | let newModel = ''
868 |
869 | // Rotate model
870 | switch (model) {
871 | case 'gpt-3.5-turbo-16k':
872 | newModel = 'gpt-4'
873 | break
874 | case 'gpt-4':
875 | newModel = 'claude-v1-100k'
876 | break
877 | case 'claude-v1-100k':
878 | newModel = 'claude-instant-v1-100k'
879 | break
880 | case 'claude-instant-v1-100k':
881 | newModel = 'gpt-3.5-turbo-16k'
882 | break
883 | }
884 | setModel(newModel)
885 |
886 | // If we're switching between GPT and Claude with default temps set, update the temp
887 | if (
888 | oldModel.includes('gpt') &&
889 | !newModel.includes('gpt')
890 | ) {
891 | // GPT to Claude
892 | if (temp === '0.7') setTemp('1')
893 | } else if (
894 | // Claude to GPT
895 | !oldModel.includes('gpt') &&
896 | newModel.includes('gpt')
897 | ) {
898 | if (temp === '1') setTemp('0.7')
899 | }
900 | }}
901 | >
902 |
913 | {{
914 | 'gpt-3.5-turbo-16k': 'GPT-3.5',
915 | 'gpt-4': 'GPT-4',
916 | 'claude-v1-100k': 'Claude v1',
917 | 'claude-instant-v1-100k': 'Claude Instant v1',
918 | 'claude-2': 'Claude v2'
919 | }[model] ?? 'Unknown'}
920 |
921 |
922 |
923 | {
933 | // Cancel if running
934 | cancel()
935 |
936 | // Delete last message
937 | const newMessages = [...messages]
938 | newMessages.pop()
939 | setMessages(newMessages)
940 |
941 | // Submit
942 | submit(newMessages)
943 | }}
944 | >
945 |
950 |
959 | Redo
960 |
961 |
962 | submit() : cancel}
966 | verticalAlignItems="center"
967 | horizontalAlignItems="center"
968 | padding={{vertical: 6, horizontal: 8}}
969 | spacing={6}
970 | hoverStyle={
971 | openAIKeyMessage.content
972 | ? {
973 | fill:
974 | loadState === 'loading'
975 | ? '#B50808'
976 | : '#832DDA'
977 | }
978 | : {}
979 | }
980 | >
981 | {loadState !== 'loading' && (
982 | `}
984 | />
985 | )}
986 | {loadState === 'loading' && (
987 | `}
989 | />
990 | )}
991 |
999 | {loadState === 'loading' ? 'Cancel' : 'Submit'}
1000 |
1001 |
1002 |
1003 | {loadState === 'error' && (
1004 |
1013 | `}
1015 | />
1016 |
1025 | {error.message}
1026 |
1027 | setLoadState('ready')}
1036 | >
1037 |
1047 | OK
1048 |
1049 |
1050 |
1051 | )}
1052 |
1053 | )
1054 | }
1055 |
1056 | widget.register(FigChat)
1057 |
--------------------------------------------------------------------------------