modifywithai 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +182 -23
- package/dist/nextjs/api.d.ts +5 -0
- package/dist/nextjs/api.js +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,8 @@ import { MWAIHead, MWAIProvider, MWAIComponents } from "modifywithai/react"
|
|
|
37
37
|
Then modify the layout to include:
|
|
38
38
|
|
|
39
39
|
1. `<MWAIHead />` inside the `<head>` tag
|
|
40
|
-
2. `<MWAIProvider>`
|
|
40
|
+
2. `<MWAIProvider>` wrapping `<body>`
|
|
41
|
+
3. `<MWAIComponents />` at the end of `<body>`, after `{children}`
|
|
41
42
|
|
|
42
43
|
Example structure:
|
|
43
44
|
|
|
@@ -48,12 +49,12 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
48
49
|
<head>
|
|
49
50
|
<MWAIHead />
|
|
50
51
|
</head>
|
|
51
|
-
<body>
|
|
52
|
-
{children}
|
|
53
52
|
<MWAIProvider id="{{MWAI_ID}}">
|
|
54
|
-
<
|
|
53
|
+
<body>
|
|
54
|
+
{children}
|
|
55
|
+
<MWAIComponents />
|
|
56
|
+
</body>
|
|
55
57
|
</MWAIProvider>
|
|
56
|
-
</body>
|
|
57
58
|
</html>
|
|
58
59
|
)
|
|
59
60
|
}
|
|
@@ -76,6 +77,7 @@ export { generateStaticParams } from "modifywithai/nextjs/api"
|
|
|
76
77
|
export const dynamicParams = false
|
|
77
78
|
|
|
78
79
|
export const { GET, POST, PUT, PATCH, DELETE } = createNextjsHandler({
|
|
80
|
+
appId: "{{MWAI_APP_ID}}", // Your MWAI app ID from the dashboard
|
|
79
81
|
getEndUserId: async (request) => {
|
|
80
82
|
// TODO: Implement user ID retrieval based on the project's auth system
|
|
81
83
|
// Return the current user's ID as a string
|
|
@@ -96,6 +98,7 @@ export { generateStaticParams } from "modifywithai/nextjs/api"
|
|
|
96
98
|
export const dynamicParams = false
|
|
97
99
|
|
|
98
100
|
export const { GET, POST, PUT, PATCH, DELETE } = createNextjsHandler({
|
|
101
|
+
appId: "{{MWAI_APP_ID}}", // Your MWAI app ID from the dashboard
|
|
99
102
|
getEndUserId: async (request) => {
|
|
100
103
|
const session = await auth.api.getSession({
|
|
101
104
|
headers: request.headers,
|
|
@@ -141,23 +144,87 @@ Choose a suitable location based on the app's structure. Common placements inclu
|
|
|
141
144
|
|
|
142
145
|
### Example Implementation
|
|
143
146
|
|
|
144
|
-
Create a client component for the button:
|
|
147
|
+
Create a client component for the button. The modal has two views:
|
|
148
|
+
|
|
149
|
+
1. **Default view**: A textarea for submitting new modifications, submit/cancel buttons, and a list showing up to 3 recent modifications (sorted by enabled first, then by creation date). If there are more than 3 modifications, a "Show all" button appears.
|
|
150
|
+
|
|
151
|
+
2. **Show all view**: When "Show all" is clicked, the modal switches to display a search bar at the top and a scrollable list of all modifications. The textarea and submit/cancel buttons are hidden in this view.
|
|
152
|
+
|
|
153
|
+
Each modification item should display a **status indicator dot** based on the `status` field:
|
|
154
|
+
- 🟢 **Green dot**: `status === "success"` - modification was applied successfully
|
|
155
|
+
- 🟡 **Yellow dot**: `status === "pending"` - modification is being processed
|
|
156
|
+
- 🔴 **Red dot**: `status === "error"` - modification failed to apply
|
|
145
157
|
|
|
146
158
|
```tsx
|
|
147
159
|
"use client"
|
|
148
160
|
|
|
149
|
-
import { useState } from "react"
|
|
150
|
-
import { useModify } from "modifywithai/react"
|
|
161
|
+
import { useState, useMemo } from "react"
|
|
162
|
+
import { useModify, useList, useEnable, useDisable } from "modifywithai/react"
|
|
163
|
+
|
|
164
|
+
// Status indicator dot component
|
|
165
|
+
function StatusDot({ status }: { status: "success" | "pending" | "error" }) {
|
|
166
|
+
const colors = {
|
|
167
|
+
success: "#22c55e", // green
|
|
168
|
+
pending: "#eab308", // yellow
|
|
169
|
+
error: "#ef4444", // red
|
|
170
|
+
}
|
|
171
|
+
return (
|
|
172
|
+
<span
|
|
173
|
+
style={{
|
|
174
|
+
display: "inline-block",
|
|
175
|
+
width: 8,
|
|
176
|
+
height: 8,
|
|
177
|
+
borderRadius: "50%",
|
|
178
|
+
backgroundColor: colors[status],
|
|
179
|
+
marginRight: 8,
|
|
180
|
+
}}
|
|
181
|
+
title={status}
|
|
182
|
+
/>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
151
185
|
|
|
152
186
|
export function ModifyWithAIButton() {
|
|
153
187
|
const [isOpen, setIsOpen] = useState(false)
|
|
154
188
|
const [prompt, setPrompt] = useState("")
|
|
155
|
-
const
|
|
189
|
+
const [showAll, setShowAll] = useState(false)
|
|
190
|
+
const [searchQuery, setSearchQuery] = useState("")
|
|
191
|
+
|
|
192
|
+
const { modify, isPending } = useModify()
|
|
193
|
+
const { data: modifications = [], isLoading: isLoadingList } = useList()
|
|
194
|
+
const { enable } = useEnable()
|
|
195
|
+
const { disable } = useDisable()
|
|
196
|
+
|
|
197
|
+
// Sort modifications: enabled first, then by createdAt (newest first)
|
|
198
|
+
const sortedModifications = useMemo(() => {
|
|
199
|
+
return [...modifications].sort((a, b) => {
|
|
200
|
+
// Enabled items first
|
|
201
|
+
if (a.enabled && !b.enabled) return -1
|
|
202
|
+
if (!a.enabled && b.enabled) return 1
|
|
203
|
+
// Then by createdAt descending (newest first)
|
|
204
|
+
return b.createdAt - a.createdAt
|
|
205
|
+
})
|
|
206
|
+
}, [modifications])
|
|
207
|
+
|
|
208
|
+
// Filter modifications by search query (searches through titles)
|
|
209
|
+
const filteredModifications = useMemo(() => {
|
|
210
|
+
if (!searchQuery.trim()) return sortedModifications
|
|
211
|
+
const query = searchQuery.toLowerCase()
|
|
212
|
+
return sortedModifications.filter((mod) =>
|
|
213
|
+
mod.title?.toLowerCase().includes(query)
|
|
214
|
+
)
|
|
215
|
+
}, [sortedModifications, searchQuery])
|
|
216
|
+
|
|
217
|
+
// Show only first 3 in default view
|
|
218
|
+
const displayedModifications = showAll
|
|
219
|
+
? filteredModifications
|
|
220
|
+
: sortedModifications.slice(0, 3)
|
|
221
|
+
|
|
222
|
+
const hasMoreThanThree = sortedModifications.length > 3
|
|
156
223
|
|
|
157
224
|
const handleSubmit = () => {
|
|
158
225
|
if (!prompt.trim()) return
|
|
159
226
|
|
|
160
|
-
|
|
227
|
+
modify(prompt, {
|
|
161
228
|
onSuccess: () => {
|
|
162
229
|
setPrompt("")
|
|
163
230
|
setIsOpen(false)
|
|
@@ -170,6 +237,26 @@ export function ModifyWithAIButton() {
|
|
|
170
237
|
})
|
|
171
238
|
}
|
|
172
239
|
|
|
240
|
+
const handleToggleModification = (mod: typeof modifications[0]) => {
|
|
241
|
+
if (mod.enabled) {
|
|
242
|
+
disable(mod.id)
|
|
243
|
+
} else {
|
|
244
|
+
enable(mod.id)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const handleClose = () => {
|
|
249
|
+
setIsOpen(false)
|
|
250
|
+
setShowAll(false)
|
|
251
|
+
setSearchQuery("")
|
|
252
|
+
setPrompt("")
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const handleBackToDefault = () => {
|
|
256
|
+
setShowAll(false)
|
|
257
|
+
setSearchQuery("")
|
|
258
|
+
}
|
|
259
|
+
|
|
173
260
|
if (!isOpen) {
|
|
174
261
|
return (
|
|
175
262
|
<button onClick={() => setIsOpen(true)}>
|
|
@@ -179,24 +266,96 @@ export function ModifyWithAIButton() {
|
|
|
179
266
|
}
|
|
180
267
|
|
|
181
268
|
return (
|
|
182
|
-
<div>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
269
|
+
<div className="modal">
|
|
270
|
+
{showAll ? (
|
|
271
|
+
// "Show all" view: search bar + scrollable list
|
|
272
|
+
<>
|
|
273
|
+
<div className="modal-header">
|
|
274
|
+
<button onClick={handleBackToDefault}>← Back</button>
|
|
275
|
+
<h3>All Modifications</h3>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<input
|
|
279
|
+
type="text"
|
|
280
|
+
value={searchQuery}
|
|
281
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
282
|
+
placeholder="Search modifications..."
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<div className="modifications-list scrollable">
|
|
286
|
+
{isLoadingList ? (
|
|
287
|
+
<p>Loading...</p>
|
|
288
|
+
) : filteredModifications.length === 0 ? (
|
|
289
|
+
<p>No modifications found</p>
|
|
290
|
+
) : (
|
|
291
|
+
filteredModifications.map((mod) => (
|
|
292
|
+
<div key={mod.id} className="modification-item">
|
|
293
|
+
<StatusDot status={mod.status} />
|
|
294
|
+
<span>{mod.title || "Untitled modification"}</span>
|
|
295
|
+
<button onClick={() => handleToggleModification(mod)}>
|
|
296
|
+
{mod.enabled ? "Disable" : "Enable"}
|
|
297
|
+
</button>
|
|
298
|
+
</div>
|
|
299
|
+
))
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
</>
|
|
303
|
+
) : (
|
|
304
|
+
// Default view: textarea + buttons + limited modifications list
|
|
305
|
+
<>
|
|
306
|
+
<textarea
|
|
307
|
+
value={prompt}
|
|
308
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
309
|
+
placeholder="Describe the changes you want to make..."
|
|
310
|
+
disabled={isPending}
|
|
311
|
+
/>
|
|
312
|
+
|
|
313
|
+
<div className="button-group">
|
|
314
|
+
<button onClick={handleSubmit} disabled={isPending || !prompt.trim()}>
|
|
315
|
+
{isPending ? "Submitting..." : "Submit"}
|
|
316
|
+
</button>
|
|
317
|
+
<button onClick={handleClose} disabled={isPending}>
|
|
318
|
+
Cancel
|
|
319
|
+
</button>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
{/* Modifications list (up to 3) */}
|
|
323
|
+
{sortedModifications.length > 0 && (
|
|
324
|
+
<div className="modifications-section">
|
|
325
|
+
<h4>Recent Modifications</h4>
|
|
326
|
+
<div className="modifications-list">
|
|
327
|
+
{displayedModifications.map((mod) => (
|
|
328
|
+
<div key={mod.id} className="modification-item">
|
|
329
|
+
<StatusDot status={mod.status} />
|
|
330
|
+
<span>{mod.title || "Untitled modification"}</span>
|
|
331
|
+
<button onClick={() => handleToggleModification(mod)}>
|
|
332
|
+
{mod.enabled ? "Disable" : "Enable"}
|
|
333
|
+
</button>
|
|
334
|
+
</div>
|
|
335
|
+
))}
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{hasMoreThanThree && (
|
|
339
|
+
<button onClick={() => setShowAll(true)}>
|
|
340
|
+
Show all ({sortedModifications.length})
|
|
341
|
+
</button>
|
|
342
|
+
)}
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
</>
|
|
346
|
+
)}
|
|
195
347
|
</div>
|
|
196
348
|
)
|
|
197
349
|
}
|
|
198
350
|
```
|
|
199
351
|
|
|
352
|
+
### Modal Views Summary
|
|
353
|
+
|
|
354
|
+
| View | Components Displayed |
|
|
355
|
+
|------|---------------------|
|
|
356
|
+
| **Default** | Textarea, Submit button, Cancel button, Up to 3 modifications, "Show all" button (if >3 mods) |
|
|
357
|
+
| **Show all** | Back button, Search bar, Scrollable list of all modifications |
|
|
358
|
+
|
|
200
359
|
### Styling Requirements
|
|
201
360
|
|
|
202
361
|
**Important:** The example above is a minimal reference implementation. You MUST:
|
package/dist/nextjs/api.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { NextRequest } from "next/server";
|
|
|
2
2
|
|
|
3
3
|
//#region src/nextjs/api.d.ts
|
|
4
4
|
type CreateApiProxyHandlerOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* The app ID for this MWAI application
|
|
7
|
+
* This identifies your app in the modifywithai system
|
|
8
|
+
*/
|
|
9
|
+
appId: string;
|
|
5
10
|
/**
|
|
6
11
|
* Base URL of the modifywithai service
|
|
7
12
|
* @default "https://modifywithai.com"
|
package/dist/nextjs/api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NextResponse as e}from"next/server";const t=[`list`,`enable`,`disable`,`modify`,`status`],n={modify:{method:`POST`,inBody:!0},disable:{method:`POST`,inBody:!0},enable:{method:`POST`,inBody:!0},list:{method:`GET`,inBody:!1}};async function r(){return t.map(e=>({path:e}))}function i(r){let{baseUrl:
|
|
1
|
+
import{NextResponse as e}from"next/server";const t=[`list`,`enable`,`disable`,`modify`,`status`],n={modify:{method:`POST`,inBody:!0,needsEndUserId:!0},disable:{method:`POST`,inBody:!0,needsEndUserId:!0},enable:{method:`POST`,inBody:!0,needsEndUserId:!0},list:{method:`GET`,inBody:!1,needsEndUserId:!1},status:{method:`GET`,inBody:!1,needsEndUserId:!1}};async function r(){return t.map(e=>({path:e}))}function i(r){let{appId:i,baseUrl:a=`https://modifywithai.com`,apiKey:o=process.env.MWAI_API_KEY,getEndUserId:s}=r;if(!o)throw Error(`MWAI API key is required. Provide it via the apiKey option or set the MWAI_API_KEY environment variable.`);async function c(r,{params:c}){let l=(await c).path;if(!t.includes(l))return e.json({error:`Not found`},{status:404});let u=n[l],d=await s(r);if(u.needsEndUserId&&!d)return e.json({error:`Unauthorized - end user ID required`},{status:401});let f=new URL(`/api/${l}`,a);r.nextUrl.searchParams.forEach((e,t)=>{f.searchParams.set(t,e)});let p=new Headers({"Content-Type":`application/json`,"x-api-key":o}),m;if(r.method===`POST`||r.method===`PUT`||r.method===`PATCH`)try{let e=await r.json();e.appId=i,u.needsEndUserId&&d&&(e.endUserId=d),m=JSON.stringify(e)}catch{let e={appId:i};u.needsEndUserId&&d&&(e.endUserId=d),m=JSON.stringify(e)}r.method===`GET`&&(f.searchParams.set(`appId`,i),d&&f.searchParams.set(`endUserId`,d));let h=await fetch(f.toString(),{method:r.method,headers:p,body:m}),g=await h.text();return new Response(g,{status:h.status,statusText:h.statusText,headers:{"Content-Type":h.headers.get(`Content-Type`)||`application/json`}})}return{GET:c,POST:c,PUT:c,PATCH:c,DELETE:c}}export{i as createNextjsHandler,r as generateStaticParams};
|