Skip to content

Commit e1e2d2a

Browse files
refactor(ai): update policy AI assistant to use new conversation components (#1849)
Co-authored-by: Daniel Fu <[email protected]>
1 parent 3d335bc commit e1e2d2a

File tree

4 files changed

+91
-82
lines changed

4 files changed

+91
-82
lines changed

apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/ai/policy-ai-assistant.tsx

Lines changed: 64 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'use client';
22

3+
import {
4+
Conversation,
5+
ConversationContent,
6+
ConversationScrollButton,
7+
} from '@comp/ui/ai-elements/conversation';
38
import {
49
PromptInput,
510
PromptInputFooter,
@@ -9,13 +14,7 @@ import {
914
import { Tool, ToolHeader } from '@comp/ui/ai-elements/tool';
1015
import { Button } from '@comp/ui/button';
1116
import { cn } from '@comp/ui/cn';
12-
import {
13-
getToolName,
14-
isToolUIPart,
15-
type ChatStatus,
16-
type ToolUIPart,
17-
type UIMessage,
18-
} from 'ai';
17+
import { getToolName, isToolUIPart, type ChatStatus, type ToolUIPart, type UIMessage } from 'ai';
1918
import { X } from 'lucide-react';
2019
import { useState } from 'react';
2120

@@ -42,8 +41,7 @@ export function PolicyAiAssistant({
4241
(m) =>
4342
m.role === 'assistant' &&
4443
m.parts.some(
45-
(p) =>
46-
isToolUIPart(p) && (p.state === 'input-streaming' || p.state === 'input-available'),
44+
(p) => isToolUIPart(p) && (p.state === 'input-streaming' || p.state === 'input-available'),
4745
),
4846
);
4947

@@ -64,59 +62,65 @@ export function PolicyAiAssistant({
6462
)}
6563
</div>
6664

67-
<div className="flex-1 overflow-y-auto p-3 space-y-3">
68-
{messages.length === 0 ? (
69-
<div className="text-sm text-muted-foreground py-4">
70-
<p>Ask me to help edit this policy.</p>
71-
<ul className="mt-2 space-y-1 text-xs">
72-
<li>"Add a data retention section"</li>
73-
<li>"Make this more SOC 2 compliant"</li>
74-
<li>"Simplify the language"</li>
75-
</ul>
76-
</div>
77-
) : (
78-
messages.map((message) => (
79-
<div
80-
key={message.id}
81-
className={cn(
82-
'text-sm',
83-
message.role === 'user'
84-
? 'ml-auto max-w-[85%] rounded-lg bg-secondary px-3 py-2'
85-
: 'text-foreground'
86-
)}
87-
>
88-
{message.parts.map((part, index) => {
89-
if (part.type === 'text') {
90-
return (
91-
<div key={`${message.id}-${index}`} className="whitespace-pre-wrap">
92-
{part.text}
93-
</div>
94-
);
95-
}
65+
<Conversation>
66+
<ConversationContent className="gap-3 p-3">
67+
{messages.length === 0 ? (
68+
<div className="text-sm text-muted-foreground py-4">
69+
<p>Ask me to help edit this policy.</p>
70+
<ul className="mt-2 space-y-1 text-xs">
71+
<li>"Add a data retention section"</li>
72+
<li>"Make this more SOC 2 compliant"</li>
73+
<li>"Simplify the language"</li>
74+
</ul>
75+
</div>
76+
) : (
77+
messages.map((message) => (
78+
<div
79+
key={message.id}
80+
className={cn(
81+
'text-sm',
82+
message.role === 'user'
83+
? 'ml-auto max-w-[85%] rounded-lg bg-secondary px-3 py-2'
84+
: 'text-foreground',
85+
)}
86+
>
87+
{message.parts.map((part, index) => {
88+
if (part.type === 'text') {
89+
return (
90+
<div key={`${message.id}-${index}`} className="whitespace-pre-wrap">
91+
{part.text}
92+
</div>
93+
);
94+
}
9695

97-
if (isToolUIPart(part) && getToolName(part) === 'proposePolicy') {
98-
const toolPart = part as ToolUIPart;
99-
const toolInput = toolPart.input as { content?: string; summary?: string };
100-
return (
101-
<Tool key={`${message.id}-${index}`} className="mt-2">
102-
<ToolHeader
103-
title={toolInput?.summary || 'Proposing policy changes'}
104-
type={toolPart.type}
105-
state={toolPart.state}
106-
/>
107-
</Tool>
108-
);
109-
}
96+
if (isToolUIPart(part) && getToolName(part) === 'proposePolicy') {
97+
const toolPart = part as ToolUIPart;
98+
const toolInput = toolPart.input as { content?: string; summary?: string };
99+
return (
100+
<Tool key={`${message.id}-${index}`} className="mt-2">
101+
<ToolHeader
102+
title={toolInput?.summary || 'Proposing policy changes'}
103+
type={toolPart.type}
104+
state={toolPart.state}
105+
/>
106+
<p className="px-3 pb-2 text-xs text-muted-foreground">
107+
View the proposed changes in the editor preview
108+
</p>
109+
</Tool>
110+
);
111+
}
110112

111-
return null;
112-
})}
113-
</div>
114-
))
115-
)}
116-
{isLoading && !hasActiveTool && (
117-
<div className="text-sm text-muted-foreground">Thinking...</div>
118-
)}
119-
</div>
113+
return null;
114+
})}
115+
</div>
116+
))
117+
)}
118+
{isLoading && !hasActiveTool && (
119+
<div className="text-sm text-muted-foreground">Thinking...</div>
120+
)}
121+
</ConversationContent>
122+
<ConversationScrollButton />
123+
</Conversation>
120124

121125
{errorMessage && (
122126
<div className="border-t bg-destructive/10 px-3 py-2">

apps/portal/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"moduleResolution": "bundler",
1616
"resolveJsonModule": true,
1717
"isolatedModules": true,
18-
"jsx": "preserve",
18+
"jsx": "react-jsx",
1919
"incremental": true,
2020
"plugins": [
2121
{

bun.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"lockfileVersion": 1,
3+
"configVersion": 0,
34
"workspaces": {
45
"": {
56
"name": "comp",

packages/ui/src/components/editor/index.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,29 +103,33 @@ export const Editor = ({
103103

104104
return (
105105
<div className="bg-background relative w-full p-4">
106-
<div className="relative">
106+
<div className="relative flex flex-col gap-4">
107107
{showToolbar && !readOnly && editor && (
108-
<div className="mb-4 border-muted rounded-sm bg-background flex items-center gap-1 p-2 border relative">
109-
<NodeSelector open={openNode} onOpenChange={setOpenNode} editor={editor} />
110-
<Separator orientation="vertical" className="h-6" />
111-
<TextButtons editor={editor} />
112-
<Separator orientation="vertical" className="h-6" />
113-
<LinkSelector open={openLink} onOpenChange={setOpenLink} editor={editor} />
114-
115-
{(showSaveStatus || showWordCount) && (
116-
<div className="absolute top-2 right-2 flex items-center gap-2">
117-
{showSaveStatus && (
118-
<div className="bg-accent text-muted-foreground px-3 py-1 text-sm rounded-sm leading-6">
119-
{saveStatus}
120-
</div>
121-
)}
122-
{showWordCount && charsCount > 0 && (
123-
<div className="bg-accent text-muted-foreground px-3 py-1 text-sm rounded-sm leading-6">
124-
{charsCount} Words
125-
</div>
126-
)}
108+
<div className="rounded-md border border-border bg-muted/40 px-3 py-2">
109+
<div className="flex w-full items-center gap-2 overflow-x-auto overflow-y-hidden">
110+
<div className="flex items-center gap-2 shrink-0">
111+
<NodeSelector open={openNode} onOpenChange={setOpenNode} editor={editor} />
112+
<Separator orientation="vertical" className="h-6 shrink-0" />
113+
<TextButtons editor={editor} />
114+
<Separator orientation="vertical" className="h-6 shrink-0" />
115+
<LinkSelector open={openLink} onOpenChange={setOpenLink} editor={editor} />
127116
</div>
128-
)}
117+
118+
{(showSaveStatus || showWordCount) && (
119+
<div className="ml-auto flex shrink-0 items-center gap-2">
120+
{showSaveStatus && (
121+
<div className="rounded-sm bg-accent px-3 py-1 text-sm leading-6 text-muted-foreground">
122+
{saveStatus}
123+
</div>
124+
)}
125+
{showWordCount && charsCount > 0 && (
126+
<div className="rounded-sm bg-accent px-3 py-1 text-sm leading-6 text-muted-foreground">
127+
{charsCount} Words
128+
</div>
129+
)}
130+
</div>
131+
)}
132+
</div>
129133
</div>
130134
)}
131135
<EditorContent

0 commit comments

Comments
 (0)