Language-aware house style and a resizable chat split

House style now follows the app language: Chinese UI forces the new
bundled humanizer-zh skill into every run, English forces humanizer -
one variant per run, picked by preference with cross-language fallback
so an older catalogue never silently loses house style. Both variants
are protected from grant/revoke, list_skills labels each with its
language, the bundle guard requires both, and startup sync imports the
new skill into existing orgs on next launch. Covered by a unit test
that flips app_state.language and asserts which humanizer compiles.

The chat split is now adjustable: drag the conversation list's right
edge (200-480px, persisted in localStorage) and the composer textarea
grew a native vertical resize grip capped at 40vh.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-09 01:14:05 -04:00
1 parent c027508b97
commit eaaf36f859
16 files changed
+931 -45

No files matched your search

+1
View File
@@ -126,6 +126,7 @@ export const zh: Record<string, string> = {
"Edit profile": "编辑个人资料",
"Former member": "已离职成员",
"Jump to latest": "跳到最新消息",
"Resize conversation list": "调整会话列表宽度",
"Upload photo": "上传照片",
"Pause agents": "暂停智能体",
"Agents paused": "已暂停 · 点击恢复",
+1 -1
View File
@@ -55,7 +55,7 @@ export function ChatComposer({
agents={agents}
employees={employees}
textareaRef={ref}
style={{ minHeight: 38 }}
style={{ minHeight: 38, maxHeight: "40vh", resize: "vertical" }}
/>
<div className="row" style={{ paddingTop: 6, borderTop: "1px solid var(--border-soft)", gap: 4 }}>
<IconButton icon="attach" label={t("Attach file")} onClick={onAttach} disabled={disabled} />
+42 -1
View File
@@ -1,9 +1,20 @@
import { useRef, useState, type PointerEvent } from "react";
import { Avatar, Icon, IconButton } from "../../shared/ui";
import type { ChatPresenceRecord } from "../../../../shared/domain";
import { formatRelative } from "../types";
import type { ConversationItem } from "./chat-helpers";
import { t } from "../../i18n/i18n";
const PANE_WIDTH_KEY = "agentcompany:chat-pane-width";
const PANE_MIN_W = 200;
const PANE_MAX_W = 480;
function storedPaneWidth(): number {
const raw = Number(window.localStorage.getItem(PANE_WIDTH_KEY));
if (!Number.isFinite(raw) || raw === 0) return 240;
return Math.min(PANE_MAX_W, Math.max(PANE_MIN_W, raw));
}
function presenceColor(status: string | undefined): string | null {
switch (status) {
case "active":
@@ -38,8 +49,29 @@ export function ChatSidebar({
onCreateSpace: () => void;
}) {
const presenceByPrincipal = new Map(presence.map((entry) => [entry.principalId, entry.status]));
// The list/conversation split is adjustable: drag the right edge, width
// persists across sessions.
const [paneWidth, setPaneWidth] = useState(storedPaneWidth);
const dragStart = useRef<{ pointerX: number; width: number } | null>(null);
const onHandleDown = (event: PointerEvent<HTMLDivElement>) => {
dragStart.current = { pointerX: event.clientX, width: paneWidth };
event.currentTarget.setPointerCapture(event.pointerId);
};
const onHandleMove = (event: PointerEvent<HTMLDivElement>) => {
if (!dragStart.current) return;
const next = Math.min(PANE_MAX_W, Math.max(PANE_MIN_W, dragStart.current.width + event.clientX - dragStart.current.pointerX));
setPaneWidth(next);
};
const onHandleUp = (event: PointerEvent<HTMLDivElement>) => {
if (!dragStart.current) return;
dragStart.current = null;
event.currentTarget.releasePointerCapture(event.pointerId);
window.localStorage.setItem(PANE_WIDTH_KEY, String(paneWidth));
};
return (
<aside className="secondary-pane">
<aside className="secondary-pane" style={{ width: paneWidth, position: "relative" }}>
<div className="row" style={{ padding: "4px 8px 12px" }}>
<div style={{ fontSize: 14, fontWeight: 600 }}>{t("Messages")}</div>
<IconButton icon="plus" label={t("New space")} onClick={onCreateSpace} />
@@ -57,6 +89,15 @@ export function ChatSidebar({
))}
</div>
</div>
<div
className="pane-resize-handle"
role="separator"
aria-orientation="vertical"
aria-label={t("Resize conversation list")}
onPointerDown={onHandleDown}
onPointerMove={onHandleMove}
onPointerUp={onHandleUp}
/>
</aside>
);
}
+12
View File
@@ -313,3 +313,15 @@ textarea.input { min-height: 84px; resize: vertical; line-height: 1.5; }
color: #231600;
}
.button.autopause-paused:hover { background: var(--amber, #f59e0b); filter: brightness(1.06); }
/* Drag handle on the chat conversation list's right edge */
.pane-resize-handle {
position: absolute;
top: 0;
right: -3px;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 2;
}
.pane-resize-handle:hover { background: color-mix(in srgb, var(--accent) 35%, transparent); }