Q173: agents work in the company's or task's language, not the display language
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
538d834647
commit
3f14e3f706
36 files changed
+268
-105
No files matched your search
@@ -695,6 +695,11 @@ export const zh: Record<string, string> = {
|
||||
"Zero data retention switched background off.": "零数据保留已关闭后台执行。",
|
||||
"Resets {date} (UTC)": "{date} 重置(UTC)",
|
||||
"Unsaved": "未保存",
|
||||
"Working language": "工作语言",
|
||||
"Company default ({language})": "公司默认({language})",
|
||||
"Agents' working language": "Agent 工作语言",
|
||||
"Agents write messages, tasks, documents and deliverables in it. The display language does not change it, and a task can set its own.": "Agent 用它撰写消息、任务、文档和交付物。显示语言不会改变它,单个任务也可以单独设置。",
|
||||
"Changes only what you see; agents keep the company's working language.": "只改变界面显示;Agent 仍使用公司的工作语言。",
|
||||
"Disconnect {label}?": "断开 {label}?",
|
||||
"Checking what this affects…": "正在检查影响范围…",
|
||||
"Agents on this provider stop working until it is reconnected or they move to another provider: {names}.": "使用该 Provider 的 Agent 将停止工作,直到重新连接或改用其他 Provider:{names}。",
|
||||
|
||||
@@ -5,7 +5,9 @@ import type { AppSnapshot, RiskLevel, TaskPriority, WritingMode } from "../../..
|
||||
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, WRITING_MODE } from "../../../shared/domain";
|
||||
import type { SnapshotActions } from "../app/useSnapshot";
|
||||
import { t } from "../i18n/i18n";
|
||||
import { WRITING_OPTIONS } from "./work/task-helpers";
|
||||
import { WRITING_OPTIONS, readWorkLanguage } from "./work/task-helpers";
|
||||
import { WorkLanguageField } from "./work/WorkLanguageField";
|
||||
import { requireActiveOrganization } from "./types";
|
||||
|
||||
const RISK_OPTIONS: ReadonlyArray<{ value: RiskLevel; label: string }> = [
|
||||
{ value: RISK_LEVEL.LOW, label: t("Low") },
|
||||
@@ -61,6 +63,7 @@ export function NewTaskDialog({
|
||||
priority: value(form, "priority") as TaskPriority,
|
||||
requiresFilesystem: form.get("requiresFilesystem") === "on",
|
||||
writingMode: value(form, "writingMode") as WritingMode,
|
||||
workLanguage: readWorkLanguage(form),
|
||||
})).then((result) => {
|
||||
if (result) onClose();
|
||||
});
|
||||
@@ -109,6 +112,7 @@ export function NewTaskDialog({
|
||||
{WRITING_OPTIONS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<WorkLanguageField companyLanguage={requireActiveOrganization(snapshot).workLanguage} defaultValue={null} />
|
||||
<label className="checkbox-row">
|
||||
<input type="checkbox" name="requiresFilesystem" /> {t("Requires workspace access")}
|
||||
</label>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { ListView } from "./work/ListView";
|
||||
import { ProgressView } from "./work/ProgressView";
|
||||
import { isBusinessTask, latestTaskRun, nextStatus, prettyRunState, providerFor, runBlocker, runTone } from "./work/task-helpers";
|
||||
import type { PageProps } from "./types";
|
||||
import { activeOrgId, type Focus } from "./types";
|
||||
import { activeOrgId, requireActiveOrganization, type Focus } from "./types";
|
||||
import { t } from "../i18n/i18n";
|
||||
|
||||
type View = "board" | "list" | "progress";
|
||||
@@ -166,6 +166,7 @@ export function WorkPage({ snapshot, actions, navigate, focus }: PageProps) {
|
||||
<TaskDetailDialog
|
||||
key={detailTask?.id ?? "none"}
|
||||
task={detailTask}
|
||||
companyLanguage={requireActiveOrganization(snapshot).workLanguage}
|
||||
agents={agents}
|
||||
projects={projects}
|
||||
goals={goals}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Field } from "../../shared/ui";
|
||||
import { Dialog } from "../../shared/Dialog";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
import { t } from "../../i18n/i18n";
|
||||
import { WORK_LANGUAGE_NAMES } from "../work/task-helpers";
|
||||
|
||||
/** Changes the company's profile and how it runs after onboarding; requests already waiting keep their state. */
|
||||
export function CompanyDialog({ organization, open, actions, onClose }: { organization: OrganizationRecord; open: boolean; actions: SnapshotActions; onClose: () => void }) {
|
||||
@@ -22,6 +23,7 @@ export function CompanyDialog({ organization, open, actions, onClose }: { organi
|
||||
executionMode: value("executionMode") === "ai_cofounder" ? "ai_cofounder" : "ai_ceo",
|
||||
autoApproveHires: form.get("autoApproveHires") === "on",
|
||||
reviewDeliverables: form.get("reviewDeliverables") === "on",
|
||||
workLanguage: value("workLanguage") === "zh" ? "zh" : "en",
|
||||
})).then(onClose, (reason: unknown) => setError(reason instanceof Error ? reason.message : String(reason)));
|
||||
};
|
||||
return (
|
||||
@@ -36,6 +38,13 @@ export function CompanyDialog({ organization, open, actions, onClose }: { organi
|
||||
<option value="ai_cofounder">{t("AI Co-Founder: asks before risky tasks")}</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label={t("Agents' working language")}>
|
||||
<select className="input" name="workLanguage" defaultValue={organization.workLanguage}>
|
||||
<option value="en">{WORK_LANGUAGE_NAMES.en}</option>
|
||||
<option value="zh">{WORK_LANGUAGE_NAMES.zh}</option>
|
||||
</select>
|
||||
</Field>
|
||||
<p style={{ fontSize: 12, color: "var(--text-3)", margin: 0 }}>{t("Agents write messages, tasks, documents and deliverables in it. The display language does not change it, and a task can set its own.")}</p>
|
||||
<label className="checkbox-row"><input type="checkbox" name="autoApproveHires" defaultChecked={organization.autoApproveHires} /> {t("Auto-approve hires")}</label>
|
||||
<label className="checkbox-row"><input type="checkbox" name="reviewDeliverables" defaultChecked={organization.reviewDeliverables} /> {t("Review deliverables before they are written")}</label>
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{t("Changes apply to work from now on; requests already waiting keep their state.")}</div>
|
||||
|
||||
@@ -114,6 +114,7 @@ export function WorkspacePanel({
|
||||
<option value="en">English</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
<span style={{ fontSize: 12, color: "var(--text-3)" }}>{t("Changes only what you see; agents keep the company's working language.")}</span>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ import type {
|
||||
TaskRunRecord,
|
||||
WorkspaceRecord,
|
||||
} from "../../../../shared/domain";
|
||||
import { TASK_STATUS, type TaskDeleteImpact, type TaskStatus, type WritingMode } from "../../../../shared/domain";
|
||||
import { TASK_STATUS, type TaskDeleteImpact, type TaskStatus, type WorkLanguage, type WritingMode } from "../../../../shared/domain";
|
||||
import { auditEventLabel, formatRelative } from "../types";
|
||||
import { WRITING_OPTIONS, descendantIds, nextStatus, prettyRunState, prettyStatus, providerFor, runBlocker, runsForTask, runTone } from "./task-helpers";
|
||||
import { WorkLanguageField } from "./WorkLanguageField";
|
||||
import { WRITING_OPTIONS, readWorkLanguage, descendantIds, nextStatus, prettyRunState, prettyStatus, providerFor, runBlocker, runsForTask, runTone } from "./task-helpers";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
import type { Focus, PageProps } from "../types";
|
||||
import { t } from "../../i18n/i18n";
|
||||
@@ -28,6 +29,7 @@ const TABS: Array<{ id: Tab; label: string }> = [
|
||||
|
||||
export function TaskDetailDialog({
|
||||
task,
|
||||
companyLanguage,
|
||||
agents,
|
||||
projects,
|
||||
goals,
|
||||
@@ -49,6 +51,7 @@ export function TaskDetailDialog({
|
||||
navigate,
|
||||
}: {
|
||||
task: TaskRecord | null;
|
||||
companyLanguage: WorkLanguage;
|
||||
agents: AgentRecord[];
|
||||
projects: ProjectRecord[];
|
||||
goals: GoalRecord[];
|
||||
@@ -114,6 +117,7 @@ export function TaskDetailDialog({
|
||||
workspaceId: val("workspaceId") || null,
|
||||
requiresFilesystem: form.get("requiresFilesystem") === "on",
|
||||
writingMode: val("writingMode") as WritingMode,
|
||||
workLanguage: readWorkLanguage(form),
|
||||
})).then((r) => { if (r) setEditing(false); }, (error: unknown) => setEditError(error instanceof Error ? error.message : String(error)));
|
||||
};
|
||||
|
||||
@@ -203,6 +207,7 @@ export function TaskDetailDialog({
|
||||
{editing && (
|
||||
<EditOverlay
|
||||
task={task}
|
||||
companyLanguage={companyLanguage}
|
||||
agents={agents}
|
||||
allTasks={allTasks}
|
||||
projects={projects}
|
||||
@@ -314,8 +319,8 @@ function ActivityTab({ audit, approvals, onOpenApproval }: { audit: AuditEventRe
|
||||
);
|
||||
}
|
||||
|
||||
function EditOverlay({ task, agents, allTasks, projects, goals, workspaces, error, onSubmit, onCancel }: {
|
||||
task: TaskRecord; agents: AgentRecord[]; allTasks: TaskRecord[];
|
||||
function EditOverlay({ task, companyLanguage, agents, allTasks, projects, goals, workspaces, error, onSubmit, onCancel }: {
|
||||
task: TaskRecord; companyLanguage: WorkLanguage; agents: AgentRecord[]; allTasks: TaskRecord[];
|
||||
projects: ProjectRecord[]; goals: GoalRecord[]; workspaces: WorkspaceRecord[]; error: string | null;
|
||||
onSubmit: (e: FormEvent<HTMLFormElement>) => void; onCancel: () => void;
|
||||
}) {
|
||||
@@ -374,6 +379,7 @@ function EditOverlay({ task, agents, allTasks, projects, goals, workspaces, erro
|
||||
{WRITING_OPTIONS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<WorkLanguageField companyLanguage={companyLanguage} defaultValue={task.workLanguage} />
|
||||
<label className="checkbox-row">
|
||||
<input type="checkbox" name="requiresFilesystem" defaultChecked={task.requiresFilesystem} /> {t("Requires workspace access")}
|
||||
</label>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { WorkLanguage } from "../../../../shared/domain";
|
||||
import { Field } from "../../shared/ui";
|
||||
import { t } from "../../i18n/i18n";
|
||||
import { WORK_LANGUAGE_NAMES } from "./task-helpers";
|
||||
|
||||
/** A task's working language; blank follows the company's, named so the user sees what blank means. */
|
||||
export function WorkLanguageField({ companyLanguage, defaultValue }: { companyLanguage: WorkLanguage; defaultValue: WorkLanguage | null }) {
|
||||
return (
|
||||
<Field label={t("Working language")}>
|
||||
<select className="input" name="workLanguage" defaultValue={defaultValue ?? ""}>
|
||||
<option value="">{t("Company default ({language})", { language: WORK_LANGUAGE_NAMES[companyLanguage] })}</option>
|
||||
<option value="en">{WORK_LANGUAGE_NAMES.en}</option>
|
||||
<option value="zh">{WORK_LANGUAGE_NAMES.zh}</option>
|
||||
</select>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
@@ -6,10 +6,22 @@ import {
|
||||
type TaskRecord,
|
||||
type TaskRunRecord,
|
||||
type TaskStatus,
|
||||
type WorkLanguage,
|
||||
type WritingMode,
|
||||
} from "../../../../shared/domain";
|
||||
import { t } from "../../i18n/i18n";
|
||||
|
||||
/** Each working language in its own script, so it reads right whatever the display language. */
|
||||
export const WORK_LANGUAGE_NAMES: Record<WorkLanguage, string> = { en: "English", zh: "简体中文" };
|
||||
|
||||
/** The task form's working language: blank follows the company. */
|
||||
export function readWorkLanguage(form: FormData): WorkLanguage | null {
|
||||
const value = String(form.get("workLanguage") ?? "");
|
||||
if (value === "") return null;
|
||||
if (value === "en" || value === "zh") return value;
|
||||
throw new Error(`Unknown working language "${value}".`);
|
||||
}
|
||||
|
||||
/** The writing modes a task can ask for, as the create and edit forms offer them. */
|
||||
export const WRITING_OPTIONS: ReadonlyArray<{ value: WritingMode; label: string }> = [
|
||||
{ value: WRITING_MODE.CONVERSATIONAL, label: t("Conversational (house style)") },
|
||||
|
||||
Reference in new issue
Block a user