Skip to content

Email display subject instead of whole email #548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ButtonList } from "@/components/ButtonList";
import type { RulesResponse } from "@/app/api/user/rules/route";
import { ProcessResultDisplay } from "@/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay";
import { NONE_RULE_ID } from "@/app/(app)/[emailAccountId]/assistant/consts";
import { createEmailDisplayValue } from "@/utils/email-display";

export function FixWithChat({
setInput,
Expand Down Expand Up @@ -84,6 +85,7 @@ export function FixWithChat({

if (setInput) {
// this is only set if we're in the correct context
// The input will automatically show as "@(Subject)" in the chat
setInput(input);
} else {
// redirect to the assistant page
Expand Down
5 changes: 5 additions & 0 deletions apps/web/components/assistant-chat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { useIsMobile } from "@/hooks/use-mobile";
import { ExamplesDialog } from "@/components/assistant-chat/examples-dialog";
import { Tooltip } from "@/components/Tooltip";
import { toastError } from "@/components/Toast";
import { createDisplayValueForInput } from "@/utils/email-display";

// Some mega hacky code used here to workaround AI SDK's use of SWR
// AI SDK uses SWR too and this messes with the global SWR config
Expand Down Expand Up @@ -180,6 +181,9 @@ function ChatUI({ chat }: { chat: ReturnType<typeof useChat> }) {
reload,
} = chat;

// Create display value for email data
const displayValue = createDisplayValueForInput(input);

return (
<div className="flex h-full min-w-0 flex-col bg-background">
<SWRProvider>
Expand Down Expand Up @@ -223,6 +227,7 @@ function ChatUI({ chat }: { chat: ReturnType<typeof useChat> }) {
// messages={messages}
setMessages={setMessages}
// append={append}
displayValue={displayValue}
/>
</form>
</div>
Expand Down
24 changes: 22 additions & 2 deletions apps/web/components/assistant-chat/multimodal-input.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import type React from "react";
import { useRef, useEffect, useCallback, memo } from "react";
import { useRef, useEffect, useCallback, memo, useState } from "react";
import { toast } from "sonner";
import { useLocalStorage, useWindowSize } from "usehooks-ts";
import type { UseChatHelpers } from "@ai-sdk/react";
Expand All @@ -23,6 +23,7 @@ function PureMultimodalInput({
// append,
handleSubmit,
className,
displayValue,
}: {
// chatId?: string;
input: UseChatHelpers["input"];
Expand All @@ -36,9 +37,11 @@ function PureMultimodalInput({
// append: UseChatHelpers["append"];
handleSubmit: UseChatHelpers["handleSubmit"];
className?: string;
displayValue?: string;
}) {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const { width } = useWindowSize();
const [isEditing, setIsEditing] = useState(false);

useEffect(() => {
if (textareaRef.current) {
Expand Down Expand Up @@ -92,6 +95,16 @@ function PureMultimodalInput({
// adjustHeight(); // handled in useEffect
};

const handleFocus = () => {
if (displayValue !== undefined && displayValue !== input) {
setIsEditing(true);
}
};

const handleBlur = () => {
setIsEditing(false);
};

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
const submitForm = useCallback(() => {
// window.history.replaceState({}, "", `/chat/${chatId}`);
Expand All @@ -106,6 +119,10 @@ function PureMultimodalInput({
}
}, [handleSubmit, setLocalStorageInput, width]);

// Use displayValue if provided and not editing, otherwise use input
const visibleValue =
displayValue !== undefined && !isEditing ? displayValue : input;

return (
<div className="relative flex w-full flex-col gap-4">
{/* {messages.length === 0 && (
Expand All @@ -116,8 +133,10 @@ function PureMultimodalInput({
data-testid="multimodal-input"
ref={textareaRef}
placeholder="Send a message..."
value={input}
value={visibleValue}
onChange={handleInput}
onFocus={handleFocus}
onBlur={handleBlur}
className={cn(
"max-h-[calc(75dvh)] min-h-[24px] resize-none overflow-hidden rounded-2xl bg-muted pb-10 !text-base dark:border-zinc-700",
className,
Expand Down Expand Up @@ -157,6 +176,7 @@ export const MultimodalInput = memo(
(prevProps, nextProps) => {
if (prevProps.input !== nextProps.input) return false;
if (prevProps.status !== nextProps.status) return false;
if (prevProps.displayValue !== nextProps.displayValue) return false;
// if (!equal(prevProps.attachments, nextProps.attachments)) return false;

return true;
Expand Down
56 changes: 56 additions & 0 deletions apps/web/utils/email-display.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { ParsedMessage } from "@/utils/types";

/**
* Creates a simplified display value for email data in chat input
* Shows "@(Subject)" format while keeping full data under the hood
*/
export function createEmailDisplayValue(message: ParsedMessage): string {
const subject = message.headers.subject || "No subject";
return `@(${subject})`;
}

/**
* Checks if the input contains email data that should be displayed in simplified format
*/
export function hasEmailData(input: string): boolean {
// Check if input contains email-related patterns
return (
input.includes("*From*:") ||
input.includes("*Subject*:") ||
input.includes("*Content*:") ||
input.includes("Email details:") ||
input.includes("Current rule applied:")
);
}

/**
* Extracts the subject from email data input for display purposes
*/
export function extractSubjectFromInput(input: string): string | null {
const subjectMatch = input.match(/\*Subject\*:\s*(.+?)(?:\n|$)/);
return subjectMatch ? subjectMatch[1].trim() : null;
}

/**
* Creates a display value for email-related input
* Replaces the email details section with a simplified version
*/
export function createDisplayValueForInput(input: string): string | undefined {
if (!hasEmailData(input)) {
return undefined; // Use original input
}

const subject = extractSubjectFromInput(input);
if (!subject) {
return input; // Keep original if no subject found
}

// Replace the email details section with simplified version
const emailDetailsPattern =
/Email details:\s*\*From\*:[\s\S]*?\*Content\*:[\s\S]*?(?=\n\n|\nCurrent rule|$)/;
const replacement = `Email details:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n📧 [${subject}]\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`;

const displayValue = input.replace(emailDetailsPattern, replacement);

return displayValue;
}
Comment on lines +38 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve return type consistency and regex robustness.

The function has inconsistent return types (string | undefined vs string) and uses a complex regex that might be brittle.

Apply this diff to improve consistency:

export function createDisplayValueForInput(input: string): string {
  if (!hasEmailData(input)) {
-    return undefined; // Use original input
+    return input; // Use original input
  }

  const subject = extractSubjectFromInput(input);
  if (!subject) {
    return input; // Keep original if no subject found
  }

  // Replace the email details section with simplified version
  const emailDetailsPattern =
-    /Email details:\s*\*From\*:[\s\S]*?\*Content\*:[\s\S]*?(?=\n\n|\nCurrent rule|$)/;
+    /Email details:\s*\*From\*:[\s\S]*?\*Content\*:[\s\S]*?(?=\n\n|\nCurrent rule|$)/i;
  const replacement = `Email details:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n📧 [${subject}]\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`;

  const displayValue = input.replace(emailDetailsPattern, replacement);

  return displayValue;
}

Update the function signature in the calling code to handle the consistent return type.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/utils/email-display.ts around lines 38 to 56, the function
createDisplayValueForInput returns either a string or undefined, causing
inconsistent return types. To fix this, ensure the function always returns a
string by returning the original input string instead of undefined when
hasEmailData(input) is false. Additionally, simplify and make the regex more
robust by refining the emailDetailsPattern to reliably match the email details
section without being brittle. Finally, update any calling code to expect a
consistent string return type from this function.

Loading