Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions frontend/src/app/(tasks)/chat/ChatPageDesktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,29 @@ export function ChatPageDesktop() {
const { selectedDeviceId, devices } = useDevices()
const selectedDevice = devices.find(d => d.device_id === selectedDeviceId)

// For existing tasks, also check the task's device_id
const taskDevice = selectedTaskDetail?.device_id
? devices.find(d => d.device_id === selectedTaskDetail.device_id)
: null
const isTaskDeviceDeleted =
selectedTaskDetail?.device_id &&
!devices.some(d => d.device_id === selectedTaskDetail.device_id)
const isTaskDeviceOffline = taskDevice?.status === 'offline'
Comment on lines +62 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition: false positive during initial device loading.

The isTaskDeviceDeleted check doesn't account for the devices loading state. When devices initializes as an empty array while fetching, any task with a device_id will incorrectly show "Device Deleted" status until the fetch completes.

This is the same issue identified in frontend/src/app/(tasks)/devices/chat/page.tsx. Consider extracting isLoading from useDevices() and guarding against it.

🐛 Proposed fix: Check loading state before determining deletion status
   // Device context - when a device is selected, switch to 'task' mode
-  const { selectedDeviceId, devices } = useDevices()
+  const { selectedDeviceId, devices, isLoading: isDevicesLoading } = useDevices()
   const selectedDevice = devices.find(d => d.device_id === selectedDeviceId)

   // For existing tasks, also check the task's device_id
   const taskDevice = selectedTaskDetail?.device_id
     ? devices.find(d => d.device_id === selectedTaskDetail.device_id)
     : null
   const isTaskDeviceDeleted =
+    !isDevicesLoading &&
     selectedTaskDetail?.device_id &&
     !devices.some(d => d.device_id === selectedTaskDetail.device_id)
   const isTaskDeviceOffline = taskDevice?.status === 'offline'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/`(tasks)/chat/ChatPageDesktop.tsx around lines 62 - 69, The
isTaskDeviceDeleted check can return a false positive while devices are loading;
update the logic to extract the loading flag from useDevices() (e.g., isLoading
or similar) and only compute isTaskDeviceDeleted when loading is false and
selectedTaskDetail?.device_id exists, otherwise treat it as not deleted; keep
taskDevice and isTaskDeviceOffline computations the same but ensure any UI that
shows "Device Deleted" depends on the guarded isTaskDeviceDeleted.


// Determine taskType based on device selection
// When a device is selected, use 'task' mode (same as /devices/chat)
// Otherwise, use 'chat' mode
const taskType = selectedDeviceId ? 'task' : 'chat'

// Compute disabled reason for device mode
const disabledReason =
selectedDeviceId && (!selectedDevice || selectedDevice.status === 'offline')
// Consider both currently selected device and task's associated device
const disabledReason = isTaskDeviceDeleted
? t('devices:device_deleted_hint')
: isTaskDeviceOffline
? t('devices:device_offline_cannot_send')
: undefined
: selectedDeviceId && (!selectedDevice || selectedDevice.status === 'offline')
? t('devices:device_offline_cannot_send')
: undefined

// Get current task title for top navigation
const currentTaskTitle = selectedTaskDetail?.title
Expand Down
19 changes: 16 additions & 3 deletions frontend/src/app/(tasks)/chat/ChatPageMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,29 @@ export function ChatPageMobile() {
const { selectedDeviceId, devices } = useDevices()
const selectedDevice = devices.find(d => d.device_id === selectedDeviceId)

// For existing tasks, also check the task's device_id
const taskDevice = selectedTaskDetail?.device_id
? devices.find(d => d.device_id === selectedTaskDetail.device_id)
: null
const isTaskDeviceDeleted =
selectedTaskDetail?.device_id &&
!devices.some(d => d.device_id === selectedTaskDetail.device_id)
const isTaskDeviceOffline = taskDevice?.status === 'offline'
Comment on lines +51 to +58
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition: false positive during initial device loading.

Same issue as ChatPageDesktop.tsx: the isTaskDeviceDeleted check doesn't account for the devices loading state. Extract isLoading from useDevices() and guard against it.

🐛 Proposed fix: Check loading state before determining deletion status
   // Device context - when a device is selected, switch to 'task' mode
-  const { selectedDeviceId, devices } = useDevices()
+  const { selectedDeviceId, devices, isLoading: isDevicesLoading } = useDevices()
   const selectedDevice = devices.find(d => d.device_id === selectedDeviceId)

   // For existing tasks, also check the task's device_id
   const taskDevice = selectedTaskDetail?.device_id
     ? devices.find(d => d.device_id === selectedTaskDetail.device_id)
     : null
   const isTaskDeviceDeleted =
+    !isDevicesLoading &&
     selectedTaskDetail?.device_id &&
     !devices.some(d => d.device_id === selectedTaskDetail.device_id)
   const isTaskDeviceOffline = taskDevice?.status === 'offline'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/`(tasks)/chat/ChatPageMobile.tsx around lines 51 - 58, The
isTaskDeviceDeleted computation can return a false positive while devices are
still loading; update ChatPageMobile to extract the isLoading flag from
useDevices() (same hook used to get devices) and guard the deletion check so
that isTaskDeviceDeleted is false while isLoading is true; specifically modify
the logic around selectedTaskDetail, devices and the isTaskDeviceDeleted
variable to include !isLoading && selectedTaskDetail?.device_id &&
!devices.some(...), matching the pattern used in ChatPageDesktop.


// Determine taskType based on device selection
// When a device is selected, use 'task' mode (same as /devices/chat)
// Otherwise, use 'chat' mode
const taskType = selectedDeviceId ? 'task' : 'chat'

// Compute disabled reason for device mode
const disabledReason =
selectedDeviceId && (!selectedDevice || selectedDevice.status === 'offline')
// Consider both currently selected device and task's associated device
const disabledReason = isTaskDeviceDeleted
? t('devices:device_deleted_hint')
: isTaskDeviceOffline
? t('devices:device_offline_cannot_send')
: undefined
: selectedDeviceId && (!selectedDevice || selectedDevice.status === 'offline')
? t('devices:device_offline_cannot_send')
: undefined

// Get current task title for top navigation
const currentTaskTitle = selectedTaskDetail?.title
Expand Down
102 changes: 74 additions & 28 deletions frontend/src/app/(tasks)/devices/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

'use client'

import { useEffect, useState, useCallback } from 'react'
import { useEffect, useState, useCallback, useMemo } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import TopNavigation from '@/features/layout/TopNavigation'
import {
Expand Down Expand Up @@ -123,9 +123,22 @@ export default function DeviceChatPage() {
// Get selected device info
const selectedDevice = devices.find(d => d.device_id === selectedDeviceId)

// For existing tasks, use task's device_id instead of selectedDeviceId for display
const taskDevice = selectedTaskDetail?.device_id
? devices.find(d => d.device_id === selectedTaskDetail.device_id)
: null
// Task is considered to have messages if it has a non-empty title and was created
const hasMessages = !!(selectedTaskDetail && selectedTaskDetail.id)

// Check if selected device is OpenClaw type
const isOpenClaw = selectedDevice ? isOpenClawDevice(selectedDevice) : false

// Check if task was associated with a device that has been deleted
const isTaskDeviceDeleted = useMemo(() => {
if (!selectedTaskDetail?.device_id) return false
return !devices.some(d => d.device_id === selectedTaskDetail.device_id)
}, [selectedTaskDetail?.device_id, devices])
Comment on lines +136 to +140
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition: false positive during initial device loading.

The isTaskDeviceDeleted check doesn't account for the loading state of the devices list. According to DeviceContext, devices initializes as an empty array while isLoading is true. This means any task with a device_id will incorrectly show "Device Deleted" status during the initial load before the devices fetch completes.

🐛 Proposed fix: Check `isLoading` before determining deletion status
+  const { devices, selectedDeviceId, setSelectedDeviceId, isLoading: isDevicesLoading } = useDevices()

   // Check if task was associated with a device that has been deleted
   const isTaskDeviceDeleted = useMemo(() => {
+    // Don't report as deleted while devices are still loading
+    if (isDevicesLoading) return false
     if (!selectedTaskDetail?.device_id) return false
     return !devices.some(d => d.device_id === selectedTaskDetail.device_id)
-  }, [selectedTaskDetail?.device_id, devices])
+  }, [selectedTaskDetail?.device_id, devices, isDevicesLoading])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/`(tasks)/devices/chat/page.tsx around lines 129 - 133,
isTaskDeviceDeleted currently treats an empty devices array as "deleted" during
initial load; update its logic in the useMemo that defines isTaskDeviceDeleted
to also check the DeviceContext loading flag (isLoading) and return false while
devices are still loading. Specifically, modify the useMemo that references
selectedTaskDetail?.device_id and devices so it references the isLoading value
from DeviceContext (or the provider hook) and short-circuits to false when
isLoading is true, then proceed to the existing devices.some(...) check to
determine real deletion status.


return (
<div className="flex smart-h-screen bg-base text-text-primary box-border">
{/* URL parameter sync */}
Expand Down Expand Up @@ -162,30 +175,57 @@ export default function DeviceChatPage() {
onMembersChanged={handleMembersChanged}
isSidebarCollapsed={isCollapsed}
>
{/* Device selector in top bar */}
<div className="flex items-center gap-2 mr-2">
<Monitor className="w-4 h-4 text-text-muted" />
<select
value={selectedDeviceId || ''}
onChange={e => handleDeviceSelect(e.target.value)}
className="bg-surface border border-border rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
>
<option value="" disabled>
{t('select_device')}
</option>
{devices.map(device => (
<option key={device.device_id} value={device.device_id}>
{device.name} (
{device.status === 'online'
? t('status_online')
: device.status === 'busy'
? t('status_busy')
: t('status_offline')}
)
</option>
))}
</select>
</div>
{/* Device selector in top bar - hide when task's device is deleted */}
{!isTaskDeviceDeleted && (
<div className="flex items-center gap-2 mr-2">
<Monitor className="w-4 h-4 text-text-muted" />
{/* For existing tasks with messages, show read-only device info */}
{hasMessages && taskDevice ? (
<div className="flex items-center gap-1.5 bg-surface border border-border rounded-md px-2 py-1 text-sm">
<span className="truncate max-w-[150px]">{taskDevice.name}</span>
<span
className={`w-2 h-2 rounded-full ${
taskDevice.status === 'online'
? 'bg-green-500'
: taskDevice.status === 'busy'
? 'bg-yellow-500'
: 'bg-gray-400'
}`}
/>
<span className="text-text-muted text-xs">
(
{taskDevice.status === 'online'
? t('status_online')
: taskDevice.status === 'busy'
? t('status_busy')
: t('status_offline')}
)
</span>
</div>
) : (
<select
value={selectedDeviceId || ''}
onChange={e => handleDeviceSelect(e.target.value)}
className="bg-surface border border-border rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
>
<option value="" disabled>
{t('select_device')}
</option>
{devices.map(device => (
<option key={device.device_id} value={device.device_id}>
{device.name} (
{device.status === 'online'
? t('status_online')
: device.status === 'busy'
? t('status_busy')
: t('status_offline')}
)
</option>
))}
</select>
)}
</div>
)}
{isMobile ? <ThemeToggle /> : <GithubStarButton />}
</TopNavigation>

Expand All @@ -199,9 +239,15 @@ export default function DeviceChatPage() {
taskType="task"
onRefreshTeams={handleRefreshTeams}
disabledReason={
!selectedDevice || selectedDevice.status === 'offline'
? t('device_offline_cannot_send')
: undefined
isTaskDeviceDeleted
? t('device_deleted_hint')
: hasMessages && taskDevice?.status === 'offline'
? t('device_offline_cannot_send')
: hasMessages && !selectedTaskDetail?.device_id
? undefined // Task was created with cloud mode, allow sending
: !selectedDevice || selectedDevice.status === 'offline'
? t('device_offline_cannot_send')
: undefined
}
hideSelectors={isOpenClaw}
/>
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/features/tasks/components/chat/ChatArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { useAttachmentUpload } from '../hooks/useAttachmentUpload'
import { useSchemeMessageActions } from '@/lib/scheme'
import { useSkillSelector } from '../../hooks/useSkillSelector'
import { useModelSelection } from '../../hooks/useModelSelection'
import { useDevices } from '@/contexts/DeviceContext'

/**
* Threshold in pixels for determining when to collapse selectors.
Expand Down Expand Up @@ -112,6 +113,15 @@ function ChatAreaContent({
// Task context
const { selectedTaskDetail, setSelectedTask, accessDenied } = useTaskContext()

// Device context - for detecting deleted device
const { devices } = useDevices()

// Check if task was associated with a device that has been deleted
const isTaskDeviceDeleted = useMemo(() => {
if (!selectedTaskDetail?.device_id) return false
return !devices.some(d => d.device_id === selectedTaskDetail.device_id)
}, [selectedTaskDetail?.device_id, devices])
Comment on lines +116 to +123
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition: false positive during initial device loading.

Same issue as in the page components: when devices is still loading (empty array), any task with a device_id will incorrectly report as deleted. Extract isLoading from useDevices() and check it in the useMemo.

🐛 Proposed fix: Guard against loading state
   // Device context - for detecting deleted device
-  const { devices } = useDevices()
+  const { devices, isLoading: isDevicesLoading } = useDevices()

   // Check if task was associated with a device that has been deleted
   const isTaskDeviceDeleted = useMemo(() => {
+    // Don't report as deleted while devices are still loading
+    if (isDevicesLoading) return false
     if (!selectedTaskDetail?.device_id) return false
     return !devices.some(d => d.device_id === selectedTaskDetail.device_id)
-  }, [selectedTaskDetail?.device_id, devices])
+  }, [selectedTaskDetail?.device_id, devices, isDevicesLoading])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Device context - for detecting deleted device
const { devices } = useDevices()
// Check if task was associated with a device that has been deleted
const isTaskDeviceDeleted = useMemo(() => {
if (!selectedTaskDetail?.device_id) return false
return !devices.some(d => d.device_id === selectedTaskDetail.device_id)
}, [selectedTaskDetail?.device_id, devices])
// Device context - for detecting deleted device
const { devices, isLoading: isDevicesLoading } = useDevices()
// Check if task was associated with a device that has been deleted
const isTaskDeviceDeleted = useMemo(() => {
// Don't report as deleted while devices are still loading
if (isDevicesLoading) return false
if (!selectedTaskDetail?.device_id) return false
return !devices.some(d => d.device_id === selectedTaskDetail.device_id)
}, [selectedTaskDetail?.device_id, devices, isDevicesLoading])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/tasks/components/chat/ChatArea.tsx` around lines 116 -
123, The isTaskDeviceDeleted calculation can return a false positive while
devices are still loading; update the useDevices() call to also extract
isLoading (e.g., const { devices, isLoading } = useDevices()) and modify the
useMemo for isTaskDeviceDeleted to return false when isLoading is true (or when
devices is undefined/null), keeping the existing checks on
selectedTaskDetail?.device_id and the devices.some(...) logic otherwise; this
ensures the useMemo (isTaskDeviceDeleted) only reports deletion after devices
have finished loading.


// Use useTaskStateMachine hook for reactive state updates (SINGLE SOURCE OF TRUTH per AGENTS.md)
const { state: taskState } = useTaskStateMachine(selectedTaskDetail?.id)

Expand Down Expand Up @@ -1040,6 +1050,8 @@ function ChatAreaContent({
onGenerateModeChange,
// Hide all selectors (for OpenClaw devices)
hideSelectors,
// Whether the task's device has been deleted
isTaskDeviceDeleted,
}

return (
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/features/tasks/components/input/ChatInputCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
'use client'

import React, { useRef, useState, useCallback } from 'react'
import { Upload, Sparkles } from 'lucide-react'
import Link from 'next/link'
import { Upload, Sparkles, Plus } from 'lucide-react'
import ChatInput from './ChatInput'
import InputBadgeDisplay from './InputBadgeDisplay'
import ExternalApiParamsInput from '../params/ExternalApiParamsInput'
Expand All @@ -16,6 +17,7 @@ import { QuoteCard } from '../text-selection'
import { ConnectionStatusBanner } from './ConnectionStatusBanner'
import type { Team, ChatTipItem, TaskType } from '@/types/api'
import { useTranslation } from '@/hooks/useTranslation'
import { paths } from '@/config/paths'
import type { SkillSelectorPopoverRef } from '../selector/SkillSelectorPopover'

export interface ChatInputCardProps extends Omit<
Expand Down Expand Up @@ -76,6 +78,9 @@ export interface ChatInputCardProps extends Omit<
// Reason why input is disabled (e.g., device offline). Shows as placeholder text.
disabledReason?: string

// Whether the task's device has been deleted (shows clickable prompt overlay)
isTaskDeviceDeleted?: boolean

// Hide all selectors (for OpenClaw devices) - only show text input + send button
hideSelectors?: boolean
}
Expand Down Expand Up @@ -121,6 +126,7 @@ export function ChatInputCard({
inputControlsRef,
hasNoTeams = false,
disabledReason,
isTaskDeviceDeleted = false,
hideSelectors,
// ChatInputControls props
selectedModel,
Expand Down Expand Up @@ -260,6 +266,20 @@ export function ChatInputCard({
</div>
)}

{/* Device Deleted Overlay - shows clickable prompt when task's device is deleted */}
{isTaskDeviceDeleted && (
<div className="absolute inset-0 z-40 rounded-3xl bg-base/95 backdrop-blur-sm flex flex-col items-center justify-center transition-all">
<p className="text-sm text-text-muted mb-3">{t('devices:device_deleted_hint')}</p>
<Link
href={paths.chat.getHref()}
className="flex items-center gap-1.5 px-4 py-2 rounded-full bg-primary text-white text-sm font-medium hover:bg-primary/90 transition-colors"
>
<Plus className="w-4 h-4" />
<span>{t('devices:start_new_chat')}</span>
</Link>
</div>
)}

{/* Unified Badge Display - Knowledge bases and attachments */}
<InputBadgeDisplay
contexts={selectedContexts}
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/features/tasks/components/input/DeviceSelectorTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ export function DeviceSelectorTab({
: null
}, [devices, selectedDeviceId, hasMessages, taskDeviceId])

// Check if the task was associated with a device that has been deleted
const isTaskDeviceDeleted = useMemo(() => {
if (!hasMessages || !taskDeviceId) return false
// If taskDeviceId exists but device not found in devices list, it means the device was deleted
return !devices.some(device => device.device_id === taskDeviceId)
}, [hasMessages, taskDeviceId, devices])
Comment on lines +330 to +335
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same race condition as in page.tsx: false positive during device loading.

This memo has the same issue as the one in page.tsx. When devices is still loading (empty array), tasks with a device_id will incorrectly appear as having a deleted device. The isLoading state from useDevices should be checked.

🐛 Proposed fix: Guard against loading state
-  const { devices, selectedDeviceId, setSelectedDeviceId, isLoading } = useDevices()
+  const { devices, selectedDeviceId, setSelectedDeviceId, isLoading: isDevicesLoading } = useDevices()

   // Check if the task was associated with a device that has been deleted
   const isTaskDeviceDeleted = useMemo(() => {
+    // Don't report as deleted while devices are still loading
+    if (isDevicesLoading) return false
     if (!hasMessages || !taskDeviceId) return false
     // If taskDeviceId exists but device not found in devices list, it means the device was deleted
     return !devices.some(device => device.device_id === taskDeviceId)
-  }, [hasMessages, taskDeviceId, devices])
+  }, [hasMessages, taskDeviceId, devices, isDevicesLoading])

Note: isLoading is already destructured on line 230, so you can use it directly instead of renaming:

   const isTaskDeviceDeleted = useMemo(() => {
+    if (isLoading) return false
     if (!hasMessages || !taskDeviceId) return false
     return !devices.some(device => device.device_id === taskDeviceId)
-  }, [hasMessages, taskDeviceId, devices])
+  }, [hasMessages, taskDeviceId, devices, isLoading])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/tasks/components/input/DeviceSelectorTab.tsx` around
lines 331 - 336, The isTaskDeviceDeleted memo in DeviceSelectorTab.tsx can
return a false positive while devices are still loading; update the useMemo
(isTaskDeviceDeleted) to also guard against the devices loading state from
useDevices by returning false when isLoading is true (in addition to the
existing hasMessages/taskDeviceId checks), so the memo only treats a device as
deleted once loading has finished and the devices list is definitive.


useEffect(() => {
if (hasMessages || isLoading || autoSelectionInitializedRef.current) {
return
Expand Down Expand Up @@ -450,6 +457,40 @@ export function DeviceSelectorTab({

// Read-only mode for existing chats
if (hasMessages) {
// When device is deleted, don't show any selector - the prompt will be shown in the input area
if (isTaskDeviceDeleted) {
return null
}

// If task has device_id but device not found (and not deleted), don't show "Public Mode"
// This handles the case where devices list is still loading or data inconsistency
if (taskDeviceId && !selectedDevice) {
// Show a loading/unknown state instead of misleading "Public Mode"
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
className={cn(
'flex items-center gap-1.5 px-2 py-1.5 rounded-md',
'bg-surface border border-border',
'text-xs text-text-secondary',
className
)}
>
<Monitor className="w-3.5 h-3.5" />
<span className="truncate max-w-[160px]">{t('local_device_prefix')}...</span>
<span className="w-1.5 h-1.5 rounded-full bg-gray-400" />
</div>
</TooltipTrigger>
<TooltipContent side="top">
<p>{t('device_offline_hint')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}

return (
<TooltipProvider>
<Tooltip>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/i18n/locales/en/devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"select_device_hint": "Select a device from the top to start sending tasks",
"device_offline_hint": "Device is offline, please select another",
"device_offline_cannot_send": "Device is offline, cannot send tasks",
"device_deleted": "Device Deleted",
"device_deleted_hint": "The device associated with this chat has been deleted",
"start_new_chat": "Start New Chat",
"device_busy_hint": "Device is busy with another task",
"select_another_device": "Please select another device or wait for current task to complete",
"refresh": "Refresh",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/i18n/locales/zh-CN/devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"select_device_hint": "从顶部选择一个在线设备开始发送任务",
"device_offline_hint": "设备已离线,请选择其他设备",
"device_offline_cannot_send": "设备不在线,无法发送任务",
"device_deleted": "设备已删除",
"device_deleted_hint": "此会话关联的设备已被删除",
"start_new_chat": "开始新对话",
"device_busy_hint": "设备正在执行其他任务",
"select_another_device": "请选择其他设备或等待当前任务完成",
"refresh": "刷新",
Expand Down
Loading