Implement AI text refinement features (shorten, elaborate, professional) for SmartPages/Lexical editor.
We need to create:
- A hook (
useAITextRefine) - manages API calls, loading state, results - A panel (
AIRefinePanel) - UI for selecting presets and displaying results
Constraint: The API layer (endpoints, auth, CSRF, request body format) lives in client/ code. Shared packages cannot import from client/.
Question: How should the Panel consume the hook?
Put both the hook and the panel in client/ code.
// client/features/shared/components/AIRefine/useAITextRefine.ts
// client/features/shared/components/AIRefine/AIRefinePanel.tsx
// Usage in Lexical plugin
const { transform, result, isLoading } = useAITextRefine();
<AIRefinePanel
result={result}
isLoading={isLoading}
onTransform={(text, preset) => transform(text, preset)}
onAccept={(text) => editor.replaceSelection(text)}
/>| Pros | Cons |
|---|---|
| Simplest implementation | Panel not reusable outside client |
| No cross-package dependencies | Cannot be used by other packages |
| All code in one place | Doesn't leverage common-ux package |
| Fastest to implement |
Best for: Projects where reusability isn't a concern.
Hook lives in client/, Panel lives in @highspot/common-ux as a purely presentational component.
// client/features/shared/hooks/useAITextRefine.ts
const { transform, result, isLoading } = useAITextRefine();
// @highspot/common-ux - Panel is purely presentational
<AIRefinePanel
text={selectedText}
result={result}
isLoading={isLoading}
presets={['shorten', 'elaborate', 'professional']}
onTransform={(text, preset) => transform(text, preset)}
onAccept={(text) => editor.replaceSelection(text)}
onCancel={() => closePanel()}
/>| Pros | Cons |
|---|---|
| Panel is reusable across packages | Verbose prop threading |
| Clear separation of concerns | Consumer must wire up hook + panel each time |
| No business logic in shared package | More boilerplate at usage sites |
| Panel is easy to test (pure props) |
Best for: When you want a reusable UI component but consumers are comfortable with manual wiring.
Hook lives in client/, Panel lives in @highspot/common-ux, plus a "smart" wrapper in client/ that combines them.
// @highspot/common-ux - Presentational panel (reusable)
<AIRefinePanel
text={...}
result={...}
isLoading={...}
onTransform={...}
onAccept={...}
onCancel={...}
/>
// client/features/shared/hooks/useAITextRefine.ts - Hook (API logic)
const { transform, result, isLoading } = useAITextRefine();
// client/features/shared/components/AIRefine.tsx - Smart wrapper
export const AIRefine = ({ text, onAccept, onCancel }) => {
const { transform, result, isLoading } = useAITextRefine();
return (
<AIRefinePanel
text={text}
result={result}
isLoading={isLoading}
onTransform={transform}
onAccept={onAccept}
onCancel={onCancel}
/>
);
};
// Usage in Lexical plugin - clean!
<AIRefine
text={selectedText}
onAccept={(text) => editor.replaceSelection(text)}
onCancel={() => closePanel()}
/>| Pros | Cons |
|---|---|
| Clean DX for consumers | Two components to maintain |
| Panel remains reusable in common-ux | Slight indirection |
| Smart wrapper hides wiring complexity | |
| Follows composition pattern | |
| Easy to add features (streaming, etc.) to wrapper |
Best for: When you want both reusability AND clean consumer DX.
Option 3: Composition Wrapper
This gives us the best of both worlds:
- The
AIRefinePanelin@highspot/common-uxis purely presentational and can be reused anywhere - The
AIRefinesmart wrapper inclient/provides a clean API for the common use case - Consumers who need custom behavior can use the Panel directly with their own hook
| Component | Location | Purpose |
|---|---|---|
useAITextRefine |
client/features/shared/hooks/ |
Hook that calls AI API |
AIRefinePanel |
@highspot/common-ux |
Presentational UI component |
AIRefine |
client/features/shared/components/ |
Smart wrapper combining hook + panel |
| JIRA Key | Description | Status |
|---|---|---|
| HS-157068 | Register AI Refine prompts in GrowthBook | Not started |
| HS-157069 | Create new package for AI features | ✅ Done |
| HS-157070 | Create useAITextTransform hook | 🔄 In Progress |
| HS-157071 | Add streaming support | Not started |
| HS-157072 | Create AIRefinePanel component | Not started |
| HS-157073 | Add Storybook stories | Not started |
| HS-157074 | Create Lexical AIRefinePlugin | Not started |
| HS-157075 | Feature flag setup | Not started |
| HS-157076 | E2E tests | Not started |
| Purpose | Path |
|---|---|
| Existing hook pattern | client/features/shared/components/ai/RoutesAIServices/hooks/useGeneralAIRequest.js |
| API request helper | client/features/shared/components/ai/RoutesAIServices/helpers/sendAIControlGeneralRequest.js |
| Streaming client | client/features/copilot/components/CopilotCenter/agent-platform/chatui/ChatUIStreamClient.ts |
| Routes/endpoints | client/features/shared/components/ai/RoutesAIServices/RoutesAIServices.js |