Skip to content
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
58 changes: 29 additions & 29 deletions apps/app/src/app/components/session/composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ComposerNotice, { type ComposerNotice as ComposerNoticeData } from "./com

import type { ComposerAttachment, ComposerDraft, ComposerPart, PromptMode, SlashCommandOption } from "../../types";
import { perfNow, recordPerfLog } from "../../lib/perf-log";
import { t } from "../../../i18n";

type MentionOption = {
id: string;
Expand Down Expand Up @@ -516,7 +517,7 @@ export default function Composer(props: ComposerProps) {
span.dataset.pasteId = part.id;
span.dataset.pasteLabel = part.label;
span.dataset.pasteLines = String(part.lines);
span.title = "Click to expand pasted text";
span.title = t("composer.expand_pasted");
span.className =
"inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold bg-gray-3 text-gray-10 border border-gray-6 cursor-pointer hover:bg-gray-4 hover:text-gray-11";
return span;
Expand Down Expand Up @@ -1058,7 +1059,7 @@ export default function Composer(props: ComposerProps) {
const addAttachments = async (files: File[]) => {
if (attachmentsDisabled()) {
props.onNotice({
title: props.attachmentsDisabledReason ?? "Attachments are unavailable.",
title: props.attachmentsDisabledReason ?? t("composer.attachments_unavailable"),
tone: "warning",
});
return;
Expand All @@ -1078,7 +1079,7 @@ export default function Composer(props: ComposerProps) {
for (const file of supportedFiles) {
if (file.size > MAX_ATTACHMENT_BYTES) {
props.onNotice({
title: `${file.name} exceeds the 8MB limit.`,
title: t("composer.file_exceeds_limit", undefined, { name: file.name }),
tone: "warning",
});
continue;
Expand All @@ -1089,7 +1090,7 @@ export default function Composer(props: ComposerProps) {
const estimatedJsonBytes = estimateInlineAttachmentBytes(processed);
if (estimatedJsonBytes > MAX_ATTACHMENT_BYTES) {
props.onNotice({
title: `${file.name} is too large after encoding. Try a smaller image.`,
title: t("composer.file_too_large_encoding", undefined, { name: file.name }),
tone: "warning",
});
continue;
Expand All @@ -1105,7 +1106,7 @@ export default function Composer(props: ComposerProps) {
});
} catch (error) {
props.onNotice({
title: error instanceof Error ? error.message : "Failed to read attachment",
title: error instanceof Error ? error.message : t("composer.failed_read_attachment"),
tone: "error",
});
}
Expand Down Expand Up @@ -1221,29 +1222,29 @@ export default function Composer(props: ComposerProps) {
props.onNotice({
title:
links.length === 1
? `Uploaded ${links[0].name} to the shared folder and inserted a link.`
: `Uploaded ${links.length} files to the shared folder and inserted links.`,
? t("composer.uploaded_single_file", undefined, { name: links[0].name })
: t("composer.uploaded_multiple_files", undefined, { count: links.length }),
tone: "success",
});
return;
}
}
props.onNotice({
title: "Couldn't upload to the shared folder. Inserted local links instead.",
title: t("composer.upload_failed_local_links"),
tone: "warning",
});
}

const text = formatLinks(fallbackLinks());
if (!text) {
props.onNotice({ title: "Unsupported attachment type.", tone: "warning" });
props.onNotice({ title: t("composer.unsupported_attachment_type"), tone: "warning" });
return;
}
insertPlainTextAtSelection(text);
updateMentionQuery();
updateSlashQuery();
emitDraftChange();
props.onNotice({ title: "Inserted links for unsupported files.", tone: "info" });
props.onNotice({ title: t("composer.inserted_links_unsupported"), tone: "info" });
};

const handlePaste = (event: ClipboardEvent) => {
Expand Down Expand Up @@ -1277,10 +1278,9 @@ export default function Composer(props: ComposerProps) {
const hasAbsoluteWindows = /(^|\s)[a-zA-Z]:\\/.test(trimmedForCheck);
if (hasFileUrl || hasAbsolutePosix || hasAbsoluteWindows) {
props.onNotice({
title:
"This is a remote worker. Sandboxes are remote too. To share files with it, upload them to the Shared folder in the sidebar.",
title: t("composer.remote_worker_paste_warning"),
tone: "warning",
actionLabel: props.onUploadInboxFiles ? "Upload to shared folder" : undefined,
actionLabel: props.onUploadInboxFiles ? t("composer.upload_to_shared_folder") : undefined,
onAction: props.onUploadInboxFiles ? () => inboxFileInputRef?.click() : undefined,
});
}
Expand Down Expand Up @@ -1572,7 +1572,7 @@ export default function Composer(props: ComposerProps) {
<div class="max-h-64 overflow-y-auto bg-dls-surface p-2" onMouseDown={(event: MouseEvent) => event.preventDefault()}>
<Show
when={mentionVisible().length}
fallback={<div class="px-3 py-2 text-xs text-gray-10">No matches found.</div>}
fallback={<div class="px-3 py-2 text-xs text-gray-10">{t("composer.no_matches")}</div>}
>
<For each={mentionVisible()}>
{(option: MentionOption) => {
Expand Down Expand Up @@ -1635,7 +1635,7 @@ export default function Composer(props: ComposerProps) {
when={slashFiltered().length}
fallback={
<div class="px-3 py-2 text-xs text-gray-10">
{slashLoading() ? "Loading commands..." : "No commands found."}
{slashLoading() ? t("composer.loading_commands") : t("composer.no_commands")}
</div>
}
>
Expand All @@ -1662,7 +1662,7 @@ export default function Composer(props: ComposerProps) {
</div>
<Show when={cmd.source && cmd.source !== "command"}>
<span class="text-[10px] uppercase tracking-wider text-gray-10 shrink-0">
{cmd.source === "skill" ? "Skill" : cmd.source === "mcp" ? "MCP" : ""}
{cmd.source === "skill" ? t("composer.skill_source") : cmd.source === "mcp" ? "MCP" : ""}
</span>
</Show>
</button>
Expand Down Expand Up @@ -1697,7 +1697,7 @@ export default function Composer(props: ComposerProps) {
<div class="max-w-[160px]">
<div class="truncate text-gray-11">{attachment.name}</div>
<div class="text-[10px] text-gray-10">
{attachment.kind === "image" ? "Image" : attachment.mimeType || "File"}
{attachment.kind === "image" ? t("composer.image_kind") : attachment.mimeType || t("composer.file_kind")}
</div>
</div>
<button
Expand All @@ -1721,7 +1721,7 @@ export default function Composer(props: ComposerProps) {
<div class="relative">
<Show when={!hasDraftContent()}>
<div class="absolute left-0 top-0 text-gray-9 text-[15px] leading-relaxed pointer-events-none">
Describe your task...
{t("composer.placeholder")}
</div>
</Show>
<div
Expand Down Expand Up @@ -1775,8 +1775,8 @@ export default function Composer(props: ComposerProps) {
disabled={attachmentsDisabled()}
title={
attachmentsDisabled()
? props.attachmentsDisabledReason ?? "Attachments are unavailable."
: "Attach files"
? props.attachmentsDisabledReason ?? t("composer.attachments_unavailable")
: t("composer.attach_files")
}
>
<Paperclip size={16} />
Expand All @@ -1794,21 +1794,21 @@ export default function Composer(props: ComposerProps) {
? "bg-gray-4 text-gray-10"
: "bg-dls-accent text-white hover:bg-[var(--dls-accent-hover)]"
}`}
title="Run task"
title={t("composer.run_task")}
>
<ArrowUp size={15} />
<span>Run task</span>
<span>{t("composer.run_task")}</span>
</button>
}
>
<button
type="button"
onClick={() => props.onStop()}
class="inline-flex items-center gap-2 rounded-full bg-gray-12 px-4 py-2 text-[13px] font-medium text-gray-1 transition-colors hover:bg-gray-11"
title="Stop"
title={t("composer.stop")}
>
<Square size={12} fill="currentColor" />
<span>Stop</span>
<span>{t("composer.stop")}</span>
</button>
</Show>
</div>
Expand All @@ -1829,7 +1829,7 @@ export default function Composer(props: ComposerProps) {
onClick={props.onToggleAgentPicker}
disabled={props.busy}
aria-expanded={props.agentPickerOpen}
title="Agent"
title={t("composer.agent_label")}
>
<span class="max-w-[140px] truncate">{props.agentLabel}</span>
<ChevronDown size={13} />
Expand All @@ -1838,14 +1838,14 @@ export default function Composer(props: ComposerProps) {
<Show when={props.agentPickerOpen}>
<div class="absolute left-0 bottom-full z-40 mb-2 w-64 overflow-hidden rounded-[18px] border border-dls-border bg-dls-surface shadow-[var(--dls-shell-shadow)]">
<div class="border-b border-dls-border px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-[0.2em] text-gray-10">
Agent
{t("composer.agent_label")}
</div>

<div class="p-2 space-y-1 max-h-64 overflow-y-auto" onMouseDown={(event: MouseEvent) => event.preventDefault()}>
<Show
when={!props.agentPickerBusy}
fallback={
<div class="px-3 py-2 text-xs text-gray-10">Loading agents...</div>
<div class="px-3 py-2 text-xs text-gray-10">{t("composer.loading_agents")}</div>
}
>
<Show when={!props.agentPickerError}>
Expand All @@ -1860,7 +1860,7 @@ export default function Composer(props: ComposerProps) {
props.onSelectAgent(null);
}}
>
<span>Default agent</span>
<span>{t("composer.default_agent")}</span>
<Show when={!props.selectedAgent}>
<Check size={14} class="text-gray-10" />
</Show>
Expand Down Expand Up @@ -1931,7 +1931,7 @@ export default function Composer(props: ComposerProps) {
<Show when={variantMenuOpen()}>
<div class="absolute left-0 bottom-full z-40 mb-2 w-48 overflow-hidden rounded-[18px] border border-dls-border bg-dls-surface shadow-[var(--dls-shell-shadow)]">
<div class="border-b border-dls-border px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-[0.2em] text-gray-10">
Behavior
{t("composer.behavior_label")}
</div>
<div class="p-2 space-y-1">
<For each={props.modelBehaviorOptions}>
Expand Down
39 changes: 20 additions & 19 deletions apps/app/src/app/components/session/context-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChevronDown, Circle, File, Folder, Package } from "lucide-solid";

import { useConnections } from "../../connections/provider";
import { SUGGESTED_PLUGINS } from "../../constants";
import { t } from "../../../i18n";
import type { McpStatus, SkillCard } from "../../types";
import { stripPluginVersion } from "../../utils/plugins";

Expand Down Expand Up @@ -106,19 +107,19 @@ const getSmartFileName = (files: string[], file: string): string => {
};

const mcpStatusLabel = (status?: McpStatus, disabled?: boolean) => {
if (disabled) return "Disabled";
if (!status) return "Disconnected";
if (disabled) return t("context_panel.mcp_disabled");
if (!status) return t("context_panel.mcp_disconnected");
switch (status.status) {
case "connected":
return "Connected";
return t("context_panel.mcp_connected");
case "needs_auth":
return "Needs auth";
return t("context_panel.mcp_needs_auth");
case "needs_client_registration":
return "Register client";
return t("context_panel.mcp_register_client");
case "failed":
return "Failed";
return t("context_panel.mcp_failed");
default:
return "Disconnected";
return t("context_panel.mcp_disconnected");
}
};

Expand Down Expand Up @@ -151,7 +152,7 @@ export default function ContextPanel(props: ContextPanelProps) {
class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium"
onClick={() => props.onToggleSection("context")}
>
<span>Context</span>
<span>{t("context_panel.context")}</span>
<ChevronDown
size={16}
class={`transition-transform text-gray-10 ${props.expandedSections.context ? "rotate-180" : ""}`.trim()}
Expand All @@ -161,12 +162,12 @@ export default function ContextPanel(props: ContextPanelProps) {
<div class="px-4 pb-4 pt-1 space-y-5">
<div>
<div class="flex items-center justify-between text-[11px] uppercase tracking-wider text-gray-9 font-semibold mb-2">
<span>Working files</span>
<span>{t("context_panel.working_files")}</span>
</div>
<div class="space-y-2">
<Show
when={props.workingFiles.length}
fallback={<div class="text-xs text-gray-9">None yet.</div>}
fallback={<div class="text-xs text-gray-9">{t("context_panel.none_yet")}</div>}
>
<For each={props.workingFiles}>
{(file) => {
Expand All @@ -182,7 +183,7 @@ export default function ContextPanel(props: ContextPanelProps) {
: "cursor-default opacity-70"
}`.trim()}
onClick={() => props.onFileClick?.(file)}
title={canOpen() ? `Open ${displayPath()}` : displayPath()}
title={canOpen() ? t("context_panel.open_file", undefined, { path: displayPath() }) : displayPath()}
disabled={!canOpen()}
>
<File size={12} class="text-gray-9" />
Expand All @@ -203,7 +204,7 @@ export default function ContextPanel(props: ContextPanelProps) {
class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium"
onClick={() => props.onToggleSection("plugins")}
>
<span>Plugins</span>
<span>{t("context_panel.plugins")}</span>
<ChevronDown
size={16}
class={`transition-transform text-gray-10 ${props.expandedSections.plugins ? "rotate-180" : ""}`.trim()}
Expand All @@ -216,7 +217,7 @@ export default function ContextPanel(props: ContextPanelProps) {
when={props.activePlugins.length}
fallback={
<div class="text-xs text-gray-9">
{props.activePluginStatus ?? "No plugins loaded."}
{props.activePluginStatus ?? t("context_panel.no_plugins")}
</div>
}
>
Expand Down Expand Up @@ -253,7 +254,7 @@ export default function ContextPanel(props: ContextPanelProps) {
class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium"
onClick={() => props.onToggleSection("mcp")}
>
<span>MCP</span>
<span>{t("context_panel.mcp")}</span>
<ChevronDown
size={16}
class={`transition-transform text-gray-10 ${props.expandedSections.mcp ? "rotate-180" : ""}`.trim()}
Expand All @@ -266,7 +267,7 @@ export default function ContextPanel(props: ContextPanelProps) {
when={connections.mcpServers().length}
fallback={
<div class="text-xs text-gray-9">
{connections.mcpStatus() ?? "No MCP servers loaded."}
{connections.mcpStatus() ?? t("context_panel.no_mcp")}
</div>
}
>
Expand Down Expand Up @@ -303,7 +304,7 @@ export default function ContextPanel(props: ContextPanelProps) {
class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium"
onClick={() => props.onToggleSection("skills")}
>
<span>Skills</span>
<span>{t("context_panel.skills")}</span>
<ChevronDown
size={16}
class={`transition-transform text-gray-10 ${props.expandedSections.skills ? "rotate-180" : ""}`.trim()}
Expand All @@ -316,7 +317,7 @@ export default function ContextPanel(props: ContextPanelProps) {
when={props.skills.length}
fallback={
<div class="text-xs text-gray-9">
{props.skillsStatus ?? "No skills loaded."}
{props.skillsStatus ?? t("context_panel.no_skills")}
</div>
}
>
Expand Down Expand Up @@ -352,7 +353,7 @@ export default function ContextPanel(props: ContextPanelProps) {
class="w-full px-4 py-3 flex items-center justify-between text-sm text-gray-12 font-medium"
onClick={() => props.onToggleSection("authorizedFolders")}
>
<span>Authorized folders</span>
<span>{t("context_panel.authorized_folders")}</span>
<ChevronDown
size={16}
class={`transition-transform text-gray-10 ${
Expand All @@ -365,7 +366,7 @@ export default function ContextPanel(props: ContextPanelProps) {
<div class="space-y-2">
<Show
when={props.authorizedDirs.length}
fallback={<div class="text-xs text-gray-9">None yet.</div>}
fallback={<div class="text-xs text-gray-9">{t("context_panel.none_yet")}</div>}
>
<For each={props.authorizedDirs.slice(0, 3)}>
{(folder) => (
Expand Down
Loading
Loading