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 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>` and `<MWAIComponents />` at the end of `<body>`, after `{children}`
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
- <MWAIComponents />
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 { mutate, isPending } = useModify()
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
- mutate(prompt, {
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
- <textarea
184
- value={prompt}
185
- onChange={(e) => setPrompt(e.target.value)}
186
- placeholder="Describe the changes you want to make..."
187
- disabled={isPending}
188
- />
189
- <button onClick={handleSubmit} disabled={isPending || !prompt.trim()}>
190
- {isPending ? "Submitting..." : "Submit"}
191
- </button>
192
- <button onClick={() => setIsOpen(false)} disabled={isPending}>
193
- Cancel
194
- </button>
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:
@@ -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"
@@ -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:i=`https://modifywithai.com`,apiKey:a=process.env.MWAI_API_KEY,getEndUserId:o}=r;if(!a)throw Error(`MWAI API key is required. Provide it via the apiKey option or set the MWAI_API_KEY environment variable.`);async function s(r,{params:s}){let c=(await s).path;if(!t.includes(c))return e.json({error:`Not found`},{status:404});let l=c,u=await o(r),d=n[l];if(d&&!u)return e.json({error:`Unauthorized - end user ID required`},{status:401});let f=new URL(`/api/${c}`,i);r.nextUrl.searchParams.forEach((e,t)=>{f.searchParams.set(t,e)});let p=new Headers({"Content-Type":`application/json`,"x-api-key":a}),m;if(r.method===`POST`||r.method===`PUT`||r.method===`PATCH`)try{let e=await r.json();d?.inBody&&u&&(e.endUserId=u),m=JSON.stringify(e)}catch{}r.method===`GET`&&d&&!d.inBody&&u&&f.searchParams.set(`endUserId`,u);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:s,POST:s,PUT:s,PATCH:s,DELETE:s}}export{i as createNextjsHandler,r as generateStaticParams};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modifywithai",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",