Fix preset value (#933)

This commit is contained in:
Will Chen
2025-08-13 17:25:21 -07:00
committed by GitHub
parent b8362a74a7
commit 4bc961ffb4
4 changed files with 81 additions and 8 deletions

View File

@@ -293,6 +293,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
onSubmit={handleSubmit}
onPaste={handlePaste}
placeholder="Ask Dyad to build..."
excludeCurrentApp={true}
/>
{isStreaming ? (

View File

@@ -83,6 +83,7 @@ export function HomeChatInput({
onPaste={handlePaste}
placeholder="Ask Dyad to build..."
disabled={isStreaming}
excludeCurrentApp={false}
/>
{/* File attachment dropdown */}

View File

@@ -1,5 +1,10 @@
import React, { useCallback, useEffect, useState } from "react";
import { $getRoot, $createParagraphNode, EditorState } from "lexical";
import {
$getRoot,
$createParagraphNode,
$createTextNode,
EditorState,
} from "lexical";
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
@@ -10,6 +15,7 @@ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
import {
BeautifulMentionsPlugin,
BeautifulMentionNode,
$createBeautifulMentionNode,
type BeautifulMentionsTheme,
type BeautifulMentionsMenuItemProps,
} from "lexical-beautiful-mentions";
@@ -18,7 +24,7 @@ import { useLoadApps } from "@/hooks/useLoadApps";
import { forwardRef } from "react";
import { useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { parseAppMentions } from "@/shared/parse_mention_apps";
import { MENTION_REGEX, parseAppMentions } from "@/shared/parse_mention_apps";
// Define the theme for mentions
const beautifulMentionsTheme: BeautifulMentionsTheme = {
@@ -129,6 +135,68 @@ function ClearEditorPlugin({
return null;
}
// Plugin to sync external value prop into the editor
function ExternalValueSyncPlugin({ value }: { value: string }) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
// Derive the display text that should appear in the editor (@Name) from the
// internal value representation (@app:Name)
const displayText = (value || "").replace(MENTION_REGEX, "@$1");
const currentText = editor.getEditorState().read(() => {
const root = $getRoot();
return root.getTextContent();
});
// If the editor already reflects the same display text, do nothing to avoid loops
if (currentText === displayText) return;
editor.update(() => {
const root = $getRoot();
root.clear();
const paragraph = $createParagraphNode();
// Build nodes from the internal value, turning @app:Name into a mention node
const mentionRegex = /@app:([a-zA-Z0-9_-]+)/g;
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = mentionRegex.exec(value)) !== null) {
const [full, name] = match;
const start = match.index;
// Append any text before the mention
if (start > lastIndex) {
const textBefore = value.slice(lastIndex, start);
if (textBefore) paragraph.append($createTextNode(textBefore));
}
// Append the actual mention node (@ trigger with value = Name)
paragraph.append($createBeautifulMentionNode("@", name));
lastIndex = start + full.length;
}
// Append any trailing text after the last mention
if (lastIndex < value.length) {
const trailing = value.slice(lastIndex);
if (trailing) paragraph.append($createTextNode(trailing));
}
// If there were no mentions at all, just append the raw value as text
if (value && paragraph.getTextContent() === "") {
paragraph.append($createTextNode(value));
}
root.append(paragraph);
paragraph.selectEnd();
});
}, [editor, value]);
return null;
}
interface LexicalChatInputProps {
value: string;
onChange: (value: string) => void;
@@ -136,6 +204,7 @@ interface LexicalChatInputProps {
onPaste?: (e: React.ClipboardEvent) => void;
placeholder?: string;
disabled?: boolean;
excludeCurrentApp: boolean;
}
function onError(error: Error) {
@@ -147,6 +216,7 @@ export function LexicalChatInput({
onChange,
onSubmit,
onPaste,
excludeCurrentApp,
placeholder = "Ask Dyad to build...",
disabled = false,
}: LexicalChatInputProps) {
@@ -168,7 +238,7 @@ export function LexicalChatInput({
// Filter out current app and already mentioned apps
const filteredApps = apps.filter((app) => {
// Exclude current app
if (app.name === currentAppName) return false;
if (excludeCurrentApp && app.name === currentAppName) return false;
// Exclude already mentioned apps (case-insensitive comparison)
if (
@@ -185,7 +255,7 @@ export function LexicalChatInput({
return {
"@": appMentions,
};
}, [apps, selectedAppId, value]);
}, [apps, selectedAppId, value, excludeCurrentApp]);
const initialConfig = {
namespace: "ChatInput",
@@ -203,7 +273,6 @@ export function LexicalChatInput({
const root = $getRoot();
let textContent = root.getTextContent();
console.time("handleEditorChange");
// Transform @AppName mentions to @app:AppName format
// This regex matches @AppName where AppName is one of our actual app names
@@ -223,7 +292,6 @@ export function LexicalChatInput({
textContent = textContent.replace(mentionRegex, "@app:$1");
}
}
console.timeEnd("handleEditorChange");
onChange(textContent);
});
},
@@ -275,6 +343,7 @@ export function LexicalChatInput({
<OnChangePlugin onChange={handleEditorChange} />
<HistoryPlugin />
<EnterKeyPlugin onSubmit={handleSubmit} />
<ExternalValueSyncPlugin value={value} />
<ClearEditorPlugin
shouldClear={shouldClear}
onCleared={handleCleared}

View File

@@ -1,11 +1,13 @@
export const MENTION_REGEX = /@app:([a-zA-Z0-9_-]+)/g;
// Helper function to parse app mentions from prompt
export function parseAppMentions(prompt: string): string[] {
// Match @app:AppName patterns in the prompt (supports letters, digits, underscores, and hyphens, but NOT spaces)
const mentionRegex = /@app:([a-zA-Z0-9_-]+)/g;
const mentions: string[] = [];
let match;
while ((match = mentionRegex.exec(prompt)) !== null) {
while ((match = MENTION_REGEX.exec(prompt)) !== null) {
mentions.push(match[1]);
}