-
- {Icon &&
}
- {selectedRepository.git_provider === "azure_devops" && (
-
- )}
+
+ {repository ? (
- {selectedRepository.selected_repository}
+ {(Icon || selectedRepository.git_provider === "azure_devops") && (
+
+ {Icon ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {repository}
-
-
-
-
+ ) : null}
+ {branch ? (
- {selectedRepository.selected_branch}
+
+
+
+ {branch}
-
+ ) : null}
);
}
diff --git a/src/components/features/conversation-panel/conversation-card/conversation-tag-chips.tsx b/src/components/features/conversation-panel/conversation-card/conversation-tag-chips.tsx
new file mode 100644
index 0000000000..a974ceb8fa
--- /dev/null
+++ b/src/components/features/conversation-panel/conversation-card/conversation-tag-chips.tsx
@@ -0,0 +1,336 @@
+import React from "react";
+import { createPortal } from "react-dom";
+import { useTranslation } from "react-i18next";
+import { I18nKey } from "#/i18n/declaration";
+import { cn } from "#/utils/utils";
+import {
+ computeVisibleTagChipCount,
+ formatConversationTagTooltip,
+ getConversationTagLabel,
+ truncateTagChipValue,
+} from "./conversation-tag-display";
+import {
+ getConversationTagIcon,
+ type ConversationTagIcon,
+} from "./conversation-tag-icons";
+import {
+ CONVERSATION_CARD_META_CHIP_CLASSNAME,
+ CONVERSATION_CARD_META_CHIP_ICON_CLASSNAME,
+ CONVERSATION_CARD_META_CHIP_ICON_SLOT_CLASSNAME,
+} from "./conversation-card-meta-chip";
+
+interface ConversationTagChipsProps {
+ tags: Array<[string, string]>;
+}
+
+/**
+ * Fixed line-height slot so Lucide / react-icons / local SVGs share the same
+ * optical center as the ``leading-4`` chip / overflow text (raw ``h-3 w-3``
+ * SVGs sit on the baseline and look high or low depending on the glyph).
+ */
+function TagIconSlot({
+ icon: Icon,
+ keyName,
+ testId,
+}: {
+ icon: ConversationTagIcon;
+ keyName?: string;
+ testId?: string;
+}) {
+ return (
+
+
+
+ );
+}
+
+function TagChipContent({
+ icon,
+ keyName,
+ value,
+ iconTestId,
+}: {
+ icon: ConversationTagIcon;
+ keyName: string;
+ value: string;
+ /**
+ * Only the visible row passes this. The off-screen measure row renders the
+ * same chips, so tagging both would emit every chip test id twice and break
+ * ``getByTestId`` (singular) for any card with tags.
+ */
+ iconTestId?: string;
+}) {
+ return (
+ <>
+
+
{truncateTagChipValue(value)}
+ >
+ );
+}
+
+/**
+ * Single-row tag chips for a conversation card. Chip labels are value-only
+ * (key lives in the tooltip); chips that do not fit fold behind a ``+N``
+ * button that opens a key/value popover.
+ *
+ * The overflow popover is portaled with ``position: fixed`` so it is not
+ * clipped by the chip row's ``overflow-hidden`` or the sidebar scroller.
+ */
+export function ConversationTagChips({ tags }: ConversationTagChipsProps) {
+ const { t } = useTranslation("openhands");
+ const containerRef = React.useRef
(null);
+ const measureRef = React.useRef(null);
+ const triggerRef = React.useRef(null);
+ const popoverRef = React.useRef(null);
+ const [visibleCount, setVisibleCount] = React.useState(tags.length);
+ const [isOverflowOpen, setIsOverflowOpen] = React.useState(false);
+ const [popoverBox, setPopoverBox] = React.useState<{
+ top: number;
+ left: number;
+ } | null>(null);
+
+ const recomputeVisibleCount = React.useCallback(() => {
+ const container = containerRef.current;
+ const measure = measureRef.current;
+ if (!container || !measure) {
+ return;
+ }
+
+ const widths = Array.from(measure.children).map(
+ (child) => (child as HTMLElement).offsetWidth,
+ );
+ setVisibleCount(computeVisibleTagChipCount(widths, container.clientWidth));
+ }, []);
+
+ const measurePopover = React.useCallback(() => {
+ const trigger = triggerRef.current;
+ if (!trigger) {
+ return;
+ }
+ const rect = trigger.getBoundingClientRect();
+ const gutter = 8;
+ const width = 16 * 16;
+ let left = rect.left;
+ if (left + width > window.innerWidth - gutter) {
+ left = Math.max(gutter, window.innerWidth - gutter - width);
+ }
+ setPopoverBox({ top: rect.bottom + 4, left });
+ }, []);
+
+ // Callers build this array fresh on every render (``getDisplayConversationTags``
+ // returns a new array), so keying the reset effect on ``tags`` identity would
+ // fire on every parent render — closing an open ``+N`` popover and forcing a
+ // layout read on each 10s panel refetch. Compare tag *content* instead.
+ const tagsKey = tags.map(([key, value]) => `${key}=${value}`).join("\u001f");
+ const lastTagsKeyRef = React.useRef(null);
+
+ React.useLayoutEffect(() => {
+ if (lastTagsKeyRef.current === tagsKey) {
+ return;
+ }
+ lastTagsKeyRef.current = tagsKey;
+ setIsOverflowOpen(false);
+ recomputeVisibleCount();
+ }, [tagsKey, recomputeVisibleCount]);
+
+ React.useEffect(() => {
+ const container = containerRef.current;
+ if (!container || typeof ResizeObserver === "undefined") {
+ return undefined;
+ }
+
+ const observer = new ResizeObserver(() => recomputeVisibleCount());
+ observer.observe(container);
+ return () => observer.disconnect();
+ }, [recomputeVisibleCount]);
+
+ React.useLayoutEffect(() => {
+ if (!isOverflowOpen) {
+ setPopoverBox(null);
+ return undefined;
+ }
+ measurePopover();
+ window.addEventListener("resize", measurePopover);
+ window.addEventListener("scroll", measurePopover, true);
+ return () => {
+ window.removeEventListener("resize", measurePopover);
+ window.removeEventListener("scroll", measurePopover, true);
+ };
+ }, [isOverflowOpen, measurePopover]);
+
+ React.useEffect(() => {
+ if (!isOverflowOpen) {
+ return undefined;
+ }
+ const onPointerDown = (event: MouseEvent) => {
+ const target = event.target as Node;
+ if (triggerRef.current?.contains(target)) {
+ return;
+ }
+ if (popoverRef.current?.contains(target)) {
+ return;
+ }
+ setIsOverflowOpen(false);
+ };
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") {
+ setIsOverflowOpen(false);
+ }
+ };
+ // mousedown (not click) so the opening click cannot race-close the panel,
+ // and so NavigationLink activation is easier to cancel on the trigger.
+ document.addEventListener("mousedown", onPointerDown);
+ document.addEventListener("keydown", onKeyDown);
+ return () => {
+ document.removeEventListener("mousedown", onPointerDown);
+ document.removeEventListener("keydown", onKeyDown);
+ };
+ }, [isOverflowOpen]);
+
+ if (tags.length === 0) {
+ return null;
+ }
+
+ const hiddenCount = Math.max(0, tags.length - visibleCount);
+ const visibleTags = tags.slice(0, visibleCount);
+ const overflowTags = tags.slice(visibleCount);
+
+ const stopCardNavigation = (event: React.SyntheticEvent) => {
+ // Don't preventDefault on mousedown — that suppresses the subsequent click
+ // when the chip sits inside a conversation NavigationLink.
+ event.stopPropagation();
+ };
+
+ const activateOverflow = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ setIsOverflowOpen((open) => !open);
+ };
+
+ return (
+
+
+ {tags.map(([key, value]) => (
+
+
+
+ ))}
+
+
+
+ {visibleTags.map(([key, value]) => (
+
+
+
+ ))}
+ {hiddenCount > 0 ? (
+
+ ) : null}
+
+
+ {isOverflowOpen &&
+ popoverBox &&
+ typeof document !== "undefined" &&
+ createPortal(
+ // Stop card-level click activation when interacting with the list.
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- dialog surface must swallow clicks
+
{
+ event.preventDefault();
+ event.stopPropagation();
+ }}
+ >
+
+ {overflowTags.map(([key, value]) => (
+
+
-
+
+
+ {getConversationTagLabel(key, t)}
+
+
+ -
+ {value}
+
+
+ ))}
+
+
,
+ document.body,
+ )}
+
+ );
+}
diff --git a/src/components/features/conversation-panel/conversation-card/conversation-tag-display.ts b/src/components/features/conversation-panel/conversation-card/conversation-tag-display.ts
new file mode 100644
index 0000000000..72d08458a5
--- /dev/null
+++ b/src/components/features/conversation-panel/conversation-card/conversation-tag-display.ts
@@ -0,0 +1,189 @@
+/**
+ * Display helpers for conversation tag chips. Pure (no React) so fit/truncate
+ * behavior can be unit-tested without laying out the sidebar card.
+ */
+
+import { I18nKey } from "#/i18n/declaration";
+
+/** Max characters shown on a chip before hard truncation with an ellipsis. */
+export const TAG_CHIP_VALUE_MAX_LENGTH = 14;
+
+/** Horizontal gap between chips (matches Tailwind ``gap-1`` = 4px). */
+export const TAG_CHIP_GAP_PX = 4;
+
+/**
+ * Reserved width for the ``+N`` overflow control when deciding how many chips
+ * fit on one row. Slightly generous so the button never wraps under a chip.
+ */
+export const TAG_CHIP_OVERFLOW_WIDTH_PX = 36;
+
+/** Known tag keys that already have a dedicated hovercard label. */
+export type ConversationTagLabelKind =
+ | "git"
+ | "repo"
+ | "branch"
+ | "workspace"
+ | "app_mode"
+ | "work_tools"
+ | "work_wsid"
+ | "other";
+
+/**
+ * Map a server tag key to a hovercard label kind. ``archiveworkspacepath`` is
+ * treated as "workspace" — the sandbox working directory for the conversation.
+ * ACM / Work stamps (``Appmode``, ``Worktools``, ``Workwsid``) get dedicated
+ * kinds so chips and hovercards show friendly labels instead of the wire key.
+ *
+ * ``origin`` / ``source`` deliberately stay "other" (humanized to "Origin" /
+ * "Source"): they name where a conversation came from — Slack, an API call, an
+ * automation — which is not a git fact. Only ``git_provider`` is "Git".
+ */
+export function getConversationTagLabelKind(
+ key: string,
+): ConversationTagLabelKind {
+ switch (key.trim().toLowerCase()) {
+ case "git_provider":
+ return "git";
+ case "repo_name":
+ case "repo":
+ case "repository":
+ return "repo";
+ case "selected_branch":
+ case "branch":
+ return "branch";
+ case "archiveworkspacepath":
+ case "workspace":
+ case "working_dir":
+ return "workspace";
+ case "appmode":
+ case "app_mode":
+ case "mode":
+ return "app_mode";
+ case "worktools":
+ case "work_tools":
+ case "tools":
+ return "work_tools";
+ case "workwsid":
+ case "work_wsid":
+ case "wsid":
+ case "workspace_id":
+ return "work_wsid";
+ default:
+ return "other";
+ }
+}
+
+/**
+ * Soften unknown snake_case / kebab-case keys for tooltips and overflow rows
+ * (``selected_branch`` is mapped above; this covers free-form keys like
+ * ``env`` → ``Env``).
+ */
+export function humanizeConversationTagKey(key: string): string {
+ const trimmed = key.trim();
+ if (!trimmed) {
+ return trimmed;
+ }
+ const words = trimmed.replace(/[_-]+/g, " ").replace(/\s+/g, " ");
+ return words.charAt(0).toUpperCase() + words.slice(1);
+}
+
+/**
+ * Localized label for a tag key (chip tooltip, overflow popover, hovercard).
+ * Known keys use the preview copy; everything else is humanized.
+ */
+export function getConversationTagLabel(
+ key: string,
+ t: (key: I18nKey) => string,
+): string {
+ switch (getConversationTagLabelKind(key)) {
+ case "git":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_GIT);
+ case "repo":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_REPO);
+ case "branch":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_BRANCH);
+ case "workspace":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_WORKSPACE);
+ case "app_mode":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_APP_MODE);
+ case "work_tools":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_WORK_TOOLS);
+ case "work_wsid":
+ return t(I18nKey.CONVERSATION_PANEL$PREVIEW_WORK_WSID);
+ default:
+ return humanizeConversationTagKey(key);
+ }
+}
+
+/** ``Branch: main`` — used by chip ``title`` tooltips. */
+export function formatConversationTagTooltip(
+ key: string,
+ value: string,
+ t: (key: I18nKey) => string,
+): string {
+ return `${getConversationTagLabel(key, t)}: ${value}`;
+}
+
+/**
+ * Hard-truncate a tag value for the chip label. The full ``key: value`` string
+ * stays available via tooltip / overflow popover.
+ *
+ * Measures and slices by code point rather than UTF-16 code unit: values
+ * stamped by Slack / Discord automations carry emoji, and cutting between the
+ * halves of a surrogate pair leaves a lone surrogate that browsers draw as a
+ * replacement glyph.
+ */
+export function truncateTagChipValue(
+ value: string,
+ maxLength: number = TAG_CHIP_VALUE_MAX_LENGTH,
+): string {
+ const characters = Array.from(value);
+ if (characters.length <= maxLength) {
+ return value;
+ }
+ if (maxLength <= 1) {
+ return "…";
+ }
+ return `${characters.slice(0, maxLength - 1).join("")}…`;
+}
+
+/**
+ * How many chips fit in ``containerWidth`` while keeping a single nowrap row
+ * and reserving space for a ``+N`` overflow control when any chips would hide.
+ *
+ * Returns ``widths.length`` when ``containerWidth <= 0`` (not laid out yet /
+ * jsdom) so callers can show every chip until a real measurement arrives.
+ * Returns ``0`` when the row is too narrow for even one chip + overflow — the
+ * UI then shows only the ``+N`` control with the full list in the popover.
+ */
+export function computeVisibleTagChipCount(
+ widths: number[],
+ containerWidth: number,
+ options: {
+ gapPx?: number;
+ overflowWidthPx?: number;
+ } = {},
+): number {
+ const gapPx = options.gapPx ?? TAG_CHIP_GAP_PX;
+ const overflowWidthPx = options.overflowWidthPx ?? TAG_CHIP_OVERFLOW_WIDTH_PX;
+
+ if (widths.length === 0) {
+ return 0;
+ }
+ if (containerWidth <= 0) {
+ return widths.length;
+ }
+
+ let used = 0;
+ for (let i = 0; i < widths.length; i += 1) {
+ const width = widths[i]!;
+ const gap = i > 0 ? gapPx : 0;
+ const remaining = widths.length - i - 1;
+ const reserve = remaining > 0 ? overflowWidthPx + gapPx : 0;
+ if (used + gap + width + reserve > containerWidth) {
+ return i;
+ }
+ used += gap + width;
+ }
+ return widths.length;
+}
diff --git a/src/components/features/conversation-panel/conversation-card/conversation-tag-icons.ts b/src/components/features/conversation-panel/conversation-card/conversation-tag-icons.ts
new file mode 100644
index 0000000000..c020a9bd5b
--- /dev/null
+++ b/src/components/features/conversation-panel/conversation-card/conversation-tag-icons.ts
@@ -0,0 +1,176 @@
+import type { ComponentType, SVGProps } from "react";
+import {
+ Bot,
+ Briefcase,
+ Building2,
+ CircleUserRound,
+ CloudCog,
+ Flag,
+ Folder,
+ FolderGit2,
+ FolderKey,
+ GitBranch,
+ GitPullRequest,
+ Globe2,
+ Hash,
+ House,
+ IdCard,
+ KeyRound,
+ Layers,
+ Link2,
+ Mails,
+ MessagesSquare,
+ Plug,
+ SquareKanban,
+ Tag,
+ Ticket,
+ UsersRound,
+ Waypoints,
+ Webhook,
+ Wrench,
+ Zap,
+ type LucideIcon,
+} from "lucide-react";
+import { FaBitbucket, FaGithub, FaGitlab } from "react-icons/fa6";
+import type { IconType } from "react-icons/lib";
+import SlackIcon from "#/icons/slack.svg?react";
+
+/**
+ * Any icon renderable inside a tag chip / overflow row. Lucide, react-icons,
+ * and local SVG React components all work as long as they accept ``className``
+ * and inherit ``currentColor`` for the muted chip text.
+ */
+export type ConversationTagIcon =
+ | LucideIcon
+ | IconType
+ | ComponentType>;
+
+/**
+ * Tag keys whose value names a git host / chat source — resolve the icon from
+ * the value (e.g. ``git_provider: github`` → GitHub mark) instead of the key.
+ */
+const SOURCE_VALUE_DRIVEN_TAG_KEYS = new Set([
+ "origin",
+ "source",
+ "git_provider",
+]);
+
+/**
+ * App-mode keys resolve from the mode value (``work`` → briefcase) when known,
+ * otherwise fall back to {@link Layers}.
+ */
+const APP_MODE_TAG_KEYS = new Set(["appmode", "app_mode", "mode"]);
+
+/**
+ * Icons for well-known conversation tag keys. Unknown keys fall back to
+ * {@link Tag}. Value-driven keys additionally resolve via the value maps below.
+ */
+const KEY_ICONS: Record = {
+ origin: Waypoints,
+ source: Link2,
+ git_provider: FaGithub,
+ owner: CircleUserRound,
+ user: CircleUserRound,
+ author: CircleUserRound,
+ assignee: CircleUserRound,
+ env: CloudCog,
+ environment: CloudCog,
+ repo: FolderGit2,
+ repository: FolderGit2,
+ repo_name: FolderGit2,
+ branch: GitBranch,
+ selected_branch: GitBranch,
+ archiveworkspacepath: Folder,
+ workspace: Folder,
+ working_dir: Folder,
+ team: UsersRound,
+ org: Building2,
+ organization: Building2,
+ channel: Hash,
+ email: Mails,
+ automation: Zap,
+ webhook: Webhook,
+ agent: Bot,
+ project: SquareKanban,
+ ticket: Ticket,
+ issue: Ticket,
+ pr: GitPullRequest,
+ pull_request: GitPullRequest,
+ priority: Flag,
+ status: IdCard,
+ id: KeyRound,
+ integration: Plug,
+ appmode: Layers,
+ app_mode: Layers,
+ mode: Layers,
+ worktools: Wrench,
+ work_tools: Wrench,
+ tools: Wrench,
+ workwsid: FolderKey,
+ work_wsid: FolderKey,
+ wsid: FolderKey,
+ workspace_id: FolderKey,
+};
+
+/**
+ * Value-specific icons for source / provider stamps (``origin``,
+ * ``git_provider``, …), keyed by the lowercase stamp value.
+ */
+const SOURCE_VALUE_ICONS: Record = {
+ slack: SlackIcon,
+ discord: MessagesSquare,
+ github: FaGithub,
+ gitlab: FaGitlab,
+ bitbucket: FaBitbucket,
+ bitbucket_data_center: FaBitbucket,
+ azure_devops: GitBranch,
+ email: Mails,
+ mail: Mails,
+ api: Plug,
+ webhook: Webhook,
+ automation: Zap,
+ review: GitPullRequest,
+ linear: SquareKanban,
+ web: Globe2,
+ ui: Globe2,
+ canvas: Globe2,
+};
+
+/**
+ * Value-specific icons for app-mode stamps (``Appmode: work`` → briefcase).
+ */
+const APP_MODE_VALUE_ICONS: Record = {
+ work: Briefcase,
+ personal: House,
+ home: House,
+ private: House,
+};
+
+/**
+ * Pick an icon that matches a conversation tag. Prefer value-specific icons
+ * for source/provider and app-mode keys; otherwise map by key; finally fall
+ * back to ``Tag``.
+ */
+export function getConversationTagIcon(
+ key: string,
+ value: string,
+): ConversationTagIcon {
+ const normalizedKey = key.trim().toLowerCase();
+ const normalizedValue = value.trim().toLowerCase();
+
+ if (SOURCE_VALUE_DRIVEN_TAG_KEYS.has(normalizedKey)) {
+ return (
+ SOURCE_VALUE_ICONS[normalizedValue] ?? KEY_ICONS[normalizedKey] ?? Tag
+ );
+ }
+
+ if (APP_MODE_TAG_KEYS.has(normalizedKey)) {
+ return (
+ APP_MODE_VALUE_ICONS[normalizedValue] ??
+ KEY_ICONS[normalizedKey] ??
+ Layers
+ );
+ }
+
+ return KEY_ICONS[normalizedKey] ?? Tag;
+}
diff --git a/src/components/features/conversation-panel/conversation-card/no-repository.tsx b/src/components/features/conversation-panel/conversation-card/no-repository.tsx
index 5070b41fc6..22568226e9 100644
--- a/src/components/features/conversation-panel/conversation-card/no-repository.tsx
+++ b/src/components/features/conversation-panel/conversation-card/no-repository.tsx
@@ -1,7 +1,13 @@
import { useTranslation } from "react-i18next";
+import { Folder } from "lucide-react";
import { I18nKey } from "#/i18n/declaration";
import RepoForkedIcon from "#/icons/repo-forked.svg?react";
import { getPathBasename } from "#/utils/path-utils";
+import {
+ CONVERSATION_CARD_META_CHIP_CLASSNAME,
+ CONVERSATION_CARD_META_CHIP_ICON_CLASSNAME,
+ CONVERSATION_CARD_META_CHIP_ICON_SLOT_CLASSNAME,
+} from "./conversation-card-meta-chip";
interface NoRepositoryProps {
workspaceWorkingDir?: string | null;
@@ -17,24 +23,37 @@ export function NoRepository({ workspaceWorkingDir }: NoRepositoryProps) {
if (folderName) {
return (
- {folderName}
+
+
+
+ {folderName}
);
}
return (
-
-
-
+
+
+
+
+
{t(I18nKey.COMMON$NO_REPOSITORY)}
-
+
);
}
diff --git a/src/components/features/conversation-panel/conversation-panel-filter-menu.tsx b/src/components/features/conversation-panel/conversation-panel-filter-menu.tsx
index 172e37cc2a..b726d379f7 100644
--- a/src/components/features/conversation-panel/conversation-panel-filter-menu.tsx
+++ b/src/components/features/conversation-panel/conversation-panel-filter-menu.tsx
@@ -323,16 +323,6 @@ export function ConversationPanelFilterMenu({
{t(I18nKey.CONVERSATION_PANEL$METADATA)}
- {
- toggleShowLlmProfiles();
- setFilterMenuOpen(false);
- }}
- />
+ {
+ toggleShowLlmProfiles();
+ setFilterMenuOpen(false);
+ }}
+ />
}
>
diff --git a/src/i18n/translation.json b/src/i18n/translation.json
index 8899e70ee0..789849d3f0 100644
--- a/src/i18n/translation.json
+++ b/src/i18n/translation.json
@@ -37212,6 +37212,142 @@
"uk": "Змінити порядок {{name}}",
"ca": "Reordenar {{name}}"
},
+ "CONVERSATION$TAGS_OVERFLOW_ARIA": {
+ "en": "Show {{count}} more tags",
+ "ja": "他に{{count}}件のタグを表示",
+ "zh-CN": "显示另外 {{count}} 个标签",
+ "zh-TW": "顯示另外 {{count}} 個標籤",
+ "ko-KR": "태그 {{count}}개 더 보기",
+ "no": "Vis {{count}} flere etiketter",
+ "ar": "عرض {{count}} وسوم إضافية",
+ "de": "{{count}} weitere Tags anzeigen",
+ "fr": "Afficher {{count}} étiquettes de plus",
+ "it": "Mostra altri {{count}} tag",
+ "pt": "Mostrar mais {{count}} etiquetas",
+ "es": "Mostrar {{count}} etiquetas más",
+ "ca": "Mostra {{count}} etiquetes més",
+ "tr": "{{count}} etiket daha göster",
+ "uk": "Показати ще {{count}} тегів"
+ },
+ "CONVERSATION_PANEL$PREVIEW_GIT": {
+ "en": "Git",
+ "ja": "Git",
+ "zh-CN": "Git",
+ "zh-TW": "Git",
+ "ko-KR": "Git",
+ "no": "Git",
+ "ar": "Git",
+ "de": "Git",
+ "fr": "Git",
+ "it": "Git",
+ "pt": "Git",
+ "es": "Git",
+ "ca": "Git",
+ "tr": "Git",
+ "uk": "Git"
+ },
+ "CONVERSATION_PANEL$PREVIEW_REPO": {
+ "en": "Repo",
+ "ja": "リポジトリ",
+ "zh-CN": "仓库",
+ "zh-TW": "倉庫",
+ "ko-KR": "저장소",
+ "no": "Repo",
+ "ar": "المستودع",
+ "de": "Repo",
+ "fr": "Dépôt",
+ "it": "Repo",
+ "pt": "Repo",
+ "es": "Repo",
+ "ca": "Repo",
+ "tr": "Depo",
+ "uk": "Репо"
+ },
+ "CONVERSATION_PANEL$PREVIEW_BRANCH": {
+ "en": "Branch",
+ "ja": "ブランチ",
+ "zh-CN": "分支",
+ "zh-TW": "分支",
+ "ko-KR": "브랜치",
+ "no": "Gren",
+ "ar": "الفرع",
+ "de": "Branch",
+ "fr": "Branche",
+ "it": "Ramo",
+ "pt": "Ramo",
+ "es": "Rama",
+ "ca": "Branca",
+ "tr": "Dal",
+ "uk": "Гілка"
+ },
+ "CONVERSATION_PANEL$PREVIEW_WORKSPACE": {
+ "en": "Workspace",
+ "ja": "ワークスペース",
+ "zh-CN": "工作区",
+ "zh-TW": "工作區",
+ "ko-KR": "워크스페이스",
+ "no": "Arbeidsområde",
+ "ar": "مساحة العمل",
+ "de": "Arbeitsbereich",
+ "fr": "Espace de travail",
+ "it": "Workspace",
+ "pt": "Espaço de trabalho",
+ "es": "Espacio de trabajo",
+ "ca": "Espai de treball",
+ "tr": "Çalışma alanı",
+ "uk": "Робочий простір"
+ },
+ "CONVERSATION_PANEL$PREVIEW_APP_MODE": {
+ "en": "App mode",
+ "ja": "アプリモード",
+ "zh-CN": "应用模式",
+ "zh-TW": "應用模式",
+ "ko-KR": "앱 모드",
+ "no": "App-modus",
+ "ar": "وضع التطبيق",
+ "de": "App-Modus",
+ "fr": "Mode app",
+ "it": "Modalità app",
+ "pt": "Modo do app",
+ "es": "Modo de la app",
+ "ca": "Mode de l'app",
+ "tr": "Uygulama modu",
+ "uk": "Режим застосунку"
+ },
+ "CONVERSATION_PANEL$PREVIEW_WORK_TOOLS": {
+ "en": "Work tools",
+ "ja": "ワークツール",
+ "zh-CN": "工作工具",
+ "zh-TW": "工作工具",
+ "ko-KR": "작업 도구",
+ "no": "Arbeidsverktøy",
+ "ar": "أدوات العمل",
+ "de": "Arbeitswerkzeuge",
+ "fr": "Outils de travail",
+ "it": "Strumenti di lavoro",
+ "pt": "Ferramentas de trabalho",
+ "es": "Herramientas de trabajo",
+ "ca": "Eines de treball",
+ "tr": "Çalışma araçları",
+ "uk": "Робочі інструменти"
+ },
+ "CONVERSATION_PANEL$PREVIEW_WORK_WSID": {
+ "en": "Workspace ID",
+ "ja": "ワークスペース ID",
+ "zh-CN": "工作区 ID",
+ "zh-TW": "工作區 ID",
+ "ko-KR": "워크스페이스 ID",
+ "no": "Arbeidsområde-ID",
+ "ar": "معرّف مساحة العمل",
+ "de": "Arbeitsbereichs-ID",
+ "fr": "ID d'espace de travail",
+ "it": "ID workspace",
+ "pt": "ID do espaço de trabalho",
+ "es": "ID del espacio de trabajo",
+ "ca": "ID de l'espai de treball",
+ "tr": "Çalışma alanı kimliği",
+ "uk": "ID робочого простору"
+ },
"SETTINGS$VERSION_NPM_RECOMMENDED": {
"en": "npm",
"ja": "npm",
diff --git a/src/stores/conversation-panel-preferences-store.ts b/src/stores/conversation-panel-preferences-store.ts
index fe0e79237e..383f97bb39 100644
--- a/src/stores/conversation-panel-preferences-store.ts
+++ b/src/stores/conversation-panel-preferences-store.ts
@@ -59,8 +59,8 @@ type ConversationPanelPreferencesStore = ConversationPanelPreferencesState &
const initialState: ConversationPanelPreferencesState = {
showOlderConversations: true,
showRepoBranchMetadata: false,
- showLlmProfiles: true,
- showTagsMetadata: true,
+ showLlmProfiles: false,
+ showTagsMetadata: false,
showHoverMetadata: true,
organizeMode: "chronological",
conversationSort: "updated",