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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Bebas_Neue, IBM_Plex_Mono, IBM_Plex_Sans } from "next/font/google";
import "./globals.css";
import { TranslationProvider } from "@/lib/i18n/TranslationProvider";

export const metadata: Metadata = {
title: "Claw3D",
Expand Down Expand Up @@ -31,7 +32,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="zh-TW" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
Expand All @@ -41,7 +42,9 @@ export default function RootLayout({
/>
</head>
<body className={`${display.variable} ${sans.variable} ${mono.variable} antialiased`}>
<main className="h-screen w-screen overflow-hidden bg-background">{children}</main>
<main className="h-screen w-screen overflow-hidden bg-background">
<TranslationProvider>{children}</TranslationProvider>
</main>
</body>
</html>
);
Expand Down
3 changes: 3 additions & 0 deletions src/features/agents/components/AgentAvatarEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { RefreshCcw, Shuffle } from "lucide-react";

import { T, useTranslation } from '@/lib/i18n/TranslationProvider';
import {
AGENT_AVATAR_BOTTOM_STYLE_OPTIONS,
AGENT_AVATAR_CLOTHING_COLOR_OPTIONS,
Expand Down Expand Up @@ -58,6 +60,7 @@ export const AgentAvatarEditorPanel = forwardRef<
}: AgentAvatarEditorPanelProps,
ref
) {
const { t } = useTranslation();
const fallbackProfile = useMemo(
() => createDefaultAgentAvatarProfile(agentId),
[agentId]
Expand Down
151 changes: 83 additions & 68 deletions src/features/agents/components/AgentChatPanel.tsx

Large diffs are not rendered by default.

30 changes: 16 additions & 14 deletions src/features/agents/components/AgentCreateModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Shuffle } from "lucide-react";
import type { AgentCreateModalSubmitPayload } from "@/features/agents/creation/types";
import { AgentAvatar } from "@/features/agents/components/AgentAvatar";
import { randomUUID } from "@/lib/uuid";
import { T, useTranslation } from '@/lib/i18n/TranslationProvider';

type AgentCreateModalProps = {
open: boolean;
Expand Down Expand Up @@ -33,6 +34,7 @@ const AgentCreateModalContent = ({
onClose,
onSubmit,
}: Omit<AgentCreateModalProps, "open">) => {
const { t } = useTranslation();
const [name, setName] = useState(() => resolveInitialName(suggestedName));
const [avatarSeed, setAvatarSeed] = useState(() => randomUUID());

Expand All @@ -50,7 +52,7 @@ const AgentCreateModalContent = ({
className="fixed inset-0 z-[120] flex items-center justify-center bg-background/80 p-4"
role="dialog"
aria-modal="true"
aria-label="Create agent"
aria-label={t('agent.create_modal_title', '建立新 Agent')}
onClick={busy ? undefined : onClose}
>
<form
Expand All @@ -65,37 +67,37 @@ const AgentCreateModalContent = ({
<div className="flex items-center justify-between border-b border-border/35 px-6 py-6">
<div>
<div className="font-mono text-[11px] font-semibold tracking-[0.06em] text-muted-foreground">
New agent
<T id="agent.create_modal_subtitle" fallback="啟用 Agent" />
</div>
<div className="mt-1 text-base font-semibold text-foreground">Launch agent</div>
<div className="mt-1 text-xs text-muted-foreground">Name it and activate immediately.</div>
<div className="mt-1 text-base font-semibold text-foreground"><T id="agent.create_modal_title" fallback="建立新 Agent" /></div>
<div className="mt-1 text-xs text-muted-foreground"><T id="agent.create_modal_desc" fallback="為 Agent 命名並立即啟用。" /></div>
</div>
<button
type="button"
className="ui-btn-ghost px-3 py-1.5 font-mono text-[11px] font-semibold tracking-[0.06em] disabled:cursor-not-allowed disabled:opacity-60"
onClick={onClose}
disabled={busy}
>
Close
<T id="common.close" fallback="關閉" />
</button>
</div>

<div className="grid gap-4 px-6 py-5">
<label className={labelClassName}>
Name
<T id="agent.identity.name" fallback="名稱" />
<input
aria-label="Agent name"
aria-label={t('agent.identity.name', '名稱')}
value={name}
onChange={(event) => setName(event.target.value)}
className={`mt-1 ${fieldClassName}`}
placeholder="My agent"
placeholder={t('agent.create_modal_name_placeholder', '我的 Agent')}
/>
</label>
<div className="-mt-2 text-[11px] text-muted-foreground">
You can rename this agent from the main chat header.
<T id="agent.create_modal_name_hint" fallback="你可以在主聊天標題中重新命名此 Agent。" />
</div>
<div className="grid justify-items-center gap-2 border-t border-border/40 pt-3">
<div className={labelClassName}>Choose avatar</div>
<div className={labelClassName}><T id="agent.create_modal_avatar_label" fallback="選擇頭像" /></div>
<AgentAvatar
seed={avatarSeed}
name={name.trim() || "New Agent"}
Expand All @@ -104,13 +106,13 @@ const AgentCreateModalContent = ({
/>
<button
type="button"
aria-label="Shuffle avatar selection"
aria-label={t('agent.create_modal_shuffle', '隨機')}
className="ui-btn-secondary inline-flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground"
onClick={() => setAvatarSeed(randomUUID())}
disabled={busy}
>
<Shuffle className="h-3.5 w-3.5" />
Shuffle
<T id="agent.create_modal_shuffle" fallback="隨機" />
</button>
</div>

Expand All @@ -122,13 +124,13 @@ const AgentCreateModalContent = ({
</div>

<div className="flex items-center justify-between border-t border-border/45 px-6 pb-4 pt-5">
<div className="text-[11px] text-muted-foreground">Authority can be configured after launch.</div>
<div className="text-[11px] text-muted-foreground"><T id="agent.create_modal_footer_hint" fallback="權限可在啟動後設定。" /></div>
<button
type="submit"
className="ui-btn-primary px-3 py-1.5 font-mono text-[11px] font-semibold tracking-[0.06em] disabled:cursor-not-allowed disabled:border-border disabled:bg-muted disabled:text-muted-foreground"
disabled={!canSubmit || busy}
>
{busy ? "Launching..." : "Launch agent"}
{busy ? t('agent.create_modal_launching', '啟動中…') : t('agent.create_modal_launch', '啟動 Agent')}
</button>
</div>
</form>
Expand Down
24 changes: 13 additions & 11 deletions src/features/agents/components/AgentEditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { GatewayClient } from "@/lib/gateway/GatewayClient";
import type { AgentFileName } from "@/lib/agents/agentFiles";
import { AGENT_FILE_META } from "@/lib/agents/agentFiles";
import { renameGatewayAgent } from "@/lib/gateway/agentConfig";
import { T, useTranslation } from '@/lib/i18n/TranslationProvider';

export type AgentEditorSection = "avatar" | AgentFileName;

Expand Down Expand Up @@ -110,6 +111,7 @@ export const AgentEditorModal = ({
onDelete,
onNavigateAgent,
}: AgentEditorModalProps) => {
const { t } = useTranslation();
const [activeSection, setActiveSection] = useState<AgentEditorSection>(initialSection);
const activeAgentIndex = agents.findIndex((entry) => entry.agentId === agent.agentId);
const previousAgent =
Expand All @@ -126,7 +128,7 @@ export const AgentEditorModal = ({
className="fixed inset-0 z-[145] flex items-center justify-center bg-background/88 p-4"
role="dialog"
aria-modal="true"
aria-label={`Edit ${agent.name}`}
aria-label={t('agent.editor_title', 'Agent 編輯器')}
onClick={onClose}
>
<div
Expand All @@ -137,21 +139,21 @@ export const AgentEditorModal = ({
type="button"
onClick={onClose}
className="absolute -right-3 -top-3 z-20 inline-flex h-10 w-10 items-center justify-center rounded-full border border-border/50 bg-background/92 text-muted-foreground shadow-lg transition-colors hover:text-foreground"
aria-label="Close agent editor"
aria-label={t('common.close', '關閉')}
>
<X className="h-4 w-4" />
</button>
<div className="ui-panel flex h-[min(90vh,920px)] w-full overflow-hidden shadow-xs">
<aside className="flex w-[240px] shrink-0 flex-col border-r border-border/50 bg-muted/20">
<div className="border-b border-border/40 px-5 py-4">
<div className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground">
Agent editor
<T id="agent.editor_title" fallback="Agent 編輯器" />
</div>
<div className="mt-1 truncate text-lg font-semibold text-foreground">
{agent.name}
</div>
<div className="mt-1 text-xs text-muted-foreground">
Edit avatar and agent brain settings from the office.
<T id="agent.editor_desc" fallback="從辦公室編輯頭像與 Agent 大腦設定。" />
</div>
{onNavigateAgent ? (
<div className="mt-4 flex items-center gap-2">
Expand All @@ -165,7 +167,7 @@ export const AgentEditorModal = ({
className="inline-flex items-center gap-1 rounded-md border border-border/50 bg-background/40 px-2.5 py-1.5 text-xs text-foreground transition-colors hover:border-border hover:bg-background/70 disabled:cursor-not-allowed disabled:opacity-40"
>
<ChevronLeft className="h-3.5 w-3.5" />
<span>Previous</span>
<span><T id="agent.editor_previous" fallback="上一頁" /></span>
</button>
<button
type="button"
Expand All @@ -176,7 +178,7 @@ export const AgentEditorModal = ({
disabled={!nextAgent}
className="inline-flex items-center gap-1 rounded-md border border-border/50 bg-background/40 px-2.5 py-1.5 text-xs text-foreground transition-colors hover:border-border hover:bg-background/70 disabled:cursor-not-allowed disabled:opacity-40"
>
<span>Next</span>
<span><T id="agent.editor_next" fallback="下一頁" /></span>
<ChevronRight className="h-3.5 w-3.5" />
</button>
</div>
Expand Down Expand Up @@ -217,9 +219,9 @@ export const AgentEditorModal = ({
>
<Trash2 className="h-4 w-4" />
<div>
<div className="text-sm font-semibold text-inherit">Delete Agent</div>
<div className="text-sm font-semibold text-inherit"><T id="agent.editor_delete_section" fallback="刪除 Agent" /></div>
<div className="text-xs text-red-100/85">
Remove this agent from Claw3D and OpenClaw.
<T id="agent.editor_delete_desc" fallback="從 Claw3D OpenClaw 移除這個 Agent。" />
</div>
</div>
</button>
Expand All @@ -240,10 +242,10 @@ export const AgentEditorModal = ({
<div className="flex min-h-0 flex-1 flex-col">
<div className="border-b border-border/40 px-6 py-4">
<div className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground">
Agent file editor
<T id="agent.editor_file_section" fallback="Agent 檔案編輯器" />
</div>
<div className="mt-1 text-sm text-muted-foreground">
Edit one agent file at a time and save it through the gateway.
<T id="agent.editor_file_desc" fallback="一次編輯一個 Agent 檔案,然後透過閘道器儲存。" />
</div>
</div>
<div className="min-h-0 flex-1">
Expand All @@ -270,7 +272,7 @@ export const AgentEditorModal = ({
</div>
) : (
<div className="flex h-full items-center justify-center p-8 text-sm text-muted-foreground">
Connect to a gateway to edit brain files.
<T id="agent.editor_empty_state" fallback="請連線到閘道器以編輯大腦檔案。" />
</div>
)}
</section>
Expand Down
19 changes: 11 additions & 8 deletions src/features/agents/components/AgentIdentityFields.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import { T, useTranslation } from '@/lib/i18n/TranslationProvider';

export type AgentIdentityValues = {
name: string;
creature: string;
Expand All @@ -21,50 +23,51 @@ export function AgentIdentityFields({
disabled = false,
onChange,
}: AgentIdentityFieldsProps) {
const { t } = useTranslation();
return (
<div className="grid gap-4 sm:grid-cols-2">
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
Name
<T id="agent.identity.name" fallback="名稱" />
<input
className={inputClassName}
value={values.name}
placeholder="e.g. Luke"
placeholder={t('agent.identity.name_placeholder', '例如 小明')}
disabled={disabled}
onChange={(event) => {
onChange("name", event.target.value);
}}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
Role
<T id="agent.identity.role" fallback="角色" />
<input
className={inputClassName}
value={values.creature}
placeholder="e.g. Product Designer"
placeholder={t('agent.identity.role_placeholder', '例如 產品設計師')}
disabled={disabled}
onChange={(event) => {
onChange("creature", event.target.value);
}}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
Vibe
<T id="agent.identity.vibe" fallback="風格" />
<input
className={inputClassName}
value={values.vibe}
placeholder="e.g. Calm, sharp, and helpful"
placeholder={t('agent.identity.vibe_placeholder', '例如 冷靜、敏銳、樂於助人')}
disabled={disabled}
onChange={(event) => {
onChange("vibe", event.target.value);
}}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
Emoji
<T id="agent.identity.emoji" fallback="表情符號" />
<input
className={inputClassName}
value={values.emoji}
placeholder="e.g. ✨"
placeholder={t('agent.identity.emoji_placeholder', '例如 ✨')}
disabled={disabled}
onChange={(event) => {
onChange("emoji", event.target.value);
Expand Down
Loading