Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ import {
saveWritingStyleBody,
} from "@/utils/actions/user.validation";
import { saveWritingStyleAction } from "@/utils/actions/user";
import { regenerateWritingStyleAction } from "@/utils/actions/assess";
import { Tiptap, type TiptapHandle } from "@/components/editor/Tiptap";
import { getActionErrorMessage } from "@/utils/error";
import { Loader2, Sparkles } from "lucide-react";

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const WRITING_STYLE_MAX_LENGTH = 2000;

export function WritingStyleSetting() {
const { data, isLoading, error } = useEmailAccountFull();
Expand Down Expand Up @@ -69,24 +73,48 @@ function WritingStyleDialog({
control,
formState: { errors },
handleSubmit,
setValue,
} = useForm<SaveWritingStyleBody>({
defaultValues: { writingStyle: currentWritingStyle },
resolver: zodResolver(saveWritingStyleBody),
});

// --- REGENERATE ACTION ---
const { execute: generate, isExecuting: isGenerating } = useAction(
regenerateWritingStyleAction.bind(null, emailAccountId),
{
onSuccess: (data) => {
const rawStyle = data?.data?.writingStyle;

if (rawStyle) {
// Truncate to ensure it passes the save validation schema
const safeStyle = rawStyle.slice(0, WRITING_STYLE_MAX_LENGTH);

setValue("writingStyle", safeStyle);
if (editorRef.current?.editor) {
editorRef.current.editor.commands.setContent(safeStyle);
}
toastSuccess({ description: "Writing style regenerated!" });
} else {
toastSuccess({ description: "Not enough data to generate style." });
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
onError: (error) => {
toastError({ description: getActionErrorMessage(error.error) });
},
},
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// -------------------------

const { execute, isExecuting } = useAction(
saveWritingStyleAction.bind(null, emailAccountId),
{
onSuccess: () => {
toastSuccess({
description: "Writing style saved!",
});
toastSuccess({ description: "Writing style saved!" });
setOpen(false);
},
onError: (error) => {
toastError({
description: getActionErrorMessage(error.error),
});
toastError({ description: getActionErrorMessage(error.error) });
},
onSettled: () => {
mutate();
Expand Down Expand Up @@ -120,7 +148,7 @@ function WritingStyleDialog({
initialContent={field.value ?? ""}
onChange={field.onChange}
output="markdown"
className="prose prose-sm dark:prose-invert max-w-none [&_p.is-editor-empty:first-child::before]:pointer-events-none [&_p.is-editor-empty:first-child::before]:float-left [&_p.is-editor-empty:first-child::before]:h-0 [&_p.is-editor-empty:first-child::before]:text-muted-foreground [&_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]"
className="prose prose-sm dark:prose-invert max-w-none"
autofocus={false}
preservePastedLineBreaks
placeholder={`Typical Length: 2-3 sentences
Expand All @@ -142,9 +170,26 @@ Notable Traits:
{errors.writingStyle.message}
</p>
)}
<Button type="submit" className="mt-4" loading={isExecuting}>
Save
</Button>

<div className="flex justify-end gap-3 mt-4">
<Button
type="button"
variant="outline"
onClick={() => generate()}
disabled={isGenerating || isExecuting}
>
{isGenerating ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Sparkles className="mr-2 h-4 w-4" />
)}
{isGenerating ? "Generating..." : "Regenerate Style"}
</Button>

<Button type="submit" loading={isExecuting} disabled={isGenerating}>
Save
</Button>
</div>
</form>
</DialogContent>
</Dialog>
Expand Down
52 changes: 52 additions & 0 deletions apps/web/utils/actions/assess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,55 @@ export const analyzeWritingStyleAction = actionClient

return { success: true };
});

export const regenerateWritingStyleAction = actionClient
.metadata({ name: "regenerateWritingStyle" })
.action(async ({ ctx: { emailAccountId, provider, logger } }) => {
const emailAccount = await prisma.emailAccount.findUnique({
where: { id: emailAccountId },
select: {
id: true,
userId: true,
email: true,
about: true,
multiRuleSelectionEnabled: true,
timezone: true,
calendarBookingLink: true,
user: { select: { aiProvider: true, aiModel: true, aiApiKey: true } },
},
});

if (!emailAccount) throw new SafeError("Email account not found");

const emailProvider = await createEmailProvider({
emailAccountId,
provider,
logger,
});
const sentMessages = await emailProvider.getSentMessages(20);

const style = await aiAnalyzeWritingStyle({
emails: sentMessages.map((email) =>
getEmailForLLM(email, { extractReply: true }),
),
emailAccount: { ...emailAccount, account: { provider } },
});

if (!style) return { writingStyle: "" };

const writingStyle = [
style.typicalLength ? `Typical Length: ${style.typicalLength}` : null,
style.formality ? `Formality: ${style.formality}` : null,
style.commonGreeting ? `Common Greeting: ${style.commonGreeting}` : null,
style.notableTraits.length
? `Notable Traits: ${formatBulletList(style.notableTraits)}`
: null,
style.examples.length
? `Examples: ${formatBulletList(style.examples)}`
: null,
]
.filter(Boolean)
.join("\n");

return { writingStyle };
});
Comment on lines +101 to +151
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Inspect actionClient and action binding patterns ==="
fd -t f "safe-action.ts" apps/web/utils -x head -220 {}

echo
echo "=== 2) Check regenerateWritingStyleAction file for context ==="
cat -n apps/web/utils/actions/assess.ts | head -160

echo
echo "=== 3) How is analyzeWritingStyleAction invoked? ==="
rg -n "analyzeWritingStyleAction" apps/web --type ts --type tsx -B2 -A2

echo
echo "=== 4) How is regenerateWritingStyleAction invoked? ==="
rg -n "regenerateWritingStyleAction" apps/web --type ts --type tsx -B2 -A2

echo
echo "=== 5) Check if saveWritingStyleAction exists for comparison ==="
rg -n "saveWritingStyleAction|saveWritingStyleBody" apps/web --type ts --type tsx -B1 -A5 | head -80

Repository: elie222/inbox-zero

Length of output: 11353


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check how actions are invoked from client ==="
rg -n "regenerateWritingStyleAction|analyzeWritingStyleAction" apps/web --type ts -A3 -B3

echo
echo "=== Look for .execute( or serverAction( patterns ==="
rg -n "\.execute|serverAction" apps/web --type ts -A2 | grep -A2 -B2 "Writing\|writing"

echo
echo "=== Check for style formatting - is it duplicated? ==="
echo "Formatting pattern in analyzeWritingStyleAction:"
sed -n '79,91p' apps/web/utils/actions/assess.ts

echo
echo "Formatting pattern in regenerateWritingStyleAction:"
sed -n '136,148p' apps/web/utils/actions/assess.ts

echo
echo "=== Look for saveWritingStyleAction and its schema ==="
rg -n "saveWritingStyleAction|writingStyle.*max|saveWritingStyleBody" apps/web --type ts -B3 -A10

echo
echo "=== Search for component calling regenerateWritingStyleAction ==="
find apps/web -name "*.tsx" -o -name "*.ts" | xargs grep -l "regenerateWritingStyleAction" 2>/dev/null | head -5

Repository: elie222/inbox-zero

Length of output: 4167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full WritingStyleSetting.tsx usage around line 80 ==="
sed -n '75,95p' apps/web/app/\(app\)/\[emailAccountId\]/assistant/settings/WritingStyleSetting.tsx

echo
echo "=== Compare with analyzeWritingStyleAction binding in assess.tsx ==="
sed -n '20,25p' apps/web/app/\(app\)/\[emailAccountId\]/assess.tsx

echo
echo "=== Check saveWritingStyleBody schema ==="
rg -n "saveWritingStyleBody" apps/web --type ts -B2 -A8 | head -30

echo
echo "=== Look for any .bind calls on regenerateWritingStyleAction ==="
rg -n "regenerateWritingStyleAction.bind" apps/web

Repository: elie222/inbox-zero

Length of output: 3217


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Verify the exact invocation in WritingStyleSetting.tsx around line 80-82 ==="
sed -n '75,100p' "apps/web/app/(app)/[emailAccountId]/assistant/settings/WritingStyleSetting.tsx"

echo
echo "=== Check if useAction hook requires binding or can handle it internally ==="
rg -n "useAction" apps/web/utils --type ts -A15 | head -50

echo
echo "=== Get full context of the style formatting function in both actions ==="
echo "--- analyzeWritingStyleAction (lines 79-91) ---"
sed -n '79,91p' apps/web/utils/actions/assess.ts

echo
echo "--- regenerateWritingStyleAction (lines 136-148) ---"
sed -n '136,148p' apps/web/utils/actions/assess.ts

echo
echo "=== Check if there's a helper function for formatting style already ==="
rg -n "formatStyle|formatWriting" apps/web --type ts

Repository: elie222/inbox-zero

Length of output: 1005


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find useAction definition and how it handles binding ==="
find apps/web -name "*.ts" -o -name "*.tsx" | xargs grep -l "export.*useAction\|function useAction" | head -3

echo
echo "=== Check the hook implementation ==="
rg -n "function useAction|export const useAction|export.*useAction" apps/web --type ts -A20 | head -60

echo
echo "=== Verify how actionClient.bind works ==="
echo "From safe-action.ts, actionClient bindArgsSchemas definition:"
sed -n '60,75p' apps/web/utils/actions/safe-action.ts

echo
echo "=== Check if regenerateWritingStyleAction actually receives emailAccountId ==="
echo "It reads from ctx: "
sed -n '101,103p' apps/web/utils/actions/assess.ts
echo "But how does ctx get emailAccountId if not bound? Check safe-action middleware..."
sed -n '60,120p' apps/web/utils/actions/safe-action.ts

Repository: elie222/inbox-zero

Length of output: 124


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find useAction hook ==="
find apps/web -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "useAction" {} \; | grep -v node_modules | head -5

echo
echo "=== Search for useAction definition ==="
rg "export.*useAction" apps/web -A10 --type ts --type tsx 2>/dev/null | head -40

echo
echo "=== Verify actionClient.bindArgsSchemas line 60-75 ==="
sed -n '60,75p' apps/web/utils/actions/safe-action.ts

echo
echo "=== Check how emailAccountId flows through actionClient middleware ==="
sed -n '95,130p' apps/web/utils/actions/safe-action.ts

Repository: elie222/inbox-zero

Length of output: 465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for useAction import source ==="
grep -r "useAction" apps/web --include="*.ts" --include="*.tsx" | grep "import\|from" | head -3

echo
echo "=== Find the hooks file ==="
find apps/web -name "*hooks*" -o -name "*use-action*" | head -10

echo
echo "=== Check node_modules for next-safe-action hooks ==="
ls -la apps/web/node_modules/next-safe-action 2>/dev/null | head -20 || echo "Checking package info..."

echo
echo "=== Verify actionClient middleware once more - the critical part ==="
sed -n '90,135p' apps/web/utils/actions/safe-action.ts

echo
echo "=== Check how regenerateWritingStyleAction is being called without binding ==="
cat -n apps/web/app/\(app\)/\[emailAccountId\]/assistant/settings/WritingStyleSetting.tsx | sed -n '1,35p'

Repository: elie222/inbox-zero

Length of output: 3611


Fix binding: regenerateWritingStyleAction requires .bind(null, emailAccountId)

In WritingStyleSetting.tsx line 81, regenerateWritingStyleAction is called without binding but uses actionClient which requires it as a bound argument. This will cause the action to fail silently since emailAccountId will be undefined in the middleware. Add .bind(null, emailAccountId) to match analyzeWritingStyleAction.

Additionally:

  • Extract the duplicated style formatting block (lines 79-91 in analyzeWritingStyleAction / 136-148 in regenerateWritingStyleAction) into a shared helper function
  • Truncate the returned writingStyle to saveWritingStyleBody's max(2000) limit to prevent validation errors on save