Skip to content

[SFT-2638]: Solspace AI#2461

Open
thejahid wants to merge 36 commits intov5from
feat/SFT-2638-ai-research
Open

[SFT-2638]: Solspace AI#2461
thejahid wants to merge 36 commits intov5from
feat/SFT-2638-ai-research

Conversation

@thejahid
Copy link
Contributor

No description provided.

thejahid and others added 26 commits February 16, 2026 14:15
…ypes and update documentation for optional properties
…feat/SFT-2638-ai-research

# Conflicts:
#	packages/plugin/src/Resources/js/client/client.js
#	packages/plugin/src/Resources/js/client/vendor.js
… into feat/SFT-2638-ai-research

# Conflicts:
#	packages/plugin/src/Resources/js/client/client.js
…oad fields in Table fields (#2448)

Co-authored-by: kjmartens <kelsey@solspace.com>
… into feat/SFT-2638-ai-research

# Conflicts:
#	packages/plugin/src/Resources/js/client/client.js
#	packages/plugin/src/Resources/js/client/vendor.js
kjmartens and others added 2 commits March 16, 2026 10:00
Co-authored-by: Gustavs Gūtmanis <gustavs@solspace.com>
… into feat/SFT-2638-ai-research

# Conflicts:
#	packages/client/src/index.tsx
#	packages/plugin/src/Resources/js/client/client.js
#	packages/plugin/src/Resources/js/client/vendor.js
#	packages/plugin/src/Resources/js/client/vendor.js.LICENSE.txt
#	packages/plugin/src/Resources/js/scripts/front-end/plugin/freeform.js
…feat/v5.15

# Conflicts:
#	composer.json
#	packages/plugin/src/Resources/js/client/client.js
… into feat/SFT-2638-ai-research

# Conflicts:
#	packages/plugin/src/Resources/js/client/client.js
#	packages/plugin/src/Resources/js/client/vendor.js
Comment on lines +20 to +25
payment_history?: {
package_price?: number;
credits?: number;
amount_added?: number;
paid_at?: string;
}[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested types which are used as Arrays should be separated out, and their type used.

type PaymentHistory = {
  package_price?: number;
  credits?: number;
  amount_added?: number;
  paid_at?: string;
}

type AiUsageResponse {
  // ...
  payment_history: PaymentHistory[];
}

Comment on lines +101 to +110
return (
<div>
<Breadcrumb id="ai" label="AI" url="ai" />
<HeaderContainer>{translate('AI')}</HeaderContainer>
<DashboardWrapper>
<CardsGrid>
<Card>
<CardLabel>
<Skeleton width={80} height={10} />
</CardLabel>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so big that it should be in its own file.
I would also move the forbidden and not found in their own files. Actually, just one file, since they're identical, except the messaging. A single boolean prop would determine which copy to display.

Comment on lines +232 to +242
function formatDate(iso: string | null | undefined): string {
if (!iso) return '—';
try {
const d = new Date(iso);
return Number.isNaN(d.getTime())
? iso
: d.toLocaleDateString(undefined, { dateStyle: 'medium' });
} catch {
return iso;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function inline here?
It gets recreated on every re-render.
There's nothing that uses anything in the scope, it should be moved to a utility file and imported and used that way.
Also we have date-fns library, which should be used for formatting dates.
And please refrain from naming variables as a, b, c, etc.

Comment on lines +206 to +227
if (isError) {
const message =
axios.isAxiosError(error) && error.response?.data?.message
? error.response.data.message
: error instanceof Error
? error.message
: translate('Failed to load usage data');
return (
<div>
<Breadcrumb id="ai" label="AI" url="ai" />
<HeaderContainer>{translate('AI')}</HeaderContainer>
<DashboardWrapper>
<EmptyState>
<EmptyStateTitle>
{translate('Error loading usage')}
</EmptyStateTitle>
<p>{message}</p>
</EmptyState>
</DashboardWrapper>
</div>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many inline cases of rendering something completely different.
All of them share the same layout, so at least bring the layout wrappers out to a separate component and use that by passing only the children, that would make this file have less filler code which makes it difficult to navigate and understand.

Comment on lines +266 to +271
{summary.mode === 'trial' && translate('Free trial')}
{summary.mode === 'plan' && translate('Active plan')}
{summary.mode === 'blocked' &&
translate('Usage limit reached')}
{(!summary.mode || summary.mode === 'unknown') &&
translate('Not configured')}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest writing a little function which does this, and then pass the mode to it.

const getSummaryModeLabel = (mode: SummaryMode): string => {
  switch (mode) {
    case "trial":
      return "Free Trial";

    // ...
  }
}

// ...

<CardValueSmall>
  {getSummaryModeLabel(summary.mode)}
</CardValueSmall>

<MetricsTableRow key={b.key}>
<MetricsTableCell>
{formatBundlePrice(
b.price,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b. Please use bundle, or do bundles.map(({ price, currency } or something

Comment on lines +65 to +78
const promptProperty = useMemo<TextareaProperty>(
() => ({
type: PropertyType.Textarea,
handle: 'prompt',
label: translate('Describe your form'),
instructions: translate('Describe the fields and purpose of the form.'),
required: true,
rows: 4,
placeholder: translate(
'e.g. Contact form with name, email, phone, and a message box'
),
}),
[]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these can be simple objects.
also - since the v5.15 changes you can pass these props directly to the <Control> without needing a property variable, so that makes it easier to use control components

Comment on lines +141 to +158
let message: string | null = null;
if (err && typeof err === 'object' && 'response' in err) {
const res = (err as { response?: { data?: unknown } }).response?.data;
if (typeof res === 'string') {
message = res;
} else if (
res &&
typeof res === 'object' &&
'message' in res &&
typeof (res as { message: unknown }).message === 'string'
) {
message = (res as { message: string }).message;
}
}
setError(
message ||
translate('Form generation failed. Please try again or rephrase.')
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks very GPT-y 😆
it would be very nice to write a handler function for extracting error messages from AJAX errors, or just use the optimistic path and assume we know how the error looks.

<button className="btn submit add icon" onClick={openCreateFormModal}>
{translate('Create a new Form')}
</button>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write a new styled component for this. Do not use inline styles

'plan' => $body['plan'] ?? null,
'bundle_key' => $body['bundle_key'] ?? null,
'currency' => $body['currency'] ?? null,
], static function ($v): bool { return $v !== null && $v !== ''; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a prime candidate for shorthand anonymous functions

Suggested change
], static function ($v): bool { return $v !== null && $v !== ''; });
], static fn ($v) => $v !== null && $v !== '');

kjmartens and others added 3 commits March 17, 2026 11:16
…feat/v5.15

# Conflicts:
#	packages/plugin/src/Resources/js/scripts/front-end/plugin/freeform.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants