Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/buddy/electron/main/config/LexoraConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const desktopConfigSchema = z.object({
permission_mode: z.enum(BUDDY_PERMISSION_MODES).default(DEFAULT_DESKTOP_CHAT_PREFERENCES.permissionMode),
welcome: z.enum(['none', 'random', ...DESKTOP_CHAT_WELCOME_VARIANT_IDS]).default(DEFAULT_DESKTOP_CHAT_PREFERENCES.welcome),
}).passthrough().prefault({}),
context_panel_mode: z.enum(['task', 'independent']).default('task'),
context_panel_mode: z.enum(['task', 'space', 'independent']).default('task'),
context_panel_global: z.boolean().default(false),
keybindings: keybindingsSchema.default({}),
task_sidebar_pinned_items: z.array(taskSidebarPinnedItemSchema)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,16 @@ describe('lexoraConfigStore', () => {
expect((await store.read()).desktop.language).toBe('en-US')
})

it('defaults global panels off and persists mode and visibility independently across restarts', async () => {
it.each(['independent', 'space'] as const)('defaults global panels off and persists %s mode and visibility independently across restarts', async (mode) => {
const { configPath, store } = await createConfigStore()
await mkdir(dirname(configPath), { recursive: true })
await writeFile(configPath, '[desktop]\nlanguage = "en-US"\n[future]\nvalue = true\n')
expect((await store.read()).desktop).toMatchObject({ contextPanelMode: 'task', contextPanelGlobal: false })
await store.update({ desktop: { contextPanelMode: 'independent', contextPanelGlobal: true } })
await store.update({ desktop: { contextPanelMode: mode, contextPanelGlobal: true } })
await store.update({ desktop: { theme: 'dark' } })
const restarted = new LexoraConfigStore({ configPath })
expect((await restarted.read()).desktop).toMatchObject({
contextPanelMode: 'independent',
contextPanelMode: mode,
contextPanelGlobal: true,
language: 'en-US',
theme: 'dark',
Expand Down
2 changes: 1 addition & 1 deletion apps/buddy/electron/shared/desktopApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface DesktopTaskSidebarPreferences {

export type DesktopChatWelcomeVariantId = typeof DESKTOP_CHAT_WELCOME_VARIANT_IDS[number]
export type DesktopChatWelcomePreference = 'none' | 'random' | DesktopChatWelcomeVariantId
export type DesktopContextPanelMode = 'task' | 'independent'
export type DesktopContextPanelMode = 'task' | 'space' | 'independent'

export const DESKTOP_CHAT_OUTLINE_POSITIONS = [
'top-left',
Expand Down
2 changes: 1 addition & 1 deletion apps/buddy/electron/shared/desktopApiSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const lexoraConfigPatchSchema: z.ZodType<LexoraConfigPatch> = z.object({
permissionMode: z.enum(BUDDY_PERMISSION_MODES).optional(),
welcome: z.enum(['none', 'random', ...DESKTOP_CHAT_WELCOME_VARIANT_IDS]).optional(),
}).strict().optional(),
contextPanelMode: z.enum(['task', 'independent']).optional(),
contextPanelMode: z.enum(['task', 'space', 'independent']).optional(),
contextPanelGlobal: z.boolean().optional(),
keybindings: keybindingsSchema.optional(),
taskSidebarPinnedItems: taskSidebarPinnedItemsSchema.optional(),
Expand Down
2 changes: 2 additions & 0 deletions apps/buddy/src/app/bootstrap/DesktopAppProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { desktopWorkbenchKey } from '../workbench/desktopWorkbenchContext'
import { useDesktopKeybindings } from '../workbench/useDesktopKeybindings'
import { useDesktopWorkbench } from '../workbench/useDesktopWorkbench'
import { useExtensionContributions } from '../workbench/useExtensionContributions'
import { useTaskResourceSpaces } from '../workbench/useTaskResourceSpaces'
import { createDesktopCapabilities } from './desktopCapabilities'
import { useDesktopAppState } from './useDesktopAppState'
import { useDesktopLifecycle } from './useDesktopLifecycle'
Expand Down Expand Up @@ -105,6 +106,7 @@ useProvideExtensionContext({ settingsLocation: settingsRegistry.extensionLocatio
onScopeDispose(workbench.controller.subscribe(() => void nextTick(extensionViews.layout)))
const selectedTask = workbench.activeTask
const resources = useTaskResourcePanel({
scopeSpaceIds: useTaskResourceSpaces({ controller: workbench.controller, pool: workbench.pool, tasks: taskIndex.index.tasks }),
activeConversationId: workbench.activeTaskId,
activeDraftId: computed(() => selectedTask.value?.workspace.composer.draftId.value ?? null),
activeBranchId: computed(() => selectedTask.value?.workspace.session.activeBranchId.value ?? null),
Expand Down
40 changes: 40 additions & 0 deletions apps/buddy/src/app/workbench/useTaskResourceSpaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { LocalConversationSummary } from '@buddy-shared/conversation/conversationApi'
import type { Ref } from 'vue'
import type { TaskWorkspacePool } from './TaskWorkspacePool'
import type { WorkbenchController } from '@/workbench/services/WorkbenchController'
import { computed, onScopeDispose, shallowRef } from 'vue'

export function useTaskResourceSpaces(options: {
controller: WorkbenchController
pool: TaskWorkspacePool
tasks: Readonly<Ref<ReadonlyArray<Pick<LocalConversationSummary, 'id' | 'spaceId'>>>>
}) {
const { controller, pool } = options
const openTasks = shallowRef(collect())
function collect() {
return Object.values(controller.layout.views)
.filter(view => view.resource.scheme === 'task' || view.resource.scheme === 'draft')
.map(({ resource }) => ({ resource, task: pool.peek(resource) }))
}
function reconcile() {
openTasks.value = collect()
}
onScopeDispose(pool.onDidChange(reconcile).dispose)
onScopeDispose(controller.onDidChangeLayout((change) => {
if (change.changedViewIds.length || change.removedViewIds.length || change.kind === 'restored')
reconcile()
}).dispose)
return computed<ReadonlyMap<string, string | null>>(() => {
const spaces = new Map(options.tasks.value.map(task => [`task:${task.id}`, task.spaceId]))
for (const { resource, task } of openTasks.value) {
const key = `${resource.scheme}:${resource.id}`
if (spaces.has(key))
continue
if (task)
spaces.set(key, task.session.spaceId.value)
else if (resource.scheme === 'draft')
spaces.set(key, typeof resource.data.spaceId === 'string' ? resource.data.spaceId : null)
}
return spaces
})
}
5 changes: 3 additions & 2 deletions apps/buddy/src/i18n/locales/en-US/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ export default {
'desktop.browser.screenshotFailed': 'Could not capture the screenshot. Retry or change the destination in browser settings.',
'desktop.settings.contextPanel': 'Resource panel',
'desktop.settings.contextPanelMode': 'Browsing mode',
'desktop.settings.contextPanelModeDescription': 'Task-linked selects resources when focusing a task. Independent keeps the current selection.',
'desktop.settings.contextPanelModeDescription': 'Follow task or space resources, or browse independently.',
'desktop.settings.contextPanelTask': 'Task-linked',
'desktop.settings.contextPanelSpace': 'Space-linked',
'desktop.settings.contextPanelIndependent': 'Independent',
'desktop.settings.contextPanelGlobal': 'Follow resources across pages',
'desktop.settings.contextPanelGlobalDescription': 'Follow task resources in settings and automations too. Open tabs always remain available.',
'desktop.settings.contextPanelGlobalDescription': 'Show the resource panel in settings and automations using the current browsing mode. Open tabs always remain available.',
'desktop.settings.proxy.title': 'Network proxy',
'desktop.settings.proxy.mode': 'Connection mode',
'desktop.settings.proxy.description': 'Changes apply immediately. Active network requests may need to be retried.',
Expand Down
5 changes: 3 additions & 2 deletions apps/buddy/src/i18n/locales/zh-CN/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ export default {
'desktop.browser.screenshotFailed': '截图失败,请重试或在浏览器设置中切换保存方式。',
'desktop.settings.contextPanel': '资源面板',
'desktop.settings.contextPanelMode': '浏览模式',
'desktop.settings.contextPanelModeDescription': '任务联动在聚焦任务时选择其资源,独立浏览保留当前选择。',
'desktop.settings.contextPanelModeDescription': '按任务或空间切换资源,独立浏览保留当前选择。',
'desktop.settings.contextPanelTask': '任务联动',
'desktop.settings.contextPanelSpace': '空间联动',
'desktop.settings.contextPanelIndependent': '独立浏览',
'desktop.settings.contextPanelGlobal': '跨页面联动',
'desktop.settings.contextPanelGlobalDescription': '在设置和自动化中也跟随任务资源;已打开的标签始终保留。',
'desktop.settings.contextPanelGlobalDescription': '在设置和自动化中也显示资源面板,沿用当前浏览模式;已打开的标签始终保留。',
'desktop.settings.proxy.title': '网络代理',
'desktop.settings.proxy.mode': '连接方式',
'desktop.settings.proxy.description': '修改后立即生效,进行中的网络请求可能需要重试。',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const pending = shallowRef(false)
const labelId = useId()
const contextPanelModes = computed(() => [
{ label: t('desktop.settings.contextPanelTask'), value: 'task' },
{ label: t('desktop.settings.contextPanelSpace'), value: 'space' },
{ label: t('desktop.settings.contextPanelIndependent'), value: 'independent' },
])
const labels = computed(() => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ContextPanelScope, TaskContextTab } from './taskContextPanel'
import type { ContextPanelSelectionScope, TaskContextTab } from './taskContextPanel'
import { artifactSchema } from '@buddy-shared/artifacts/artifactApi'
import { changeSetSummarySchema } from '@buddy-shared/changes/changeApi'
import { contextPanelSourceSchema } from '@buddy-shared/context-panel/contextPanel'
Expand All @@ -7,7 +7,7 @@ import { z } from 'zod'

const identity = z.string().min(1).max(256)
const scopeSchema = z.union([z.literal('independent'), z.literal('workspace'), z.templateLiteral(['task:', z.string().min(1)]), z.templateLiteral(['draft:', z.string().min(1)])])
const selectionSchema = z.tuple([scopeSchema, identity])
const selectionSchema = z.tuple([z.union([scopeSchema, z.templateLiteral(['space:', z.string().min(1)])]), identity])
const base = z.object({ id: identity, scope: scopeSchema, source: contextPanelSourceSchema.optional() })
const tabSchema = z.discriminatedUnion('kind', [
base.extend({ kind: z.literal('browser'), conversationId: identity.nullable(), browserKey: identity.optional() }),
Expand All @@ -24,7 +24,7 @@ export function readContextTab(value: unknown): TaskContextTab | null {
return parsed.data
}

export function readContextSelection(value: unknown): readonly [ContextPanelScope, string] | null {
export function readContextSelection(value: unknown): readonly [ContextPanelSelectionScope, string] | null {
const parsed = selectionSchema.safeParse(value)
return parsed.success ? parsed.data : null
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DesktopContextPanelMode } from '@buddy-electron/shared/desktopApi'
import type { LocalArtifact } from '@buddy-shared/artifacts/artifactApi'
import type { LocalChangeSetSummary } from '@buddy-shared/changes/changeApi'
import type { ContextPanelSource } from '@buddy-shared/context-panel/contextPanel'
Expand All @@ -6,6 +7,14 @@ import type { SpaceFileTarget } from '@buddy-shared/spaces/spaceFileApi'

export type ArtifactViewMode = 'preview' | 'source'
export type ContextPanelScope = `task:${string}` | `draft:${string}` | 'independent' | 'workspace'
export type ContextPanelSelectionScope = ContextPanelScope | `space:${string}`

export function contextPanelSelectionScope(mode: DesktopContextPanelMode, scope: ContextPanelScope, spaceIds: ReadonlyMap<string, string | null>): ContextPanelSelectionScope {
if (mode === 'independent')
return 'independent'
const spaceId = mode === 'space' ? spaceIds.get(scope) : null
return spaceId ? `space:${spaceId}` : scope
}

interface ContextTabSource {
scope: ContextPanelScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function contextPanelFixture(overrides: Partial<UseTaskContextPanelOption
activeBranchId: shallowRef('branch'),
activeRunId: shallowRef(null),
mode: shallowRef('task'),
scopeSpaceIds: shallowRef(new Map()),
taskVisible: shallowRef(true),
spaces: shallowRef([]),
changeSets: shallowRef([]),
Expand Down
Loading