assistant-ui 0.0.79 → 0.0.80

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.
@@ -0,0 +1,355 @@
1
+ import { createTransformer } from "../utils/createTransformer";
2
+
3
+ type ConditionFragment = {
4
+ expression: string;
5
+ negated: boolean;
6
+ };
7
+
8
+ // Map ThreadPrimitive.If props to condition expressions
9
+ const threadPropMap: Record<
10
+ string,
11
+ (value: unknown) => ConditionFragment | null
12
+ > = {
13
+ empty: (v) => ({
14
+ expression: "s.thread.isEmpty",
15
+ negated: v === false,
16
+ }),
17
+ running: (v) => ({
18
+ expression: "s.thread.isRunning",
19
+ negated: v === false,
20
+ }),
21
+ disabled: (v) => ({
22
+ expression: "s.thread.isDisabled",
23
+ negated: v === false,
24
+ }),
25
+ };
26
+
27
+ // Map MessagePrimitive.If props to condition expressions
28
+ const messagePropMap: Record<
29
+ string,
30
+ (value: unknown) => ConditionFragment | null
31
+ > = {
32
+ user: () => ({ expression: 's.message.role === "user"', negated: false }),
33
+ assistant: () => ({
34
+ expression: 's.message.role === "assistant"',
35
+ negated: false,
36
+ }),
37
+ system: () => ({
38
+ expression: 's.message.role === "system"',
39
+ negated: false,
40
+ }),
41
+ hasBranches: () => ({
42
+ expression: "s.message.branchCount >= 2",
43
+ negated: false,
44
+ }),
45
+ copied: (v) => ({
46
+ expression: "s.message.isCopied",
47
+ negated: v === false,
48
+ }),
49
+ last: (v) => ({
50
+ expression: "s.message.isLast",
51
+ negated: v === false,
52
+ }),
53
+ lastOrHover: () => ({
54
+ expression: "s.message.isHovering || s.message.isLast",
55
+ negated: false,
56
+ }),
57
+ speaking: (v) => ({
58
+ expression: "s.message.speech != null",
59
+ negated: v === false,
60
+ }),
61
+ hasAttachments: (v) =>
62
+ v === true
63
+ ? {
64
+ expression:
65
+ 's.message.role === "user" && !!s.message.attachments?.length',
66
+ negated: false,
67
+ }
68
+ : {
69
+ expression:
70
+ 's.message.role !== "user" || !s.message.attachments?.length',
71
+ negated: false,
72
+ },
73
+ hasContent: (v) => ({
74
+ expression: "s.message.parts.length > 0",
75
+ negated: v === false,
76
+ }),
77
+ submittedFeedback: (v) => {
78
+ if (v === null) {
79
+ return {
80
+ expression:
81
+ "(s.message.metadata.submittedFeedback?.type ?? null) === null",
82
+ negated: false,
83
+ };
84
+ }
85
+ return {
86
+ expression: `s.message.metadata.submittedFeedback?.type === "${v}"`,
87
+ negated: false,
88
+ };
89
+ },
90
+ };
91
+
92
+ // Map ComposerPrimitive.If props to condition expressions
93
+ const composerPropMap: Record<
94
+ string,
95
+ (value: unknown) => ConditionFragment | null
96
+ > = {
97
+ editing: (v) => ({
98
+ expression: "s.composer.isEditing",
99
+ negated: v === false,
100
+ }),
101
+ dictation: (v) => ({
102
+ expression: "s.composer.dictation != null",
103
+ negated: v === false,
104
+ }),
105
+ };
106
+
107
+ const primitiveMap: Record<
108
+ string,
109
+ Record<string, (value: unknown) => ConditionFragment | null>
110
+ > = {
111
+ ThreadPrimitive: threadPropMap,
112
+ MessagePrimitive: messagePropMap,
113
+ ComposerPrimitive: composerPropMap,
114
+ };
115
+
116
+ // Map of XPrimitive.Component → fixed condition (no props needed)
117
+ const fixedConditionMap: Record<string, Record<string, string>> = {
118
+ ThreadPrimitive: {
119
+ Empty: "s.thread.isEmpty",
120
+ },
121
+ };
122
+
123
+ /**
124
+ * Extract the value of a JSX attribute.
125
+ * - Boolean prop (no value): `<X.If user>` → `true`
126
+ * - `{true}` / `{false}`: → `true` / `false`
127
+ * - `{"positive"}`: → `"positive"`
128
+ * - `{null}`: → `null`
129
+ */
130
+ const getAttrValue = (j: any, attr: any): unknown => {
131
+ // Boolean attribute (no value), e.g. `<X.If user>`
132
+ if (attr.value === null || attr.value === undefined) {
133
+ return true;
134
+ }
135
+
136
+ // JSX expression container: `{true}`, `{false}`, `{"positive"}`, `{null}`
137
+ if (j.JSXExpressionContainer.check(attr.value)) {
138
+ const expr = attr.value.expression;
139
+ if (j.BooleanLiteral.check(expr)) return expr.value;
140
+ if (j.Literal.check(expr)) {
141
+ if (expr.value === null) return null;
142
+ return expr.value;
143
+ }
144
+ if (j.NullLiteral.check(expr)) return null;
145
+ if (j.Identifier.check(expr) && expr.name === "undefined") return undefined;
146
+ }
147
+
148
+ // String literal
149
+ if (j.StringLiteral.check(attr.value) || j.Literal.check(attr.value)) {
150
+ return attr.value.value;
151
+ }
152
+
153
+ return undefined;
154
+ };
155
+
156
+ const buildConditionString = (fragments: ConditionFragment[]): string => {
157
+ const parts = fragments.map((f) =>
158
+ f.negated ? `!${f.expression}` : f.expression,
159
+ );
160
+ if (parts.length === 1) return parts[0]!;
161
+ return parts.join(" && ");
162
+ };
163
+
164
+ const migratePrimitiveIfToAuiIf = createTransformer(
165
+ ({ j, root, markAsChanged }) => {
166
+ let needsAuiIfImport = false;
167
+
168
+ // Track which primitive namespaces are imported
169
+ const importedPrimitives = new Set<string>();
170
+ root.find(j.ImportDeclaration).forEach((path: any) => {
171
+ const source = path.value.source.value;
172
+ if (typeof source === "string" && source.startsWith("@assistant-ui/")) {
173
+ path.value.specifiers?.forEach((specifier: any) => {
174
+ if (j.ImportSpecifier.check(specifier)) {
175
+ const name = String(
176
+ specifier.local?.name ?? specifier.imported.name,
177
+ );
178
+ if (primitiveMap[name] || fixedConditionMap[name]) {
179
+ importedPrimitives.add(name);
180
+ }
181
+ }
182
+ });
183
+ }
184
+ });
185
+
186
+ if (importedPrimitives.size === 0) return;
187
+
188
+ // Process fixed-condition components: <ThreadPrimitive.Empty> → <AuiIf condition={...}>
189
+ root.find(j.JSXOpeningElement).forEach((path: any) => {
190
+ const name = path.value.name;
191
+ if (!j.JSXMemberExpression.check(name)) return;
192
+ if (!j.JSXIdentifier.check(name.object)) return;
193
+ if (!j.JSXIdentifier.check(name.property)) return;
194
+
195
+ const primitiveName = name.object.name as string;
196
+ const propertyName = name.property.name as string;
197
+ const fixedMap = fixedConditionMap[primitiveName];
198
+ if (!fixedMap) return;
199
+ const conditionBody = fixedMap[propertyName];
200
+ if (!conditionBody) return;
201
+ if (!importedPrimitives.has(primitiveName)) return;
202
+
203
+ // Only transform if there are no props (other than children, which are implicit)
204
+ const attrs: any[] = path.value.attributes || [];
205
+ if (attrs.length > 0) return;
206
+
207
+ const arrowFnAst = j(`(s) => ${conditionBody}`)
208
+ .find(j.ArrowFunctionExpression)
209
+ .paths()[0]!.value;
210
+
211
+ path.value.name = j.jsxIdentifier("AuiIf");
212
+ path.value.attributes = [
213
+ j.jsxAttribute(
214
+ j.jsxIdentifier("condition"),
215
+ j.jsxExpressionContainer(arrowFnAst),
216
+ ),
217
+ ];
218
+
219
+ needsAuiIfImport = true;
220
+ markAsChanged();
221
+ });
222
+
223
+ // Update closing elements for fixed-condition components
224
+ root.find(j.JSXClosingElement).forEach((path: any) => {
225
+ const name = path.value.name;
226
+ if (!j.JSXMemberExpression.check(name)) return;
227
+ if (!j.JSXIdentifier.check(name.object)) return;
228
+ if (!j.JSXIdentifier.check(name.property)) return;
229
+
230
+ const primitiveName = name.object.name as string;
231
+ const propertyName = name.property.name as string;
232
+ const fixedMap = fixedConditionMap[primitiveName];
233
+ if (!fixedMap || !fixedMap[propertyName]) return;
234
+ if (!importedPrimitives.has(primitiveName)) return;
235
+
236
+ path.value.name = j.jsxIdentifier("AuiIf");
237
+ markAsChanged();
238
+ });
239
+
240
+ // Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
241
+ root.find(j.JSXOpeningElement).forEach((path: any) => {
242
+ const name = path.value.name;
243
+
244
+ // Check for `<XPrimitive.If ...>`
245
+ if (!j.JSXMemberExpression.check(name)) return;
246
+ if (!j.JSXIdentifier.check(name.object)) return;
247
+ if (!j.JSXIdentifier.check(name.property)) return;
248
+ if (name.property.name !== "If") return;
249
+
250
+ const primitiveName = name.object.name;
251
+ const propMap = primitiveMap[primitiveName];
252
+ if (!propMap) return;
253
+ if (!importedPrimitives.has(primitiveName)) return;
254
+
255
+ // Extract props
256
+ const attrs: any[] = path.value.attributes || [];
257
+ const fragments: ConditionFragment[] = [];
258
+ let hasUnknownProp = false;
259
+
260
+ for (const attr of attrs) {
261
+ if (!j.JSXAttribute.check(attr)) {
262
+ // JSX spread attributes — can't migrate
263
+ hasUnknownProp = true;
264
+ continue;
265
+ }
266
+ const propName =
267
+ typeof attr.name.name === "string" ? attr.name.name : null;
268
+ if (!propName) continue;
269
+
270
+ const mapper = propMap[propName];
271
+ if (!mapper) {
272
+ hasUnknownProp = true;
273
+ continue;
274
+ }
275
+
276
+ const value = getAttrValue(j, attr);
277
+ const fragment = mapper(value);
278
+ if (fragment) {
279
+ fragments.push(fragment);
280
+ }
281
+ }
282
+
283
+ // If we couldn't map all props, skip this element
284
+ if (hasUnknownProp || fragments.length === 0) return;
285
+
286
+ const conditionBody = buildConditionString(fragments);
287
+
288
+ // Parse the arrow function as an expression to get a proper AST node
289
+ const arrowFnAst = j(`(s) => ${conditionBody}`)
290
+ .find(j.ArrowFunctionExpression)
291
+ .paths()[0]!.value;
292
+
293
+ // Replace <XPrimitive.If ...> with <AuiIf condition={...}>
294
+ path.value.name = j.jsxIdentifier("AuiIf");
295
+
296
+ // Replace all attributes with a single condition prop
297
+ path.value.attributes = [
298
+ j.jsxAttribute(
299
+ j.jsxIdentifier("condition"),
300
+ j.jsxExpressionContainer(arrowFnAst),
301
+ ),
302
+ ];
303
+
304
+ needsAuiIfImport = true;
305
+ markAsChanged();
306
+ });
307
+
308
+ // Update closing elements to match
309
+ root.find(j.JSXClosingElement).forEach((path: any) => {
310
+ const name = path.value.name;
311
+ if (!j.JSXMemberExpression.check(name)) return;
312
+ if (!j.JSXIdentifier.check(name.object)) return;
313
+ if (!j.JSXIdentifier.check(name.property)) return;
314
+ if (name.property.name !== "If") return;
315
+
316
+ const primitiveName = name.object.name;
317
+ if (!primitiveMap[primitiveName]) return;
318
+ if (!importedPrimitives.has(primitiveName)) return;
319
+
320
+ path.value.name = j.jsxIdentifier("AuiIf");
321
+ markAsChanged();
322
+ });
323
+
324
+ // Add AuiIf import if needed
325
+ if (needsAuiIfImport) {
326
+ let hasAuiIfImport = false;
327
+ let assistantUiImport: any = null;
328
+
329
+ root.find(j.ImportDeclaration).forEach((path: any) => {
330
+ const source = path.value.source.value;
331
+ if (typeof source === "string" && source.startsWith("@assistant-ui/")) {
332
+ assistantUiImport = path;
333
+ path.value.specifiers?.forEach((specifier: any) => {
334
+ if (
335
+ j.ImportSpecifier.check(specifier) &&
336
+ (specifier.imported.name === "AuiIf" ||
337
+ specifier.local?.name === "AuiIf")
338
+ ) {
339
+ hasAuiIfImport = true;
340
+ }
341
+ });
342
+ }
343
+ });
344
+
345
+ if (!hasAuiIfImport && assistantUiImport) {
346
+ assistantUiImport.value.specifiers.push(
347
+ j.importSpecifier(j.identifier("AuiIf")),
348
+ );
349
+ markAsChanged();
350
+ }
351
+ }
352
+ },
353
+ );
354
+
355
+ export default migratePrimitiveIfToAuiIf;
@@ -16,7 +16,9 @@ export interface CreateFromExampleOptions {
16
16
  const VALID_EXAMPLES = [
17
17
  "with-ag-ui",
18
18
  "with-ai-sdk-v6",
19
+ "with-artifacts",
19
20
  "with-assistant-transport",
21
+ "with-chain-of-thought",
20
22
  "with-cloud",
21
23
  "with-custom-thread-list",
22
24
  "with-elevenlabs-scribe",
@@ -13,6 +13,7 @@ const bundle = [
13
13
  "v0-11/content-part-to-message-part",
14
14
  "v0-12/assistant-api-to-aui",
15
15
  "v0-12/event-names-to-camelcase",
16
+ "v0-12/primitive-if-to-aui-if",
16
17
  ];
17
18
 
18
19
  const log = debug("codemod:upgrade");