cyrus-studio 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -65
- package/dist/index.cjs +121 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +41 -0
- package/dist/index.mjs +91 -0
- package/dist/protocol.d.ts +35 -0
- package/dist/protocol.js +31 -0
- package/dist/src/index.d.ts +1176 -0
- package/dist/src/index.js +41 -0
- package/package.json +34 -50
- package/dist/assets/AuthDialog-bjTyuBr7.js +0 -1
- package/dist/assets/CanvaSaveModal-BfM3xRxS.js +0 -1
- package/dist/assets/CodingWorkspace-BKcqDIZx.js +0 -6
- package/dist/assets/CreateAppDialog-D8utCQ_b.js +0 -1236
- package/dist/assets/CreateProjectDialog-BVn3XyiE.js +0 -1
- package/dist/assets/HistoryTimelineDialog-B6LM34kT.js +0 -1
- package/dist/assets/ImportModal-B5pjYSjk.js +0 -83
- package/dist/assets/KeyboardShortcutsDialog-azy2kfng.js +0 -1
- package/dist/assets/LiveRuntimeWorkspace-CFPr1I0-.js +0 -1
- package/dist/assets/ShareExportDialog-BrQois88.js +0 -1
- package/dist/assets/UserProfileDialog-B2TENVlq.js +0 -1
- package/dist/assets/WelcomeOnboardingModal-Dyig1GK1.js +0 -1
- package/dist/assets/index-Cc_706Lp.css +0 -2
- package/dist/assets/index-DM7S-Aaa.js +0 -793
- package/dist/assets/reactTsGenerator-CpUD2_5K.js +0 -1330
- package/dist/assets/rolldown-runtime-Dd_uD5pT.js +0 -1
- package/dist/assets/themes-Iute1uX4.js +0 -1
- package/dist/assets/typechecker-BbGTunMD.js +0 -2
- package/dist/assets/vendor-firebase-DNMzpOGz.js +0 -32
- package/dist/assets/vendor-icons-CT-OIlA-.js +0 -13
- package/dist/assets/vendor-jszip-BhaZ3FnA.js +0 -2
- package/dist/assets/vendor-react-DVnpuVEx.js +0 -227
- package/dist/assets/vendor-utils-BqqQ8OQf.js +0 -1
- package/dist/favicon.svg +0 -1
- package/dist/icons.svg +0 -24
- package/dist/index.html +0 -50
- package/dist/login-bg.png +0 -0
- package/dist/test-sdk-sandbox.html +0 -125
package/README.md
CHANGED
|
@@ -1,113 +1,155 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cyrus-studio
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://cyrustudio.vercel.app"><img src="https://img.shields.io/badge/Live_App-cyrustudio.vercel.app-6366f1?style=for-the-badge&logo=vercel&logoColor=white" alt="Live Demo" /></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/cyrus-studio"><img src="https://img.shields.io/npm/v/cyrus-studio?style=for-the-badge&color=blue" alt="NPM Version" /></a>
|
|
6
6
|
<a href="https://www.npmjs.com/package/cyrus-studio"><img src="https://img.shields.io/npm/dm/cyrus-studio?style=for-the-badge&color=green" alt="NPM Downloads" /></a>
|
|
7
|
-
<
|
|
7
|
+
<img src="https://img.shields.io/badge/License-MIT-amber?style=for-the-badge" alt="License" />
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+
> **AI Visual Canvas Studio** — Embed Cyrus in any React app, generate full UIs from prompts, and export clean code for React, Flutter, SwiftUI & HTML.
|
|
11
|
+
|
|
12
|
+
🚀 **Try the Live App**: **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)**
|
|
13
|
+
|
|
10
14
|
---
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
## Install
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
```bash
|
|
19
|
+
npm install cyrus-studio
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> **Peer dependencies required**: `react >= 18` and `react-dom >= 18`
|
|
15
23
|
|
|
16
24
|
---
|
|
17
25
|
|
|
18
|
-
##
|
|
26
|
+
## Usage
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
- ⚡ **Multi-Framework Code Generation**:
|
|
22
|
-
- ⚛️ **React / Next.js** (Tailwind CSS, TypeScript)
|
|
23
|
-
- 📱 **Flutter / Dart** (Widget tree with theme binding)
|
|
24
|
-
- 🍎 **SwiftUI** (Native declarative iOS/macOS views)
|
|
25
|
-
- 🌐 **Clean HTML5 / CSS** (Semantic layout with Tailwind)
|
|
26
|
-
- 🤖 **AI Design & Code Copilot**: Integrate OpenAI, Google Gemini, Anthropic Claude, or local Ollama models to generate layouts, copy, and components via natural language.
|
|
27
|
-
- 👥 **Real-Time Multiplayer Collaboration**: Multiplayer room with live cursor tracking, conflict resolution, and commenting.
|
|
28
|
-
- 📐 **Responsive Viewport Controls**: Instant switching between Desktop (1440px), Tablet (768px), and Mobile (390px) viewports with auto-scaling.
|
|
29
|
-
- 🌗 **Design Systems & Theme Engine**: Global color palettes, typography tokens, component instances, and dynamic light/dark mode preview.
|
|
30
|
-
- 📦 **One-Click Export**: Download structured ZIP bundles, export individual page source files, or save high-resolution PNG/PDF artboards.
|
|
28
|
+
### 1. Embed the Cyrus Canvas in your React App
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
```tsx
|
|
31
|
+
import { CyrusCanvasIframe } from 'cyrus-studio';
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
export default function App() {
|
|
34
|
+
return (
|
|
35
|
+
<CyrusCanvasIframe
|
|
36
|
+
hostUrl="https://cyrustudio.vercel.app"
|
|
37
|
+
projectId="my-project"
|
|
38
|
+
theme="dark"
|
|
39
|
+
style={{ height: '800px' }}
|
|
40
|
+
onStateChange={(state) => console.log('Canvas state:', state)}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
35
45
|
|
|
36
|
-
###
|
|
37
|
-
Open **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)** in your browser and start designing immediately.
|
|
46
|
+
### 2. Generate an Embed URL Programmatically
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
```ts
|
|
49
|
+
import { createCyrusEmbedUrl } from 'cyrus-studio';
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
const url = createCyrusEmbedUrl({
|
|
52
|
+
hostUrl: 'https://cyrustudio.vercel.app',
|
|
53
|
+
projectId: 'landing-v2',
|
|
54
|
+
theme: 'light',
|
|
55
|
+
readOnly: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(url);
|
|
59
|
+
// https://cyrustudio.vercel.app/#/embed/landing-v2?theme=light&readOnly=true
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Use the Cyrus IR (Intermediate Representation) Protocol
|
|
63
|
+
|
|
64
|
+
The Cyrus IR is a universal JSON schema for AI-generated UIs. Use it to pass designs between your AI pipeline and the Cyrus canvas.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import {
|
|
68
|
+
validateAndRepairCyrusIR,
|
|
69
|
+
getCyrusLLMSystemPrompt,
|
|
70
|
+
type CyrusIRDocument,
|
|
71
|
+
} from 'cyrus-studio';
|
|
45
72
|
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
// Get the system prompt to paste into your LLM
|
|
74
|
+
const systemPrompt = getCyrusLLMSystemPrompt();
|
|
48
75
|
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
// Validate and repair raw LLM output
|
|
77
|
+
const rawAiOutput = `{ "version": "1.0", "projectTitle": "My App", "pages": [...] }`;
|
|
78
|
+
const doc: CyrusIRDocument = validateAndRepairCyrusIR(rawAiOutput);
|
|
79
|
+
|
|
80
|
+
console.log(doc.pages[0].elements);
|
|
51
81
|
```
|
|
52
82
|
|
|
53
|
-
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### `CyrusCanvasIframe`
|
|
54
88
|
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
React component that embeds the Cyrus Visual Canvas as an iframe.
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Default | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `hostUrl` | `string` | `http://localhost:5173` | URL of the Cyrus app |
|
|
94
|
+
| `projectId` | `string` | `"demo"` | Project ID to load in canvas |
|
|
95
|
+
| `theme` | `"light" \| "dark"` | `"light"` | Canvas color theme |
|
|
96
|
+
| `onStateChange` | `(state: any) => void` | — | Callback fired when canvas state changes |
|
|
97
|
+
| `style` | `CSSProperties` | — | Inline styles for the iframe |
|
|
98
|
+
| `className` | `string` | — | CSS class for the iframe |
|
|
99
|
+
|
|
100
|
+
All standard `<iframe>` HTML attributes are also supported.
|
|
57
101
|
|
|
58
102
|
---
|
|
59
103
|
|
|
60
|
-
|
|
104
|
+
### `createCyrusEmbedUrl(options)`
|
|
61
105
|
|
|
62
|
-
|
|
106
|
+
Returns an embeddable URL string.
|
|
63
107
|
|
|
64
|
-
```
|
|
65
|
-
|
|
108
|
+
```ts
|
|
109
|
+
createCyrusEmbedUrl({
|
|
110
|
+
projectId?: string; // Project to load
|
|
111
|
+
hostUrl?: string; // Cyrus host (default: localhost:5173)
|
|
112
|
+
theme?: 'light' | 'dark';
|
|
113
|
+
readOnly?: boolean;
|
|
114
|
+
})
|
|
66
115
|
```
|
|
67
116
|
|
|
68
|
-
|
|
69
|
-
| :--- | :--- |
|
|
70
|
-
| `VITE_STRIPE_PUBLISHABLE_KEY` | Stripe Publishable Key for billing checkout. |
|
|
71
|
-
| `STRIPE_SECRET_KEY` | Stripe Secret Key for serverless subscription sessions. |
|
|
72
|
-
| `GEMINI_API_KEY` | Google Gemini API key for AI generation. |
|
|
73
|
-
| `OPENAI_API_KEY` | OpenAI API key for GPT-4o design assistant. |
|
|
74
|
-
| `ANTHROPIC_API_KEY` | Anthropic Claude API key. |
|
|
75
|
-
| `JWT_SECRET` | Secret string for production session token signing. |
|
|
117
|
+
---
|
|
76
118
|
|
|
77
|
-
|
|
119
|
+
### `validateAndRepairCyrusIR(rawInput)`
|
|
78
120
|
|
|
79
|
-
|
|
121
|
+
Parses, validates, and repairs raw AI output (string or object) into a valid `CyrusIRDocument`. Safe to call with any LLM response — never throws.
|
|
80
122
|
|
|
81
|
-
|
|
123
|
+
---
|
|
82
124
|
|
|
83
|
-
|
|
84
|
-
# Start local development server (Client + Server concurrently)
|
|
85
|
-
npm run dev
|
|
125
|
+
### `getCyrusLLMSystemPrompt()`
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
npm run typecheck
|
|
127
|
+
Returns the recommended system prompt string to paste into Claude, GPT-4, Gemini, or any other LLM so it outputs valid Cyrus IR JSON.
|
|
89
128
|
|
|
90
|
-
|
|
91
|
-
npm run test
|
|
129
|
+
---
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
npm run build
|
|
131
|
+
## TypeScript Types
|
|
95
132
|
|
|
96
|
-
|
|
97
|
-
|
|
133
|
+
```ts
|
|
134
|
+
import type {
|
|
135
|
+
CyrusIRDocument,
|
|
136
|
+
CyrusIRPage,
|
|
137
|
+
CyrusIRElement,
|
|
138
|
+
CyrusIframeProps,
|
|
139
|
+
} from 'cyrus-studio';
|
|
98
140
|
```
|
|
99
141
|
|
|
100
142
|
---
|
|
101
143
|
|
|
102
|
-
##
|
|
144
|
+
## CommonJS Support
|
|
103
145
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
146
|
+
```js
|
|
147
|
+
// Works with require() too
|
|
148
|
+
const { CyrusCanvasIframe, createCyrusEmbedUrl } = require('cyrus-studio');
|
|
149
|
+
```
|
|
108
150
|
|
|
109
151
|
---
|
|
110
152
|
|
|
111
|
-
##
|
|
153
|
+
## License
|
|
112
154
|
|
|
113
|
-
|
|
155
|
+
MIT © [Cyrus Studio Team](https://cyrustudio.vercel.app)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// packages/cyrus-sdk/src/index.ts
|
|
30
|
+
var index_exports = {};
|
|
31
|
+
__export(index_exports, {
|
|
32
|
+
CyrusCanvasIframe: () => CyrusCanvasIframe,
|
|
33
|
+
createCyrusEmbedUrl: () => createCyrusEmbedUrl,
|
|
34
|
+
getCyrusLLMSystemPrompt: () => getCyrusLLMSystemPrompt,
|
|
35
|
+
validateAndRepairCyrusIR: () => validateAndRepairCyrusIR
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
var import_react = __toESM(require("react"), 1);
|
|
39
|
+
|
|
40
|
+
// packages/cyrus-sdk/src/protocol.ts
|
|
41
|
+
function validateAndRepairCyrusIR(rawInput) {
|
|
42
|
+
let doc = rawInput;
|
|
43
|
+
if (typeof rawInput === "string") {
|
|
44
|
+
try {
|
|
45
|
+
const cleaned = rawInput.replace(/```json\s*/gi, "").replace(/```\s*$/gi, "").trim();
|
|
46
|
+
doc = JSON.parse(cleaned);
|
|
47
|
+
} catch {
|
|
48
|
+
return { version: "1.0", projectTitle: "AI Application", pages: [{ name: "Page 1", elements: [] }] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!doc || typeof doc !== "object") {
|
|
52
|
+
return { version: "1.0", projectTitle: "AI Application", pages: [{ name: "Page 1", elements: [] }] };
|
|
53
|
+
}
|
|
54
|
+
const pages = Array.isArray(doc.pages) ? doc.pages : [{ name: "Home", elements: doc.elements || [] }];
|
|
55
|
+
return {
|
|
56
|
+
version: "1.0",
|
|
57
|
+
projectTitle: String(doc.projectTitle || doc.name || "AI Application"),
|
|
58
|
+
type: doc.type || "webapp",
|
|
59
|
+
theme: doc.theme || "light",
|
|
60
|
+
palette: doc.palette || { primary: "#0071e3", secondary: "#7d2ae8", background: "#ffffff", surface: "#f5f5f7", text: "#1d1d1f" },
|
|
61
|
+
pages: pages.map((p, idx) => ({
|
|
62
|
+
name: p.name || `Page ${idx + 1}`,
|
|
63
|
+
slug: p.slug || `page-${idx + 1}`,
|
|
64
|
+
elements: Array.isArray(p.elements) ? p.elements : []
|
|
65
|
+
}))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function getCyrusLLMSystemPrompt() {
|
|
69
|
+
return `Output your designs strictly in the Cyrus IR JSON format:
|
|
70
|
+
{
|
|
71
|
+
"version": "1.0",
|
|
72
|
+
"projectTitle": "Title",
|
|
73
|
+
"type": "webapp",
|
|
74
|
+
"theme": "light",
|
|
75
|
+
"pages": [
|
|
76
|
+
{ "name": "Home", "elements": [{ "type": "navbar" }, { "type": "heading" }, { "type": "button" }] }
|
|
77
|
+
]
|
|
78
|
+
}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// packages/cyrus-sdk/src/index.ts
|
|
82
|
+
function createCyrusEmbedUrl(options = {}) {
|
|
83
|
+
const host = options.hostUrl || "http://localhost:5173";
|
|
84
|
+
const pid = options.projectId || "demo";
|
|
85
|
+
const theme = options.theme || "light";
|
|
86
|
+
return `${host}/#/embed/${pid}?theme=${theme}&readOnly=${Boolean(options.readOnly)}`;
|
|
87
|
+
}
|
|
88
|
+
function CyrusCanvasIframe({
|
|
89
|
+
projectId = "demo",
|
|
90
|
+
hostUrl = "http://localhost:5173",
|
|
91
|
+
theme = "light",
|
|
92
|
+
onStateChange,
|
|
93
|
+
style,
|
|
94
|
+
className,
|
|
95
|
+
...rest
|
|
96
|
+
}) {
|
|
97
|
+
const embedUrl = createCyrusEmbedUrl({ projectId, hostUrl, theme });
|
|
98
|
+
import_react.default.useEffect(() => {
|
|
99
|
+
if (!onStateChange || typeof window === "undefined") return;
|
|
100
|
+
const handleMessage = (event) => {
|
|
101
|
+
if (event.data && event.data.type === "CYRUS_STATE_CHANGED") {
|
|
102
|
+
onStateChange(event.data.payload);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
window.addEventListener("message", handleMessage);
|
|
106
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
107
|
+
}, [onStateChange]);
|
|
108
|
+
return import_react.default.createElement("iframe", {
|
|
109
|
+
src: embedUrl,
|
|
110
|
+
className: className || "w-full h-full border-0 rounded-2xl shadow-lg",
|
|
111
|
+
style: {
|
|
112
|
+
width: "100%",
|
|
113
|
+
height: "600px",
|
|
114
|
+
border: "none",
|
|
115
|
+
borderRadius: "16px",
|
|
116
|
+
...style
|
|
117
|
+
},
|
|
118
|
+
title: "Cyrus Visual Canvas Studio",
|
|
119
|
+
...rest
|
|
120
|
+
});
|
|
121
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CyrusIRElement {
|
|
4
|
+
id?: string;
|
|
5
|
+
type: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
x?: number;
|
|
8
|
+
y?: number;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
styles?: Record<string, any>;
|
|
12
|
+
content?: Record<string, any>;
|
|
13
|
+
children?: CyrusIRElement[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CyrusIRPage {
|
|
17
|
+
id?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
slug?: string;
|
|
20
|
+
backgroundColor?: string;
|
|
21
|
+
elements: CyrusIRElement[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CyrusIRDocument {
|
|
25
|
+
version: '1.0';
|
|
26
|
+
projectTitle: string;
|
|
27
|
+
type?: 'webapp' | 'website' | 'dashboard' | 'landing' | 'mobile';
|
|
28
|
+
theme?: 'light' | 'dark' | 'system';
|
|
29
|
+
palette?: {
|
|
30
|
+
primary?: string;
|
|
31
|
+
secondary?: string;
|
|
32
|
+
background?: string;
|
|
33
|
+
surface?: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
};
|
|
36
|
+
pages: CyrusIRPage[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export declare function validateAndRepairCyrusIR(rawInput: any): CyrusIRDocument;
|
|
40
|
+
export declare function irToProject(ir: CyrusIRDocument): any;
|
|
41
|
+
export declare function projectToCyrusIR(project: any): CyrusIRDocument;
|
|
42
|
+
export declare function getCyrusLLMSystemPrompt(): string;
|
|
43
|
+
export declare function createCyrusEmbedUrl(options: { projectId?: string; hostUrl?: string; theme?: 'light' | 'dark'; readOnly?: boolean; }): string;
|
|
44
|
+
|
|
45
|
+
export interface CyrusIframeProps extends React.IframeHTMLAttributes<HTMLIFrameElement> {
|
|
46
|
+
projectId?: string;
|
|
47
|
+
hostUrl?: string;
|
|
48
|
+
theme?: 'light' | 'dark';
|
|
49
|
+
onStateChange?: (state: any) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export declare function CyrusCanvasIframe(props: CyrusIframeProps): React.ReactElement;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export * from './protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generates an embeddable Cyrus studio URL from a project ID or custom host.
|
|
5
|
+
*/
|
|
6
|
+
export function createCyrusEmbedUrl(options = {}) {
|
|
7
|
+
const host = options.hostUrl || 'http://localhost:5173';
|
|
8
|
+
const pid = options.projectId || 'demo';
|
|
9
|
+
const theme = options.theme || 'light';
|
|
10
|
+
return `${host}/#/embed/${pid}?theme=${theme}&readOnly=${Boolean(options.readOnly)}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* React Component to embed the Cyrus Visual Canvas Studio in any web app.
|
|
14
|
+
*/
|
|
15
|
+
export function CyrusCanvasIframe({ projectId = 'demo', hostUrl = 'http://localhost:5173', theme = 'light', onStateChange, style, className, ...rest }) {
|
|
16
|
+
const embedUrl = createCyrusEmbedUrl({ projectId, hostUrl, theme });
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (!onStateChange || typeof window === 'undefined')
|
|
19
|
+
return;
|
|
20
|
+
const handleMessage = (event) => {
|
|
21
|
+
if (event.data && event.data.type === 'CYRUS_STATE_CHANGED') {
|
|
22
|
+
onStateChange(event.data.payload);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
window.addEventListener('message', handleMessage);
|
|
26
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
27
|
+
}, [onStateChange]);
|
|
28
|
+
return React.createElement('iframe', {
|
|
29
|
+
src: embedUrl,
|
|
30
|
+
className: className || 'w-full h-full border-0 rounded-2xl shadow-lg',
|
|
31
|
+
style: {
|
|
32
|
+
width: '100%',
|
|
33
|
+
height: '600px',
|
|
34
|
+
border: 'none',
|
|
35
|
+
borderRadius: '16px',
|
|
36
|
+
...style,
|
|
37
|
+
},
|
|
38
|
+
title: 'Cyrus Visual Canvas Studio',
|
|
39
|
+
...rest,
|
|
40
|
+
});
|
|
41
|
+
}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// packages/cyrus-sdk/src/index.ts
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
// packages/cyrus-sdk/src/protocol.ts
|
|
5
|
+
function validateAndRepairCyrusIR(rawInput) {
|
|
6
|
+
let doc = rawInput;
|
|
7
|
+
if (typeof rawInput === "string") {
|
|
8
|
+
try {
|
|
9
|
+
const cleaned = rawInput.replace(/```json\s*/gi, "").replace(/```\s*$/gi, "").trim();
|
|
10
|
+
doc = JSON.parse(cleaned);
|
|
11
|
+
} catch {
|
|
12
|
+
return { version: "1.0", projectTitle: "AI Application", pages: [{ name: "Page 1", elements: [] }] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (!doc || typeof doc !== "object") {
|
|
16
|
+
return { version: "1.0", projectTitle: "AI Application", pages: [{ name: "Page 1", elements: [] }] };
|
|
17
|
+
}
|
|
18
|
+
const pages = Array.isArray(doc.pages) ? doc.pages : [{ name: "Home", elements: doc.elements || [] }];
|
|
19
|
+
return {
|
|
20
|
+
version: "1.0",
|
|
21
|
+
projectTitle: String(doc.projectTitle || doc.name || "AI Application"),
|
|
22
|
+
type: doc.type || "webapp",
|
|
23
|
+
theme: doc.theme || "light",
|
|
24
|
+
palette: doc.palette || { primary: "#0071e3", secondary: "#7d2ae8", background: "#ffffff", surface: "#f5f5f7", text: "#1d1d1f" },
|
|
25
|
+
pages: pages.map((p, idx) => ({
|
|
26
|
+
name: p.name || `Page ${idx + 1}`,
|
|
27
|
+
slug: p.slug || `page-${idx + 1}`,
|
|
28
|
+
elements: Array.isArray(p.elements) ? p.elements : []
|
|
29
|
+
}))
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getCyrusLLMSystemPrompt() {
|
|
33
|
+
return `Output your designs strictly in the Cyrus IR JSON format:
|
|
34
|
+
{
|
|
35
|
+
"version": "1.0",
|
|
36
|
+
"projectTitle": "Title",
|
|
37
|
+
"type": "webapp",
|
|
38
|
+
"theme": "light",
|
|
39
|
+
"pages": [
|
|
40
|
+
{ "name": "Home", "elements": [{ "type": "navbar" }, { "type": "heading" }, { "type": "button" }] }
|
|
41
|
+
]
|
|
42
|
+
}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// packages/cyrus-sdk/src/index.ts
|
|
46
|
+
function createCyrusEmbedUrl(options = {}) {
|
|
47
|
+
const host = options.hostUrl || "http://localhost:5173";
|
|
48
|
+
const pid = options.projectId || "demo";
|
|
49
|
+
const theme = options.theme || "light";
|
|
50
|
+
return `${host}/#/embed/${pid}?theme=${theme}&readOnly=${Boolean(options.readOnly)}`;
|
|
51
|
+
}
|
|
52
|
+
function CyrusCanvasIframe({
|
|
53
|
+
projectId = "demo",
|
|
54
|
+
hostUrl = "http://localhost:5173",
|
|
55
|
+
theme = "light",
|
|
56
|
+
onStateChange,
|
|
57
|
+
style,
|
|
58
|
+
className,
|
|
59
|
+
...rest
|
|
60
|
+
}) {
|
|
61
|
+
const embedUrl = createCyrusEmbedUrl({ projectId, hostUrl, theme });
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
if (!onStateChange || typeof window === "undefined") return;
|
|
64
|
+
const handleMessage = (event) => {
|
|
65
|
+
if (event.data && event.data.type === "CYRUS_STATE_CHANGED") {
|
|
66
|
+
onStateChange(event.data.payload);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
window.addEventListener("message", handleMessage);
|
|
70
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
71
|
+
}, [onStateChange]);
|
|
72
|
+
return React.createElement("iframe", {
|
|
73
|
+
src: embedUrl,
|
|
74
|
+
className: className || "w-full h-full border-0 rounded-2xl shadow-lg",
|
|
75
|
+
style: {
|
|
76
|
+
width: "100%",
|
|
77
|
+
height: "600px",
|
|
78
|
+
border: "none",
|
|
79
|
+
borderRadius: "16px",
|
|
80
|
+
...style
|
|
81
|
+
},
|
|
82
|
+
title: "Cyrus Visual Canvas Studio",
|
|
83
|
+
...rest
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
CyrusCanvasIframe,
|
|
88
|
+
createCyrusEmbedUrl,
|
|
89
|
+
getCyrusLLMSystemPrompt,
|
|
90
|
+
validateAndRepairCyrusIR
|
|
91
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface CyrusIRElement {
|
|
2
|
+
id?: string;
|
|
3
|
+
type: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
x?: number;
|
|
6
|
+
y?: number;
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
styles?: Record<string, any>;
|
|
10
|
+
content?: Record<string, any>;
|
|
11
|
+
children?: CyrusIRElement[];
|
|
12
|
+
}
|
|
13
|
+
export interface CyrusIRPage {
|
|
14
|
+
id?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
slug?: string;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
elements: CyrusIRElement[];
|
|
19
|
+
}
|
|
20
|
+
export interface CyrusIRDocument {
|
|
21
|
+
version: '1.0';
|
|
22
|
+
projectTitle: string;
|
|
23
|
+
type?: 'webapp' | 'website' | 'dashboard' | 'landing' | 'mobile';
|
|
24
|
+
theme?: 'light' | 'dark' | 'system';
|
|
25
|
+
palette?: {
|
|
26
|
+
primary?: string;
|
|
27
|
+
secondary?: string;
|
|
28
|
+
background?: string;
|
|
29
|
+
surface?: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
};
|
|
32
|
+
pages: CyrusIRPage[];
|
|
33
|
+
}
|
|
34
|
+
export declare function validateAndRepairCyrusIR(rawInput: any): CyrusIRDocument;
|
|
35
|
+
export declare function getCyrusLLMSystemPrompt(): string;
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function validateAndRepairCyrusIR(rawInput) {
|
|
2
|
+
let doc = rawInput;
|
|
3
|
+
if (typeof rawInput === 'string') {
|
|
4
|
+
try {
|
|
5
|
+
const cleaned = rawInput.replace(/```json\s*/gi, '').replace(/```\s*$/gi, '').trim();
|
|
6
|
+
doc = JSON.parse(cleaned);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return { version: '1.0', projectTitle: 'AI Application', pages: [{ name: 'Page 1', elements: [] }] };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (!doc || typeof doc !== 'object') {
|
|
13
|
+
return { version: '1.0', projectTitle: 'AI Application', pages: [{ name: 'Page 1', elements: [] }] };
|
|
14
|
+
}
|
|
15
|
+
const pages = Array.isArray(doc.pages) ? doc.pages : [{ name: 'Home', elements: doc.elements || [] }];
|
|
16
|
+
return {
|
|
17
|
+
version: '1.0',
|
|
18
|
+
projectTitle: String(doc.projectTitle || doc.name || 'AI Application'),
|
|
19
|
+
type: doc.type || 'webapp',
|
|
20
|
+
theme: doc.theme || 'light',
|
|
21
|
+
palette: doc.palette || { primary: '#0071e3', secondary: '#7d2ae8', background: '#ffffff', surface: '#f5f5f7', text: '#1d1d1f' },
|
|
22
|
+
pages: pages.map((p, idx) => ({
|
|
23
|
+
name: p.name || `Page ${idx + 1}`,
|
|
24
|
+
slug: p.slug || `page-${idx + 1}`,
|
|
25
|
+
elements: Array.isArray(p.elements) ? p.elements : [],
|
|
26
|
+
})),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function getCyrusLLMSystemPrompt() {
|
|
30
|
+
return `Output your designs strictly in the Cyrus IR JSON format:\n{\n "version": "1.0",\n "projectTitle": "Title",\n "type": "webapp",\n "theme": "light",\n "pages": [\n { "name": "Home", "elements": [{ "type": "navbar" }, { "type": "heading" }, { "type": "button" }] }\n ]\n}`;
|
|
31
|
+
}
|