(null);
const { settings } = useSettings();
const { isStreaming } = useStreamChat({
hasChatId: false,
@@ -39,26 +37,6 @@ export function HomeChatInput({
handlePaste,
} = useAttachments();
- const adjustHeight = () => {
- const textarea = textareaRef.current;
- if (textarea) {
- textarea.style.height = "0px";
- const scrollHeight = textarea.scrollHeight;
- textarea.style.height = `${scrollHeight + 4}px`;
- }
- };
-
- useEffect(() => {
- adjustHeight();
- }, [inputValue]);
-
- const handleKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- handleCustomSubmit();
- }
- };
-
// Custom submit function that wraps the provided onSubmit
const handleCustomSubmit = () => {
if ((!inputValue.trim() && attachments.length === 0) || isStreaming) {
@@ -98,16 +76,13 @@ export function HomeChatInput({
+
+ );
+}
diff --git a/src/components/chat/TokenBar.tsx b/src/components/chat/TokenBar.tsx
index e64c87e..db2eb3c 100644
--- a/src/components/chat/TokenBar.tsx
+++ b/src/components/chat/TokenBar.tsx
@@ -6,7 +6,13 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useCountTokens } from "@/hooks/useCountTokens";
-import { MessageSquare, Code, Bot, AlignLeft } from "lucide-react";
+import {
+ MessageSquare,
+ Code,
+ Bot,
+ AlignLeft,
+ ExternalLink,
+} from "lucide-react";
import { chatInputValueAtom } from "@/atoms/chatAtoms";
import { useAtom } from "jotai";
import { useSettings } from "@/hooks/useSettings";
@@ -45,6 +51,7 @@ export function TokenBar({ chatId }: TokenBarProps) {
totalTokens,
messageHistoryTokens,
codebaseTokens,
+ mentionedAppsTokens,
systemPromptTokens,
inputTokens,
contextWindow,
@@ -55,6 +62,7 @@ export function TokenBar({ chatId }: TokenBarProps) {
// Calculate widths for each token type
const messageHistoryPercent = (messageHistoryTokens / contextWindow) * 100;
const codebasePercent = (codebaseTokens / contextWindow) * 100;
+ const mentionedAppsPercent = (mentionedAppsTokens / contextWindow) * 100;
const systemPromptPercent = (systemPromptTokens / contextWindow) * 100;
const inputPercent = (inputTokens / contextWindow) * 100;
@@ -82,6 +90,11 @@ export function TokenBar({ chatId }: TokenBarProps) {
className="h-full bg-green-400"
style={{ width: `${codebasePercent}%` }}
/>
+ {/* Mentioned apps tokens */}
+
{/* System prompt tokens */}
Codebase
{codebaseTokens.toLocaleString()}
+
+ Mentioned Apps
+ {mentionedAppsTokens.toLocaleString()}
+
System Prompt
{systemPromptTokens.toLocaleString()}
diff --git a/src/ipc/handlers/chat_stream_handlers.ts b/src/ipc/handlers/chat_stream_handlers.ts
index 6a54f81..7b3d5da 100644
--- a/src/ipc/handlers/chat_stream_handlers.ts
+++ b/src/ipc/handlers/chat_stream_handlers.ts
@@ -59,6 +59,8 @@ import {
import { fileExists } from "../utils/file_utils";
import { FileUploadsState } from "../utils/file_uploads_state";
import { OpenAIResponsesProviderOptions } from "@ai-sdk/openai";
+import { extractMentionedAppsCodebases } from "../utils/mention_apps";
+import { parseAppMentions } from "@/shared/parse_mention_apps";
type AsyncIterableStream = AsyncIterable & ReadableStream;
@@ -380,11 +382,38 @@ ${componentSnippet}
}
: validateChatContext(updatedChat.app.chatContext);
+ // Parse app mentions from the prompt
+ const mentionedAppNames = parseAppMentions(req.prompt);
+
+ // Extract codebase for current app
const { formattedOutput: codebaseInfo, files } = await extractCodebase({
appPath,
chatContext,
});
+ // Extract codebases for mentioned apps
+ const mentionedAppsCodebases = await extractMentionedAppsCodebases(
+ mentionedAppNames,
+ updatedChat.app.id, // Exclude current app
+ );
+
+ // Combine current app codebase with mentioned apps' codebases
+ let otherAppsCodebaseInfo = "";
+ if (mentionedAppsCodebases.length > 0) {
+ const mentionedAppsSection = mentionedAppsCodebases
+ .map(
+ ({ appName, codebaseInfo }) =>
+ `\n\n=== Referenced App: ${appName} ===\n${codebaseInfo}`,
+ )
+ .join("");
+
+ otherAppsCodebaseInfo = mentionedAppsSection;
+
+ logger.log(
+ `Added ${mentionedAppsCodebases.length} mentioned app codebases`,
+ );
+ }
+
logger.log(`Extracted codebase information from ${appPath}`);
logger.log(
"codebaseInfo: length",
@@ -446,6 +475,15 @@ ${componentSnippet}
aiRules: await readAiRules(getDyadAppPath(updatedChat.app.path)),
chatMode: settings.selectedChatMode,
});
+
+ // Add information about mentioned apps if any
+ if (otherAppsCodebaseInfo) {
+ const mentionedAppsList = mentionedAppsCodebases
+ .map(({ appName }) => appName)
+ .join(", ");
+
+ systemPrompt += `\n\n# Referenced Apps\nThe user has mentioned the following apps in their prompt: ${mentionedAppsList}. Their codebases have been included in the context for your reference. When referring to these apps, you can understand their structure and code to provide better assistance, however you should NOT edit the files in these referenced apps. The referenced apps are NOT part of the current app and are READ-ONLY.`;
+ }
if (
updatedChat.app?.supabaseProjectId &&
settings.supabase?.accessToken?.value
@@ -529,8 +567,22 @@ This conversation includes one or more image attachments. When the user uploads
},
] as const);
+ const otherCodebasePrefix = otherAppsCodebaseInfo
+ ? ([
+ {
+ role: "user",
+ content: createOtherAppsCodebasePrompt(otherAppsCodebaseInfo),
+ },
+ {
+ role: "assistant",
+ content: "OK.",
+ },
+ ] as const)
+ : [];
+
let chatMessages: CoreMessage[] = [
...codebasePrefix,
+ ...otherCodebasePrefix,
...limitedMessageHistory.map((msg) => ({
role: msg.role as "user" | "assistant" | "system",
// Why remove thinking tags?
@@ -1201,3 +1253,13 @@ const CODEBASE_PROMPT_PREFIX = "This is my codebase.";
function createCodebasePrompt(codebaseInfo: string): string {
return `${CODEBASE_PROMPT_PREFIX} ${codebaseInfo}`;
}
+
+function createOtherAppsCodebasePrompt(otherAppsCodebaseInfo: string): string {
+ return `
+# Referenced Apps
+
+These are the other apps that I've mentioned in my prompt. These other apps' codebases are READ-ONLY.
+
+${otherAppsCodebaseInfo}
+`;
+}
diff --git a/src/ipc/handlers/token_count_handlers.ts b/src/ipc/handlers/token_count_handlers.ts
index 09268ff..35d2b11 100644
--- a/src/ipc/handlers/token_count_handlers.ts
+++ b/src/ipc/handlers/token_count_handlers.ts
@@ -20,6 +20,8 @@ import { estimateTokens, getContextWindow } from "../utils/token_utils";
import { createLoggedHandler } from "./safe_handle";
import { validateChatContext } from "../utils/context_paths_utils";
import { readSettings } from "@/main/settings";
+import { extractMentionedAppsCodebases } from "../utils/mention_apps";
+import { parseAppMentions } from "@/shared/parse_mention_apps";
const logger = log.scope("token_count_handlers");
@@ -53,6 +55,10 @@ export function registerTokenCountHandlers() {
const inputTokens = estimateTokens(req.input);
const settings = readSettings();
+
+ // Parse app mentions from the input
+ const mentionedAppNames = parseAppMentions(req.input);
+
// Count system prompt tokens
let systemPrompt = constructSystemPrompt({
aiRules: await readAiRules(getDyadAppPath(chat.app.path)),
@@ -92,17 +98,42 @@ export function registerTokenCountHandlers() {
);
}
+ // Extract codebases for mentioned apps
+ const mentionedAppsCodebases = await extractMentionedAppsCodebases(
+ mentionedAppNames,
+ chat.app?.id, // Exclude current app
+ );
+
+ // Calculate tokens for mentioned apps
+ let mentionedAppsTokens = 0;
+ if (mentionedAppsCodebases.length > 0) {
+ const mentionedAppsContent = mentionedAppsCodebases
+ .map(
+ ({ appName, codebaseInfo }) =>
+ `\n\n=== Referenced App: ${appName} ===\n${codebaseInfo}`,
+ )
+ .join("");
+
+ mentionedAppsTokens = estimateTokens(mentionedAppsContent);
+
+ logger.log(
+ `Extracted ${mentionedAppsCodebases.length} mentioned app codebases, tokens: ${mentionedAppsTokens}`,
+ );
+ }
+
// Calculate total tokens
const totalTokens =
messageHistoryTokens +
inputTokens +
systemPromptTokens +
- codebaseTokens;
+ codebaseTokens +
+ mentionedAppsTokens;
return {
totalTokens,
messageHistoryTokens,
codebaseTokens,
+ mentionedAppsTokens,
inputTokens,
systemPromptTokens,
contextWindow: await getContextWindow(),
diff --git a/src/ipc/ipc_types.ts b/src/ipc/ipc_types.ts
index 4699af8..1b4c9a2 100644
--- a/src/ipc/ipc_types.ts
+++ b/src/ipc/ipc_types.ts
@@ -152,6 +152,7 @@ export interface TokenCountResult {
totalTokens: number;
messageHistoryTokens: number;
codebaseTokens: number;
+ mentionedAppsTokens: number;
inputTokens: number;
systemPromptTokens: number;
contextWindow: number;
diff --git a/src/ipc/utils/mention_apps.ts b/src/ipc/utils/mention_apps.ts
new file mode 100644
index 0000000..a22a96c
--- /dev/null
+++ b/src/ipc/utils/mention_apps.ts
@@ -0,0 +1,53 @@
+import { db } from "../../db";
+import { getDyadAppPath } from "../../paths/paths";
+import { extractCodebase } from "../../utils/codebase";
+import { validateChatContext } from "../utils/context_paths_utils";
+import log from "electron-log";
+
+const logger = log.scope("mention_apps");
+
+// Helper function to extract codebases from mentioned apps
+export async function extractMentionedAppsCodebases(
+ mentionedAppNames: string[],
+ excludeCurrentAppId?: number,
+): Promise<{ appName: string; codebaseInfo: string }[]> {
+ if (mentionedAppNames.length === 0) {
+ return [];
+ }
+
+ // Get all apps
+ const allApps = await db.query.apps.findMany();
+
+ const mentionedApps = allApps.filter(
+ (app) =>
+ mentionedAppNames.some(
+ (mentionName) => app.name.toLowerCase() === mentionName.toLowerCase(),
+ ) && app.id !== excludeCurrentAppId,
+ );
+
+ const results: { appName: string; codebaseInfo: string }[] = [];
+
+ for (const app of mentionedApps) {
+ try {
+ const appPath = getDyadAppPath(app.path);
+ const chatContext = validateChatContext(app.chatContext);
+
+ const { formattedOutput } = await extractCodebase({
+ appPath,
+ chatContext,
+ });
+
+ results.push({
+ appName: app.name,
+ codebaseInfo: formattedOutput,
+ });
+
+ logger.log(`Extracted codebase for mentioned app: ${app.name}`);
+ } catch (error) {
+ logger.error(`Error extracting codebase for app ${app.name}:`, error);
+ // Continue with other apps even if one fails
+ }
+ }
+
+ return results;
+}
diff --git a/src/shared/parse_mention_apps.ts b/src/shared/parse_mention_apps.ts
new file mode 100644
index 0000000..1dc2e1f
--- /dev/null
+++ b/src/shared/parse_mention_apps.ts
@@ -0,0 +1,13 @@
+// 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) {
+ mentions.push(match[1]);
+ }
+
+ return mentions;
+}