-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode.ts
More file actions
402 lines (327 loc) · 13.3 KB
/
code.ts
File metadata and controls
402 lines (327 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
// @ts-nocheck
// This plugin will open a window to prompt the user to enter a number, and
// it will then create that many rectangles on the screen.
// This file holds the main code for plugins. Code in this file has access to
// the *figma document* via the figma global object.
// You can access browser APIs in the <script> tag inside "ui.html" which has a
// full browser environment (See https://www.figma.com/plugin-docs/how-plugins-run).
// This shows the HTML page in "ui.html".
figma.showUI(__html__, { width: 500, height: 700 });
// ts-ignore()
// Function to fetch data from the dummy API and store it in a string
// Call the function to fetch data
let initialSelection = [...figma.currentPage.selection];
let svgs = {};
async function deleteAllFigmaClientStorageValues() {
try {
// Retrieve all keys from the client storage
const keys = await figma.clientStorage.keysAsync();
// Iterate over the keys and delete them one by one
for (const key of keys) {
await figma.clientStorage.deleteAsync(key);
}
console.log('All Figma client storage values have been deleted.');
} catch (error) {
console.error('An error occurred while deleting client storage values:', error);
}
}
// deleteAllFigmaClientStorageValues()
function generateTempIcon(node: SceneNode) {
let svg = `<svg height="${node.height}" width="${node.width}">
<rect width="${node.width}" height="${node.height}" style="fill:grey;" />
</svg>`
return svg;
}
function getPadding(node: SceneNode) {
return `${node.paddingTop}px ${node.paddingRight}px ${node.paddingBottom}px ${node.paddingLeft}px`;
}
function removePaddingFromGeneratedCSSAndUseCorrectVars(node: SceneNode, cssObject) {
let hasPadding = false;
for (const key in cssObject) {
// If the key includes the word 'padding', delete it from the object
if (key.includes('padding')) {
hasPadding = true;
delete cssObject[key];
}
}
if (hasPadding) {
cssObject["padding"] = getPadding(node);
}
return cssObject;
}
async function getNodeCSS(node: SceneNode, selector, useRawSvg) {
console.log("in getNodeCss(node: SceneNode, selector, useRawSvg)", `getNodeCss(node: ${node}, selector: ${selector}, useRawSvg: ${useRawSvg})`);
let cssString = '';
// const css = await node.getCSSAsync().then((res) => {
// return JSON.stringify(res, null, 2);
// });
// // Construct the CSS string for the node
// cssString += `#${selector} {\n${css}\n}\n\n`;
if (node.name.includes("Icon")) {
const instruction = await figma.clientStorage.getAsync(node.id);
cssString += `// Node name: ${node.name}\n`
if (instruction != undefined) {
cssString += `// Instructions: ${instruction}\n`
}
cssString += `// only include the icon, heres the svg: \n`;
let svg = "";
if (useRawSvg) {
svgs[`{{${node.id}}}`] = await node.exportAsync({ format: 'SVG_STRING' });
svg = `Please use this keyword when using the SVG, place it within the HTML you generate and ignore the syntax rule in the meantime, I will replace it with regex later: {{${node.id}}} example: instead of <svg>...</svg> do {{${node.id}}} inside the HTML, yes I know it's not valid syntax but do it please.`;
} else {
svg = generateTempIcon(node);
}
cssString += `#${selector} {\n ${svg}\n}\n\n`;
return cssString;
}
if (node.children && node.children.length > 0) {
for (let i = 0; i < node.children.length; i++) {
let child = node.children[i];
const instruction = await figma.clientStorage.getAsync(child.id);
const css = await child.getCSSAsync().then((res) => {
removePaddingFromGeneratedCSSAndUseCorrectVars(child, res);
let resString = JSON.stringify(res, null, 2).split("");
resString[0] = '';
resString[resString.length - 1] = '';
return resString.join("");
});
if (!child.name.includes("Icon")) {
cssString += `// Node name: ${child.name}\n`
if (instruction != undefined) {
cssString += `// Instructions: ${instruction}\n`
}
cssString += `#${selector}:nth-child(${i + 1}) {\n ${css}\n}\n\n`;
}
};
}
return cssString;
}
async function processNode(node, selectorPrefix = '', name = '', useRawSvg) {
console.log("processNode(node, selectorPrefix = '', name = '', useRawSvg)", {node, selectorPrefix, name, useRawSvg})
let cssString = '';
const nodeName = selectorPrefix + name;
cssString += await getNodeCSS(node, nodeName, useRawSvg);
// Recursively process child nodes
if (node.children && node.children.length > 0) {
for (let i = 0; i < node.children.length; i++) {
// we want the name to always be in child notation, so the only css name we have is the parent one
// rest of definitions are okay to be <parentSelector>:nth-child(1):nth-child(n) and so forth
let name = `${nodeName}:nth-child(${i + 1})`;
console.log("processNode(node.children[i], `${name}`, '', useRawSvg)", {nodeChildrenAtI: node.children[i], selectorPrefix: name, name: '', useRawSvg})
cssString += await processNode(node.children[i], `${name}`, '', useRawSvg);
}
}
return cssString;
}
type ServiceConfig = {
resourceName?: string;
deploymentName?: string;
apiKey?: string;
// Add more properties as needed for different services
};
// Define specific types for different services if they have unique fields
type AzureOpenAIServiceConfig = ServiceConfig & {
// Fields specific to Azure Open AI
};
type OpenAIServiceConfig = ServiceConfig & {
// Fields specific to Open AI
};
type AnthropicServiceConfig = ServiceConfig & {
// Fields specific to Anthropic
};
// General type for all service configurations
type AnyServiceConfig = AzureOpenAIServiceConfig | OpenAIServiceConfig | AnthropicServiceConfig;
// Helper function to save settings for any service
async function saveServiceSettings(service: string, configData: AnyServiceConfig) {
console.log("in figmaPlugin::saveServiceSettings()::service", service);
console.log("in figmaPlugin::saveServiceSettings()::configData", configData);
for (const key of Object.keys(configData)) {
console.log("in figmaPlugin::saveServiceSettings()::forloop::key", key);
await figma.clientStorage.setAsync(key, configData[key]);
}
}
// Helper function to load settings for any service
async function loadServiceSettings(service: string) {
console.log("in loadServiceSettings::service", service);
// Assuming that all services have the same set of basic keys:
const keys = ['resourceName', 'deploymentName', 'apiKey'];
console.log("in loadServiceSettings::keys", keys);
const config = {};
console.log("in loadServiceSettings::config", config);
for (const key of keys) {
let service_key = `${service}-${key}`;
console.log("in loadServiceSettings::forloop::service_key", service_key);
let val = await figma.clientStorage.getAsync(service_key);
console.log("in loadServiceSettings::forloop::val", val);
if (val)
config[service_key] = val;
console.log("in loadServiceSettings::forloop::after val::config", config);
}
console.log("in loadServiceSettings::outsideforloop::config", config);
return config;
}
// Calls to "parent.postMessage" from within the HTML page will trigger this
// callback. The callback will be passed the "pluginMessage" property of the
// posted message.
figma.ui.onmessage = async msg => {
// One way of distinguishing between different types of messages sent from
// your HTML page is to use an object with a "type" property like this.
if (msg.type === 'save-settings') {
console.log('save-settings plugin', msg.service, msg.configData);
await saveServiceSettings(msg.service, msg.configData);
figma.ui.postMessage({
type: 'settings-saved',
service: msg.service
});
}
if (msg.type === 'load-settings') {
console.log('load-settigns plugin', msg.service);
const serviceSettings = await loadServiceSettings(msg.service);
figma.ui.postMessage({
type: 'load-settings',
service: msg.service,
configData: serviceSettings
});
}
if (msg.type === 'update-configuration') {
console.log('update-settings plugin', msg.service, msg.configData);
// Perform update logic here
figma.ui.postMessage({
type: 'configuration-updated',
service: msg.service
});
}
const selection = figma.currentPage.selection;
let cssString: string = "";
if (msg.type === 'generate-layout-instructions') {
console.log("generate-layout-instructions msg::", msg);
for (const node of figma.currentPage.selection) {
if (selection.length === 0) {
figma.ui.postMessage({ type: 'error', message: 'No nodes selected.' });
return;
}
for (const node of selection) {
const name = node.name.replace(/\s/g, '');
const instruction = await figma.clientStorage.getAsync(node.id);
if (instruction)
cssString += `// Instruction[${name}]: ${instruction}\n`;
cssString += `#${name}\n`
cssString += await node.getCSSAsync().then((res) => { removePaddingFromGeneratedCSSAndUseCorrectVars(node, res); return JSON.stringify(res, null, 2) }) + '\n';
cssString += await processNode(node, '', node.name.replace(/\s/g, ''), msg.useRawSvg);
}
}
figma.ui.postMessage({
type: 'set-instructions',
instructions: cssString,
svgs
});
svgs = {};
}
if (msg.type === 'generate-ai') {
// Define the parameters
const resourceName = await figma.clientStorage.getAsync('resourceName');
const deploymentName = await figma.clientStorage.getAsync('deploymentName');
const apiKey = await figma.clientStorage.getAsync('apiKey');
const apiVersion = '2023-07-01-preview';
// Construct the URL using template literals
const url = `https://${resourceName}.openai.azure.com/openai/deployments/${deploymentName}/chat/completions?api-version=${apiVersion}`;
// Define the data to be sent
const data = {
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Reply with hello world please :)" },
],
temperature: 0.3,
top_p: 0.3,
max_tokens: 20000,
stream: true
};
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': apiKey
},
body: JSON.stringify(data)
}).then(response => {
return response.text();
// figma.ui.postMessage({
// type: 'set-openai',
// instructions: JSON.stringify(data.choices[0].message.content)
// });
}).then(text => {
// Here we have the entire response body as text.
// For an event stream, this is not ideal because we cannot process events as they arrive.
console.log('Received text:', text);
// If you were to manually process the event stream text, you would need to split
// the text by the event stream's usual delimiter ("\n\n") and handle each event.
const events = text.split('\n\n');
for (let event of events) {
// Further processing for each event...
console.log('Event:', event);
}
})
.catch((error) => {
console.error('Error:', error);
});
}
if (msg.type === 'get-nodes') {
const nodesData = await getNodes();
figma.ui.postMessage({ type: 'nodes-data', data: nodesData });
}
if (msg.type === 'save-node-config') {
for (const node of msg.nodes) {
await figma.clientStorage.setAsync(node.node_id, node.node_instructions);
}
figma.ui.postMessage({ type: 'save-complete' });
}
if (msg.type === 'select-node') {
const nodeToSelect = figma.getNodeById(msg.node_id);
if (nodeToSelect) {
figma.viewport.scrollAndZoomIntoView([nodeToSelect]);
figma.currentPage.selection = [nodeToSelect];
}
}
if (msg.type === 'get-node-instruction') {
const instruction = await figma.clientStorage.getAsync(msg.node_id);
figma.ui.postMessage({ type: 'node-instruction-data', data: { node_id: msg.node_id, instruction: instruction } });
}
// Make sure to close the plugin when you're done. Otherwise the plugin will
// keep running, which shows the cancel button at the bottom of the screen.
// figma.closePlugin();
};
async function getNodes(): Promise<any[]> {
const selectedNodes = figma.currentPage.selection;
// Return an empty array if no nodes are selected
if (selectedNodes.length === 0) {
figma.notify('No nodes selected');
return [];
}
// Helper function to recursively process nodes and their children
async function processNode2(node: SceneNode): Promise<any> {
// Fetch instructions for the current node
const instruction = await figma.clientStorage.getAsync(node.id) || '';
// Base node data
let nodeData = {
id: node.id,
name: node.name,
instruction: instruction,
children: []
};
// If the node has children, process each one and add to the node's children array
if ('children' in node && node.children.length > 0) {
for (const child of node.children) {
const childData = await processNode2(child);
nodeData.children.push(childData); // Add child data to the parent node
}
}
return nodeData;
}
// Process each selected node and its children
let nodesData = [];
for (const node of selectedNodes) {
const nodeData = await processNode2(node);
nodesData.push(nodeData);
}
return nodesData;
}