diff --git a/crates/openfang-api/static/i18n/en.json b/crates/openfang-api/static/i18n/en.json new file mode 100644 index 0000000000..79c3bf4d26 --- /dev/null +++ b/crates/openfang-api/static/i18n/en.json @@ -0,0 +1,608 @@ +{ + "app.name": "OpenFang", + "app.version": "v", + + "nav.chat": "Chat", + "nav.monitor": "Monitor", + "nav.overview": "Overview", + "nav.analytics": "Analytics", + "nav.logs": "Logs", + "nav.agents": "Agents", + "nav.sessions": "Sessions", + "nav.approvals": "Approvals", + "nav.comms": "Comms", + "nav.automation": "Automation", + "nav.workflows": "Workflows", + "nav.scheduler": "Scheduler", + "nav.extensions": "Extensions", + "nav.channels": "Channels", + "nav.skills": "Skills", + "nav.hands": "Hands", + "nav.system": "System", + "nav.runtime": "Runtime", + "nav.settings": "Settings", + + "auth.sign_in": "Sign In", + "auth.enter_credentials": "Enter your dashboard credentials.", + "auth.username": "Username", + "auth.password": "Password", + "auth.api_key_required": "API Key Required", + "auth.api_key_desc": "This instance requires an API key. Enter the key from your config.toml.", + "auth.api_key_hint": "Add api_key = \"your-key\" at the top of ~/.openfang/config.toml (not under any [section]).", + "auth.enter_api_key": "Enter API key...", + "auth.unlock_dashboard": "Unlock Dashboard", + "auth.login_failed": "Login failed", + + "status.agents_running": "agent(s) running", + "status.connecting": "Connecting...", + "status.reconnecting": "Reconnecting...", + "status.disconnected": "disconnected", + "status.ws": "WS", + "status.http": "HTTP", + "status.ready": "Ready", + "status.loading": "Loading...", + "status.loading_workflows": "Loading workflows...", + "status.loading_channels": "Loading channels...", + "status.loading_skills": "Loading skills...", + "status.loading_jobs": "Loading scheduled jobs...", + "status.loading_triggers": "Loading triggers...", + "status.loading_history": "Loading run history...", + "status.loading_hands": "Loading hands...", + "status.loading_active_hands": "Loading active hands...", + "status.loading_mcp": "Loading MCP servers...", + "status.loading_files": "Loading files...", + "status.loading_skills_details": "Loading skills details...", + "status.no_channels_match": "No channels match your search", + + "actions.logout": "Logout", + "actions.new_agent": "New Agent", + "actions.browse_skills": "Browse Skills", + "actions.add_channel": "Add Channel", + "actions.create_workflow": "Create Workflow", + "actions.settings": "Settings", + "actions.create_agent": "Create Agent", + "actions.configure_provider": "Configure Provider", + "actions.cancel": "Cancel", + "actions.confirm": "Confirm", + "actions.save": "Save", + "actions.delete": "Delete", + "actions.edit": "Edit", + "actions.clone": "Clone", + "actions.stop": "Stop", + "actions.run": "Run", + "actions.enable": "Enable", + "actions.disable": "Disable", + "actions.view_all": "View All", + "actions.retry": "Retry", + "actions.refresh": "Refresh", + "actions.approve": "Approve", + "actions.reject": "Reject", + "actions.update": "Update", + "actions.test_connection": "Test Connection", + "actions.remove": "Remove", + "actions.save_test": "Save & Test", + "actions.export_toml": "Export TOML", + "actions.save_workflow": "Save Workflow", + "actions.auto_layout": "Auto Layout", + "actions.clear": "Clear", + "actions.zoom_out": "Zoom out", + "actions.zoom_in": "Zoom in", + "actions.fit": "Fit", + "actions.duplicate": "Duplicate", + "actions.copy_clipboard": "Copy to Clipboard", + "actions.copied": "Copied!", + "actions.copy": "Copy", + "actions.hide_code": "Hide Code", + "actions.view_code": "View Code", + "actions.install": "Install", + "actions.installing": "Installing...", + "actions.installed": "Installed", + "actions.load_more": "Load More", + "actions.back_to_browse": "Back to browse", + "actions.activate": "Activate", + "actions.create_schedule": "Create Schedule", + "actions.submit": "Submit", + "actions.close": "Close", + "actions.next": "Next", + "actions.back": "Back", + "actions.spawn_agent": "Spawn Agent", + "actions.spawning": "Spawning...", + "actions.create_job": "Create Job", + "actions.spawn_wizard": "Wizard", + "actions.raw_toml": "Raw TOML", + "actions.setup_wizard": "Setup Wizard", + "actions.configure_manually": "Configure Manually", + "actions.dismiss": "Dismiss", + "actions.create_workflow_btn": "Create Workflow", + "actions.execute": "Execute", + "actions.executing": "Executing...", + "actions.generated_toml": "Generated TOML", + "actions.running": "Running...", + + "footer.shortcuts": "Ctrl+K agents | Ctrl+N new", + + "theme.light": "Light", + "theme.system": "System", + "theme.dark": "Dark", + + "errors.connection_error": "Connection Error", + "errors.daemon_unreachable": "Cannot reach daemon — is openfang running?", + "errors.not_authorized": "Not authorized — check your API key", + "errors.permission_denied": "Permission denied", + "errors.resource_not_found": "Resource not found", + "errors.rate_limited": "Rate limited — slow down and try again", + "errors.request_too_large": "Request too large", + "errors.server_error": "Server error — check daemon logs", + "errors.daemon_unavailable": "Daemon unavailable — is it running?", + "errors.unexpected": "Unexpected error", + "errors.reconnected": "Reconnected", + "errors.connection_lost": "Connection lost, reconnecting...", + "errors.switched_http": "Connection lost — switched to HTTP mode", + "errors.connection_lost": "Connection lost, reconnecting...", + "errors.switched_http": "Connection lost — switched to HTTP mode", + "errors.reconnected": "Reconnected", + + "toasts.approval_waiting": "An agent is waiting for approval. Open Approvals to review.", + "toasts.agent_created": "Agent Created", + "toasts.agent_stopped": "Agent Stopped", + "toasts.tool_used": "Tool Used", + "toasts.tool_completed": "Tool Completed", + "toasts.message_in": "Message In", + "toasts.response_sent": "Response Sent", + "toasts.session_reset": "Session Reset", + "toasts.compacted": "Compacted", + "toasts.model_changed": "Model Changed", + "toasts.login_attempt": "Login Attempt", + "toasts.login_ok": "Login OK", + "toasts.login_failed": "Login Failed", + "toasts.denied": "Denied", + "toasts.rate_limited": "Rate Limited", + "toasts.workflow_run": "Workflow Run", + "toasts.trigger_fired": "Trigger Fired", + "toasts.skill_installed": "Skill Installed", + "toasts.mcp_connected": "MCP Connected", + "toasts.session_deleted": "Session deleted", + + "overview.welcome": "Welcome to OpenFang", + "overview.getting_started": "Getting Started", + "overview.setup_wizard": "Setup Wizard", + "overview.steps_completed": "of 5 steps completed", + "overview.agents_running": "Agents Running", + "overview.tokens_used": "Tokens Used", + "overview.total_cost": "Total Cost", + "overview.uptime": "Uptime", + "overview.channels": "Channels", + "overview.skills": "Skills", + "overview.mcp_servers": "MCP Servers", + "overview.tool_calls": "Tool Calls", + "overview.providers": "Providers", + "overview.recent_activity": "Recent Activity", + "overview.no_recent_activity": "No Recent Activity", + "overview.chat_with_agent": "Chat with an Agent", + "overview.system_health": "System Health", + "overview.healthy": "Healthy", + "overview.unreachable": "Unreachable", + "overview.security_systems": "Security Systems", + "overview.llm_providers": "LLM Providers", + "overview.defense_active": "9 defense-in-depth systems active", + "overview.quick_actions": "Quick Actions", + + "setup.configure_provider": "Configure an LLM provider", + "setup.create_first_agent": "Create your first agent", + "setup.send_first_message": "Send your first message", + "setup.connect_channel": "Connect a messaging channel", + "setup.browse_install_skill": "Browse or install a skill", + + "tooltips.cooling_down": "cooling down (rate limited)", + "tooltips.circuit_open": "circuit breaker open", + "tooltips.ready": "ready", + "tooltips.not_configured": "not configured", + + "chat.placeholder": "Message OpenFang... (/ for commands)", + "chat.ready": "Ready", + "chat.generating": "Generating...", + "chat.queued": "queued", + "chat.sessions": "Sessions", + "chat.new_session": "+ New", + "chat.no_sessions": "No sessions", + "chat.search_messages": "Search messages...", + "chat.select_agent": "Select an agent to start chatting", + "chat.recording": "Recording... release to send", + "chat.drop_files": "Drop files here", + "chat.attach_file": "Attach file", + "chat.stop_generating": "Stop generating", + "chat.switch_model": "Switch model", + "chat.search_models": "Search models...", + "chat.no_models_found": "No models found", + "chat.available_models": "Available models — pick one or keep typing", + "chat.switching": "Switching...", + "chat.model_switched": "Switched to", + "chat.model_switch_failed": "Model switch failed", + "chat.using_http_mode": "Using HTTP mode (no streaming)", + "chat.session_name_prompt": "Session name (optional):", + "chat.session_created": "Session created", + "chat.session_create_failed": "Failed to create session", + "chat.stop_agent_title": "Stop Agent", + "chat.stop_agent_confirm": "Stop agent", + "chat.agent_stopped": "Agent stopped", + "chat.stop_agent_failed": "Failed to stop agent", + "chat.welcome_message": "**Welcome to OpenFang Chat!**\n\n- Type `/` to see available commands\n- `/help` shows all commands\n- `/think on` enables extended reasoning\n- `/context` shows context window usage\n- `/verbose off` hides tool details\n- `Ctrl+Shift+F` toggles focus mode\n- Drag & drop files to attach them\n- `Ctrl+/` opens the command palette", + + "chat.slash.help": "Show available commands", + "chat.slash.agents": "Switch to Agents page", + "chat.slash.new": "New session (clear history)", + "chat.slash.compact": "Compact session context", + "chat.slash.model": "Show or switch model (/model [name])", + "chat.slash.stop": "Cancel current agent run", + "chat.slash.usage": "Show token usage", + "chat.slash.think": "Toggle reasoning (/think [on|off|stream])", + "chat.slash.context": "Show context window usage", + "chat.slash.verbose": "Toggle tool details (/verbose [off|on|full])", + "chat.slash.queue": "Check if agent is processing", + "chat.slash.status": "Show system status", + "chat.slash.clear": "Clear chat", + "chat.slash.exit": "Disconnect from agent", + "chat.slash.budget": "Show budget limits and costs", + "chat.slash.peers": "Show OFP network status", + "chat.slash.a2a": "List A2A agents", + + "commands.help": "Show available commands", + "commands.agents": "Switch to Agents page", + "commands.new": "Reset session", + "commands.switch": "Switch agent", + "commands.clear": "Clear conversation", + "commands.model": "Switch model", + "commands.think": "Toggle reasoning mode", + "commands.focus": "Toggle focus mode", + "commands.theme": "Cycle theme", + + "tips.commands": "Type / for commands", + "tips.think": "/think on for reasoning", + "tips.focus": "Ctrl+Shift+F for focus mode", + + "agents.info": "Info", + "agents.files": "Files", + "agents.config": "Config", + "agents.chat": "Chat", + "agents.clone": "Clone", + "agents.clear_history": "Clear History", + "agents.change": "Change", + "agents.none_fallback": "None — add a fallback chain", + "agents.add": "+ Add", + "agents.loading_files": "Loading files...", + "agents.no_workspace_files": "No workspace files found", + "agents.save_config": "Save Config", + "agents.tool_filters": "Tool Filters", + "agents.allowlist": "Allowlist", + "agents.blocklist": "Blocklist", + "agents.agent_name": "Agent Name", + "agents.emoji": "Emoji", + "agents.color": "Color", + "agents.archetype": "Archetype", + "agents.provider": "Provider", + "agents.model": "Model", + "agents.system_prompt": "System Prompt", + "agents.soul_persona": "Soul / Persona", + "agents.tool_profile": "Tool Profile", + "agents.minimal_profile": "Minimal — Read-only file access", + "agents.coding_profile": "Coding — Files + shell + web fetch", + "agents.fullstack_profile": "Full-Stack — Files + shell + web fetch + search", + "agents.research_profile": "Research — Web + search + analysis", + "agents.admin_profile": "Admin — Full system access (dangerous)", + "agents.agent_created": "Agent Created", + "agents.agent_stopped": "Agent Stopped", + "agents.agent_deleted": "Agent Deleted", + + "presets.professional": "Professional", + "presets.professional_desc": "Precise, business-oriented assistant focused on efficiency and clarity. Prioritizes actionable insights and structured communication.", + "presets.professional_soul": "Communicate in a clear, professional tone. Be direct and structured. Use formal language and data-driven reasoning. Prioritize accuracy over personality.", + "presets.friendly": "Friendly", + "presets.friendly_desc": "Warm and approachable assistant that builds rapport and uses conversational language. Great for brainstorming and exploration.", + "presets.friendly_soul": "Be warm, approachable, and conversational. Use casual language and show genuine interest in the user. Add personality to your responses while staying helpful.", + "presets.technical": "Technical", + "presets.technical_desc": "Expert developer companion optimized for code, architecture, and technical problem-solving. Precise terminology, deep dives, benchmarks.", + "presets.technical_soul": "Focus on technical accuracy and depth. Use precise terminology. Show your work and reasoning. Prefer code examples and structured explanations.", + "presets.creative": "Creative", + "presets.creative_desc": "Imaginative collaborator for content creation, design thinking, and unconventional solutions. Embraces ambiguity and explores possibilities.", + "presets.creative_soul": "Be imaginative and expressive. Use vivid language, analogies, and unexpected connections. Encourage creative thinking and explore multiple perspectives.", + "presets.concise": "Concise", + "presets.concise_desc": "Minimal and direct assistant that respects your time. Cuts through noise to deliver focused, actionable responses.", + "presets.concise_soul": "Be extremely brief and to the point. No filler, no pleasantries. Answer in the fewest words possible while remaining accurate and complete.", + "presets.mentor": "Mentor", + "presets.mentor_desc": "Patient educator that explains concepts thoroughly, provides context, and guides learning. Socratic method when appropriate.", + "presets.mentor_soul": "Be patient and encouraging like a great teacher. Break down complex topics step by step. Ask guiding questions. Celebrate progress and build confidence.", + + "agents.profile.minimal": "Minimal", + "agents.profile.minimal_desc": "Read-only file access", + "agents.profile.coding": "Coding", + "agents.profile.coding_desc": "Files + shell + web fetch", + "agents.profile.research": "Research", + "agents.profile.research_desc": "Web search + file read/write", + "agents.profile.messaging": "Messaging", + "agents.profile.messaging_desc": "Agents + memory access", + "agents.profile.automation": "Automation", + "agents.profile.automation_desc": "All tools except custom", + "agents.profile.balanced": "Balanced", + "agents.profile.balanced_desc": "General-purpose tool set", + "agents.profile.precise": "Precise", + "agents.profile.precise_desc": "Focused tool set for accuracy", + "agents.profile.creative": "Creative", + "agents.profile.creative_desc": "Full tools with creative emphasis", + "agents.profile.full": "Full", + "agents.profile.full_desc": "All 35+ tools", + + "wizard.general_assistant": "General Assistant", + "wizard.general_assistant_desc": "You are a versatile AI assistant that helps users with a wide range of tasks. You are knowledgeable, helpful, and able to adapt to the user's needs.", + "wizard.code_helper": "Code Helper", + "wizard.code_helper_desc": "You are an expert programming assistant specialized in software development. You help write, debug, and refactor code across multiple languages.", + "wizard.researcher": "Research Assistant", + "wizard.researcher_desc": "You are a research assistant that helps users find, analyze, and synthesize information from various sources.", + "wizard.writer": "Writer", + "wizard.writer_desc": "You are a skilled writer that helps with content creation, editing, and creative writing projects.", + "wizard.data_analyst": "Data Analyst", + "wizard.data_analyst_desc": "You are a data analyst that helps explore, analyze, and visualize data to extract insights.", + "wizard.devops": "DevOps Engineer", + "wizard.devops_desc": "You are a DevOps engineer that helps with infrastructure, deployment, CI/CD, and system administration.", + "wizard.support": "Customer Support", + "wizard.support_desc": "You are a customer support representative that helps resolve inquiries with patience and professionalism.", + "wizard.tutor": "Tutor", + "wizard.tutor_desc": "You are an educational tutor that explains concepts clearly and adapts teaching to the student's level.", + "wizard.api_designer": "API Designer", + "wizard.api_designer_desc": "You are an API designer that helps create well-structured, intuitive APIs following best practices.", + "wizard.meeting_notes": "Meeting Notes", + "wizard.meeting_notes_desc": "You are a meeting notes specialist that summarizes discussions, extracts action items, and tracks decisions.", + + "wizard.step_welcome": "Welcome", + "wizard.step_provider": "Provider", + "wizard.step_agent": "Agent", + "wizard.step_try_it": "Try It", + "wizard.step_channel": "Channel", + "wizard.step_done": "Done", + + "wizard.cat_general": "General", + "wizard.cat_development": "Development", + "wizard.cat_research": "Research", + "wizard.cat_writing": "Writing", + "wizard.cat_business": "Business", + + "wizard.channel_telegram": "Telegram", + "wizard.channel_telegram_desc": "Connect your agent to a Telegram bot for messaging.", + "wizard.channel_telegram_token": "Bot Token", + "wizard.channel_telegram_help": "Create a bot via @BotFather on Telegram to get your token.", + "wizard.channel_discord": "Discord", + "wizard.channel_discord_desc": "Connect your agent to a Discord server via bot token.", + "wizard.channel_discord_token": "Bot Token", + "wizard.channel_discord_help": "Create a Discord application at discord.com/developers and add a bot.", + "wizard.channel_slack": "Slack", + "wizard.channel_slack_desc": "Connect your agent to a Slack workspace.", + "wizard.channel_slack_token": "Bot Token", + "wizard.channel_slack_help": "Create a Slack app at api.slack.com/apps and install it to your workspace.", + + "wizard.profile_minimal": "Minimal", + "wizard.profile_minimal_desc": "Read-only file access", + "wizard.profile_coding": "Coding", + "wizard.profile_coding_desc": "Files + shell + web fetch", + "wizard.profile_research": "Research", + "wizard.profile_research_desc": "Web search + file read/write", + "wizard.profile_balanced": "Balanced", + "wizard.profile_balanced_desc": "General-purpose tool set", + "wizard.profile_precise": "Precise", + "wizard.profile_precise_desc": "Focused tool set for accuracy", + "wizard.profile_creative": "Creative", + "wizard.profile_creative_desc": "Full tools with creative emphasis", + "wizard.profile_full": "Full", + "wizard.profile_full_desc": "All 35+ tools", + + "wizard.enter_api_key": "Please enter an API key", + "wizard.api_key_saved": "API key saved for", + "wizard.failed_save_key": "Failed to save key:", + "wizard.connected": "connected", + "wizard.connection_failed": "Connection failed", + "wizard.test_failed": "Test failed:", + "wizard.enter_agent_name": "Please enter a name for your agent", + "wizard.agent_created": "Agent created", + "wizard.failed_create_agent": "Failed to create agent:", + "wizard.enter_token": "Please enter the", + "wizard.channel_configured": "configured and activated.", + "wizard.failed_configure": "Failed:", + + "wizard.suggestions.general.1": "What can you help me with?", + "wizard.suggestions.general.2": "Tell me a fun fact", + "wizard.suggestions.general.3": "Summarize the latest AI news", + "wizard.suggestions.development.1": "Write a Python hello world", + "wizard.suggestions.development.2": "Explain async/await", + "wizard.suggestions.development.3": "Review this code snippet", + "wizard.suggestions.research.1": "Explain quantum computing simply", + "wizard.suggestions.research.2": "Compare React vs Vue", + "wizard.suggestions.research.3": "What are the latest trends in AI?", + "wizard.suggestions.writing.1": "Help me write a professional email", + "wizard.suggestions.writing.2": "Improve this paragraph", + "wizard.suggestions.writing.3": "Write a blog intro about AI", + "wizard.suggestions.business.1": "Draft a meeting agenda", + "wizard.suggestions.business.2": "How do I handle a complaint?", + "wizard.suggestions.business.3": "Create a project status update", + + "approvals.title": "Execution Approvals", + "approvals.pending": "pending", + "approvals.all": "All", + "approvals.pending_tab": "Pending", + "approvals.approved": "Approved", + "approvals.rejected": "Rejected", + "approvals.expired": "Expired", + "approvals.no_approvals": "No approvals", + "approvals.approve": "Approve", + "approvals.reject": "Reject", + + "workflows.title": "Workflows", + "workflows.visual_builder": "Visual Builder", + "workflows.what_are": "What are Workflows?", + "workflows.no_workflows": "No workflows yet", + "workflows.sequential": "Sequential", + "workflows.fan_out": "Fan Out", + "workflows.conditional": "Conditional", + "workflows.loop": "Loop", + "workflows.add_step": "+ Add Step", + "workflows.execute": "Execute", + "workflows.result": "Result", + "workflows.node_palette": "Node Palette", + "workflows.drag_nodes": "Drag nodes onto the canvas", + "workflows.steps_connections": "steps, connections", + "workflows.agent": "Agent", + "workflows.prompt_template": "Prompt Template", + "workflows.expression": "Expression", + "workflows.top_port_true": "Top port = true, bottom port = false", + "workflows.max_iterations": "Max Iterations", + "workflows.until_stop": "Until (stop condition)", + "workflows.fan_out_count": "Fan-out Count", + "workflows.wait_all": "Wait for all", + "workflows.first_finish": "First to finish", + "workflows.majority_vote": "Majority vote", + "workflows.connection_selected": "Connection selected", + "workflows.delete_connection": "Delete Connection", + + "scheduler.title": "Scheduler", + "scheduler.scheduled_jobs": "Scheduled Jobs", + "scheduler.event_triggers": "Event Triggers", + "scheduler.run_history": "Run History", + "scheduler.new_job": "+ New Job", + "scheduler.job_name": "Job Name", + "scheduler.cron_expression": "Cron Expression", + "scheduler.quick_presets": "Quick Presets", + "scheduler.target_agent": "Target Agent", + "scheduler.any_agent": "Any available agent", + "scheduler.message_send": "Message to Send", + "scheduler.enabled": "Enabled (will start running immediately)", + "scheduler.disabled": "Disabled (create paused)", + "scheduler.active": "Active", + "scheduler.paused": "Paused", + "scheduler.cron_job": "Cron Job", + "scheduler.trigger": "Trigger", + "scheduler.no_jobs": "No scheduled jobs", + "scheduler.no_triggers": "No event triggers", + "scheduler.no_history": "No run history yet", + + "channels.title": "Channels", + "channels.configured": "configured", + "channels.search": "Search channels...", + "channels.setup": "Set up", + "channels.edit": "Edit", + "channels.configure": "Configure", + "channels.verify": "Verify", + "channels.ready": "Ready", + "channels.is_ready": "is ready!", + "channels.get_credentials": "How to get credentials", + "channels.show_advanced": "Show advanced", + "channels.hide_advanced": "Hide advanced", + "channels.connecting": "Connecting to WhatsApp Web gateway...", + "channels.linked_success": "WhatsApp linked successfully!", + "channels.business_api": "Business API", + + "skills.title": "Skills & Ecosystem", + "skills.installed": "Installed", + "skills.clawhub": "ClawHub", + "skills.mcp_servers": "MCP Servers", + "skills.quick_start": "Quick Start", + "skills.no_installed": "No skills installed", + "skills.browse_clawhub": "Browse ClawHub", + "skills.search_clawhub": "Search ClawHub skills...", + "skills.trending": "Trending", + "skills.most_downloaded": "Most Downloaded", + "skills.most_starred": "Most Starred", + "skills.recently_updated": "Recently Updated", + "skills.categories": "CATEGORIES", + "skills.already_installed": "Already Installed", + "skills.no_skills_found": "No skills found", + "skills.security_warnings": "Security Warnings", + "skills.security_scan": "Skills are security-scanned before installation", + "skills.create": "Create Skill", + "skills.created": "Created", + + "skills.cat_coding": "Coding & IDEs", + "skills.cat_git": "Git & GitHub", + "skills.cat_frontend": "Web & Frontend", + "skills.cat_devops": "DevOps & Cloud", + "skills.cat_database": "Database", + "skills.cat_security": "Security", + "skills.cat_ai": "AI & ML", + "skills.cat_data": "Data & Analytics", + "skills.cat_mobile": "Mobile", + "skills.cat_desktop": "Desktop Apps", + "skills.cat_api": "API & Integrations", + "skills.cat_testing": "Testing", + "skills.cat_docs": "Documentation", + "skills.cat_productivity": "Productivity", + "skills.cat_other": "Other", + + "skills.cat_browser": "Browser & Automation", + "skills.cat_search": "Search & Research", + "skills.cat_communication": "Communication", + "skills.cat_media": "Media & Streaming", + "skills.cat_notes": "Notes & PKM", + "skills.cat_cli": "CLI Utilities", + "skills.cat_marketing": "Marketing & Sales", + "skills.cat_finance": "Finance", + "skills.cat_smarthome": "Smart Home & IoT", + + "skills.uninstall_skill": "Uninstall Skill", + "skills.uninstall_confirm": "Uninstall skill", + + "skills.source_clawhub": "ClawHub", + "skills.source_openclaw": "OpenClaw", + "skills.source_builtin": "Built-in", + "skills.source_local": "Local", + + "hands.title": "Hands — Curated Autonomous Capability Packages", + "hands.available": "Available", + "hands.active": "Active", + "hands.ready": "Ready", + "hands.setup_needed": "Setup needed", + "hands.requirements": "REQUIREMENTS", + "hands.details": "Details", + "hands.no_hands": "No hands available", + + "sessions.title": "Sessions", + "sessions.memory": "Memory", + "sessions.delete_session": "Delete Session", + "sessions.delete_confirm": "This will permanently remove the session and its messages.", + "sessions.delete_key": "Delete Key", + "sessions.delete_key_confirm": "Delete key", + + "logs.title": "Logs", + "logs.live": "Live", + "logs.audit_trail": "Audit Trail", + + "settings.title": "Settings", + "settings.providers": "Providers", + "settings.models": "Models", + "settings.config": "Config", + "settings.tools": "Tools", + "settings.migration": "Migration", + "settings.security": "Security", + "settings.network": "Network", + "settings.migration": "Migration", + "settings.language": "Language", + + "settings.sec_path_traversal": "Path Traversal Prevention", + "settings.sec_path_traversal_desc": "Blocks attempts to access files outside the workspace directory using .. or absolute paths.", + "settings.sec_ssrf": "SSRF Protection", + "settings.sec_ssrf_desc": "Prevents agents from making requests to internal IP ranges (localhost, cloud metadata, private networks).", + "settings.sec_capability": "Capability-Based Access Control", + "settings.sec_capability_desc": "Agents can only access explicitly granted capabilities. No implicit access to tools or data.", + "settings.sec_taint": "Taint Tracking", + "settings.sec_taint_desc": "Tracks untrusted data (user input, file content) through agent reasoning to prevent prompt injection.", + "settings.sec_sandbox": "WASM Sandbox", + "settings.sec_sandbox_desc": "Executes untrusted code in isolated WebAssembly sandboxes with memory and syscall restrictions.", + "settings.sec_audit": "Merkle Audit", + "settings.sec_audit_desc": "Maintains a verifiable audit log of all agent actions using Merkle tree cryptography.", + "settings.sec_workspace": "Workspace Isolation", + "settings.sec_workspace_desc": "Each agent has an isolated workspace directory. No cross-agent file access unless explicitly granted.", + "settings.sec_rate_limit": "Rate Limiting", + "settings.sec_rate_limit_desc": "Enforces per-agent and global rate limits to prevent resource exhaustion and cost overruns.", + "settings.sec_approval": "Execution Approvals", + "settings.sec_approval_desc": "Requires human approval for high-risk actions (shell commands, file writes, external requests).", + + "settings.sec_enabled": "Enabled", + "settings.sec_disabled": "Disabled", + "settings.sec_inherited": "Inherited", + "settings.sec_global": "Global" +} diff --git a/crates/openfang-api/static/i18n/i18n.js b/crates/openfang-api/static/i18n/i18n.js new file mode 100644 index 0000000000..5ed1b2b4e8 --- /dev/null +++ b/crates/openfang-api/static/i18n/i18n.js @@ -0,0 +1,230 @@ +/** + * OpenFang i18n (Internationalization) Module + * + * Provides runtime language switching for the OpenFang dashboard UI. + * Supports English (default) and Russian. + * + * Usage: + * - HTML: Overview + * - JS: window.t('nav.overview') + * - Auto-applies translations on load based on stored/preferred language + */ + +(function() { + 'use strict'; + + // Language store + let currentLang = 'en'; + let translations = {}; + let isInitialized = false; + + /** + * Load translations from a JSON file + * @param {string} lang - Language code (en, ru) + * @returns {Promise} Translation object + */ + async function loadTranslations(lang) { + try { + // Use cached translations if available + if (window.__i18nCache && window.__i18nCache[lang]) { + return window.__i18nCache[lang]; + } + + const response = await fetch(`/i18n/${lang}.json`); + if (!response.ok) { + console.warn(`[i18n] Failed to load ${lang}.json, falling back to en`); + if (lang !== 'en') { + return loadTranslations('en'); + } + return {}; + } + + const data = await response.json(); + + // Cache for future use + if (!window.__i18nCache) window.__i18nCache = {}; + window.__i18nCache[lang] = data; + + return data; + } catch (error) { + console.error(`[i18n] Error loading translations for ${lang}:`, error); + if (lang !== 'en') { + return loadTranslations('en'); + } + return {}; + } + } + + /** + * Get a translated string by key + * @param {string} key - Translation key (e.g., 'nav.overview') + * @param {Object} params - Optional interpolation parameters + * @returns {string} Translated string or key if not found + */ + function t(key, params) { + if (!isInitialized) { + console.warn('[i18n] Not initialized, returning key'); + return key; + } + + let text = translations[key] || key; + + // Handle interpolation (e.g., 'Hello, {{name}}') + if (params && typeof params === 'object') { + Object.keys(params).forEach(param => { + text = text.replace(new RegExp(`{{${param}}}`, 'g'), params[param]); + }); + } + + return text; + } + + /** + * Apply translations to all elements with data-i18n attribute + * Also updates the lang attribute + */ + function applyTranslations() { + // Update document language + document.documentElement.lang = currentLang; + + // Find and translate all elements with data-i18n attribute + const elements = document.querySelectorAll('[data-i18n]'); + elements.forEach(el => { + const key = el.getAttribute('data-i18n'); + const translation = t(key); + + // Check if element is a form input/textarea + if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') { + // For form elements, only update if it's a placeholder or aria-label + if (el.hasAttribute('placeholder')) { + el.placeholder = translation; + } + if (el.hasAttribute('aria-label')) { + el.setAttribute('aria-label', translation); + } + if (el.hasAttribute('title')) { + el.setAttribute('title', translation); + } + } else { + // For regular elements, update text content + el.textContent = translation; + } + }); + + // Update elements with data-i18n-* attributes for attributes + const attrElements = document.querySelectorAll('[data-i18n-placeholder], [data-i18n-title], [data-i18n-aria-label]'); + attrElements.forEach(el => { + if (el.hasAttribute('data-i18n-placeholder')) { + el.placeholder = t(el.getAttribute('data-i18n-placeholder')); + } + if (el.hasAttribute('data-i18n-title')) { + el.title = t(el.getAttribute('data-i18n-title')); + } + if (el.hasAttribute('data-i18n-aria-label')) { + el.setAttribute('aria-label', t(el.getAttribute('data-i18n-aria-label'))); + } + }); + + // Update meta tags + const metaDesc = document.querySelector('meta[name="description"]'); + if (metaDesc) { + const desc = t('app.description', { name: 'OpenFang' }); + if (desc !== 'app.description') { + metaDesc.content = desc; + } + } + + console.log(`[i18n] Applied translations for language: ${currentLang}`); + } + + /** + * Set the current language and apply translations + * @param {string} lang - Language code (en, ru) + * @param {boolean} persist - Whether to save to localStorage + */ + async function setLanguage(lang, persist = true) { + if (!['en', 'ru'].includes(lang)) { + console.warn(`[i18n] Unknown language: ${lang}, defaulting to en`); + lang = 'en'; + } + + currentLang = lang; + translations = await loadTranslations(lang); + isInitialized = true; + + // Save preference + if (persist) { + localStorage.setItem('openfang_language', lang); + } + + // Apply to DOM + applyTranslations(); + + // Dispatch event for Alpine.js components to react + window.dispatchEvent(new CustomEvent('i18n:language-changed', { + detail: { language: lang } + })); + } + + /** + * Get the current language + * @returns {string} Current language code + */ + function getLanguage() { + return currentLang; + } + + /** + * Initialize i18n system + * Loads language preference and applies translations + */ + async function init() { + // Determine language priority: + // 1. localStorage (user preference) + // 2. Browser language + // 3. Default to English + + let lang = localStorage.getItem('openfang_language'); + + if (!lang) { + // Try to detect browser language + const browserLang = navigator.language || navigator.userLanguage || ''; + if (browserLang.startsWith('ru')) { + lang = 'ru'; + } else { + lang = 'en'; + } + } + + await setLanguage(lang, false); + } + + /** + * Get available languages + * @returns {Array<{code: string, name: string}>} + */ + function getAvailableLanguages() { + return [ + { code: 'en', name: 'English' }, + { code: 'ru', name: 'Русский' } + ]; + } + + // Expose to global scope + window.i18n = { + t, + setLanguage, + getLanguage, + getAvailableLanguages, + init, + isInitialized: () => isInitialized + }; + + // Auto-initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + +})(); diff --git a/crates/openfang-api/static/i18n/ru.json b/crates/openfang-api/static/i18n/ru.json new file mode 100644 index 0000000000..2b91ed41e8 --- /dev/null +++ b/crates/openfang-api/static/i18n/ru.json @@ -0,0 +1,607 @@ +{ + "app.name": "OpenFang", + "app.version": "v", + + "nav.chat": "Чат", + "nav.monitor": "Мониторинг", + "nav.overview": "Обзор", + "nav.analytics": "Аналитика", + "nav.logs": "Логи", + "nav.agents": "Агенты", + "nav.sessions": "Сессии", + "nav.approvals": "Одобрения", + "nav.comms": "Коммуникации", + "nav.automation": "Автоматизация", + "nav.workflows": "Рабочие процессы", + "nav.scheduler": "Планировщик", + "nav.extensions": "Расширения", + "nav.channels": "Каналы", + "nav.skills": "Навыки", + "nav.hands": "Руки", + "nav.system": "Система", + "nav.runtime": "Среда выполнения", + "nav.settings": "Настройки", + + "auth.sign_in": "Войти", + "auth.enter_credentials": "Введите учётные данные панели управления.", + "auth.username": "Имя пользователя", + "auth.password": "Пароль", + "auth.api_key_required": "Требуется API-ключ", + "auth.api_key_desc": "Этот экземпляр требует API-ключ. Введите ключ из вашего config.toml.", + "auth.api_key_hint": "Добавьте api_key = \"your-key\" в начало ~/.openfang/config.toml (не внутри секции).", + "auth.enter_api_key": "Введите API-ключ...", + "auth.unlock_dashboard": "Разблокировать панель", + "auth.login_failed": "Ошибка входа", + + "status.agents_running": "агент(ов) запущено", + "status.connecting": "Подключение...", + "status.reconnecting": "Переподключение...", + "status.disconnected": "отключено", + "status.ws": "ВС", + "status.http": "HTTP", + "status.ready": "Готово", + "status.loading": "Загрузка...", + "status.loading_workflows": "Загрузка рабочих процессов...", + "status.loading_channels": "Загрузка каналов...", + "status.loading_skills": "Загрузка навыков...", + "status.loading_jobs": "Загрузка заданий...", + "status.loading_triggers": "Загрузка триггеров...", + "status.loading_history": "Загрузка истории...", + "status.loading_hands": "Загрузка модулей...", + "status.loading_active_hands": "Загрузка активных модулей...", + "status.loading_mcp": "Загрузка MCP-серверов...", + "status.loading_files": "Загрузка файлов...", + "status.loading_skills_details": "Загрузка деталей навыков...", + "status.no_channels_match": "Нет каналов по запросу", + + "actions.logout": "Выйти", + "actions.new_agent": "Новый агент", + "actions.browse_skills": "Навыки", + "actions.add_channel": "Добавить канал", + "actions.create_workflow": "Создать процесс", + "actions.settings": "Настройки", + "actions.create_agent": "Создать агента", + "actions.configure_provider": "Настроить провайдера", + "actions.cancel": "Отмена", + "actions.confirm": "Подтвердить", + "actions.save": "Сохранить", + "actions.delete": "Удалить", + "actions.edit": "Редактировать", + "actions.clone": "Клонировать", + "actions.stop": "Остановить", + "actions.run": "Запустить", + "actions.enable": "Включить", + "actions.disable": "Отключить", + "actions.view_all": "Показать все", + "actions.retry": "Повторить", + "actions.refresh": "Обновить", + "actions.approve": "Одобрить", + "actions.reject": "Отклонить", + "actions.update": "Обновить", + "actions.test_connection": "Проверить подключение", + "actions.remove": "Удалить", + "actions.save_test": "Сохранить и проверить", + "actions.export_toml": "Экспорт TOML", + "actions.save_workflow": "Сохранить процесс", + "actions.auto_layout": "Автораскладка", + "actions.clear": "Очистить", + "actions.zoom_out": "Уменьшить", + "actions.zoom_in": "Увеличить", + "actions.fit": "По размеру", + "actions.duplicate": "Дублировать", + "actions.copy_clipboard": "Копировать в буфер", + "actions.copied": "Скопировано!", + "actions.copy": "Копировать", + "actions.hide_code": "Скрыть код", + "actions.view_code": "Показать код", + "actions.install": "Установить", + "actions.installing": "Установка...", + "actions.installed": "Установлено", + "actions.load_more": "Загрузить ещё", + "actions.back_to_browse": "Назад", + "actions.activate": "Активировать", + "actions.create_schedule": "Создать расписание", + "actions.submit": "Отправить", + "actions.close": "Закрыть", + "actions.next": "Далее", + "actions.back": "Назад", + "actions.spawn_agent": "Создать агента", + "actions.spawning": "Создание...", + "actions.spawn_wizard": "Мастер", + "actions.raw_toml": "TOML", + "actions.setup_wizard": "Мастер настройки", + "actions.configure_manually": "Настроить вручную", + "actions.dismiss": "Закрыть", + "actions.create_workflow_btn": "Создать процесс", + "actions.execute": "Выполнить", + "actions.executing": "Выполнение...", + "actions.generated_toml": "Сгенерированный TOML", + "actions.running": "Выполняется...", + + "footer.shortcuts": "Ctrl+K агенты | Ctrl+N новый", + + "theme.light": "Светлая", + "theme.system": "Системная", + "theme.dark": "Тёмная", + + "errors.connection_error": "Ошибка подключения", + "errors.daemon_unreachable": "Не удаётся связаться с демоном — запущен ли openfang?", + "errors.not_authorized": "Не авторизован — проверьте API-ключ", + "errors.permission_denied": "Доступ запрещён", + "errors.resource_not_found": "Ресурс не найден", + "errors.rate_limited": "Превышен лимит — подождите и попробуйте снова", + "errors.request_too_large": "Запрос слишком большой", + "errors.server_error": "Ошибка сервера — проверьте логи демона", + "errors.daemon_unavailable": "Демон недоступен — запущен ли он?", + "errors.unexpected": "Неожиданная ошибка", + "errors.reconnected": "Переподключено", + "errors.connection_lost": "Соединение потеряно, переподключение...", + "errors.switched_http": "Соединение потеряно — переход на режим HTTP", + "errors.connection_lost": "Соединение потеряно, переподключение...", + "errors.switched_http": "Соединение потеряно — переход на режим HTTP", + "errors.reconnected": "Переподключено", + + "toasts.approval_waiting": "Агент ожидает одобрения. Откройте раздел Одобрения.", + "toasts.agent_created": "Агент создан", + "toasts.agent_stopped": "Агент остановлен", + "toasts.tool_used": "Инструмент использован", + "toasts.tool_completed": "Инструмент завершён", + "toasts.message_in": "Входящее сообщение", + "toasts.response_sent": "Ответ отправлен", + "toasts.session_reset": "Сессия сброшена", + "toasts.compacted": "Сжато", + "toasts.model_changed": "Модель изменена", + "toasts.login_attempt": "Попытка входа", + "toasts.login_ok": "Вход успешен", + "toasts.login_failed": "Ошибка входа", + "toasts.denied": "Отклонено", + "toasts.rate_limited": "Лимит запросов", + "toasts.workflow_run": "Запуск процесса", + "toasts.trigger_fired": "Триггер сработал", + "toasts.skill_installed": "Навык установлен", + "toasts.mcp_connected": "MCP подключён", + "toasts.session_deleted": "Сессия удалена", + + "overview.welcome": "Добро пожаловать в OpenFang", + "overview.getting_started": "Начало работы", + "overview.setup_wizard": "Мастер настройки", + "overview.steps_completed": "из 5 шагов выполнено", + "overview.agents_running": "Агентов запущено", + "overview.tokens_used": "Использовано токенов", + "overview.total_cost": "Общая стоимость", + "overview.uptime": "Время работы", + "overview.channels": "Каналы", + "overview.skills": "Навыки", + "overview.mcp_servers": "MCP-серверы", + "overview.tool_calls": "Вызовов инструментов", + "overview.providers": "Провайдеры", + "overview.recent_activity": "Недавняя активность", + "overview.no_recent_activity": "Нет недавней активности", + "overview.chat_with_agent": "Написать агенту", + "overview.system_health": "Состояние системы", + "overview.healthy": "Исправно", + "overview.unreachable": "Недоступно", + "overview.security_systems": "Системы безопасности", + "overview.llm_providers": "LLM-провайдеры", + "overview.defense_active": "9 уровней защиты активно", + "overview.quick_actions": "Быстрые действия", + + "setup.configure_provider": "Настройте LLM-провайдера", + "setup.create_first_agent": "Создайте первого агента", + "setup.send_first_message": "Отправьте первое сообщение", + "setup.connect_channel": "Подключите канал связи", + "setup.browse_install_skill": "Найдите или установите навык", + + "tooltips.cooling_down": "остывает (лимит запросов)", + "tooltips.circuit_open": "автомат сработал", + "tooltips.ready": "готово", + "tooltips.not_configured": "не настроено", + + "chat.placeholder": "Напишите OpenFang... (/ для команд)", + "chat.ready": "Готово", + "chat.generating": "Генерация...", + "chat.queued": "в очереди", + "chat.sessions": "Сессии", + "chat.new_session": "+ Новая", + "chat.no_sessions": "Нет сессий", + "chat.search_messages": "Поиск сообщений...", + "chat.select_agent": "Выберите агента для начала общения", + "chat.recording": "Запись... отпустите для отправки", + "chat.drop_files": "Перетащите файлы сюда", + "chat.attach_file": "Прикрепить файл", + "chat.stop_generating": "Остановить генерацию", + "chat.switch_model": "Сменить модель", + "chat.search_models": "Поиск моделей...", + "chat.no_models_found": "Модели не найдены", + "chat.available_models": "Доступные модели — выберите или продолжите ввод", + "chat.switching": "Переключение...", + "chat.model_switched": "Модель изменена на", + "chat.model_switch_failed": "Не удалось сменить модель", + "chat.using_http_mode": "Используется HTTP режим (без потоковой передачи)", + "chat.session_name_prompt": "Название сессии (необязательно):", + "chat.session_created": "Сессия создана", + "chat.session_create_failed": "Не удалось создать сессию", + "chat.stop_agent_title": "Остановить агента", + "chat.stop_agent_confirm": "Остановить агента", + "chat.agent_stopped": "Агент остановлен", + "chat.stop_agent_failed": "Не удалось остановить агента", + "chat.welcome_message": "**Добро пожаловать в OpenFang Чат!**\n\n- Введите `/` для просмотра команд\n- `/help` покажет все команды\n- `/think on` включает расширенные размышления\n- `/context` покажет использование контекста\n- `/verbose off` скроет детали инструментов\n- `Ctrl+Shift+F` переключает режим фокуса\n- Перетащите файлы для прикрепления\n- `Ctrl+/` открывает палитру команд", + + "chat.slash.help": "Показать доступные команды", + "chat.slash.agents": "Перейти на страницу агентов", + "chat.slash.new": "Новая сессия (очистить историю)", + "chat.slash.compact": "Сжать контекст сессии", + "chat.slash.model": "Показать или сменить модель (/model [имя])", + "chat.slash.stop": "Отменить текущий запуск агента", + "chat.slash.usage": "Показать использование токенов", + "chat.slash.think": "Переключить размышления (/think [on|off|stream])", + "chat.slash.context": "Показать использование контекста", + "chat.slash.verbose": "Переключить детали инструментов (/verbose [off|on|full])", + "chat.slash.queue": "Проверить очередь обработки", + "chat.slash.status": "Показать статус системы", + "chat.slash.clear": "Очистить чат", + "chat.slash.exit": "Отключиться от агента", + "chat.slash.budget": "Показать лимиты и расходы", + "chat.slash.peers": "Показать статус сети OFP", + "chat.slash.a2a": "Список A2A агентов", + + "commands.help": "Показать доступные команды", + "commands.agents": "Перейти на страницу агентов", + "commands.new": "Новая сессия", + "commands.switch": "Сменить агента", + "commands.clear": "Очистить диалог", + "commands.model": "Сменить модель", + "commands.think": "Переключить режим размышлений", + "commands.focus": "Переключить режим фокуса", + "commands.theme": "Сменить тему", + + "tips.commands": "Введите / для команд", + "tips.think": "/think on для размышлений", + "tips.focus": "Ctrl+Shift+F для режима фокуса", + + "agents.info": "Информация", + "agents.files": "Файлы", + "agents.config": "Настройки", + "agents.chat": "Чат", + "agents.clone": "Клонировать", + "agents.clear_history": "Очистить историю", + "agents.change": "Изменить", + "agents.none_fallback": "Нет — добавить цепочку резервов", + "agents.add": "+ Добавить", + "agents.loading_files": "Загрузка файлов...", + "agents.no_workspace_files": "Файлы рабочей области не найдены", + "agents.save_config": "Сохранить настройки", + "agents.tool_filters": "Фильтры инструментов", + "agents.allowlist": "Белый список", + "agents.blocklist": "Чёрный список", + "agents.agent_name": "Имя агента", + "agents.emoji": "Эмодзи", + "agents.color": "Цвет", + "agents.archetype": "Архетип", + "agents.provider": "Провайдер", + "agents.model": "Модель", + "agents.system_prompt": "Системный промпт", + "agents.soul_persona": "Душa / Персона", + "agents.tool_profile": "Профиль инструментов", + "agents.minimal_profile": "Минимальный — только чтение файлов", + "agents.coding_profile": "Кодинг — файлы + оболочка + веб-запросы", + "agents.fullstack_profile": "Full-Stack — файлы + оболочка + веб + поиск", + "agents.research_profile": "Исследование — веб + поиск + анализ", + "agents.admin_profile": "Админ — полный доступ к системе (опасно)", + "agents.agent_created": "Агент создан", + "agents.agent_stopped": "Агент остановлен", + "agents.agent_deleted": "Агент удалён", + + "presets.professional": "Деловой", + "presets.professional_desc": "Точный, бизнес-ориентированный ассистент, сосредоточенный на эффективности и ясности. Приоритет — практические выводы и структурированная коммуникация.", + "presets.professional_soul": "Общайтесь чётко и профессионально. Будьте прямым и структурированным. Используйте формальный язык и выводы на основе данных. ставьте точность выше личности.", + "presets.friendly": "Дружелюбный", + "presets.friendly_desc": "Тёплый и открытый ассистент, который выстраивает rapport и использует разговорный язык. Отлично подходит для мозгового штурма и исследования.", + "presets.friendly_soul": "Будьте тёплым, доступным и разговорчивым. Используйте неформальный язык и проявляйте искренний интерес к пользователю. Добавляйте личность к вашим ответам, оставаясь полезным.", + "presets.technical": "Технический", + "presets.technical_desc": "Эксперт-помощник по разработке, оптимизированный для кода, архитектуры и технических задач. Точная терминология, глубокие погружения, бенчмарки.", + "presets.technical_soul": "Сосредоточьтесь на технической точности и глубине. Используйте точную терминологию. Покажите вашу работу и рассуждения. Предпочитайте примеры кода и структурированные объяснения.", + "presets.creative": "Креативный", + "presets.creative_desc": "Творческий партнёр для создания контента, дизайн-мышления и нестандартных решений. Приветствует неоднозначность и исследует возможности.", + "presets.creative_soul": "Будьте изобретательным и выразительным. Используйте яркий язык, аналогии и неожиданные связи. Поощряйте творческое мышление и исследуйте различные перспективы.", + "presets.concise": "Краткий", + "presets.concise_desc": "Минималистичный и прямой ассистент, который ценит ваше время. Убирает лишнее и даёт сфокусированные, практичные ответы.", + "presets.concise_soul": "Будьте предельно кратким и точным. Без воды и формальностей. Отвечайте наименьшим количеством слов, оставаясь точным и полным.", + "presets.mentor": "Наставник", + "presets.mentor_desc": "Терпеливый педагог, который подробно объясняет концепции, даёт контекст и направляет обучение. Метод Сократа при необходимости.", + "presets.mentor_soul": "Будьте терпеливым и ободряющим как хороший учитель. Разбивайте сложные темы по шагам. Задавайте направляющие вопросы. Празднуйте прогресс и укрепляйте уверенность.", + + "agents.profile.minimal": "Минимальный", + "agents.profile.minimal_desc": "Только чтение файлов", + "agents.profile.coding": "Кодинг", + "agents.profile.coding_desc": "Файлы + оболочка + веб-запросы", + "agents.profile.research": "Исследование", + "agents.profile.research_desc": "Веб-поиск + чтение/запись файлов", + "agents.profile.messaging": "Коммуникации", + "agents.profile.messaging_desc": "Агенты + доступ к памяти", + "agents.profile.automation": "Автоматизация", + "agents.profile.automation_desc": "Все инструменты кроме пользовательских", + "agents.profile.balanced": "Сбалансированный", + "agents.profile.balanced_desc": "Набор инструментов общего назначения", + "agents.profile.precise": "Точный", + "agents.profile.precise_desc": "Фокусированный набор инструментов для точности", + "agents.profile.creative": "Креативный", + "agents.profile.creative_desc": "Полный набор инструментов с творческим уклоном", + "agents.profile.full": "Полный", + "agents.profile.full_desc": "Все 35+ инструментов", + + "wizard.general_assistant": "Универсальный ассистент", + "wizard.general_assistant_desc": "Вы универсальный AI-ассистент, который помогает пользователям с широким кругом задач. Вы знающий, полезный и способны адаптироваться к потребностям пользователя.", + "wizard.code_helper": "Помощник по коду", + "wizard.code_helper_desc": "Вы опытный программный ассистент, специализирующийся на разработке ПО. Вы помогаете писать, отлаживать и рефакторить код на разных языках.", + "wizard.researcher": "Исследовательский ассистент", + "wizard.researcher_desc": "Вы исследовательский ассистент, который помогает находить, анализировать и синтезировать информацию из различных источников.", + "wizard.writer": "Писатель", + "wizard.writer_desc": "Вы квалифицированный писатель, который помогает с созданием контента, редактированием и творческими проектами.", + "wizard.data_analyst": "Аналитик данных", + "wizard.data_analyst_desc": "Вы аналитик данных, который помогает исследовать, анализировать и визуализировать данные для извлечения инсайтов.", + "wizard.devops": "DevOps-инженер", + "wizard.devops_desc": "Вы DevOps-инженер, который помогает с инфраструктурой, деплоем, CI/CD и системным администрированием.", + "wizard.support": "Поддержка клиентов", + "wizard.support_desc": "Вы представитель поддержки клиентов, который помогает решать вопросы с терпением и профессионализмом.", + "wizard.tutor": "Репетитор", + "wizard.tutor_desc": "Вы образовательный репетитор, который ясно объясняет концепции и адаптирует обучение к уровню ученика.", + "wizard.api_designer": "API-дизайнер", + "wizard.api_designer_desc": "Вы дизайнер API, который помогает создавать хорошо структурированные, интуитивные API по лучшим практикам.", + "wizard.meeting_notes": "Заметки к встрече", + "wizard.meeting_notes_desc": "Вы специалист по заметкам встреч, который суммирует обсуждения, извлекает задачи и отслеживает решения.", + + "wizard.step_welcome": "Приветствие", + "wizard.step_provider": "Провайдер", + "wizard.step_agent": "Агент", + "wizard.step_try_it": "Попробовать", + "wizard.step_channel": "Канал", + "wizard.step_done": "Готово", + + "wizard.cat_general": "Общее", + "wizard.cat_development": "Разработка", + "wizard.cat_research": "Исследования", + "wizard.cat_writing": "Написание", + "wizard.cat_business": "Бизнес", + + "wizard.channel_telegram": "Telegram", + "wizard.channel_telegram_desc": "Подключите агента к Telegram-боту для обмена сообщениями.", + "wizard.channel_telegram_token": "Токен бота", + "wizard.channel_telegram_help": "Создайте бота через @BotFather в Telegram, чтобы получить токен.", + "wizard.channel_discord": "Discord", + "wizard.channel_discord_desc": "Подключите агента к Discord-серверу через токен бота.", + "wizard.channel_discord_token": "Токен бота", + "wizard.channel_discord_help": "Создайте приложение Discord на discord.com/developers и добавьте бота.", + "wizard.channel_slack": "Slack", + "wizard.channel_slack_desc": "Подключите агента к рабочему пространству Slack.", + "wizard.channel_slack_token": "Токен бота", + "wizard.channel_slack_help": "Создайте приложение Slack на api.slack.com/apps и установите его в рабочее пространство.", + + "wizard.profile_minimal": "Минимальный", + "wizard.profile_minimal_desc": "Только чтение файлов", + "wizard.profile_coding": "Кодинг", + "wizard.profile_coding_desc": "Файлы + оболочка + веб-запросы", + "wizard.profile_research": "Исследование", + "wizard.profile_research_desc": "Веб-поиск + чтение/запись файлов", + "wizard.profile_balanced": "Сбалансированный", + "wizard.profile_balanced_desc": "Набор инструментов общего назначения", + "wizard.profile_precise": "Точный", + "wizard.profile_precise_desc": "Фокусированный набор инструментов для точности", + "wizard.profile_creative": "Креативный", + "wizard.profile_creative_desc": "Полный набор инструментов с творческим уклоном", + "wizard.profile_full": "Полный", + "wizard.profile_full_desc": "Все 35+ инструментов", + + "wizard.enter_api_key": "Пожалуйста, введите API-ключ", + "wizard.api_key_saved": "API-ключ сохранён для", + "wizard.failed_save_key": "Не удалось сохранить ключ:", + "wizard.connected": "подключён", + "wizard.connection_failed": "Ошибка подключения", + "wizard.test_failed": "Тест не прошёл:", + "wizard.enter_agent_name": "Пожалуйста, введите имя агента", + "wizard.agent_created": "Агент создан", + "wizard.failed_create_agent": "Не удалось создать агента:", + "wizard.enter_token": "Пожалуйста, введите", + "wizard.channel_configured": "настроен и активирован.", + "wizard.failed_configure": "Ошибка:", + + "wizard.suggestions.general.1": "Чем вы можете помочь?", + "wizard.suggestions.general.2": "Расскажите интересный факт", + "wizard.suggestions.general.3": "Резюмируйте последние новости AI", + "wizard.suggestions.development.1": "Напишите Python hello world", + "wizard.suggestions.development.2": "Объясните async/await", + "wizard.suggestions.development.3": "Проверьте этот фрагмент кода", + "wizard.suggestions.research.1": "Объясните квантовые вычисления просто", + "wizard.suggestions.research.2": "Сравните React и Vue", + "wizard.suggestions.research.3": "Какие последние тренды в AI?", + "wizard.suggestions.writing.1": "Помогите написать профессиональное письмо", + "wizard.suggestions.writing.2": "Улучшите этот абзац", + "wizard.suggestions.writing.3": "Напишите введение в блог об AI", + "wizard.suggestions.business.1": "Составьте повестку встречи", + "wizard.suggestions.business.2": "Как обработать жалобу?", + "wizard.suggestions.business.3": "Создайте статус-отчёт проекта", + + "approvals.title": "Одобрения выполнения", + "approvals.pending": "ожидает", + "approvals.all": "Все", + "approvals.pending_tab": "Ожидающие", + "approvals.approved": "Одобрено", + "approvals.rejected": "Отклонено", + "approvals.expired": "Истекло", + "approvals.no_approvals": "Нет одобрений", + "approvals.approve": "Одобрить", + "approvals.reject": "Отклонить", + + "workflows.title": "Рабочие процессы", + "workflows.visual_builder": "Визуальный конструктор", + "workflows.what_are": "Что такое рабочие процессы?", + "workflows.no_workflows": "Нет рабочих процессов", + "workflows.sequential": "Последовательный", + "workflows.fan_out": "Распределение", + "workflows.conditional": "Условный", + "workflows.loop": "Цикл", + "workflows.add_step": "+ Добавить шаг", + "workflows.execute": "Выполнить", + "workflows.result": "Результат", + "workflows.node_palette": "Палитра узлов", + "workflows.drag_nodes": "Перетащите узлы на холст", + "workflows.steps_connections": "шагов, связей", + "workflows.agent": "Агент", + "workflows.prompt_template": "Шаблон промпта", + "workflows.expression": "Выражение", + "workflows.top_port_true": "Верхний порт = истина, нижний = ложь", + "workflows.max_iterations": "Макс. итераций", + "workflows.until_stop": "До (условие остановки)", + "workflows.fan_out_count": "Количество ветвей", + "workflows.wait_all": "Ждать все", + "workflows.first_finish": "Первый завершился", + "workflows.majority_vote": "Большинство", + "workflows.connection_selected": "Связь выбрана", + "workflows.delete_connection": "Удалить связь", + + "scheduler.title": "Планировщик", + "scheduler.scheduled_jobs": "Запланированные задания", + "scheduler.event_triggers": "Триггеры событий", + "scheduler.run_history": "История запусков", + "scheduler.new_job": "+ Новое задание", + "scheduler.job_name": "Название задания", + "scheduler.cron_expression": "Cron-выражение", + "scheduler.quick_presets": "Быстрые шаблоны", + "scheduler.target_agent": "Целевой агент", + "scheduler.any_agent": "Любой доступный агент", + "scheduler.message_send": "Сообщение для отправки", + "scheduler.enabled": "Включено (запустится сразу)", + "scheduler.disabled": "Отключено (создать приостановленным)", + "scheduler.active": "Активно", + "scheduler.paused": "Приостановлено", + "scheduler.cron_job": "Cron-задание", + "scheduler.trigger": "Триггер", + "scheduler.no_jobs": "Нет запланированных заданий", + "scheduler.no_triggers": "Нет триггеров событий", + "scheduler.no_history": "Нет истории запусков", + + "channels.title": "Каналы", + "channels.configured": "настроено", + "channels.search": "Поиск каналов...", + "channels.setup": "Настроить", + "channels.edit": "Изменить", + "channels.configure": "Настройка", + "channels.verify": "Проверить", + "channels.ready": "Готово", + "channels.is_ready": "готово!", + "channels.get_credentials": "Как получить учётные данные", + "channels.show_advanced": "Показать расширенные", + "channels.hide_advanced": "Скрыть расширенные", + "channels.connecting": "Подключение к шлюзу WhatsApp Web...", + "channels.linked_success": "WhatsApp успешно связан!", + "channels.business_api": "Business API", + + "skills.title": "Навыки и экосистема", + "skills.installed": "Установленные", + "skills.clawhub": "ClawHub", + "skills.mcp_servers": "MCP-серверы", + "skills.quick_start": "Быстрый старт", + "skills.no_installed": "Нет установленных навыков", + "skills.browse_clawhub": "Обзор ClawHub", + "skills.search_clawhub": "Поиск навыков ClawHub...", + "skills.trending": "Популярные", + "skills.most_downloaded": "Самые скачиваемые", + "skills.most_starred": "Самые оценённые", + "skills.recently_updated": "Недавно обновлённые", + "skills.categories": "КАТЕГОРИИ", + "skills.already_installed": "Уже установлено", + "skills.no_skills_found": "Навыки не найдены", + "skills.security_warnings": "Предупреждения безопасности", + "skills.security_scan": "Навыки проверяются на безопасность перед установкой", + "skills.create": "Создать навык", + "skills.created": "Создан", + + "skills.cat_coding": "Кодинг и IDE", + "skills.cat_git": "Git и GitHub", + "skills.cat_frontend": "Веб и фронтенд", + "skills.cat_devops": "DevOps и облака", + "skills.cat_database": "Базы данных", + "skills.cat_security": "Безопасность", + "skills.cat_ai": "AI и ML", + "skills.cat_data": "Данные и аналитика", + "skills.cat_mobile": "Мобильная разработка", + "skills.cat_desktop": "Десктопные приложения", + "skills.cat_api": "API и интеграции", + "skills.cat_testing": "Тестирование", + "skills.cat_docs": "Документация", + "skills.cat_productivity": "Продуктивность", + "skills.cat_other": "Другое", + + "skills.cat_browser": "Браузер и автоматизация", + "skills.cat_search": "Поиск и исследования", + "skills.cat_communication": "Коммуникации", + "skills.cat_media": "Медиа и стриминг", + "skills.cat_notes": "Заметки и PKM", + "skills.cat_cli": "CLI утилиты", + "skills.cat_marketing": "Маркетинг и продажи", + "skills.cat_finance": "Финансы", + "skills.cat_smarthome": "Умный дом и IoT", + + "skills.uninstall_skill": "Удалить навык", + "skills.uninstall_confirm": "Удалить навык", + + "skills.source_clawhub": "ClawHub", + "skills.source_openclaw": "OpenClaw", + "skills.source_builtin": "Встроенный", + "skills.source_local": "Локальный", + + "hands.title": "Руки — Наборы автономных возможностей", + "hands.available": "Доступные", + "hands.active": "Активные", + "hands.ready": "Готово", + "hands.setup_needed": "Требуется настройка", + "hands.requirements": "ТРЕБОВАНИЯ", + "hands.details": "Подробности", + "hands.no_hands": "Нет доступных модулей", + + "sessions.title": "Сессии", + "sessions.memory": "Память", + "sessions.delete_session": "Удалить сессию", + "sessions.delete_confirm": "Это навсегда удалит сессию и все её сообщения.", + "sessions.delete_key": "Удалить ключ", + "sessions.delete_key_confirm": "Удалить ключ", + + "logs.title": "Логи", + "logs.live": "Онлайн", + "logs.audit_trail": "Аудит", + + "settings.title": "Настройки", + "settings.providers": "Провайдеры", + "settings.models": "Модели", + "settings.config": "Конфигурация", + "settings.tools": "Инструменты", + "settings.migration": "Миграция", + "settings.security": "Безопасность", + "settings.network": "Сеть", + "settings.migration": "Миграция", + "settings.language": "Язык", + + "settings.sec_path_traversal": "Защита от обхода пути", + "settings.sec_path_traversal_desc": "Блокирует попытки доступа к файлам за пределами рабочей директории через .. или абсолютные пути.", + "settings.sec_ssrf": "Защита от SSRF", + "settings.sec_ssrf_desc": "Предотвращает запросы агентов к внутренним IP-диапазонам (localhost, облачный метаданные, частные сети).", + "settings.sec_capability": "Управление доступом по возможностям", + "settings.sec_capability_desc": "Агенты могут получать доступ только к явно предоставленным возможностям. Нет неявного доступа к инструментам или данным.", + "settings.sec_taint": "Отслеживание заражения", + "settings.sec_taint_desc": "Отслеживает ненадёжные данные (ввод пользователя, содержимое файлов) через рассуждения агента для предотвращения инъекции промпта.", + "settings.sec_sandbox": "WASM-песочница", + "settings.sec_sandbox_desc": "Выполняет ненадёжный код в изолированных WebAssembly-песочницах с ограничениями памяти и системных вызовов.", + "settings.sec_audit": "Меркл-проверка", + "settings.sec_audit_desc": "Ведёт верифицируемый журнал аудита всех действий агентов с использованием криптографии деревьев Меркла.", + "settings.sec_workspace": "Изоляция рабочих областей", + "settings.sec_workspace_desc": "Каждый агент имеет изолированную рабочую директорию. Нет межагентного доступа к файлам без явного разрешения.", + "settings.sec_rate_limit": "Ограничение частоты", + "settings.sec_rate_limit_desc": "Устанавливает лимиты на запросы для каждого агента и глобально для предотвращения истощения ресурсов и перерасхода.", + "settings.sec_approval": "Одобрения выполнения", + "settings.sec_approval_desc": "Требует одобрения человека для рискованных действий (команды оболочки, запись файлов, внешние запросы).", + + "settings.sec_enabled": "Включено", + "settings.sec_disabled": "Отключено", + "settings.sec_inherited": "Унаследовано", + "settings.sec_global": "Глобально" +} diff --git a/crates/openfang-api/static/js/pages/agents.js b/crates/openfang-api/static/js/pages/agents.js index b46b24bfdb..12475bbf19 100644 --- a/crates/openfang-api/static/js/pages/agents.js +++ b/crates/openfang-api/static/js/pages/agents.js @@ -53,14 +53,23 @@ function agentsPage() { '\u{2764}\uFE0F', '\u{1F31F}', '\u{1F527}', '\u{1F4DD}', '\u{1F4A1}', '\u{1F3A8}' ], archetypeOptions: ['Assistant', 'Researcher', 'Coder', 'Writer', 'DevOps', 'Support', 'Analyst', 'Custom'], - personalityPresets: [ - { id: 'professional', label: 'Professional', soul: 'Communicate in a clear, professional tone. Be direct and structured. Use formal language and data-driven reasoning. Prioritize accuracy over personality.' }, - { id: 'friendly', label: 'Friendly', soul: 'Be warm, approachable, and conversational. Use casual language and show genuine interest in the user. Add personality to your responses while staying helpful.' }, - { id: 'technical', label: 'Technical', soul: 'Focus on technical accuracy and depth. Use precise terminology. Show your work and reasoning. Prefer code examples and structured explanations.' }, - { id: 'creative', label: 'Creative', soul: 'Be imaginative and expressive. Use vivid language, analogies, and unexpected connections. Encourage creative thinking and explore multiple perspectives.' }, - { id: 'concise', label: 'Concise', soul: 'Be extremely brief and to the point. No filler, no pleasantries. Answer in the fewest words possible while remaining accurate and complete.' }, - { id: 'mentor', label: 'Mentor', soul: 'Be patient and encouraging like a great teacher. Break down complex topics step by step. Ask guiding questions. Celebrate progress and build confidence.' } - ], + _personalityPresetsLoaded: false, + personalityPresets: [], // Loaded dynamically with i18n + + // Load personality presets with i18n + loadPersonalityPresets: function() { + if (this._personalityPresetsLoaded) return; + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; + this.personalityPresets = [ + { id: 'professional', label: t('presets.professional'), soul: t('presets.professional_soul') }, + { id: 'friendly', label: t('presets.friendly'), soul: t('presets.friendly_soul') }, + { id: 'technical', label: t('presets.technical'), soul: t('presets.technical_soul') }, + { id: 'creative', label: t('presets.creative'), soul: t('presets.creative_soul') }, + { id: 'concise', label: t('presets.concise'), soul: t('presets.concise_soul') }, + { id: 'mentor', label: t('presets.mentor'), soul: t('presets.mentor_soul') } + ]; + this._personalityPresetsLoaded = true; + }, // -- Detail modal tabs -- detailTab: 'info', @@ -99,21 +108,31 @@ function agentsPage() { // Load templates from API async init() { await this.loadTemplates(); - }, - - // ── Profile Descriptions ── - profileDescriptions: { - minimal: { label: 'Minimal', desc: 'Read-only file access' }, - coding: { label: 'Coding', desc: 'Files + shell + web fetch' }, - research: { label: 'Research', desc: 'Web search + file read/write' }, - messaging: { label: 'Messaging', desc: 'Agents + memory access' }, - automation: { label: 'Automation', desc: 'All tools except custom' }, - balanced: { label: 'Balanced', desc: 'General-purpose tool set' }, - precise: { label: 'Precise', desc: 'Focused tool set for accuracy' }, - creative: { label: 'Creative', desc: 'Full tools with creative emphasis' }, - full: { label: 'Full', desc: 'All 35+ tools' } + // Load personality presets with i18n + this.loadPersonalityPresets(); + }, + + // ── Profile Descriptions (loaded dynamically with i18n) ── + _profileDescriptionsLoaded: false, + profileDescriptions: {}, + loadProfileDescriptions: function() { + if (this._profileDescriptionsLoaded) return; + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; + this.profileDescriptions = { + minimal: { label: t('agents.profile.minimal'), desc: t('agents.profile.minimal_desc') }, + coding: { label: t('agents.profile.coding'), desc: t('agents.profile.coding_desc') }, + research: { label: t('agents.profile.research'), desc: t('agents.profile.research_desc') }, + messaging: { label: t('agents.profile.messaging'), desc: t('agents.profile.messaging_desc') }, + automation: { label: t('agents.profile.automation'), desc: t('agents.profile.automation_desc') }, + balanced: { label: t('agents.profile.balanced'), desc: t('agents.profile.balanced_desc') }, + precise: { label: t('agents.profile.precise'), desc: t('agents.profile.precise_desc') }, + creative: { label: t('agents.profile.creative'), desc: t('agents.profile.creative_desc') }, + full: { label: t('agents.profile.full'), desc: t('agents.profile.full_desc') } + }; + this._profileDescriptionsLoaded = true; }, profileInfo: function(name) { + this.loadProfileDescriptions(); return this.profileDescriptions[name] || { label: name, desc: '' }; }, @@ -197,6 +216,8 @@ function agentsPage() { try { await Alpine.store('app').refreshAgents(); await this.loadTemplates(); + this.loadPersonalityPresets(); + this.loadProfileDescriptions(); } catch(e) { this.loadError = e.message || 'Could not load agents. Is the daemon running?'; } diff --git a/crates/openfang-api/static/js/pages/chat.js b/crates/openfang-api/static/js/pages/chat.js index 821141792b..5ce96e6853 100644 --- a/crates/openfang-api/static/js/pages/chat.js +++ b/crates/openfang-api/static/js/pages/chat.js @@ -42,34 +42,31 @@ function chatPage() { modelSwitching: false, _modelCache: null, _modelCacheTime: 0, - slashCommands: [ - { cmd: '/help', desc: 'Show available commands' }, - { cmd: '/agents', desc: 'Switch to Agents page' }, - { cmd: '/new', desc: 'Reset session (clear history)' }, - { cmd: '/compact', desc: 'Trigger LLM session compaction' }, - { cmd: '/model', desc: 'Show or switch model (/model [name])' }, - { cmd: '/stop', desc: 'Cancel current agent run' }, - { cmd: '/usage', desc: 'Show session token usage & cost' }, - { cmd: '/think', desc: 'Toggle extended thinking (/think [on|off|stream])' }, - { cmd: '/context', desc: 'Show context window usage & pressure' }, - { cmd: '/verbose', desc: 'Cycle tool detail level (/verbose [off|on|full])' }, - { cmd: '/queue', desc: 'Check if agent is processing' }, - { cmd: '/status', desc: 'Show system status' }, - { cmd: '/clear', desc: 'Clear chat display' }, - { cmd: '/exit', desc: 'Disconnect from agent' }, - { cmd: '/budget', desc: 'Show spending limits and current costs' }, - { cmd: '/peers', desc: 'Show OFP peer network status' }, - { cmd: '/a2a', desc: 'List discovered external A2A agents' } - ], + slashCommands: [], // Loaded dynamically with i18n in init() + _slashCommandsLoaded: false, tokenCount: 0, // ── Tip Bar ── tipIndex: 0, - tips: ['Type / for commands', '/think on for reasoning', 'Ctrl+Shift+F for focus mode', 'Drag files to attach', '/model to switch models', '/context to check usage', '/verbose off to hide tool details'], + tips: [], + _tipsInitialized: false, tipTimer: null, get currentTip() { if (localStorage.getItem('of-tips-off') === 'true') return ''; - return this.tips[this.tipIndex % this.tips.length]; + if (!this._tipsInitialized) { + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; + this.tips = [ + t('tips.commands'), + t('tips.think'), + t('tips.focus'), + 'Drag files to attach', + '/model to switch models', + '/context to check usage', + '/verbose off to hide tool details' + ]; + this._tipsInitialized = true; + } + return this.tips[this.tipIndex % this.tips.length] || ''; }, dismissTips: function() { localStorage.setItem('of-tips-off', 'true'); }, startTipCycle: function() { @@ -137,6 +134,9 @@ function chatPage() { init() { var self = this; + // Initialize slash commands with i18n + this.initSlashCommands(); + // Start tip cycle this.startTipCycle(); @@ -264,19 +264,46 @@ function chatPage() { if (model.id === this.currentAgent.model_name) { this.showModelSwitcher = false; return; } var self = this; this.modelSwitching = true; + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; OpenFangAPI.put('/api/agents/' + this.currentAgent.id + '/model', { model: model.id }).then(function(resp) { // Use server-resolved model/provider to stay in sync (fixes #387/#466) self.currentAgent.model_name = (resp && resp.model) || model.id; self.currentAgent.model_provider = (resp && resp.provider) || model.provider; - OpenFangToast.success('Switched to ' + (model.display_name || model.id)); + OpenFangToast.success(t('chat.model_switched') + ' ' + (model.display_name || model.id)); self.showModelSwitcher = false; self.modelSwitching = false; }).catch(function(e) { - OpenFangToast.error('Switch failed: ' + e.message); + OpenFangToast.error(t('chat.model_switch_failed') + ': ' + e.message); self.modelSwitching = false; }); }, + // Initialize slash commands with i18n translations + initSlashCommands: function() { + if (this._slashCommandsLoaded) return; + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; + this.slashCommands = [ + { cmd: '/help', desc: t('chat.slash.help') }, + { cmd: '/agents', desc: t('chat.slash.agents') }, + { cmd: '/new', desc: t('chat.slash.new') }, + { cmd: '/compact', desc: t('chat.slash.compact') }, + { cmd: '/model', desc: t('chat.slash.model') }, + { cmd: '/stop', desc: t('chat.slash.stop') }, + { cmd: '/usage', desc: t('chat.slash.usage') }, + { cmd: '/think', desc: t('chat.slash.think') }, + { cmd: '/context', desc: t('chat.slash.context') }, + { cmd: '/verbose', desc: t('chat.slash.verbose') }, + { cmd: '/queue', desc: t('chat.slash.queue') }, + { cmd: '/status', desc: t('chat.slash.status') }, + { cmd: '/clear', desc: t('chat.slash.clear') }, + { cmd: '/exit', desc: t('chat.slash.exit') }, + { cmd: '/budget', desc: t('chat.slash.budget') }, + { cmd: '/peers', desc: t('chat.slash.peers') }, + { cmd: '/a2a', desc: t('chat.slash.a2a') } + ]; + this._slashCommandsLoaded = true; + }, + // Fetch dynamic slash commands from server fetchCommands: function() { var self = this; @@ -486,21 +513,14 @@ function chatPage() { this.currentAgent = agent; this.messages = []; this.connectWs(agent.id); + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; // Show welcome tips on first use if (!localStorage.getItem('of-chat-tips-seen')) { var localMsgId = 0; this.messages.push({ id: ++localMsgId, role: 'system', - text: '**Welcome to OpenFang Chat!**\n\n' + - '- Type `/` to see available commands\n' + - '- `/help` shows all commands\n' + - '- `/think on` enables extended reasoning\n' + - '- `/context` shows context window usage\n' + - '- `/verbose off` hides tool details\n' + - '- `Ctrl+Shift+F` toggles focus mode\n' + - '- Drag & drop files to attach them\n' + - '- `Ctrl+/` opens the command palette', + text: t('chat.welcome_message'), meta: '', tools: [] }); @@ -557,7 +577,8 @@ function chatPage() { // Multi-session: create a new session async createSession() { if (!this.currentAgent) return; - var label = prompt('Session name (optional):'); + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; + var label = prompt(t('chat.session_name_prompt')); if (label === null) return; // cancelled try { await OpenFangAPI.post('/api/agents/' + this.currentAgent.id + '/sessions', { @@ -567,9 +588,9 @@ function chatPage() { await this.loadSession(this.currentAgent.id); this.messages = []; this.scrollToBottom(); - if (typeof OpenFangToast !== 'undefined') OpenFangToast.success('New session created'); + if (typeof OpenFangToast !== 'undefined') OpenFangToast.success(t('chat.session_created')); } catch(e) { - if (typeof OpenFangToast !== 'undefined') OpenFangToast.error('Failed to create session'); + if (typeof OpenFangToast !== 'undefined') OpenFangToast.error(t('chat.session_create_failed')); } }, @@ -1006,8 +1027,9 @@ function chatPage() { } // HTTP fallback + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; if (!OpenFangAPI.isWsConnected()) { - OpenFangToast.info('Using HTTP mode (no streaming)'); + OpenFangToast.info(t('chat.using_http_mode')); } this.messages.push({ id: ++msgId, role: 'agent', text: '', meta: '', thinking: true, tools: [], ts: Date.now() }); this.scrollToBottom(); @@ -1050,18 +1072,19 @@ function chatPage() { killAgent() { if (!this.currentAgent) return; var self = this; + var t = typeof window.t === 'function' ? window.t : function(s) { return s; }; var name = this.currentAgent.name; - OpenFangToast.confirm('Stop Agent', 'Stop agent "' + name + '"? The agent will be shut down.', async function() { + OpenFangToast.confirm(t('chat.stop_agent_title'), t('chat.stop_agent_confirm') + ' "' + name + '"?', async function() { try { await OpenFangAPI.del('/api/agents/' + self.currentAgent.id); OpenFangAPI.wsDisconnect(); self._wsAgent = null; self.currentAgent = null; self.messages = []; - OpenFangToast.success('Agent "' + name + '" stopped'); + OpenFangToast.success(t('chat.agent_stopped') + ' "' + name + '"'); Alpine.store('app').refreshAgents(); } catch(e) { - OpenFangToast.error('Failed to stop agent: ' + e.message); + OpenFangToast.error(t('chat.stop_agent_failed') + ': ' + e.message); } }); }, diff --git a/crates/openfang-api/static/js/pages/sessions.js b/crates/openfang-api/static/js/pages/sessions.js index b0c075467b..a2c716f9f6 100644 --- a/crates/openfang-api/static/js/pages/sessions.js +++ b/crates/openfang-api/static/js/pages/sessions.js @@ -64,15 +64,20 @@ function sessionsPage() { deleteSession(sessionId) { var self = this; - OpenFangToast.confirm('Delete Session', 'This will permanently remove the session and its messages.', async function() { - try { - await OpenFangAPI.del('/api/sessions/' + sessionId); - self.sessions = self.sessions.filter(function(s) { return s.session_id !== sessionId; }); - OpenFangToast.success('Session deleted'); - } catch(e) { - OpenFangToast.error('Failed to delete session: ' + e.message); + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + OpenFangToast.confirm( + t('sessions.delete_session') || 'Delete Session', + t('sessions.delete_confirm') || 'This will permanently remove the session and its messages.', + async function() { + try { + await OpenFangAPI.del('/api/sessions/' + sessionId); + self.sessions = self.sessions.filter(function(s) { return s.session_id !== sessionId; }); + OpenFangToast.success('Session deleted'); + } catch(e) { + OpenFangToast.error('Failed to delete session: ' + e.message); + } } - }); + ); }, // -- Memory methods -- @@ -108,15 +113,20 @@ function sessionsPage() { deleteKey(key) { var self = this; - OpenFangToast.confirm('Delete Key', 'Delete key "' + key + '"? This cannot be undone.', async function() { - try { - await OpenFangAPI.del('/api/memory/agents/' + self.memAgentId + '/kv/' + encodeURIComponent(key)); - OpenFangToast.success('Key "' + key + '" deleted'); - await self.loadKv(); - } catch(e) { - OpenFangToast.error('Failed to delete key: ' + e.message); + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + OpenFangToast.confirm( + t('sessions.delete_key') || 'Delete Key', + (t('sessions.delete_key_confirm') || 'Delete key') + ' "' + key + '"? This cannot be undone.', + async function() { + try { + await OpenFangAPI.del('/api/memory/agents/' + self.memAgentId + '/kv/' + encodeURIComponent(key)); + OpenFangToast.success('Key "' + key + '" deleted'); + await self.loadKv(); + } catch(e) { + OpenFangToast.error('Failed to delete key: ' + e.message); + } } - }); + ); }, startEdit(kv) { diff --git a/crates/openfang-api/static/js/pages/skills.js b/crates/openfang-api/static/js/pages/skills.js index cb0910cd14..22c7478bf3 100644 --- a/crates/openfang-api/static/js/pages/skills.js +++ b/crates/openfang-api/static/js/pages/skills.js @@ -34,27 +34,30 @@ function skillsPage() { mcpServers: [], mcpLoading: false, - // Category definitions from the OpenClaw ecosystem - categories: [ - { id: 'coding', name: 'Coding & IDEs' }, - { id: 'git', name: 'Git & GitHub' }, - { id: 'web', name: 'Web & Frontend' }, - { id: 'devops', name: 'DevOps & Cloud' }, - { id: 'browser', name: 'Browser & Automation' }, - { id: 'search', name: 'Search & Research' }, - { id: 'ai', name: 'AI & LLMs' }, - { id: 'data', name: 'Data & Analytics' }, - { id: 'productivity', name: 'Productivity' }, - { id: 'communication', name: 'Communication' }, - { id: 'media', name: 'Media & Streaming' }, - { id: 'notes', name: 'Notes & PKM' }, - { id: 'security', name: 'Security' }, - { id: 'cli', name: 'CLI Utilities' }, - { id: 'marketing', name: 'Marketing & Sales' }, - { id: 'finance', name: 'Finance' }, - { id: 'smart-home', name: 'Smart Home & IoT' }, - { id: 'docs', name: 'PDF & Documents' }, - ], + // Category definitions from the OpenClaw ecosystem (loaded from i18n) + get categories() { + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + return [ + { id: 'coding', name: t('skills.cat_coding') || 'Coding & IDEs' }, + { id: 'git', name: t('skills.cat_git') || 'Git & GitHub' }, + { id: 'web', name: t('skills.cat_frontend') || 'Web & Frontend' }, + { id: 'devops', name: t('skills.cat_devops') || 'DevOps & Cloud' }, + { id: 'browser', name: t('skills.cat_browser') || 'Browser & Automation' }, + { id: 'search', name: t('skills.cat_search') || 'Search & Research' }, + { id: 'ai', name: t('skills.cat_ai') || 'AI & ML' }, + { id: 'data', name: t('skills.cat_data') || 'Data & Analytics' }, + { id: 'productivity', name: t('skills.cat_productivity') || 'Productivity' }, + { id: 'communication', name: t('skills.cat_communication') || 'Communication' }, + { id: 'media', name: t('skills.cat_media') || 'Media & Streaming' }, + { id: 'notes', name: t('skills.cat_notes') || 'Notes & PKM' }, + { id: 'security', name: t('skills.cat_security') || 'Security' }, + { id: 'cli', name: t('skills.cat_cli') || 'CLI Utilities' }, + { id: 'marketing', name: t('skills.cat_marketing') || 'Marketing & Sales' }, + { id: 'finance', name: t('skills.cat_finance') || 'Finance' }, + { id: 'smart-home', name: t('skills.cat_smarthome') || 'Smart Home & IoT' }, + { id: 'docs', name: t('skills.cat_docs') || 'Documentation' }, + ]; + }, runtimeBadge: function(rt) { var r = (rt || '').toLowerCase(); @@ -264,15 +267,20 @@ function skillsPage() { // Uninstall uninstallSkill: function(name) { var self = this; - OpenFangToast.confirm('Uninstall Skill', 'Uninstall skill "' + name + '"? This cannot be undone.', async function() { - try { - await OpenFangAPI.post('/api/skills/uninstall', { name: name }); - OpenFangToast.success('Skill "' + name + '" uninstalled'); - await self.loadSkills(); - } catch(e) { - OpenFangToast.error('Failed to uninstall skill: ' + e.message); + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + OpenFangToast.confirm( + t('skills.uninstall_skill') || 'Uninstall Skill', + t('skills.uninstall_confirm') + ' "' + name + '"? This cannot be undone.', + async function() { + try { + await OpenFangAPI.post('/api/skills/uninstall', { name: name }); + OpenFangToast.success('Skill "' + name + '" uninstalled'); + await self.loadSkills(); + } catch(e) { + OpenFangToast.error('Failed to uninstall skill: ' + e.message); + } } - }); + ); }, // Create prompt-only skill diff --git a/crates/openfang-api/static/js/pages/wizard.js b/crates/openfang-api/static/js/pages/wizard.js index e5b19e4c57..a0a77c90fa 100644 --- a/crates/openfang-api/static/js/pages/wizard.js +++ b/crates/openfang-api/static/js/pages/wizard.js @@ -158,15 +158,17 @@ function wizardPage() { return this.templates.filter(function(t) { return t.category === cat; }); }, - // Step 3: Profile/tool descriptions - profileDescriptions: { - minimal: { label: 'Minimal', desc: 'Read-only file access' }, - coding: { label: 'Coding', desc: 'Files + shell + web fetch' }, - research: { label: 'Research', desc: 'Web search + file read/write' }, - balanced: { label: 'Balanced', desc: 'General-purpose tool set' }, - precise: { label: 'Precise', desc: 'Focused tool set for accuracy' }, - creative: { label: 'Creative', desc: 'Full tools with creative emphasis' }, - full: { label: 'Full', desc: 'All 35+ tools' } + // Step 3: Profile/tool descriptions (loaded from i18n) + get profileDescriptions() { + return { + minimal: { label: window.i18n ? window.i18n.t('wizard.profile_minimal') : 'Minimal', desc: window.i18n ? window.i18n.t('wizard.profile_minimal_desc') : 'Read-only file access' }, + coding: { label: window.i18n ? window.i18n.t('wizard.profile_coding') : 'Coding', desc: window.i18n ? window.i18n.t('wizard.profile_coding_desc') : 'Files + shell + web fetch' }, + research: { label: window.i18n ? window.i18n.t('wizard.profile_research') : 'Research', desc: window.i18n ? window.i18n.t('wizard.profile_research_desc') : 'Web search + file read/write' }, + balanced: { label: window.i18n ? window.i18n.t('wizard.profile_balanced') : 'Balanced', desc: window.i18n ? window.i18n.t('wizard.profile_balanced_desc') : 'General-purpose tool set' }, + precise: { label: window.i18n ? window.i18n.t('wizard.profile_precise') : 'Precise', desc: window.i18n ? window.i18n.t('wizard.profile_precise_desc') : 'Focused tool set for accuracy' }, + creative: { label: window.i18n ? window.i18n.t('wizard.profile_creative') : 'Creative', desc: window.i18n ? window.i18n.t('wizard.profile_creative_desc') : 'Full tools with creative emphasis' }, + full: { label: window.i18n ? window.i18n.t('wizard.profile_full') : 'Full', desc: window.i18n ? window.i18n.t('wizard.profile_full_desc') : 'All 35+ tools' } + }; }, profileInfo: function(name) { return this.profileDescriptions[name] || { label: name, desc: '' }; }, @@ -174,12 +176,35 @@ function wizardPage() { tryItMessages: [], tryItInput: '', tryItSending: false, - suggestedMessages: { - 'General': ['What can you help me with?', 'Tell me a fun fact', 'Summarize the latest AI news'], - 'Development': ['Write a Python hello world', 'Explain async/await', 'Review this code snippet'], - 'Research': ['Explain quantum computing simply', 'Compare React vs Vue', 'What are the latest trends in AI?'], - 'Writing': ['Help me write a professional email', 'Improve this paragraph', 'Write a blog intro about AI'], - 'Business': ['Draft a meeting agenda', 'How do I handle a complaint?', 'Create a project status update'] + get suggestedMessages() { + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + return { + 'General': [ + t('wizard.suggestions.general.1') || 'What can you help me with?', + t('wizard.suggestions.general.2') || 'Tell me a fun fact', + t('wizard.suggestions.general.3') || 'Summarize the latest AI news' + ], + 'Development': [ + t('wizard.suggestions.development.1') || 'Write a Python hello world', + t('wizard.suggestions.development.2') || 'Explain async/await', + t('wizard.suggestions.development.3') || 'Review this code snippet' + ], + 'Research': [ + t('wizard.suggestions.research.1') || 'Explain quantum computing simply', + t('wizard.suggestions.research.2') || 'Compare React vs Vue', + t('wizard.suggestions.research.3') || 'What are the latest trends in AI?' + ], + 'Writing': [ + t('wizard.suggestions.writing.1') || 'Help me write a professional email', + t('wizard.suggestions.writing.2') || 'Improve this paragraph', + t('wizard.suggestions.writing.3') || 'Write a blog intro about AI' + ], + 'Business': [ + t('wizard.suggestions.business.1') || 'Draft a meeting agenda', + t('wizard.suggestions.business.2') || 'How do I handle a complaint?', + t('wizard.suggestions.business.3') || 'Create a project status update' + ] + }; }, get currentSuggestions() { var tpl = this.templates[this.selectedTemplate]; @@ -204,38 +229,41 @@ function wizardPage() { // Step 5: Channel setup (optional) channelType: '', - channelOptions: [ - { - name: 'telegram', - display_name: 'Telegram', - icon: 'TG', - description: 'Connect your agent to a Telegram bot for messaging.', - token_label: 'Bot Token', - token_placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', - token_env: 'TELEGRAM_BOT_TOKEN', - help: 'Create a bot via @BotFather on Telegram to get your token.' - }, - { - name: 'discord', - display_name: 'Discord', - icon: 'DC', - description: 'Connect your agent to a Discord server via bot token.', - token_label: 'Bot Token', - token_placeholder: 'MTIz...abc', - token_env: 'DISCORD_BOT_TOKEN', - help: 'Create a Discord application at discord.com/developers and add a bot.' - }, - { - name: 'slack', - display_name: 'Slack', - icon: 'SL', - description: 'Connect your agent to a Slack workspace.', - token_label: 'Bot Token', - token_placeholder: 'xoxb-...', - token_env: 'SLACK_BOT_TOKEN', - help: 'Create a Slack app at api.slack.com/apps and install it to your workspace.' - } - ], + get channelOptions() { + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + return [ + { + name: 'telegram', + display_name: t('wizard.channel_telegram') || 'Telegram', + icon: 'TG', + description: t('wizard.channel_telegram_desc') || 'Connect your agent to a Telegram bot for messaging.', + token_label: t('wizard.channel_telegram_token') || 'Bot Token', + token_placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', + token_env: 'TELEGRAM_BOT_TOKEN', + help: t('wizard.channel_telegram_help') || 'Create a bot via @BotFather on Telegram to get your token.' + }, + { + name: 'discord', + display_name: t('wizard.channel_discord') || 'Discord', + icon: 'DC', + description: t('wizard.channel_discord_desc') || 'Connect your agent to a Discord server via bot token.', + token_label: t('wizard.channel_discord_token') || 'Bot Token', + token_placeholder: 'MTIz...abc', + token_env: 'DISCORD_BOT_TOKEN', + help: t('wizard.channel_discord_help') || 'Create a Discord application at discord.com/developers and add a bot.' + }, + { + name: 'slack', + display_name: t('wizard.channel_slack') || 'Slack', + icon: 'SL', + description: t('wizard.channel_slack_desc') || 'Connect your agent to a Slack workspace.', + token_label: t('wizard.channel_slack_token') || 'Bot Token', + token_placeholder: 'xoxb-...', + token_env: 'SLACK_BOT_TOKEN', + help: t('wizard.channel_slack_help') || 'Create a Slack app at api.slack.com/apps and install it to your workspace.' + } + ]; + }, channelToken: '', configuringChannel: false, channelConfigured: false, @@ -297,7 +325,15 @@ function wizardPage() { }, stepLabel(n) { - var labels = ['Welcome', 'Provider', 'Agent', 'Try It', 'Channel', 'Done']; + var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; }; + var labels = [ + t('wizard.step_welcome') || 'Welcome', + t('wizard.step_provider') || 'Provider', + t('wizard.step_agent') || 'Agent', + t('wizard.step_try_it') || 'Try It', + t('wizard.step_channel') || 'Channel', + t('wizard.step_done') || 'Done' + ]; return labels[n - 1] || ''; }, @@ -382,7 +418,7 @@ function wizardPage() { if (!provider) return; var key = this.apiKeyInput.trim(); if (!key) { - OpenFangToast.error('Please enter an API key'); + OpenFangToast.error(window.i18n ? window.i18n.t('wizard.enter_api_key') : 'Please enter an API key'); return; } this.savingKey = true; @@ -391,12 +427,12 @@ function wizardPage() { this.apiKeyInput = ''; this.keySaved = true; this.setupSummary.provider = provider.display_name; - OpenFangToast.success('API key saved for ' + provider.display_name); + OpenFangToast.success((window.i18n ? window.i18n.t('wizard.api_key_saved') : 'API key saved for') + ' ' + provider.display_name); await this.loadProviders(); // Auto-test after saving await this.testKey(); } catch(e) { - OpenFangToast.error('Failed to save key: ' + e.message); + OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_save_key') : 'Failed to save key:') + ' ' + e.message); } this.savingKey = false; }, @@ -410,13 +446,13 @@ function wizardPage() { var result = await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/test', {}); this.testResult = result; if (result.status === 'ok') { - OpenFangToast.success(provider.display_name + ' connected (' + (result.latency_ms || '?') + 'ms)'); + OpenFangToast.success(provider.display_name + ' ' + (window.i18n ? window.i18n.t('wizard.connected') : 'connected') + ' (' + (result.latency_ms || '?') + 'ms)'); } else { - OpenFangToast.error(provider.display_name + ': ' + (result.error || 'Connection failed')); + OpenFangToast.error(provider.display_name + ': ' + (result.error || (window.i18n ? window.i18n.t('wizard.connection_failed') : 'Connection failed'))); } } catch(e) { this.testResult = { status: 'error', error: e.message }; - OpenFangToast.error('Test failed: ' + e.message); + OpenFangToast.error((window.i18n ? window.i18n.t('wizard.test_failed') : 'Test failed:') + ' ' + e.message); } this.testingProvider = false; }, @@ -458,7 +494,7 @@ function wizardPage() { if (!tpl) return; var name = this.agentName.trim(); if (!name) { - OpenFangToast.error('Please enter a name for your agent'); + OpenFangToast.error(window.i18n ? window.i18n.t('wizard.enter_agent_name') : 'Please enter a name for your agent'); return; } @@ -485,13 +521,13 @@ function wizardPage() { if (res.agent_id) { this.createdAgent = { id: res.agent_id, name: res.name || name }; this.setupSummary.agent = res.name || name; - OpenFangToast.success('Agent "' + (res.name || name) + '" created'); + OpenFangToast.success((window.i18n ? window.i18n.t('wizard.agent_created') : 'Agent') + ' "' + (res.name || name) + '" ' + (window.i18n ? window.i18n.t('wizard.agent_created_suffix') || 'created' : 'created')); await Alpine.store('app').refreshAgents(); } else { - OpenFangToast.error('Failed: ' + (res.error || 'Unknown error')); + OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_create_agent') : 'Failed:') + ' ' + (res.error || 'Unknown error')); } } catch(e) { - OpenFangToast.error('Failed to create agent: ' + e.message); + OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_create_agent') : 'Failed to create agent:') + ' ' + e.message); } this.creatingAgent = false; }, @@ -538,7 +574,7 @@ function wizardPage() { if (!ch) return; var token = this.channelToken.trim(); if (!token) { - OpenFangToast.error('Please enter the ' + ch.token_label); + OpenFangToast.error((window.i18n ? window.i18n.t('wizard.enter_token') : 'Please enter the') + ' ' + ch.token_label); return; } this.configuringChannel = true; @@ -549,9 +585,9 @@ function wizardPage() { await OpenFangAPI.post('/api/channels/' + ch.name + '/configure', { fields: fields }); this.channelConfigured = true; this.setupSummary.channel = ch.display_name; - OpenFangToast.success(ch.display_name + ' configured and activated.'); + OpenFangToast.success(ch.display_name + ' ' + (window.i18n ? window.i18n.t('wizard.channel_configured') : 'configured and activated.')); } catch(e) { - OpenFangToast.error('Failed: ' + (e.message || 'Unknown error')); + OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_configure') : 'Failed:') + ' ' + (e.message || 'Unknown error')); } this.configuringChannel = false; },