diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 1df58ebb..ee71be4b 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -61,6 +61,9 @@ import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; import { useThemeSystem } from '@/contexts/useThemeSystem'; import { GitHubIssuePickerDialog } from '@/components/session/GitHubIssuePickerDialog'; import { GitHubPrPickerDialog } from '@/components/session/GitHubPrPickerDialog'; +import { GitLabIssuePickerDialog } from '@/components/session/GitLabIssuePickerDialog'; +import { GitLabMrPickerDialog } from '@/components/session/GitLabMrPickerDialog'; +import { useGitProvider } from '@/lib/gitProvider'; import { Icon } from "@/components/icon/Icon"; import { DraftPresetChips } from './DraftPresetChips'; import { useChatSearchDirectory } from '@/hooks/useChatSearchDirectory'; @@ -376,6 +379,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const { currentTheme } = useThemeSystem(); const chatSearchDirectory = useChatSearchDirectory(); const isGitRepo = useIsGitRepo(currentDirectory); + const gitProvider = useGitProvider(currentDirectory); const currentGitStatus = useGitStore((state) => currentDirectory ? state.directories.get(currentDirectory)?.status ?? null : null, ); @@ -663,6 +667,8 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo // Issue linking state const [issuePickerOpen, setIssuePickerOpen] = React.useState(false); const [prPickerOpen, setPrPickerOpen] = React.useState(false); + const [gitlabIssuePickerOpen, setGitlabIssuePickerOpen] = React.useState(false); + const [gitlabMrPickerOpen, setGitlabMrPickerOpen] = React.useState(false); const [linkedIssue, setLinkedIssue] = React.useState<{ number: number; title: string; @@ -945,12 +951,20 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo }, [isExpandedInput, setExpandedInput]); const openIssuePicker = React.useCallback(() => { - setIssuePickerOpen(true); - }, []); + if (gitProvider === 'gitlab') { + setGitlabIssuePickerOpen(true); + } else { + setIssuePickerOpen(true); + } + }, [gitProvider]); const openPrPicker = React.useCallback(() => { - setPrPickerOpen(true); - }, []); + if (gitProvider === 'gitlab') { + setGitlabMrPickerOpen(true); + } else { + setPrPickerOpen(true); + } + }, [gitProvider]); const getSubmitErrorMessage = (error: unknown, fallback: string) => { const message = error instanceof Error ? error.message : ''; @@ -2545,7 +2559,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo author={linkedIssue.author} openInBrowserLabel={t('chat.chatInput.linked.issue.openInBrowserAria')} removeLabel={t('chat.chatInput.linked.issue.removeAria')} - onReopenPicker={() => setIssuePickerOpen(true)} + onReopenPicker={openIssuePicker} onRemove={() => setLinkedIssue(null)} /> ) : null} @@ -2558,7 +2572,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo branches={{ head: linkedPr.head, base: linkedPr.base }} openInBrowserLabel={t('chat.chatInput.linked.pr.openInBrowserAria')} removeLabel={t('chat.chatInput.linked.pr.removeAria')} - onReopenPicker={() => setPrPickerOpen(true)} + onReopenPicker={openPrPicker} onRemove={() => setLinkedPr(null)} /> ) : null} @@ -2878,6 +2892,23 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo setLinkedIssue(null); }} /> + { + setLinkedIssue(issue); + setLinkedPr(null); + }} + /> + { + setLinkedPr(pr); + setLinkedIssue(null); + }} + /> = ({ onOpenSettings, scrollTo requestAnimationFrame(openIssuePicker); }} > - - {t('chat.chatInput.actions.linkGithubIssue')} + + {gitProvider === 'gitlab' ? t('chat.chatInput.actions.linkGitlabIssue') : t('chat.chatInput.actions.linkGithubIssue')} diff --git a/packages/ui/src/components/chat/composer/ui/ComposerAttachmentControls.tsx b/packages/ui/src/components/chat/composer/ui/ComposerAttachmentControls.tsx index 2387ccb9..df4a070a 100644 --- a/packages/ui/src/components/chat/composer/ui/ComposerAttachmentControls.tsx +++ b/packages/ui/src/components/chat/composer/ui/ComposerAttachmentControls.tsx @@ -27,7 +27,7 @@ type ComposerAttachmentControlsProps = { handlePickLocalFiles: () => void; openIssuePicker: () => void; openPrPicker: () => void; - /** Only shows the GitHub issue/PR attach actions when the repo is GitHub. */ + /** Shows the GitHub issue/PR or GitLab issue/MR attach actions based on the repo provider. */ gitProvider?: GitProvider | null; onOpenSettings?: () => void; onMenuOpenChange?: (open: boolean) => void; @@ -121,6 +121,25 @@ export const ComposerAttachmentControls = React.memo(function ComposerAttachment {t('chat.chatInput.actions.linkGithubPr')} + ) : gitProvider === 'gitlab' ? ( + <> + { + requestAnimationFrame(openIssuePicker); + }} + > + + {t('chat.chatInput.actions.linkGitlabIssue')} + + { + requestAnimationFrame(openPrPicker); + }} + > + + {t('chat.chatInput.actions.linkGitlabMr')} + + ) : null} diff --git a/packages/ui/src/components/session/GitLabIssuePickerDialog.tsx b/packages/ui/src/components/session/GitLabIssuePickerDialog.tsx new file mode 100644 index 00000000..3fd2253e --- /dev/null +++ b/packages/ui/src/components/session/GitLabIssuePickerDialog.tsx @@ -0,0 +1,733 @@ +import React from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; +import { toast } from '@/components/ui'; +import { Icon } from "@/components/icon/Icon"; +import { cn } from '@/lib/utils'; +import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; +import { useProjectsStore } from '@/stores/useProjectsStore'; +import { useDirectoryStore } from '@/stores/useDirectoryStore'; +import { useSessionUIStore } from '@/sync/session-ui-store'; +import { useSelectionStore } from '@/sync/selection-store'; +import * as sessionActions from '@/sync/session-actions'; +import { buildLinkedIssue } from '@/lib/linkedIssues'; +import { useConfigStore } from '@/stores/useConfigStore'; +import { useUIStore } from '@/stores/useUIStore'; +import { useGitLabAuthStore } from '@/stores/useGitLabAuthStore'; +import { renderMagicPrompt } from '@/lib/magicPrompts'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; +import { useDeviceInfo } from '@/lib/device'; +import { createWorktreeSessionForNewBranch } from '@/lib/worktreeSessionCreator'; +import { generateBranchSlug } from '@/lib/git/branchNameGenerator'; +import { useDebouncedValue } from '@/hooks/useDebouncedValue'; +import type { GitLabIssue, GitLabIssueComment, GitLabIssuesListResult, GitLabIssueSummary } from '@/lib/api/types'; +import { useI18n } from '@/lib/i18n'; + +const parseIssueNumber = (value: string): number | null => { + const trimmed = value.trim(); + if (!trimmed) return null; + + const urlMatch = trimmed.match(/\/issues\/(\d+)(?:\b|\/|$)/i); + if (urlMatch) { + const parsed = Number(urlMatch[1]); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; + } + + const hashMatch = trimmed.match(/^#?(\d+)$/); + if (hashMatch) { + const parsed = Number(hashMatch[1]); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; + } + + return null; +}; + +const buildIssueContextText = (args: { + repo: GitLabIssuesListResult['repo'] | undefined; + issue: GitLabIssue; + comments: GitLabIssueComment[]; +}) => { + const payload = { + repo: args.repo ?? null, + issue: args.issue, + comments: args.comments, + }; + return `GitLab issue context (JSON)\n${JSON.stringify(payload, null, 2)}`; +}; + +export function GitLabIssuePickerDialog({ + open, + onOpenChange, + mode = 'createSession', + onSelect, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + mode?: 'createSession' | 'select'; + onSelect?: (issue: { number: number; title: string; url: string; contextText: string; author?: { login: string; avatarUrl?: string } }) => void; +}) { + const { t } = useI18n(); + const { gitlab } = useRuntimeAPIs(); + const gitlabAuthStatus = useGitLabAuthStore((state) => state.status); + const gitlabAuthChecked = useGitLabAuthStore((state) => state.hasChecked); + const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen); + const setSettingsPage = useUIStore((state) => state.setSettingsPage); + const isMobile = useUIStore((state) => state.isMobile); + const { isTablet } = useDeviceInfo(); + const alwaysShowActions = isMobile || isTablet; + const activeProject = useProjectsStore((state) => state.getActiveProject()); + const currentDirectory = useDirectoryStore((state) => state.currentDirectory); + + const projectDirectory = React.useMemo(() => { + return activeProject?.path?.trim() || currentDirectory?.trim() || null; + }, [activeProject?.path, currentDirectory]); + + const [query, setQuery] = React.useState(''); + const [createInWorktree, setCreateInWorktree] = React.useState(false); + const [result, setResult] = React.useState(null); + const [issues, setIssues] = React.useState([]); + const [page, setPage] = React.useState(1); + const [hasMore, setHasMore] = React.useState(false); + const [startingIssueNumber, setStartingIssueNumber] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + const [isLoadingMore, setIsLoadingMore] = React.useState(false); + const [error, setError] = React.useState(null); + + const directNumber = React.useMemo(() => parseIssueNumber(query), [query]); + const debouncedQuery = useDebouncedValue(query, 350); + const isTextSearch = debouncedQuery.trim().length > 0 && !directNumber; + + const refresh = React.useCallback(async () => { + if (!projectDirectory) { + setResult(null); + setError(t('session.gitlabIssuePicker.error.noActiveProject')); + return; + } + if (gitlabAuthChecked && gitlabAuthStatus?.connected === false) { + setResult({ connected: false, issues: [], page: 1, hasMore: false }); + setIssues([]); + setHasMore(false); + setPage(1); + setError(null); + return; + } + if (!gitlab?.issuesList) { + setResult(null); + setError(t('session.gitlabIssuePicker.error.runtimeUnavailable')); + return; + } + + setIsLoading(true); + setError(null); + try { + const next = await gitlab.issuesList(projectDirectory, { page: 1 }); + setResult(next); + setIssues(next.issues ?? []); + setPage(next.page ?? 1); + setHasMore(Boolean(next.hasMore)); + if (next.connected === false) { + setError(null); + } + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setIsLoading(false); + } + }, [gitlab, gitlabAuthChecked, gitlabAuthStatus, projectDirectory, t]); + + React.useEffect(() => { + if (!open || !projectDirectory) return; + if (gitlabAuthChecked && gitlabAuthStatus?.connected === false) return; + if (!gitlab?.issuesList) return; + if (!debouncedQuery.trim() || directNumber) { + void refresh(); + return; + } + + const controller = new AbortController(); + setIsLoading(true); + setError(null); + + gitlab.issuesList(projectDirectory, { page: 1, query: debouncedQuery.trim() }) + .then((next) => { + if (controller.signal.aborted) return; + setResult(next); + setIssues(next.issues ?? []); + setPage(next.page ?? 1); + setHasMore(Boolean(next.hasMore)); + }) + .catch((e) => { + if (controller.signal.aborted) return; + setError(e instanceof Error ? e.message : String(e)); + }) + .finally(() => { + if (!controller.signal.aborted) setIsLoading(false); + }); + + return () => controller.abort(); + }, [open, projectDirectory, gitlab, gitlabAuthChecked, gitlabAuthStatus, debouncedQuery, directNumber, refresh, t]); + + const loadMore = React.useCallback(async () => { + if (!projectDirectory) return; + if (!gitlab?.issuesList) return; + if (isLoadingMore || isLoading) return; + if (!hasMore) return; + + setIsLoadingMore(true); + try { + const nextPage = page + 1; + const next = isTextSearch + ? await gitlab.issuesList(projectDirectory, { page: nextPage, query: debouncedQuery.trim() }) + : await gitlab.issuesList(projectDirectory, { page: nextPage }); + setResult(next); + setIssues((prev) => [...prev, ...(next.issues ?? [])]); + setPage(next.page ?? nextPage); + setHasMore(Boolean(next.hasMore)); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + toast.error(t('session.gitlabIssuePicker.toast.loadMoreFailed'), { description: message }); + } finally { + setIsLoadingMore(false); + } + }, [gitlab, hasMore, isLoading, isLoadingMore, isTextSearch, debouncedQuery, page, projectDirectory, t]); + + React.useEffect(() => { + if (!open) { + setQuery(''); + setCreateInWorktree(false); + setStartingIssueNumber(null); + setError(null); + setResult(null); + setIssues([]); + setPage(1); + setHasMore(false); + setIsLoading(false); + return; + } + void refresh(); + }, [open, refresh]); + + React.useEffect(() => { + if (!open) return; + if (gitlabAuthChecked && gitlabAuthStatus?.connected === false) { + setResult({ connected: false, issues: [], page: 1, hasMore: false }); + setIssues([]); + setHasMore(false); + setPage(1); + setError(null); + } + }, [gitlabAuthChecked, gitlabAuthStatus, open]); + + const connected = gitlabAuthChecked ? result?.connected !== false : true; + const repoUrl = result?.repo?.url ?? null; + + const openGitLabSettings = React.useCallback(() => { + setSettingsPage('git'); + setSettingsDialogOpen(true); + }, [setSettingsDialogOpen, setSettingsPage]); + + const resolveDefaultAgentName = React.useCallback((): string | undefined => { + const configState = useConfigStore.getState(); + const visibleAgents = configState.getVisibleAgents(); + + if (configState.settingsDefaultAgent) { + const settingsAgent = visibleAgents.find((a) => a.name === configState.settingsDefaultAgent); + if (settingsAgent) { + return settingsAgent.name; + } + } + + return ( + visibleAgents.find((agent) => agent.name === 'build')?.name || + visibleAgents[0]?.name + ); + }, []); + + const resolveDefaultModelSelection = React.useCallback((): { providerID: string; modelID: string } | null => { + const configState = useConfigStore.getState(); + const settingsDefaultModel = configState.settingsDefaultModel; + if (!settingsDefaultModel) { + return null; + } + + const parsed = parseModelIdentifier(settingsDefaultModel); + if (!parsed) { + return null; + } + const { providerId: providerID, modelId: modelID } = parsed; + + const modelMetadata = configState.getModelMetadata(providerID, modelID); + if (!modelMetadata) { + return null; + } + + return { providerID, modelID }; + }, []); + + const resolveDefaultVariant = React.useCallback((providerID: string, modelID: string): string | undefined => { + const configState = useConfigStore.getState(); + const settingsDefaultVariant = configState.settingsDefaultVariant; + const currentVariant = configState.currentProviderId === providerID && configState.currentModelId === modelID + ? configState.currentVariant + : undefined; + + const provider = configState.providers.find((p) => p.id === providerID); + const model = provider?.models.find((m: Record) => (m as { id?: string }).id === modelID) as + | { variants?: Record } + | undefined; + const variants = model?.variants; + if (!variants) { + return settingsDefaultVariant || currentVariant || undefined; + } + if (settingsDefaultVariant && Object.prototype.hasOwnProperty.call(variants, settingsDefaultVariant)) { + return settingsDefaultVariant; + } + if (currentVariant && Object.prototype.hasOwnProperty.call(variants, currentVariant)) { + return currentVariant; + } + return undefined; + }, []); + + const startSession = React.useCallback(async (issueNumber: number) => { + if (mode === 'select') { + // In select mode, fetch full issue details and return via onSelect + if (!projectDirectory) { + toast.error(t('session.gitlabIssuePicker.error.noActiveProject')); + return; + } + if (!gitlab?.issueGet || !gitlab?.issueComments) { + toast.error(t('session.gitlabIssuePicker.error.runtimeUnavailable')); + return; + } + if (startingIssueNumber) return; + setStartingIssueNumber(issueNumber); + try { + const issueRes = await gitlab.issueGet(projectDirectory, issueNumber); + if (issueRes.connected === false) { + toast.error(t('session.gitlabIssuePicker.error.notConnected')); + return; + } + if (!issueRes.repo) { + toast.error(t('session.gitlabIssuePicker.error.repoNotResolvable'), { + description: t('session.gitlabIssuePicker.error.repoMustBeGitlab'), + }); + return; + } + const issue = issueRes.issue; + if (!issue) { + toast.error(t('session.gitlabIssuePicker.error.issueNotFound')); + return; + } + + const commentsRes = await gitlab.issueComments(projectDirectory, issueNumber); + if (commentsRes.connected === false) { + toast.error(t('session.gitlabIssuePicker.error.notConnected')); + return; + } + const comments = commentsRes.comments ?? []; + + // Build full context text like in createSession mode + const contextText = buildIssueContextText({ repo: issueRes.repo, issue, comments }); + + if (onSelect) { + onSelect({ + number: issue.number, + title: issue.title, + url: issue.url, + contextText, + author: issue.author ? { + login: issue.author.username, + avatarUrl: issue.author.avatarUrl, + } : undefined, + }); + } + onOpenChange(false); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + toast.error(t('session.gitlabIssuePicker.toast.loadIssueDetailsFailed'), { description: message }); + } finally { + setStartingIssueNumber(null); + } + return; + } + + if (!projectDirectory) { + toast.error(t('session.gitlabIssuePicker.error.noActiveProject')); + return; + } + if (!gitlab?.issueGet || !gitlab?.issueComments) { + toast.error(t('session.gitlabIssuePicker.error.runtimeUnavailable')); + return; + } + if (startingIssueNumber) return; + setStartingIssueNumber(issueNumber); + try { + const issueRes = await gitlab.issueGet(projectDirectory, issueNumber); + if (issueRes.connected === false) { + toast.error(t('session.gitlabIssuePicker.error.notConnected')); + return; + } + if (!issueRes.repo) { + toast.error(t('session.gitlabIssuePicker.error.repoNotResolvable'), { + description: t('session.gitlabIssuePicker.error.repoMustBeGitlab'), + }); + return; + } + const issue = issueRes.issue; + if (!issue) { + toast.error(t('session.gitlabIssuePicker.error.issueNotFound')); + return; + } + + const commentsRes = await gitlab.issueComments(projectDirectory, issueNumber); + if (commentsRes.connected === false) { + toast.error(t('session.gitlabIssuePicker.error.notConnected')); + return; + } + const comments = commentsRes.comments ?? []; + + const sessionTitle = `#${issue.number} ${issue.title}`.trim(); + + const { sessionId, sessionDirectory } = await (async () => { + if (createInWorktree) { + const preferred = `issue-${issue.number}-${generateBranchSlug()}`; + const created = await createWorktreeSessionForNewBranch( + projectDirectory, + preferred, + undefined, + { returnAfterDirectoryCreated: true } + ); + if (!created?.id) { + throw new Error('Failed to create worktree session'); + } + return { sessionId: created.id, sessionDirectory: created.path }; + } + + const session = await sessionActions.createSession(sessionTitle, projectDirectory, null); + if (!session?.id) { + throw new Error('Failed to create session'); + } + return { sessionId: session.id, sessionDirectory: session.directory ?? projectDirectory }; + })(); + + // Ensure worktree-based sessions also get the issue title. + void sessionActions.updateSessionTitle(sessionId, sessionTitle).catch(() => undefined); + + try { + useSessionUIStore.getState().initializeNewOpenChamberSession(sessionId, useConfigStore.getState().agents); + } catch { + // ignore + } + + // Close modal immediately after session exists (don't wait for message send). + onOpenChange(false); + + const configState = useConfigStore.getState(); + const lastUsedProvider = useSelectionStore.getState().lastUsedProvider; + + const defaultModel = resolveDefaultModelSelection(); + const providerID = defaultModel?.providerID || configState.currentProviderId || lastUsedProvider?.providerID; + const modelID = defaultModel?.modelID || configState.currentModelId || lastUsedProvider?.modelID; + const agentName = resolveDefaultAgentName() || configState.currentAgentName || undefined; + if (!providerID || !modelID) { + toast.error(t('session.gitlabIssuePicker.error.noModelSelected')); + return; + } + + const variant = resolveDefaultVariant(providerID, modelID); + + const visiblePromptText = await renderMagicPrompt('gitlab.issue.review.visible', { + issue_number: String(issue.number), + }); + const instructionsText = await renderMagicPrompt('gitlab.issue.review.instructions'); + const contextText = buildIssueContextText({ repo: issueRes.repo, issue, comments }); + + // Record the thread this session was created for, so it stays visible as + // a context source once the opening message has scrolled away. A + // snapshot, never re-fetched; a failed write must not fail the flow. + void sessionActions.setLinkedIssue( + sessionId, + sessionDirectory, + buildLinkedIssue({ + url: issue.url, + number: issue.number, + title: issue.title, + kind: 'issue', + author: issue.author ? { + login: issue.author.username, + avatarUrl: issue.author.avatarUrl, + } : undefined, + linkedAt: Date.now(), + }), + true, + ).catch(() => undefined); + + void useSessionUIStore.getState().sendMessage( + visiblePromptText, + providerID, + modelID, + agentName, + undefined, + undefined, + [ + { text: instructionsText, synthetic: true }, + { text: contextText, synthetic: true }, + ], + variant, + undefined, + { sessionId }, + ).catch((e) => { + const message = e instanceof Error ? e.message : String(e); + toast.error(t('session.gitlabIssuePicker.toast.sendContextFailed'), { + description: message, + }); + }); + + toast.success(t('session.gitlabIssuePicker.toast.sessionCreated')); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + toast.error(t('session.gitlabIssuePicker.toast.startSessionFailed'), { description: message }); + } finally { + setStartingIssueNumber(null); + } + }, [createInWorktree, gitlab, mode, onOpenChange, onSelect, projectDirectory, resolveDefaultAgentName, resolveDefaultModelSelection, resolveDefaultVariant, startingIssueNumber, t]); + + const title = mode === 'select' ? t('session.gitlabIssuePicker.title.select') : t('session.gitlabIssuePicker.title.createSession'); + const description = mode === 'select' + ? t('session.gitlabIssuePicker.description.select') + : t('session.gitlabIssuePicker.description.createSession'); + + const content = ( + <> +
+ + setQuery(e.target.value)} + className="pl-9 w-full" + /> +
+ +
+ {!projectDirectory ? ( +
{t('session.gitlabIssuePicker.empty.noActiveProject')}
+ ) : null} + + {!gitlab ? ( +
{t('session.gitlabIssuePicker.empty.runtimeUnavailable')}
+ ) : null} + + {isLoading ? ( +
+ + {t('session.gitlabIssuePicker.loading.issues')} +
+ ) : null} + + {connected === false ? ( +
+
{t('session.gitlabIssuePicker.empty.notConnected')}
+
+ +
+
+ ) : null} + + {error ? ( +
{error}
+ ) : null} + + {directNumber && projectDirectory && gitlab && connected ? ( +
void startSession(directNumber)} + > + # +

+ {t('session.gitlabIssuePicker.actions.useIssue', { number: directNumber })} +

+
+ {startingIssueNumber === directNumber ? ( + + ) : null} +
+
+ ) : null} + + {issues.length === 0 && !isLoading && connected && gitlab && projectDirectory ? ( +
{debouncedQuery.trim() ? t('session.gitlabIssuePicker.empty.noIssuesFound') : t('session.gitlabIssuePicker.empty.noOpenIssuesFound')}
+ ) : null} + + {issues.map((issue) => ( +
void startSession(issue.number)} + > + + #{issue.number} + +
+

+ {issue.title} +

+
+ + +
+ ))} + + {hasMore && connected && projectDirectory && gitlab ? ( +
+ +
+ ) : null} +
+ + {mode !== 'select' && ( +
+

{t('session.gitlabIssuePicker.actions.sectionTitle')}

+
+
setCreateInWorktree((v) => !v)} + onKeyDown={(e) => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + setCreateInWorktree((v) => !v); + } + }} + > + + {t('session.gitlabIssuePicker.actions.createInWorktree')} + (issue-<number>-<slug>) +
+
+
+ {repoUrl ? ( + + ) : null} + +
+
+
+ )} + + ); + + if (isMobile) { + return ( + onOpenChange(false)} + renderHeader={(closeButton) => ( +
+
+

{title}

+ {closeButton} +
+

{description}

+
+ )} + > + {content} +
+ ); + } + + return ( + + + + + + {title} + + + {description} + + + + {content} + + + ); +} diff --git a/packages/ui/src/components/session/GitLabMrPickerDialog.tsx b/packages/ui/src/components/session/GitLabMrPickerDialog.tsx new file mode 100644 index 00000000..335deef6 --- /dev/null +++ b/packages/ui/src/components/session/GitLabMrPickerDialog.tsx @@ -0,0 +1,477 @@ +import React from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; +import { toast } from '@/components/ui'; +import { Icon } from "@/components/icon/Icon"; +import { cn } from '@/lib/utils'; +import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; +import { useProjectsStore } from '@/stores/useProjectsStore'; +import { useUIStore } from '@/stores/useUIStore'; +import { useGitLabAuthStore } from '@/stores/useGitLabAuthStore'; +import { renderMagicPrompt } from '@/lib/magicPrompts'; +import { useDeviceInfo } from '@/lib/device'; +import { useDebouncedValue } from '@/hooks/useDebouncedValue'; +import type { GitLabMergeRequestContextResult, GitLabMergeRequestSummary, GitLabMergeRequestsListResult } from '@/lib/api/types'; +import { useI18n } from '@/lib/i18n'; + +const parsePrNumber = (value: string): number | null => { + const trimmed = value.trim(); + if (!trimmed) return null; + + const urlMatch = trimmed.match(/\/merge_requests\/(\d+)(?:\b|\/|$)/i); + if (urlMatch) { + const parsed = Number(urlMatch[1]); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; + } + + const shortMatch = trimmed.match(/^!?(\d+)$/); + if (shortMatch) { + const parsed = Number(shortMatch[1]); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; + } + + return null; +}; + +const buildMergeRequestContextText = (payload: GitLabMergeRequestContextResult) => { + return `GitLab merge request context (JSON)\n${JSON.stringify(payload, null, 2)}`; +}; + +export function GitLabMrPickerDialog({ + open, + onOpenChange, + onSelect, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + onSelect?: (mr: { + number: number; + title: string; + url: string; + head: string; + base: string; + includeDiff: boolean; + instructionsText: string; + contextText: string; + author?: { login: string; avatarUrl?: string }; + }) => void; +}) { + const { t } = useI18n(); + const { gitlab } = useRuntimeAPIs(); + const gitlabAuthStatus = useGitLabAuthStore((state) => state.status); + const gitlabAuthChecked = useGitLabAuthStore((state) => state.hasChecked); + const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen); + const setSettingsPage = useUIStore((state) => state.setSettingsPage); + const isMobile = useUIStore((state) => state.isMobile); + const { isTablet } = useDeviceInfo(); + const alwaysShowActions = isMobile || isTablet; + const activeProject = useProjectsStore((state) => state.getActiveProject()); + + const projectDirectory = activeProject?.path ?? null; + + const [query, setQuery] = React.useState(''); + const [includeDiff, setIncludeDiff] = React.useState(false); + const [result, setResult] = React.useState(null); + const [mrs, setMrs] = React.useState([]); + const [page, setPage] = React.useState(1); + const [hasMore, setHasMore] = React.useState(false); + const [loadingMrNumber, setLoadingMrNumber] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + const [isLoadingMore, setIsLoadingMore] = React.useState(false); + const [error, setError] = React.useState(null); + + const directNumber = React.useMemo(() => parsePrNumber(query), [query]); + const debouncedQuery = useDebouncedValue(query, 350); + const isTextSearch = debouncedQuery.trim().length > 0 && !directNumber; + + const refresh = React.useCallback(async () => { + if (!projectDirectory) { + setResult(null); + setError(t('session.gitlabMrPicker.error.noActiveProject')); + return; + } + if (gitlabAuthChecked && gitlabAuthStatus?.connected === false) { + setResult({ connected: false, mrs: [], page: 1, hasMore: false }); + setMrs([]); + setHasMore(false); + setPage(1); + setError(null); + return; + } + if (!gitlab?.mrsList) { + setResult(null); + setError(t('session.gitlabMrPicker.error.runtimeUnavailable')); + return; + } + + setIsLoading(true); + setError(null); + try { + const next = await gitlab.mrsList(projectDirectory, { page: 1 }); + setResult(next); + setMrs(next.mrs ?? []); + setPage(next.page ?? 1); + setHasMore(Boolean(next.hasMore)); + if (next.connected === false) { + setError(null); + } + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setIsLoading(false); + } + }, [gitlab, gitlabAuthChecked, gitlabAuthStatus, projectDirectory, t]); + + React.useEffect(() => { + if (!open || !projectDirectory) return; + if (gitlabAuthChecked && gitlabAuthStatus?.connected === false) return; + if (!gitlab?.mrsList) return; + if (!debouncedQuery.trim() || directNumber) { + void refresh(); + return; + } + + const controller = new AbortController(); + setIsLoading(true); + setError(null); + + gitlab.mrsList(projectDirectory, { page: 1, query: debouncedQuery.trim() }) + .then((next) => { + if (controller.signal.aborted) return; + setResult(next); + setMrs(next.mrs ?? []); + setPage(next.page ?? 1); + setHasMore(Boolean(next.hasMore)); + }) + .catch((e) => { + if (controller.signal.aborted) return; + setError(e instanceof Error ? e.message : String(e)); + }) + .finally(() => { + if (!controller.signal.aborted) setIsLoading(false); + }); + + return () => controller.abort(); + }, [open, projectDirectory, gitlab, gitlabAuthChecked, gitlabAuthStatus, debouncedQuery, directNumber, refresh, t]); + + const loadMore = React.useCallback(async () => { + if (!projectDirectory) return; + if (!gitlab?.mrsList) return; + if (isLoadingMore || isLoading) return; + if (!hasMore) return; + + setIsLoadingMore(true); + try { + const nextPage = page + 1; + const next = isTextSearch + ? await gitlab.mrsList(projectDirectory, { page: nextPage, query: debouncedQuery.trim() }) + : await gitlab.mrsList(projectDirectory, { page: nextPage }); + setResult(next); + setMrs((prev) => [...prev, ...(next.mrs ?? [])]); + setPage(next.page ?? nextPage); + setHasMore(Boolean(next.hasMore)); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + toast.error(t('session.gitlabMrPicker.toast.loadMoreFailed'), { description: message }); + } finally { + setIsLoadingMore(false); + } + }, [gitlab, hasMore, isLoading, isLoadingMore, isTextSearch, debouncedQuery, page, projectDirectory, t]); + + React.useEffect(() => { + if (!open) { + setQuery(''); + setIncludeDiff(false); + setLoadingMrNumber(null); + setError(null); + setResult(null); + setMrs([]); + setPage(1); + setHasMore(false); + setIsLoading(false); + return; + } + void refresh(); + }, [open, refresh]); + + React.useEffect(() => { + if (!open) return; + if (gitlabAuthChecked && gitlabAuthStatus?.connected === false) { + setResult({ connected: false, mrs: [], page: 1, hasMore: false }); + setMrs([]); + setHasMore(false); + setPage(1); + setError(null); + } + }, [gitlabAuthChecked, gitlabAuthStatus, open]); + + const connected = gitlabAuthChecked ? result?.connected !== false : true; + + const openGitLabSettings = React.useCallback(() => { + setSettingsPage('git'); + setSettingsDialogOpen(true); + }, [setSettingsDialogOpen, setSettingsPage]); + + const attachMr = React.useCallback(async (mrNumber: number) => { + if (!projectDirectory) { + toast.error(t('session.gitlabMrPicker.error.noActiveProject')); + return; + } + if (!gitlab?.mrContext) { + toast.error(t('session.gitlabMrPicker.error.runtimeUnavailable')); + return; + } + if (loadingMrNumber) return; + + setLoadingMrNumber(mrNumber); + try { + const context = await gitlab.mrContext(projectDirectory, mrNumber, { + includeDiff, + }); + + if (context.connected === false) { + toast.error(t('session.gitlabMrPicker.error.notConnected')); + return; + } + + if (!context.mr) { + toast.error(t('session.gitlabMrPicker.error.mrNotFound')); + return; + } + + if (!context.repo) { + toast.error(t('session.gitlabMrPicker.error.repoNotResolvable'), { + description: t('session.gitlabMrPicker.error.repoMustBeGitlab'), + }); + return; + } + + if (onSelect) { + const instructionsText = await renderMagicPrompt('gitlab.pr.review.instructions'); + onSelect({ + number: context.mr.number, + title: context.mr.title, + url: context.mr.url, + head: context.mr.sourceBranch, + base: context.mr.targetBranch, + includeDiff, + instructionsText, + contextText: buildMergeRequestContextText(context), + author: context.mr.author + ? { + login: context.mr.author.username, + avatarUrl: context.mr.author.avatarUrl, + } + : undefined, + }); + } + onOpenChange(false); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + toast.error(t('session.gitlabMrPicker.toast.loadDetailsFailed'), { description: message }); + } finally { + setLoadingMrNumber(null); + } + }, [gitlab, includeDiff, loadingMrNumber, onOpenChange, onSelect, projectDirectory, t]); + + const title = t('session.gitlabMrPicker.title'); + const description = t('session.gitlabMrPicker.description'); + + const content = ( + <> +
+
+ + setQuery(e.target.value)} + className="pl-9 w-full" + /> +
+ +
+ +
+ {!projectDirectory ? ( +
{t('session.gitlabMrPicker.empty.noActiveProject')}
+ ) : null} + + {!gitlab ? ( +
{t('session.gitlabMrPicker.empty.runtimeUnavailable')}
+ ) : null} + + {isLoading ? ( +
+ + {t('session.gitlabMrPicker.loading.mergeRequests')} +
+ ) : null} + + {connected === false ? ( +
+
{t('session.gitlabMrPicker.empty.notConnected')}
+
+ +
+
+ ) : null} + + {error ? ( +
{error}
+ ) : null} + + {directNumber && projectDirectory && gitlab && connected ? ( +
void attachMr(directNumber)} + > + ! +

+ {t('session.gitlabMrPicker.actions.useMergeRequest', { number: directNumber })} +

+
+ {loadingMrNumber === directNumber ? ( + + ) : null} +
+
+ ) : null} + + {mrs.length === 0 && !isLoading && connected && gitlab && projectDirectory ? ( +
{debouncedQuery.trim() ? t('session.gitlabMrPicker.empty.noMergeRequestsFound') : t('session.gitlabMrPicker.empty.noOpenMergeRequestsFound')}
+ ) : null} + + {mrs.map((mr) => ( +
void attachMr(mr.number)} + > +
+

+ !{mr.number} + {mr.title} +

+

{mr.sourceBranch} → {mr.targetBranch}

+
+ + +
+ ))} + + {hasMore && connected && projectDirectory && gitlab ? ( +
+ +
+ ) : null} +
+ + ); + + if (isMobile) { + return ( + onOpenChange(false)} + renderHeader={(closeButton) => ( +
+
+

{title}

+ {closeButton} +
+

{description}

+
+ )} + > + {content} +
+ ); + } + + return ( + + + + + + {title} + + + {description} + + + + {content} + + + ); +} diff --git a/packages/ui/src/lib/i18n/messages/de.ts b/packages/ui/src/lib/i18n/messages/de.ts index 6c24957c..5fdae5eb 100644 --- a/packages/ui/src/lib/i18n/messages/de.ts +++ b/packages/ui/src/lib/i18n/messages/de.ts @@ -1683,6 +1683,39 @@ export const dict = { 'session.githubIssuePicker.actions.createInWorktree': 'In Worktree erstellen', 'session.githubIssuePicker.actions.openRepo': 'Repository öffnen', 'session.githubIssuePicker.actions.refresh': 'Aktualisieren', + 'session.gitlabIssuePicker.error.noActiveProject': 'Kein aktives Projekt', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'GitLab-Laufzeit-API nicht verfügbar', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab nicht verbunden', + 'session.gitlabIssuePicker.error.repoNotResolvable': 'Repository kann nicht aufgelöst werden', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'Das origin-Remote muss eine GitLab-URL sein', + 'session.gitlabIssuePicker.error.issueNotFound': 'Problem nicht gefunden', + 'session.gitlabIssuePicker.error.noModelSelected': 'Kein Modell ausgewählt', + 'session.gitlabIssuePicker.toast.loadMoreFailed': 'Fehler beim Laden weiterer Probleme', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': 'Fehler beim Laden der Problemdetails', + 'session.gitlabIssuePicker.toast.sendContextFailed': 'Fehler beim Senden des Problem-Kontexts', + 'session.gitlabIssuePicker.toast.sessionCreated': 'Sitzung aus Problem erstellt', + 'session.gitlabIssuePicker.toast.startSessionFailed': 'Fehler beim Starten der Sitzung', + 'session.gitlabIssuePicker.title.select': 'GitLab-Problem verknüpfen', + 'session.gitlabIssuePicker.title.createSession': 'Neue Sitzung aus GitLab-Problem', + 'session.gitlabIssuePicker.description.select': 'Wählen Sie ein Problem, um es mit dieser Sitzung zu verknüpfen.', + 'session.gitlabIssuePicker.description.createSession': 'Erstellt eine neue Sitzung mit verstecktem Problem-Kontext (Titel/Inhalt/Labels/Kommentare).', + 'session.gitlabIssuePicker.searchPlaceholder': 'Suche nach Titel oder fügen Sie eine Problem-URL ein', + 'session.gitlabIssuePicker.empty.noActiveProject': 'Kein aktives Projekt ausgewählt.', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'GitLab-Laufzeit-API nicht verfügbar.', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab nicht verbunden. Verbinden Sie Ihr GitLab-Konto in den Einstellungen.', + 'session.gitlabIssuePicker.empty.noIssuesFound': 'Keine Probleme gefunden', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': 'Keine offenen Probleme gefunden', + 'session.gitlabIssuePicker.loading.issues': 'Probleme werden geladen...', + 'session.gitlabIssuePicker.loading.more': 'Wird geladen...', + 'session.gitlabIssuePicker.actions.openSettings': 'Einstellungen öffnen', + 'session.gitlabIssuePicker.actions.useIssue': 'Problem #{number} verwenden', + 'session.gitlabIssuePicker.actions.openInGitLabAria': 'In GitLab öffnen', + 'session.gitlabIssuePicker.actions.loadMore': 'Mehr laden', + 'session.gitlabIssuePicker.actions.sectionTitle': 'Aktionen', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': 'Worktree umschalten', + 'session.gitlabIssuePicker.actions.createInWorktree': 'In Worktree erstellen', + 'session.gitlabIssuePicker.actions.openRepo': 'Repository öffnen', + 'session.gitlabIssuePicker.actions.refresh': 'Aktualisieren', 'session.githubPrPicker.error.noActiveProject': 'Kein aktives Projekt', 'session.githubPrPicker.error.runtimeUnavailable': 'GitHub-Laufzeit-API nicht verfügbar', 'session.githubPrPicker.error.notConnected': 'GitHub nicht verbunden', @@ -1707,6 +1740,30 @@ export const dict = { 'session.githubPrPicker.actions.usePullRequest': 'Pull Request #{number} verwenden', 'session.githubPrPicker.actions.openInGitHubAria': 'In GitHub öffnen', 'session.githubPrPicker.actions.loadMore': 'Mehr laden', + 'session.gitlabMrPicker.error.noActiveProject': 'Kein aktives Projekt', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'GitLab-Laufzeit-API nicht verfügbar', + 'session.gitlabMrPicker.error.notConnected': 'GitLab nicht verbunden', + 'session.gitlabMrPicker.error.mrNotFound': 'Merge Request nicht gefunden', + 'session.gitlabMrPicker.error.repoNotResolvable': 'Repository kann nicht aufgelöst werden', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'Das origin-Remote muss eine GitLab-URL sein', + 'session.gitlabMrPicker.toast.loadMoreFailed': 'Fehler beim Laden weiterer Merge Requests', + 'session.gitlabMrPicker.toast.loadDetailsFailed': 'Fehler beim Laden der Merge-Request-Details', + 'session.gitlabMrPicker.title': 'GitLab-Merge Request verknüpfen', + 'session.gitlabMrPicker.description': 'Wählen Sie einen Merge Request aus, um den Überprüfungs-Kontext zu dieser Nachricht hinzuzufügen.', + 'session.gitlabMrPicker.searchPlaceholder': 'Suche nach Titel oder fügen Sie eine Merge-Request-URL ein', + 'session.gitlabMrPicker.includeDiffAria': 'MR-Diff in angehängten Kontext einfügen', + 'session.gitlabMrPicker.includeDiff': 'MR-Diff einfügen', + 'session.gitlabMrPicker.empty.noActiveProject': 'Kein aktives Projekt ausgewählt.', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'GitLab-Laufzeit-API nicht verfügbar.', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab nicht verbunden. Verbinden Sie Ihr GitLab-Konto in den Einstellungen.', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': 'Keine Merge Requests gefunden', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': 'Keine offenen Merge Requests gefunden', + 'session.gitlabMrPicker.loading.mergeRequests': 'Merge Requests werden geladen...', + 'session.gitlabMrPicker.loading.more': 'Wird geladen...', + 'session.gitlabMrPicker.actions.openSettings': 'Einstellungen öffnen', + 'session.gitlabMrPicker.actions.useMergeRequest': 'Merge Request !{number} verwenden', + 'session.gitlabMrPicker.actions.openInGitLabAria': 'In GitLab öffnen', + 'session.gitlabMrPicker.actions.loadMore': 'Mehr laden', 'session.newWorktree.title': 'Neuer Worktree', 'session.newWorktree.mode.newBranch': 'Neue Branch', 'session.newWorktree.mode.existingBranch': 'Vorhandener Branch', @@ -2010,6 +2067,8 @@ export const dict = { 'chat.chatInput.actions.addAttachment': 'Anhang hinzufügen', 'chat.chatInput.actions.linkGithubIssue': 'GitHub-Issue verknüpfen', 'chat.chatInput.actions.linkGithubPr': 'GitHub-PR verknüpfen', + 'chat.chatInput.actions.linkGitlabIssue': 'GitLab-Issue verknüpfen', + 'chat.chatInput.actions.linkGitlabMr': 'GitLab-MR verknüpfen', 'chat.chatInput.actions.modelAgentSettings': 'Modell- und Agenteneinstellungen', 'chat.chatInput.actions.sendMessageAria': 'Nachricht senden', 'chat.chatInput.actions.queueMessageAria': 'Nachricht in die Warteschlange stellen', diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index 3d19ce15..666113b2 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -1840,6 +1840,39 @@ export const dict = { 'session.githubIssuePicker.actions.createInWorktree': 'Create in worktree', 'session.githubIssuePicker.actions.openRepo': 'Open Repo', 'session.githubIssuePicker.actions.refresh': 'Refresh', + 'session.gitlabIssuePicker.error.noActiveProject': 'No active project', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'GitLab runtime API unavailable', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab not connected', + 'session.gitlabIssuePicker.error.repoNotResolvable': 'Repo not resolvable', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'origin remote must be a GitLab URL', + 'session.gitlabIssuePicker.error.issueNotFound': 'Issue not found', + 'session.gitlabIssuePicker.error.noModelSelected': 'No model selected', + 'session.gitlabIssuePicker.toast.loadMoreFailed': 'Failed to load more issues', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': 'Failed to load issue details', + 'session.gitlabIssuePicker.toast.sendContextFailed': 'Failed to send issue context', + 'session.gitlabIssuePicker.toast.sessionCreated': 'Session created from issue', + 'session.gitlabIssuePicker.toast.startSessionFailed': 'Failed to start session', + 'session.gitlabIssuePicker.title.select': 'Link GitLab Issue', + 'session.gitlabIssuePicker.title.createSession': 'New Session From GitLab Issue', + 'session.gitlabIssuePicker.description.select': 'Select an issue to link to this session.', + 'session.gitlabIssuePicker.description.createSession': 'Seeds a new session with hidden issue context (title/body/labels/comments).', + 'session.gitlabIssuePicker.searchPlaceholder': 'Search by title or paste an issue URL', + 'session.gitlabIssuePicker.empty.noActiveProject': 'No active project selected.', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'GitLab runtime API unavailable.', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab not connected. Connect your GitLab account in settings.', + 'session.gitlabIssuePicker.empty.noIssuesFound': 'No issues found', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': 'No open issues found', + 'session.gitlabIssuePicker.loading.issues': 'Loading issues...', + 'session.gitlabIssuePicker.loading.more': 'Loading...', + 'session.gitlabIssuePicker.actions.openSettings': 'Open settings', + 'session.gitlabIssuePicker.actions.useIssue': 'Use issue #{number}', + 'session.gitlabIssuePicker.actions.openInGitLabAria': 'Open in GitLab', + 'session.gitlabIssuePicker.actions.loadMore': 'Load more', + 'session.gitlabIssuePicker.actions.sectionTitle': 'Actions', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': 'Toggle worktree', + 'session.gitlabIssuePicker.actions.createInWorktree': 'Create in worktree', + 'session.gitlabIssuePicker.actions.openRepo': 'Open Repo', + 'session.gitlabIssuePicker.actions.refresh': 'Refresh', 'session.githubPrPicker.error.noActiveProject': 'No active project', 'session.githubPrPicker.error.runtimeUnavailable': 'GitHub runtime API unavailable', 'session.githubPrPicker.error.notConnected': 'GitHub not connected', @@ -1864,6 +1897,30 @@ export const dict = { 'session.githubPrPicker.actions.usePullRequest': 'Use pull request #{number}', 'session.githubPrPicker.actions.openInGitHubAria': 'Open in GitHub', 'session.githubPrPicker.actions.loadMore': 'Load more', + 'session.gitlabMrPicker.error.noActiveProject': 'No active project', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'GitLab runtime API unavailable', + 'session.gitlabMrPicker.error.notConnected': 'GitLab not connected', + 'session.gitlabMrPicker.error.mrNotFound': 'Merge request not found', + 'session.gitlabMrPicker.error.repoNotResolvable': 'Repo not resolvable', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'origin remote must be a GitLab URL', + 'session.gitlabMrPicker.toast.loadMoreFailed': 'Failed to load more merge requests', + 'session.gitlabMrPicker.toast.loadDetailsFailed': 'Failed to load merge request details', + 'session.gitlabMrPicker.title': 'Link GitLab Merge Request', + 'session.gitlabMrPicker.description': 'Select a merge request to attach review context to this message.', + 'session.gitlabMrPicker.searchPlaceholder': 'Search by title or paste a merge request URL', + 'session.gitlabMrPicker.includeDiffAria': 'Include MR diff in attached context', + 'session.gitlabMrPicker.includeDiff': 'Include MR diff', + 'session.gitlabMrPicker.empty.noActiveProject': 'No active project selected.', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'GitLab runtime API unavailable.', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab not connected. Connect your GitLab account in settings.', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': 'No merge requests found', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': 'No open merge requests found', + 'session.gitlabMrPicker.loading.mergeRequests': 'Loading merge requests...', + 'session.gitlabMrPicker.loading.more': 'Loading...', + 'session.gitlabMrPicker.actions.openSettings': 'Open settings', + 'session.gitlabMrPicker.actions.useMergeRequest': 'Use merge request !{number}', + 'session.gitlabMrPicker.actions.openInGitLabAria': 'Open in GitLab', + 'session.gitlabMrPicker.actions.loadMore': 'Load more', 'session.newWorktree.title': 'New Worktree', 'session.newWorktree.mode.newBranch': 'New Branch', 'session.newWorktree.mode.existingBranch': 'Existing Branch', @@ -2182,6 +2239,8 @@ export const dict = { 'chat.chatInput.actions.addAttachment': 'Add attachment', 'chat.chatInput.actions.linkGithubIssue': 'Link GitHub Issue', 'chat.chatInput.actions.linkGithubPr': 'Link GitHub PR', + 'chat.chatInput.actions.linkGitlabIssue': 'Link GitLab Issue', + 'chat.chatInput.actions.linkGitlabMr': 'Link GitLab MR', 'chat.chatInput.actions.modelAgentSettings': 'Model and agent settings', 'chat.chatInput.actions.sendMessageAria': 'Send message', 'chat.chatInput.actions.queueMessageAria': 'Queue message', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 47d7299e..51424df8 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1818,6 +1818,39 @@ export const dict: Record = { "session.githubIssuePicker.actions.createInWorktree": "Crear en worktree", "session.githubIssuePicker.actions.openRepo": "Abrir repositorio", "session.githubIssuePicker.actions.refresh": "Actualizar", + "session.gitlabIssuePicker.error.noActiveProject": "No hay ningún proyecto activo", + "session.gitlabIssuePicker.error.runtimeUnavailable": "API de runtime de GitLab no disponible", + "session.gitlabIssuePicker.error.notConnected": "GitLab no está conectado", + "session.gitlabIssuePicker.error.repoNotResolvable": "No se pudo resolver el repositorio", + "session.gitlabIssuePicker.error.repoMustBeGitlab": "El remoto origin debe ser una URL de GitLab", + "session.gitlabIssuePicker.error.issueNotFound": "No se encontró el issue", + "session.gitlabIssuePicker.error.noModelSelected": "No hay ningún modelo seleccionado", + "session.gitlabIssuePicker.toast.loadMoreFailed": "No se pudo cargar más issues", + "session.gitlabIssuePicker.toast.loadIssueDetailsFailed": "No se pudieron cargar los detalles del issue", + "session.gitlabIssuePicker.toast.sendContextFailed": "No se pudo enviar el contexto del issue", + "session.gitlabIssuePicker.toast.sessionCreated": "Sesión creada desde el issue", + "session.gitlabIssuePicker.toast.startSessionFailed": "No se pudo iniciar la sesión", + "session.gitlabIssuePicker.title.select": "Vincular issue de GitLab", + "session.gitlabIssuePicker.title.createSession": "Nueva sesión desde issue de GitLab", + "session.gitlabIssuePicker.description.select": "Selecciona un issue para vincularlo a esta sesión.", + "session.gitlabIssuePicker.description.createSession": "Inicia una nueva sesión con contexto oculto del issue (título/cuerpo/etiquetas/comentarios).", + "session.gitlabIssuePicker.searchPlaceholder": "Buscar por título o pega la URL del issue", + "session.gitlabIssuePicker.empty.noActiveProject": "No hay ningún proyecto activo seleccionado.", + "session.gitlabIssuePicker.empty.runtimeUnavailable": "API de runtime de GitLab no disponible.", + "session.gitlabIssuePicker.empty.notConnected": "GitLab no está conectado. Conecta tu cuenta de GitLab en configuración.", + "session.gitlabIssuePicker.empty.noIssuesFound": "No se encontraron issues", + "session.gitlabIssuePicker.empty.noOpenIssuesFound": "No se encontraron issues abiertos", + "session.gitlabIssuePicker.loading.issues": "Cargando issues...", + "session.gitlabIssuePicker.loading.more": "Cargando...", + "session.gitlabIssuePicker.actions.openSettings": "Abrir configuración", + "session.gitlabIssuePicker.actions.useIssue": "Usar issue #{number}", + "session.gitlabIssuePicker.actions.openInGitLabAria": "Abrir en GitLab", + "session.gitlabIssuePicker.actions.loadMore": "Cargar más", + "session.gitlabIssuePicker.actions.sectionTitle": "Acciones", + "session.gitlabIssuePicker.actions.toggleWorktreeAria": "Activar worktree", + "session.gitlabIssuePicker.actions.createInWorktree": "Crear en worktree", + "session.gitlabIssuePicker.actions.openRepo": "Abrir repositorio", + "session.gitlabIssuePicker.actions.refresh": "Actualizar", "session.githubPrPicker.error.noActiveProject": "No hay ningún proyecto activo", "session.githubPrPicker.error.runtimeUnavailable": "API de runtime de GitHub no disponible", "session.githubPrPicker.error.notConnected": "GitHub no está conectado", @@ -1842,6 +1875,30 @@ export const dict: Record = { "session.githubPrPicker.actions.usePullRequest": "Usar PR #{number}", "session.githubPrPicker.actions.openInGitHubAria": "Abrir en GitHub", "session.githubPrPicker.actions.loadMore": "Cargar más", + "session.gitlabMrPicker.error.noActiveProject": "No hay ningún proyecto activo", + "session.gitlabMrPicker.error.runtimeUnavailable": "API de runtime de GitLab no disponible", + "session.gitlabMrPicker.error.notConnected": "GitLab no está conectado", + "session.gitlabMrPicker.error.mrNotFound": "No se encontró el MR", + "session.gitlabMrPicker.error.repoNotResolvable": "No se pudo resolver el repositorio", + "session.gitlabMrPicker.error.repoMustBeGitlab": "El remoto origin debe ser una URL de GitLab", + "session.gitlabMrPicker.toast.loadMoreFailed": "No se pudieron cargar más MR", + "session.gitlabMrPicker.toast.loadDetailsFailed": "No se pudieron cargar los detalles del MR", + "session.gitlabMrPicker.title": "Vincular MR de GitLab", + "session.gitlabMrPicker.description": "Selecciona un MR para adjuntar contexto de revisión a este mensaje.", + "session.gitlabMrPicker.searchPlaceholder": "Buscar por título o pega la URL del MR", + "session.gitlabMrPicker.includeDiffAria": "Incluir diff del MR en el contexto adjunto", + "session.gitlabMrPicker.includeDiff": "Incluir diff del MR", + "session.gitlabMrPicker.empty.noActiveProject": "No hay ningún proyecto activo seleccionado.", + "session.gitlabMrPicker.empty.runtimeUnavailable": "API de runtime de GitLab no disponible.", + "session.gitlabMrPicker.empty.notConnected": "GitLab no está conectado. Conecta tu cuenta de GitLab en configuración.", + "session.gitlabMrPicker.empty.noMergeRequestsFound": "No se encontraron MR", + "session.gitlabMrPicker.empty.noOpenMergeRequestsFound": "No se encontraron MR abiertos", + "session.gitlabMrPicker.loading.mergeRequests": "Cargando MR...", + "session.gitlabMrPicker.loading.more": "Cargando...", + "session.gitlabMrPicker.actions.openSettings": "Abrir configuración", + "session.gitlabMrPicker.actions.useMergeRequest": "Usar MR !{number}", + "session.gitlabMrPicker.actions.openInGitLabAria": "Abrir en GitLab", + "session.gitlabMrPicker.actions.loadMore": "Cargar más", "session.newWorktree.title": "Nuevo worktree", "session.newWorktree.mode.newBranch": "Nueva rama", "session.newWorktree.mode.existingBranch": "Rama existente", @@ -2160,6 +2217,8 @@ export const dict: Record = { "chat.chatInput.actions.addAttachment": "Añadir adjunto", "chat.chatInput.actions.linkGithubIssue": "Vincular issue de GitHub", "chat.chatInput.actions.linkGithubPr": "Vincular PR de GitHub", + "chat.chatInput.actions.linkGitlabIssue": "Vincular issue de GitLab", + "chat.chatInput.actions.linkGitlabMr": "Vincular MR de GitLab", "chat.chatInput.actions.modelAgentSettings": "Configuración del modelo y agente", "chat.chatInput.actions.sendMessageAria": "Enviar mensaje", "chat.chatInput.actions.queueMessageAria": "Poner mensaje en cola", diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index 1911a1f3..4bc24344 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -1598,6 +1598,39 @@ export const dict = { 'session.githubIssuePicker.actions.createInWorktree': 'Créer dans le worktree', 'session.githubIssuePicker.actions.openRepo': 'Ouvrir le dépôt', 'session.githubIssuePicker.actions.refresh': 'Rafraîchir', + 'session.gitlabIssuePicker.error.noActiveProject': 'Aucun projet actif', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'Exécution GitLab API indisponible', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab non connecté', + 'session.gitlabIssuePicker.error.repoNotResolvable': 'Repo non résoluble', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'Le dépôt distant origin doit pointer vers une URL GitLab', + 'session.gitlabIssuePicker.error.issueNotFound': 'Problème introuvable', + 'session.gitlabIssuePicker.error.noModelSelected': 'Aucun modèle sélectionné', + 'session.gitlabIssuePicker.toast.loadMoreFailed': 'Échec du chargement d\'autres problèmes', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': 'Échec du chargement des détails du problème', + 'session.gitlabIssuePicker.toast.sendContextFailed': 'Échec de l\'envoi du contexte du problème', + 'session.gitlabIssuePicker.toast.sessionCreated': 'Session créée à partir du problème', + 'session.gitlabIssuePicker.toast.startSessionFailed': 'Échec du démarrage de la session', + 'session.gitlabIssuePicker.title.select': 'Lien vers le problème GitLab', + 'session.gitlabIssuePicker.title.createSession': 'Nouvelle session du problème GitLab', + 'session.gitlabIssuePicker.description.select': 'Sélectionnez un problème à lier à cette session.', + 'session.gitlabIssuePicker.description.createSession': 'Lance une nouvelle session avec un contexte de problème masqué (titre/corps/étiquettes/commentaires).', + 'session.gitlabIssuePicker.searchPlaceholder': 'Recherchez par titre ou collez l\'URL du problème', + 'session.gitlabIssuePicker.empty.noActiveProject': 'Aucun projet actif sélectionné.', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'L\'environnement d\'exécution GitLab API n\'est pas disponible.', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab non connecté. Connectez votre compte GitLab dans les paramètres.', + 'session.gitlabIssuePicker.empty.noIssuesFound': 'Aucun problème trouvé', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': 'Aucun problème ouvert trouvé', + 'session.gitlabIssuePicker.loading.issues': 'Chargement des problèmes...', + 'session.gitlabIssuePicker.loading.more': 'Chargement...', + 'session.gitlabIssuePicker.actions.openSettings': 'Ouvrir les paramètres', + 'session.gitlabIssuePicker.actions.useIssue': 'Utilisez le problème #{number}', + 'session.gitlabIssuePicker.actions.openInGitLabAria': 'Ouvrir dans GitLab', + 'session.gitlabIssuePicker.actions.loadMore': 'Charger plus', + 'session.gitlabIssuePicker.actions.sectionTitle': 'Actions', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': 'Basculer le worktree', + 'session.gitlabIssuePicker.actions.createInWorktree': 'Créer dans le worktree', + 'session.gitlabIssuePicker.actions.openRepo': 'Ouvrir le dépôt', + 'session.gitlabIssuePicker.actions.refresh': 'Rafraîchir', 'session.githubPrPicker.error.noActiveProject': 'Aucun projet actif', 'session.githubPrPicker.error.runtimeUnavailable': 'Exécution GitHub API indisponible', 'session.githubPrPicker.error.notConnected': 'GitHub non connecté', @@ -1622,6 +1655,30 @@ export const dict = { 'session.githubPrPicker.actions.usePullRequest': 'Utiliser la demande d\'extraction #{number}', 'session.githubPrPicker.actions.openInGitHubAria': 'Ouvrir dans GitHub', 'session.githubPrPicker.actions.loadMore': 'Charger plus', + 'session.gitlabMrPicker.error.noActiveProject': 'Aucun projet actif', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'Exécution GitLab API indisponible', + 'session.gitlabMrPicker.error.notConnected': 'GitLab non connecté', + 'session.gitlabMrPicker.error.mrNotFound': 'Demande de fusion introuvable', + 'session.gitlabMrPicker.error.repoNotResolvable': 'Repo non résoluble', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'Le dépôt distant origin doit pointer vers une URL GitLab', + 'session.gitlabMrPicker.toast.loadMoreFailed': 'Échec du chargement d\'autres demandes de fusion', + 'session.gitlabMrPicker.toast.loadDetailsFailed': 'Échec du chargement des détails de la demande de fusion', + 'session.gitlabMrPicker.title': 'Lier le MR GitLab', + 'session.gitlabMrPicker.description': 'Sélectionnez un MR pour joindre un contexte de revue à ce message.', + 'session.gitlabMrPicker.searchPlaceholder': 'Recherchez par titre ou collez l\'URL du MR', + 'session.gitlabMrPicker.includeDiffAria': 'Inclure le diff du MR dans le contexte ci-joint', + 'session.gitlabMrPicker.includeDiff': 'Inclure le diff du MR', + 'session.gitlabMrPicker.empty.noActiveProject': 'Aucun projet actif sélectionné.', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'L\'environnement d\'exécution GitLab API n\'est pas disponible.', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab non connecté. Connectez votre compte GitLab dans les paramètres.', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': 'Aucune demande de fusion trouvée', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': 'Aucune demande de fusion ouverte trouvée', + 'session.gitlabMrPicker.loading.mergeRequests': 'Chargement des demandes de fusion...', + 'session.gitlabMrPicker.loading.more': 'Chargement...', + 'session.gitlabMrPicker.actions.openSettings': 'Ouvrir les paramètres', + 'session.gitlabMrPicker.actions.useMergeRequest': 'Utiliser le MR !{number}', + 'session.gitlabMrPicker.actions.openInGitLabAria': 'Ouvrir dans GitLab', + 'session.gitlabMrPicker.actions.loadMore': 'Charger plus', 'session.newWorktree.title': 'Nouveau worktree', 'session.newWorktree.mode.newBranch': 'Nouvelle branche', 'session.newWorktree.mode.existingBranch': 'Branche existante', @@ -1903,6 +1960,8 @@ export const dict = { 'chat.chatInput.actions.addAttachment': 'Ajouter une pièce jointe', 'chat.chatInput.actions.linkGithubIssue': 'Lien vers le problème GitHub', 'chat.chatInput.actions.linkGithubPr': 'Lien GitHub PR', + 'chat.chatInput.actions.linkGitlabIssue': 'Lien vers le problème GitLab', + 'chat.chatInput.actions.linkGitlabMr': 'Lien GitLab MR', 'chat.chatInput.actions.modelAgentSettings': 'Paramètres du modèle et de l\'agent', 'chat.chatInput.actions.sendMessageAria': 'Envoyer un message', 'chat.chatInput.actions.queueMessageAria': 'Message de file d\'attente', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index 250e63af..bae492c6 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -1836,6 +1836,39 @@ export const dict: Record = { 'session.githubIssuePicker.actions.createInWorktree': 'ワークツリーで作成', 'session.githubIssuePicker.actions.openRepo': 'リポジトリを開く', 'session.githubIssuePicker.actions.refresh': '更新', + 'session.gitlabIssuePicker.error.noActiveProject': 'アクティブなプロジェクトがありません', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'GitLabランタイムAPIは利用できません', + 'session.gitlabIssuePicker.error.notConnected': 'GitLabに接続されていません', + 'session.gitlabIssuePicker.error.repoNotResolvable': 'リポジトリを解決できません', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'originリモートはGitLab URLである必要があります', + 'session.gitlabIssuePicker.error.issueNotFound': 'Issueが見つかりません', + 'session.gitlabIssuePicker.error.noModelSelected': 'モデルが選択されていません', + 'session.gitlabIssuePicker.toast.loadMoreFailed': 'さらにIssueを読み込めませんでした', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': 'Issueの詳細の読み込みに失敗しました', + 'session.gitlabIssuePicker.toast.sendContextFailed': 'Issueコンテキストの送信に失敗しました', + 'session.gitlabIssuePicker.toast.sessionCreated': 'Issueからセッションを作成しました', + 'session.gitlabIssuePicker.toast.startSessionFailed': 'セッションの開始に失敗しました', + 'session.gitlabIssuePicker.title.select': 'GitLab Issueをリンク', + 'session.gitlabIssuePicker.title.createSession': 'GitLab Issueから新しいセッション', + 'session.gitlabIssuePicker.description.select': 'このセッションにリンクするIssueを選択してください。', + 'session.gitlabIssuePicker.description.createSession': '非表示のIssueコンテキスト(タイトル/本文/ラベル/コメント)を含む新しいセッションを作成します。', + 'session.gitlabIssuePicker.searchPlaceholder': 'タイトルで検索するか、Issue URLを貼り付けてください', + 'session.gitlabIssuePicker.empty.noActiveProject': 'アクティブなプロジェクトが選択されていません。', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'GitLabランタイムAPIは利用できません。', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLabに接続されていません。設定でGitLabアカウントを接続してください。', + 'session.gitlabIssuePicker.empty.noIssuesFound': 'Issueが見つかりません', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': 'オープンなIssueが見つかりません', + 'session.gitlabIssuePicker.loading.issues': 'Issueを読み込み中...', + 'session.gitlabIssuePicker.loading.more': '読み込み中...', + 'session.gitlabIssuePicker.actions.openSettings': '設定を開く', + 'session.gitlabIssuePicker.actions.useIssue': 'Issue #{number}を使用', + 'session.gitlabIssuePicker.actions.openInGitLabAria': 'GitLabで開く', + 'session.gitlabIssuePicker.actions.loadMore': 'さらに読み込む', + 'session.gitlabIssuePicker.actions.sectionTitle': 'アクション', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': 'ワークツリーの切り替え', + 'session.gitlabIssuePicker.actions.createInWorktree': 'ワークツリーで作成', + 'session.gitlabIssuePicker.actions.openRepo': 'リポジトリを開く', + 'session.gitlabIssuePicker.actions.refresh': '更新', 'session.githubPrPicker.error.noActiveProject': 'アクティブなプロジェクトがありません', 'session.githubPrPicker.error.runtimeUnavailable': 'GitHubランタイムAPIは利用できません', 'session.githubPrPicker.error.notConnected': 'GitHubに接続されていません', @@ -1860,6 +1893,30 @@ export const dict: Record = { 'session.githubPrPicker.actions.usePullRequest': 'プルリクエスト #{number}を使用', 'session.githubPrPicker.actions.openInGitHubAria': 'GitHubで開く', 'session.githubPrPicker.actions.loadMore': 'さらに読み込む', + 'session.gitlabMrPicker.error.noActiveProject': 'アクティブなプロジェクトがありません', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'GitLabランタイムAPIは利用できません', + 'session.gitlabMrPicker.error.notConnected': 'GitLabに接続されていません', + 'session.gitlabMrPicker.error.mrNotFound': 'マージリクエストが見つかりません', + 'session.gitlabMrPicker.error.repoNotResolvable': 'リポジトリを解決できません', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'originリモートはGitLab URLである必要があります', + 'session.gitlabMrPicker.toast.loadMoreFailed': 'さらにマージリクエストを読み込めませんでした', + 'session.gitlabMrPicker.toast.loadDetailsFailed': 'マージリクエストの詳細の読み込みに失敗しました', + 'session.gitlabMrPicker.title': 'GitLabマージリクエストをリンク', + 'session.gitlabMrPicker.description': 'このメッセージにレビューコンテキストを添付するマージリクエストを選択してください。', + 'session.gitlabMrPicker.searchPlaceholder': 'タイトルで検索するか、マージリクエストURLを貼り付けてください', + 'session.gitlabMrPicker.includeDiffAria': '添付コンテキストにMR差分を含める', + 'session.gitlabMrPicker.includeDiff': 'MR差分を含める', + 'session.gitlabMrPicker.empty.noActiveProject': 'アクティブなプロジェクトが選択されていません。', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'GitLabランタイムAPIは利用できません。', + 'session.gitlabMrPicker.empty.notConnected': 'GitLabに接続されていません。設定でGitLabアカウントを接続してください。', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': 'マージリクエストが見つかりません', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': 'オープンなマージリクエストが見つかりません', + 'session.gitlabMrPicker.loading.mergeRequests': 'マージリクエストを読み込み中...', + 'session.gitlabMrPicker.loading.more': '読み込み中...', + 'session.gitlabMrPicker.actions.openSettings': '設定を開く', + 'session.gitlabMrPicker.actions.useMergeRequest': 'マージリクエスト !{number}を使用', + 'session.gitlabMrPicker.actions.openInGitLabAria': 'GitLabで開く', + 'session.gitlabMrPicker.actions.loadMore': 'さらに読み込む', 'session.newWorktree.title': '新しいワークツリー', 'session.newWorktree.mode.newBranch': '新しいブランチ', 'session.newWorktree.mode.existingBranch': '既存のブランチ', @@ -2178,6 +2235,8 @@ export const dict: Record = { 'chat.chatInput.actions.addAttachment': '添付ファイルを追加', 'chat.chatInput.actions.linkGithubIssue': 'GitHub Issueをリンク', 'chat.chatInput.actions.linkGithubPr': 'GitHub PRをリンク', + 'chat.chatInput.actions.linkGitlabIssue': 'GitLab Issueをリンク', + 'chat.chatInput.actions.linkGitlabMr': 'GitLab MRをリンク', 'chat.chatInput.actions.modelAgentSettings': 'モデルとエージェント設定', 'chat.chatInput.actions.sendMessageAria': 'メッセージを送信', 'chat.chatInput.actions.queueMessageAria': 'メッセージをキュー', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 96f95770..dbf204b8 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1842,6 +1842,39 @@ export const dict: Record = { 'session.githubIssuePicker.actions.createInWorktree': '워크트리에서 생성', 'session.githubIssuePicker.actions.openRepo': '레포지토리 열기', 'session.githubIssuePicker.actions.refresh': '새로고침', + 'session.gitlabIssuePicker.error.noActiveProject': '활성 프로젝트가 없습니다', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'GitLab 런타임 API를 사용할 수 없습니다', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab에 연결되어 있지 않습니다', + 'session.gitlabIssuePicker.error.repoNotResolvable': '저장소를 확인할 수 없습니다', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'origin 리모트는 GitLab URL이어야 합니다', + 'session.gitlabIssuePicker.error.issueNotFound': '이슈를 찾을 수 없습니다', + 'session.gitlabIssuePicker.error.noModelSelected': '선택된 모델이 없습니다', + 'session.gitlabIssuePicker.toast.loadMoreFailed': '이슈를 더 불러오지 못했습니다', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': '이슈 상세 정보를 불러오지 못했습니다', + 'session.gitlabIssuePicker.toast.sendContextFailed': '이슈 컨텍스트 전송에 실패했습니다', + 'session.gitlabIssuePicker.toast.sessionCreated': '이슈에서 세션을 생성했습니다', + 'session.gitlabIssuePicker.toast.startSessionFailed': '세션 시작에 실패했습니다', + 'session.gitlabIssuePicker.title.select': 'GitLab 이슈 연결', + 'session.gitlabIssuePicker.title.createSession': 'GitLab 이슈로 새 세션 만들기', + 'session.gitlabIssuePicker.description.select': '이 세션에 연결할 이슈를 선택하세요.', + 'session.gitlabIssuePicker.description.createSession': '제목, 본문, 라벨, 댓글을 숨겨진 이슈 컨텍스트로 새 세션에 추가합니다.', + 'session.gitlabIssuePicker.searchPlaceholder': '제목으로 검색하거나 이슈 URL을 붙여넣으세요', + 'session.gitlabIssuePicker.empty.noActiveProject': '선택된 활성 프로젝트가 없습니다', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'GitLab 런타임 API를 사용할 수 없습니다.', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab에 연결되지 않았습니다. 설정에서 GitLab 계정을 연결하세요.', + 'session.gitlabIssuePicker.empty.noIssuesFound': '이슈 없음', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': '열린 이슈가 없습니다', + 'session.gitlabIssuePicker.loading.issues': '이슈 로드 중…', + 'session.gitlabIssuePicker.loading.more': '로드 중…', + 'session.gitlabIssuePicker.actions.openSettings': '설정 열기', + 'session.gitlabIssuePicker.actions.useIssue': '이슈 #{number} 사용', + 'session.gitlabIssuePicker.actions.openInGitLabAria': 'GitLab에서 열기', + 'session.gitlabIssuePicker.actions.loadMore': '더 불러오기', + 'session.gitlabIssuePicker.actions.sectionTitle': '작업', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': '토글 워크트리', + 'session.gitlabIssuePicker.actions.createInWorktree': '워크트리에서 생성', + 'session.gitlabIssuePicker.actions.openRepo': '레포지토리 열기', + 'session.gitlabIssuePicker.actions.refresh': '새로고침', 'session.githubPrPicker.error.noActiveProject': '활성 프로젝트가 없습니다', 'session.githubPrPicker.error.runtimeUnavailable': 'GitHub 런타임 API를 사용할 수 없습니다', 'session.githubPrPicker.error.notConnected': 'GitHub에 연결되어 있지 않습니다', @@ -1866,6 +1899,30 @@ export const dict: Record = { 'session.githubPrPicker.actions.usePullRequest': 'PR #{number} 사용', 'session.githubPrPicker.actions.openInGitHubAria': 'GitHub에서 열기', 'session.githubPrPicker.actions.loadMore': '더 불러오기', + 'session.gitlabMrPicker.error.noActiveProject': '활성 프로젝트가 없습니다', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'GitLab 런타임 API를 사용할 수 없습니다', + 'session.gitlabMrPicker.error.notConnected': 'GitLab에 연결되어 있지 않습니다', + 'session.gitlabMrPicker.error.mrNotFound': 'MR을 찾을 수 없습니다', + 'session.gitlabMrPicker.error.repoNotResolvable': '저장소를 확인할 수 없습니다', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'origin 리모트는 GitLab URL이어야 합니다', + 'session.gitlabMrPicker.toast.loadMoreFailed': 'MR을 더 불러오지 못했습니다', + 'session.gitlabMrPicker.toast.loadDetailsFailed': 'MR 상세 정보를 불러오지 못했습니다', + 'session.gitlabMrPicker.title': 'GitLab MR 연결', + 'session.gitlabMrPicker.description': '이 메시지에 리뷰 컨텍스트로 첨부할 MR을 선택하세요.', + 'session.gitlabMrPicker.searchPlaceholder': '제목으로 검색하거나 MR URL을 붙여넣으세요', + 'session.gitlabMrPicker.includeDiffAria': 'MR 변경사항을 첨부 컨텍스트에 포함', + 'session.gitlabMrPicker.includeDiff': 'MR 변경사항 포함', + 'session.gitlabMrPicker.empty.noActiveProject': '선택된 활성 프로젝트가 없습니다', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'GitLab 런타임 API를 사용할 수 없습니다.', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab에 연결되지 않았습니다. 설정에서 GitLab 계정을 연결하세요.', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': 'MR이 없습니다', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': '열린 MR이 없습니다', + 'session.gitlabMrPicker.loading.mergeRequests': 'MR 불러오는 중…', + 'session.gitlabMrPicker.loading.more': '로드 중…', + 'session.gitlabMrPicker.actions.openSettings': '설정 열기', + 'session.gitlabMrPicker.actions.useMergeRequest': 'MR !{number} 사용', + 'session.gitlabMrPicker.actions.openInGitLabAria': 'GitLab에서 열기', + 'session.gitlabMrPicker.actions.loadMore': '더 불러오기', 'session.newWorktree.title': '새 워크트리', 'session.newWorktree.mode.newBranch': '새 브랜치', 'session.newWorktree.mode.existingBranch': '기존 브랜치', @@ -2182,6 +2239,8 @@ export const dict: Record = { 'chat.chatInput.actions.addAttachment': '첨부 파일 추가', 'chat.chatInput.actions.linkGithubIssue': 'GitHub 이슈 연결', 'chat.chatInput.actions.linkGithubPr': 'GitHub PR 연결', + 'chat.chatInput.actions.linkGitlabIssue': 'GitLab 이슈 연결', + 'chat.chatInput.actions.linkGitlabMr': 'GitLab MR 연결', 'chat.chatInput.actions.modelAgentSettings': '모델 및 에이전트 설정', 'chat.chatInput.actions.sendMessageAria': '보내기 메시지', 'chat.chatInput.actions.queueMessageAria': '메시지 대기열에 추가', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index 97ce96f5..24c1b104 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -1202,6 +1202,8 @@ export const dict: Record = { 'chat.chatInput.actions.commands': 'Commands', 'chat.chatInput.actions.linkGithubIssue': 'Link GitHub Issue', 'chat.chatInput.actions.linkGithubPr': 'Link GitHub PR', + 'chat.chatInput.actions.linkGitlabIssue': 'Połącz zgłoszenie GitLab', + 'chat.chatInput.actions.linkGitlabMr': 'Połącz merge request GitLab', 'chat.chatInput.actions.modelAgentSettings': 'Model and agent settings', 'chat.chatInput.actions.queueMessageAria': 'Queue message', 'chat.chatInput.actions.sendMessageAria': 'Send message', @@ -2694,6 +2696,39 @@ export const dict: Record = { 'session.githubIssuePicker.toast.sendContextFailed': 'Nie udało się wysłać kontekstu zgłoszenia', 'session.githubIssuePicker.toast.sessionCreated': 'Sesja utworzona ze zgłoszenia', 'session.githubIssuePicker.toast.startSessionFailed': 'Nie udało się uruchomić sesji', + 'session.gitlabIssuePicker.actions.createInWorktree': 'Utwórz w drzewie pracy', + 'session.gitlabIssuePicker.actions.loadMore': 'Załaduj więcej', + 'session.gitlabIssuePicker.actions.openInGitLabAria': 'Otwórz w GitLab', + 'session.gitlabIssuePicker.actions.openRepo': 'Otwórz repozytorium', + 'session.gitlabIssuePicker.actions.openSettings': 'Otwórz ustawienia', + 'session.gitlabIssuePicker.actions.refresh': 'Odśwież', + 'session.gitlabIssuePicker.actions.sectionTitle': 'Akcje', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': 'Przełącz drzewo pracy', + 'session.gitlabIssuePicker.actions.useIssue': 'Użyj zgłoszenia #{number}', + 'session.gitlabIssuePicker.description.createSession': 'Tworzy nową sesję z ukrytym kontekstem zgłoszenia (tytuł/treść/etykiety/komentarze).', + 'session.gitlabIssuePicker.description.select': 'Wybierz zgłoszenie, które chcesz powiązać z tą sesją.', + 'session.gitlabIssuePicker.empty.noActiveProject': 'Nie wybrano aktywnego projektu.', + 'session.gitlabIssuePicker.empty.noIssuesFound': 'Nie znaleziono zgłoszeń', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': 'Nie znaleziono otwartych zgłoszeń', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab nie jest połączony. Połącz konto GitLab w ustawieniach.', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'API środowiska GitLab jest niedostępne.', + 'session.gitlabIssuePicker.error.issueNotFound': 'Nie znaleziono zgłoszenia', + 'session.gitlabIssuePicker.error.noActiveProject': 'Brak aktywnego projektu', + 'session.gitlabIssuePicker.error.noModelSelected': 'Nie wybrano modelu', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab nie jest połączony', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'remote origin musi być adresem URL GitLab', + 'session.gitlabIssuePicker.error.repoNotResolvable': 'Nie udało się rozpoznać repozytorium', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'API środowiska GitLab jest niedostępne', + 'session.gitlabIssuePicker.loading.issues': 'Ładowanie zgłoszeń...', + 'session.gitlabIssuePicker.loading.more': 'Ładowanie...', + 'session.gitlabIssuePicker.searchPlaceholder': 'Szukaj po tytule lub wklej adres URL zgłoszenia', + 'session.gitlabIssuePicker.title.createSession': 'Nowa sesja ze zgłoszenia GitLab', + 'session.gitlabIssuePicker.title.select': 'Połącz zgłoszenie GitLab', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': 'Nie udało się załadować szczegółów zgłoszenia', + 'session.gitlabIssuePicker.toast.loadMoreFailed': 'Nie udało się załadować kolejnych zgłoszeń', + 'session.gitlabIssuePicker.toast.sendContextFailed': 'Nie udało się wysłać kontekstu zgłoszenia', + 'session.gitlabIssuePicker.toast.sessionCreated': 'Sesja utworzona ze zgłoszenia', + 'session.gitlabIssuePicker.toast.startSessionFailed': 'Nie udało się uruchomić sesji', 'session.githubPrPicker.actions.loadMore': 'Załaduj więcej', 'session.githubPrPicker.actions.openInGitHubAria': 'Otwórz w GitHub', 'session.githubPrPicker.actions.openSettings': 'Otwórz ustawienia', @@ -2718,6 +2753,30 @@ export const dict: Record = { 'session.githubPrPicker.title': 'Połącz pull request GitHub', 'session.githubPrPicker.toast.loadDetailsFailed': 'Nie udało się załadować szczegółów pull requesta', 'session.githubPrPicker.toast.loadMoreFailed': 'Nie udało się załadować kolejnych pull requestów', + 'session.gitlabMrPicker.actions.loadMore': 'Załaduj więcej', + 'session.gitlabMrPicker.actions.openInGitLabAria': 'Otwórz w GitLab', + 'session.gitlabMrPicker.actions.openSettings': 'Otwórz ustawienia', + 'session.gitlabMrPicker.actions.useMergeRequest': 'Użyj merge requesta !{number}', + 'session.gitlabMrPicker.description': 'Wybierz merge request, aby dołączyć kontekst recenzji do tej wiadomości.', + 'session.gitlabMrPicker.empty.noActiveProject': 'Nie wybrano aktywnego projektu.', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': 'Nie znaleziono merge requestów', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': 'Nie znaleziono otwartych merge requestów', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab nie jest połączony. Połącz konto GitLab w ustawieniach.', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'API środowiska GitLab jest niedostępne.', + 'session.gitlabMrPicker.error.mrNotFound': 'Nie znaleziono merge requesta', + 'session.gitlabMrPicker.error.noActiveProject': 'Brak aktywnego projektu', + 'session.gitlabMrPicker.error.notConnected': 'GitLab nie jest połączony', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'remote origin musi być adresem URL GitLab', + 'session.gitlabMrPicker.error.repoNotResolvable': 'Nie udało się rozpoznać repozytorium', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'API środowiska GitLab jest niedostępne', + 'session.gitlabMrPicker.includeDiff': 'Dołącz diff MR', + 'session.gitlabMrPicker.includeDiffAria': 'Dołącz diff MR do załączonego kontekstu', + 'session.gitlabMrPicker.loading.mergeRequests': 'Ładowanie merge requestów...', + 'session.gitlabMrPicker.loading.more': 'Ładowanie...', + 'session.gitlabMrPicker.searchPlaceholder': 'Szukaj po tytule lub wklej adres URL merge requesta', + 'session.gitlabMrPicker.title': 'Połącz merge request GitLab', + 'session.gitlabMrPicker.toast.loadDetailsFailed': 'Nie udało się załadować szczegółów merge requesta', + 'session.gitlabMrPicker.toast.loadMoreFailed': 'Nie udało się załadować kolejnych merge requestów', 'session.newWorktree.actions.cancel': 'Anuluj', 'session.newWorktree.actions.change': 'Zmień', 'session.newWorktree.actions.createWorktree': 'Utwórz drzewo pracy', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index deaf986f..fc6092f4 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1818,6 +1818,39 @@ export const dict: Record = { "session.githubIssuePicker.actions.createInWorktree": "Criar em worktree", "session.githubIssuePicker.actions.openRepo": "Abrir repositório", "session.githubIssuePicker.actions.refresh": "Atualizar", + "session.gitlabIssuePicker.error.noActiveProject": "Não há nenhum projeto ativo", + "session.gitlabIssuePicker.error.runtimeUnavailable": "API de runtime de GitLab não disponível", + "session.gitlabIssuePicker.error.notConnected": "GitLab não está conectado", + "session.gitlabIssuePicker.error.repoNotResolvable": "Não foi possível resolver o repositório", + "session.gitlabIssuePicker.error.repoMustBeGitlab": "O remoto origin deve ser uma URL de GitLab", + "session.gitlabIssuePicker.error.issueNotFound": "Issue não encontrada", + "session.gitlabIssuePicker.error.noModelSelected": "Não há nenhum modelo selecionado", + "session.gitlabIssuePicker.toast.loadMoreFailed": "Não foi possível carregar mais issues", + "session.gitlabIssuePicker.toast.loadIssueDetailsFailed": "Não foi possível carregar os detalhes da issue", + "session.gitlabIssuePicker.toast.sendContextFailed": "Não foi possível enviar o contexto da issue", + "session.gitlabIssuePicker.toast.sessionCreated": "Sessão criada a partir da issue", + "session.gitlabIssuePicker.toast.startSessionFailed": "Não foi possível iniciar a sessão", + "session.gitlabIssuePicker.title.select": "Vincular issue de GitLab", + "session.gitlabIssuePicker.title.createSession": "Nova sessão de issue de GitLab", + "session.gitlabIssuePicker.description.select": "Selecione uma issue para vinculá-la a esta sessão.", + "session.gitlabIssuePicker.description.createSession": "Inicie uma nova sessão com contexto oculto do issue (título/corpo/etiquetas/comentários).", + "session.gitlabIssuePicker.searchPlaceholder": "Pesquisar por título ou cole a URL da issue", + "session.gitlabIssuePicker.empty.noActiveProject": "Não há nenhum projeto ativo selecionado.", + "session.gitlabIssuePicker.empty.runtimeUnavailable": "API de runtime de GitLab não disponível.", + "session.gitlabIssuePicker.empty.notConnected": "GitLab não está conectado. Conecte sua conta do GitLab nas configurações.", + "session.gitlabIssuePicker.empty.noIssuesFound": "Nenhuma issue encontrada", + "session.gitlabIssuePicker.empty.noOpenIssuesFound": "Nenhuma issue aberta encontrada", + "session.gitlabIssuePicker.loading.issues": "Carregando issues...", + "session.gitlabIssuePicker.loading.more": "Carregando...", + "session.gitlabIssuePicker.actions.openSettings": "Abrir configurações", + "session.gitlabIssuePicker.actions.useIssue": "Usar issue #{number}", + "session.gitlabIssuePicker.actions.openInGitLabAria": "Abrir em GitLab", + "session.gitlabIssuePicker.actions.loadMore": "Carregar mais", + "session.gitlabIssuePicker.actions.sectionTitle": "Ações", + "session.gitlabIssuePicker.actions.toggleWorktreeAria": "Ativar worktree", + "session.gitlabIssuePicker.actions.createInWorktree": "Criar em worktree", + "session.gitlabIssuePicker.actions.openRepo": "Abrir repositório", + "session.gitlabIssuePicker.actions.refresh": "Atualizar", "session.githubPrPicker.error.noActiveProject": "Não há nenhum projeto ativo", "session.githubPrPicker.error.runtimeUnavailable": "API de runtime de GitHub não disponível", "session.githubPrPicker.error.notConnected": "GitHub não está conectado", @@ -1842,6 +1875,30 @@ export const dict: Record = { "session.githubPrPicker.actions.usePullRequest": "Usar PR #{number}", "session.githubPrPicker.actions.openInGitHubAria": "Abrir em GitHub", "session.githubPrPicker.actions.loadMore": "Carregar mais", + "session.gitlabMrPicker.error.noActiveProject": "Não há nenhum projeto ativo", + "session.gitlabMrPicker.error.runtimeUnavailable": "API de runtime de GitLab não disponível", + "session.gitlabMrPicker.error.notConnected": "GitLab não está conectado", + "session.gitlabMrPicker.error.mrNotFound": "MR não encontrado", + "session.gitlabMrPicker.error.repoNotResolvable": "Não foi possível resolver o repositório", + "session.gitlabMrPicker.error.repoMustBeGitlab": "O remoto origin deve ser uma URL de GitLab", + "session.gitlabMrPicker.toast.loadMoreFailed": "Não foi possível carregar mais MRs", + "session.gitlabMrPicker.toast.loadDetailsFailed": "Não foi possível carregar os detalhes do MR", + "session.gitlabMrPicker.title": "Vincular MR de GitLab", + "session.gitlabMrPicker.description": "Selecione um MR para anexar contexto de revisão a esta mensagem.", + "session.gitlabMrPicker.searchPlaceholder": "Pesquisar por título ou cole a URL do MR", + "session.gitlabMrPicker.includeDiffAria": "Incluir diff do MR no contexto adjunto", + "session.gitlabMrPicker.includeDiff": "Incluir diff do MR", + "session.gitlabMrPicker.empty.noActiveProject": "Não há nenhum projeto ativo selecionado.", + "session.gitlabMrPicker.empty.runtimeUnavailable": "API de runtime de GitLab não disponível.", + "session.gitlabMrPicker.empty.notConnected": "GitLab não está conectado. Conecte sua conta do GitLab nas configurações.", + "session.gitlabMrPicker.empty.noMergeRequestsFound": "Nenhum MR encontrado", + "session.gitlabMrPicker.empty.noOpenMergeRequestsFound": "Nenhum MR aberto encontrado", + "session.gitlabMrPicker.loading.mergeRequests": "Carregando MRs...", + "session.gitlabMrPicker.loading.more": "Carregando...", + "session.gitlabMrPicker.actions.openSettings": "Abrir configurações", + "session.gitlabMrPicker.actions.useMergeRequest": "Usar MR !{number}", + "session.gitlabMrPicker.actions.openInGitLabAria": "Abrir em GitLab", + "session.gitlabMrPicker.actions.loadMore": "Carregar mais", "session.newWorktree.title": "Novo worktree", "session.newWorktree.mode.newBranch": "Nova branch", "session.newWorktree.mode.existingBranch": "Branch existente", @@ -2160,6 +2217,8 @@ export const dict: Record = { "chat.chatInput.actions.addAttachment": "Adicionar adjunto", "chat.chatInput.actions.linkGithubIssue": "Vincular issue de GitHub", "chat.chatInput.actions.linkGithubPr": "Vincular PR de GitHub", + "chat.chatInput.actions.linkGitlabIssue": "Vincular issue de GitLab", + "chat.chatInput.actions.linkGitlabMr": "Vincular MR de GitLab", "chat.chatInput.actions.modelAgentSettings": "Configurações de modelo e agente", "chat.chatInput.actions.sendMessageAria": "Enviar mensagem", "chat.chatInput.actions.queueMessageAria": "Colocar mensagem na fila", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 88b9e7cf..b2d77d6b 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1818,6 +1818,39 @@ export const dict: Record = { "session.githubIssuePicker.actions.createInWorktree": "Створити в worktree", "session.githubIssuePicker.actions.openRepo": "Відкрити репозиторій", "session.githubIssuePicker.actions.refresh": "Оновити", + "session.gitlabIssuePicker.error.noActiveProject": "Немає активного проєкту", + "session.gitlabIssuePicker.error.runtimeUnavailable": "GitLab API недоступний", + "session.gitlabIssuePicker.error.notConnected": "GitLab не підключено", + "session.gitlabIssuePicker.error.repoNotResolvable": "Не вдалося визначити репозиторій", + "session.gitlabIssuePicker.error.repoMustBeGitlab": "remote origin має бути GitLab URL", + "session.gitlabIssuePicker.error.issueNotFound": "Issue не знайдено", + "session.gitlabIssuePicker.error.noModelSelected": "Модель не вибрано", + "session.gitlabIssuePicker.toast.loadMoreFailed": "Не вдалося завантажити додаткові issue", + "session.gitlabIssuePicker.toast.loadIssueDetailsFailed": "Не вдалося завантажити деталі issue", + "session.gitlabIssuePicker.toast.sendContextFailed": "Не вдалося надіслати контекст issue", + "session.gitlabIssuePicker.toast.sessionCreated": "Сесію створено з issue", + "session.gitlabIssuePicker.toast.startSessionFailed": "Не вдалося почати сесію", + "session.gitlabIssuePicker.title.select": "Пов’язати GitLab issue", + "session.gitlabIssuePicker.title.createSession": "Нова сесія з GitLab issue", + "session.gitlabIssuePicker.description.select": "Виберіть issue, щоб пов’язати її з цією сесією.", + "session.gitlabIssuePicker.description.createSession": "Запускає нову сесію із прихованим контекстом issue: заголовком, текстом, мітками й коментарями.", + "session.gitlabIssuePicker.searchPlaceholder": "Пошук за назвою або вставте URL issue", + "session.gitlabIssuePicker.empty.noActiveProject": "Не вибрано жодного активного проєкту.", + "session.gitlabIssuePicker.empty.runtimeUnavailable": "GitLab API недоступний.", + "session.gitlabIssuePicker.empty.notConnected": "GitLab не підключено. Підключіть обліковий запис GitLab у налаштуваннях.", + "session.gitlabIssuePicker.empty.noIssuesFound": "Issue не знайдено", + "session.gitlabIssuePicker.empty.noOpenIssuesFound": "Відкритих issue не знайдено", + "session.gitlabIssuePicker.loading.issues": "Завантаження issue...", + "session.gitlabIssuePicker.loading.more": "Завантаження...", + "session.gitlabIssuePicker.actions.openSettings": "Відкрити налаштування", + "session.gitlabIssuePicker.actions.useIssue": "Використовувати issue №{number}", + "session.gitlabIssuePicker.actions.openInGitLabAria": "Відкрити в GitLab", + "session.gitlabIssuePicker.actions.loadMore": "Завантажити ще", + "session.gitlabIssuePicker.actions.sectionTitle": "Дії", + "session.gitlabIssuePicker.actions.toggleWorktreeAria": "Перемкнути worktree", + "session.gitlabIssuePicker.actions.createInWorktree": "Створити в worktree", + "session.gitlabIssuePicker.actions.openRepo": "Відкрити репозиторій", + "session.gitlabIssuePicker.actions.refresh": "Оновити", "session.githubPrPicker.error.noActiveProject": "Немає активного проєкту", "session.githubPrPicker.error.runtimeUnavailable": "GitHub API недоступний", "session.githubPrPicker.error.notConnected": "GitHub не підключено", @@ -1842,6 +1875,30 @@ export const dict: Record = { "session.githubPrPicker.actions.usePullRequest": "Використовувати PR #{number}", "session.githubPrPicker.actions.openInGitHubAria": "Відкрити в GitHub", "session.githubPrPicker.actions.loadMore": "Завантажити ще", + "session.gitlabMrPicker.error.noActiveProject": "Немає активного проєкту", + "session.gitlabMrPicker.error.runtimeUnavailable": "GitLab API недоступний", + "session.gitlabMrPicker.error.notConnected": "GitLab не підключено", + "session.gitlabMrPicker.error.mrNotFound": "MR не знайдено", + "session.gitlabMrPicker.error.repoNotResolvable": "Не вдалося визначити репозиторій", + "session.gitlabMrPicker.error.repoMustBeGitlab": "remote origin має бути GitLab URL", + "session.gitlabMrPicker.toast.loadMoreFailed": "Не вдалося завантажити додаткові MR", + "session.gitlabMrPicker.toast.loadDetailsFailed": "Не вдалося завантажити деталі MR", + "session.gitlabMrPicker.title": "Пов’язати GitLab MR", + "session.gitlabMrPicker.description": "Виберіть MR, щоб додати контекст рев’ю до цього повідомлення.", + "session.gitlabMrPicker.searchPlaceholder": "Пошук за назвою або вставте URL MR", + "session.gitlabMrPicker.includeDiffAria": "Додати diff MR у вкладений контекст", + "session.gitlabMrPicker.includeDiff": "Додати diff MR", + "session.gitlabMrPicker.empty.noActiveProject": "Не вибрано жодного активного проєкту.", + "session.gitlabMrPicker.empty.runtimeUnavailable": "GitLab API недоступний.", + "session.gitlabMrPicker.empty.notConnected": "GitLab не підключено. Підключіть обліковий запис GitLab у налаштуваннях.", + "session.gitlabMrPicker.empty.noMergeRequestsFound": "MR не знайдено", + "session.gitlabMrPicker.empty.noOpenMergeRequestsFound": "Не знайдено відкритих MR", + "session.gitlabMrPicker.loading.mergeRequests": "Завантаження MR...", + "session.gitlabMrPicker.loading.more": "Завантаження...", + "session.gitlabMrPicker.actions.openSettings": "Відкрити налаштування", + "session.gitlabMrPicker.actions.useMergeRequest": "Використовувати MR !{number}", + "session.gitlabMrPicker.actions.openInGitLabAria": "Відкрити в GitLab", + "session.gitlabMrPicker.actions.loadMore": "Завантажити ще", "session.newWorktree.title": "Нове worktree", "session.newWorktree.mode.newBranch": "Нова гілка", "session.newWorktree.mode.existingBranch": "Наявна гілка", @@ -2160,6 +2217,8 @@ export const dict: Record = { "chat.chatInput.actions.addAttachment": "Додати вкладення", "chat.chatInput.actions.linkGithubIssue": "Пов’язати GitHub issue", "chat.chatInput.actions.linkGithubPr": "Пов’язати GitHub PR", + "chat.chatInput.actions.linkGitlabIssue": "Пов’язати GitLab issue", + "chat.chatInput.actions.linkGitlabMr": "Пов’язати GitLab MR", "chat.chatInput.actions.modelAgentSettings": "Параметри моделі та агента", "chat.chatInput.actions.sendMessageAria": "Надіслати повідомлення", "chat.chatInput.actions.queueMessageAria": "Поставити повідомлення в чергу", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 06a404e9..892f2f60 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1806,6 +1806,39 @@ export const dict: Record = { 'session.githubIssuePicker.actions.createInWorktree': '在工作树中创建', 'session.githubIssuePicker.actions.openRepo': '打开仓库', 'session.githubIssuePicker.actions.refresh': '刷新', + 'session.gitlabIssuePicker.error.noActiveProject': '没有活动项目', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'GitLab 运行时 API 不可用', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab 未连接', + 'session.gitlabIssuePicker.error.repoNotResolvable': '无法解析仓库', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'origin 远程仓库必须是 GitLab URL', + 'session.gitlabIssuePicker.error.issueNotFound': '未找到 Issue', + 'session.gitlabIssuePicker.error.noModelSelected': '未选择模型', + 'session.gitlabIssuePicker.toast.loadMoreFailed': '加载更多 Issue 失败', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': '加载 Issue 详情失败', + 'session.gitlabIssuePicker.toast.sendContextFailed': '发送 Issue 上下文失败', + 'session.gitlabIssuePicker.toast.sessionCreated': '已从 Issue 创建会话', + 'session.gitlabIssuePicker.toast.startSessionFailed': '启动会话失败', + 'session.gitlabIssuePicker.title.select': '关联 GitLab Issue', + 'session.gitlabIssuePicker.title.createSession': '从 GitLab Issue 新建会话', + 'session.gitlabIssuePicker.description.select': '选择一个 Issue 关联到当前会话。', + 'session.gitlabIssuePicker.description.createSession': '使用隐藏的 Issue 上下文(标题/正文/标签/评论)初始化新会话。', + 'session.gitlabIssuePicker.searchPlaceholder': '按标题搜索,或粘贴 Issue URL', + 'session.gitlabIssuePicker.empty.noActiveProject': '未选择活动项目。', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'GitLab 运行时 API 不可用。', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab 未连接。请在设置中连接 GitLab 账号。', + 'session.gitlabIssuePicker.empty.noIssuesFound': '未找到 Issue', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': '没有未关闭的 Issue', + 'session.gitlabIssuePicker.loading.issues': '正在加载 Issue...', + 'session.gitlabIssuePicker.loading.more': '加载中...', + 'session.gitlabIssuePicker.actions.openSettings': '打开设置', + 'session.gitlabIssuePicker.actions.useIssue': '使用 Issue #{number}', + 'session.gitlabIssuePicker.actions.openInGitLabAria': '在 GitLab 中打开', + 'session.gitlabIssuePicker.actions.loadMore': '加载更多', + 'session.gitlabIssuePicker.actions.sectionTitle': '操作', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': '切换工作树', + 'session.gitlabIssuePicker.actions.createInWorktree': '在工作树中创建', + 'session.gitlabIssuePicker.actions.openRepo': '打开仓库', + 'session.gitlabIssuePicker.actions.refresh': '刷新', 'session.githubPrPicker.error.noActiveProject': '没有活动项目', 'session.githubPrPicker.error.runtimeUnavailable': 'GitHub 运行时 API 不可用', 'session.githubPrPicker.error.notConnected': 'GitHub 未连接', @@ -1830,6 +1863,30 @@ export const dict: Record = { 'session.githubPrPicker.actions.usePullRequest': '使用 Pull Request #{number}', 'session.githubPrPicker.actions.openInGitHubAria': '在 GitHub 中打开', 'session.githubPrPicker.actions.loadMore': '加载更多', + 'session.gitlabMrPicker.error.noActiveProject': '没有活动项目', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'GitLab 运行时 API 不可用', + 'session.gitlabMrPicker.error.notConnected': 'GitLab 未连接', + 'session.gitlabMrPicker.error.mrNotFound': '未找到 Merge Request', + 'session.gitlabMrPicker.error.repoNotResolvable': '无法解析仓库', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'origin 远程仓库必须是 GitLab URL', + 'session.gitlabMrPicker.toast.loadMoreFailed': '加载更多 Merge Request 失败', + 'session.gitlabMrPicker.toast.loadDetailsFailed': '加载 Merge Request 详情失败', + 'session.gitlabMrPicker.title': '关联 GitLab Merge Request', + 'session.gitlabMrPicker.description': '选择一个 Merge Request,将审查上下文附加到此消息。', + 'session.gitlabMrPicker.searchPlaceholder': '按标题搜索,或粘贴 Merge Request URL', + 'session.gitlabMrPicker.includeDiffAria': '在附加上下文中包含 MR 差异', + 'session.gitlabMrPicker.includeDiff': '包含 MR 差异', + 'session.gitlabMrPicker.empty.noActiveProject': '未选择活动项目。', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'GitLab 运行时 API 不可用。', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab 未连接。请在设置中连接 GitLab 账号。', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': '未找到 Merge Request', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': '没有未关闭的 Merge Request', + 'session.gitlabMrPicker.loading.mergeRequests': '正在加载 Merge Request...', + 'session.gitlabMrPicker.loading.more': '加载中...', + 'session.gitlabMrPicker.actions.openSettings': '打开设置', + 'session.gitlabMrPicker.actions.useMergeRequest': '使用 Merge Request !{number}', + 'session.gitlabMrPicker.actions.openInGitLabAria': '在 GitLab 中打开', + 'session.gitlabMrPicker.actions.loadMore': '加载更多', 'session.newWorktree.title': '新建工作树', 'session.newWorktree.mode.newBranch': '新分支', 'session.newWorktree.mode.existingBranch': '现有分支', @@ -2148,6 +2205,8 @@ export const dict: Record = { 'chat.chatInput.actions.addAttachment': '添加附件', 'chat.chatInput.actions.linkGithubIssue': '关联 GitHub Issue', 'chat.chatInput.actions.linkGithubPr': '关联 GitHub PR', + 'chat.chatInput.actions.linkGitlabIssue': '关联 GitLab Issue', + 'chat.chatInput.actions.linkGitlabMr': '关联 GitLab MR', 'chat.chatInput.actions.modelAgentSettings': '模型与智能体设置', 'chat.chatInput.actions.sendMessageAria': '发送消息', 'chat.chatInput.actions.queueMessageAria': '将消息加入队列', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index 921bacf1..43ad93fa 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -1810,6 +1810,39 @@ export const dict: Record = { 'session.githubIssuePicker.actions.createInWorktree': '在 worktree 中建立', 'session.githubIssuePicker.actions.openRepo': '開啟儲存庫', 'session.githubIssuePicker.actions.refresh': '重新整理', + 'session.gitlabIssuePicker.error.noActiveProject': '沒有活動專案', + 'session.gitlabIssuePicker.error.runtimeUnavailable': 'GitLab 執行時 API 無法使用', + 'session.gitlabIssuePicker.error.notConnected': 'GitLab 未連線', + 'session.gitlabIssuePicker.error.repoNotResolvable': '無法解析儲存庫', + 'session.gitlabIssuePicker.error.repoMustBeGitlab': 'origin 遠端儲存庫必須是 GitLab URL', + 'session.gitlabIssuePicker.error.issueNotFound': '找不到 Issue', + 'session.gitlabIssuePicker.error.noModelSelected': '未選擇模型', + 'session.gitlabIssuePicker.toast.loadMoreFailed': '載入更多 Issue 失敗', + 'session.gitlabIssuePicker.toast.loadIssueDetailsFailed': '載入 Issue 詳情失敗', + 'session.gitlabIssuePicker.toast.sendContextFailed': '傳送 Issue 上下文失敗', + 'session.gitlabIssuePicker.toast.sessionCreated': '已從 Issue 建立會話', + 'session.gitlabIssuePicker.toast.startSessionFailed': '啟動會話失敗', + 'session.gitlabIssuePicker.title.select': '關聯 GitLab Issue', + 'session.gitlabIssuePicker.title.createSession': '從 GitLab Issue 新增會話', + 'session.gitlabIssuePicker.description.select': '選擇一個 Issue 關聯到目前會話。', + 'session.gitlabIssuePicker.description.createSession': '使用隱藏的 Issue 上下文(標題/內文/標籤/留言)初始化新會話。', + 'session.gitlabIssuePicker.searchPlaceholder': '按標題搜尋,或貼上 Issue URL', + 'session.gitlabIssuePicker.empty.noActiveProject': '未選擇活動專案。', + 'session.gitlabIssuePicker.empty.runtimeUnavailable': 'GitLab 執行時 API 無法使用。', + 'session.gitlabIssuePicker.empty.notConnected': 'GitLab 未連線。請在設定中連接 GitLab 帳號。', + 'session.gitlabIssuePicker.empty.noIssuesFound': '找不到 Issue', + 'session.gitlabIssuePicker.empty.noOpenIssuesFound': '沒有未關閉的 Issue', + 'session.gitlabIssuePicker.loading.issues': '正在載入 Issue...', + 'session.gitlabIssuePicker.loading.more': '載入中...', + 'session.gitlabIssuePicker.actions.openSettings': '開啟設定', + 'session.gitlabIssuePicker.actions.useIssue': '使用 Issue #{number}', + 'session.gitlabIssuePicker.actions.openInGitLabAria': '在 GitLab 中開啟', + 'session.gitlabIssuePicker.actions.loadMore': '載入更多', + 'session.gitlabIssuePicker.actions.sectionTitle': '操作', + 'session.gitlabIssuePicker.actions.toggleWorktreeAria': '切換 worktree', + 'session.gitlabIssuePicker.actions.createInWorktree': '在 worktree 中建立', + 'session.gitlabIssuePicker.actions.openRepo': '開啟儲存庫', + 'session.gitlabIssuePicker.actions.refresh': '重新整理', 'session.githubPrPicker.error.noActiveProject': '沒有活動專案', 'session.githubPrPicker.error.runtimeUnavailable': 'GitHub 執行時 API 無法使用', 'session.githubPrPicker.error.notConnected': 'GitHub 未連線', @@ -1834,6 +1867,30 @@ export const dict: Record = { 'session.githubPrPicker.actions.usePullRequest': '使用 Pull Request #{number}', 'session.githubPrPicker.actions.openInGitHubAria': '在 GitHub 中開啟', 'session.githubPrPicker.actions.loadMore': '載入更多', + 'session.gitlabMrPicker.error.noActiveProject': '沒有活動專案', + 'session.gitlabMrPicker.error.runtimeUnavailable': 'GitLab 執行時 API 無法使用', + 'session.gitlabMrPicker.error.notConnected': 'GitLab 未連線', + 'session.gitlabMrPicker.error.mrNotFound': '找不到 Merge Request', + 'session.gitlabMrPicker.error.repoNotResolvable': '無法解析儲存庫', + 'session.gitlabMrPicker.error.repoMustBeGitlab': 'origin 遠端儲存庫必須是 GitLab URL', + 'session.gitlabMrPicker.toast.loadMoreFailed': '載入更多 Merge Request 失敗', + 'session.gitlabMrPicker.toast.loadDetailsFailed': '載入 Merge Request 詳情失敗', + 'session.gitlabMrPicker.title': '關聯 GitLab Merge Request', + 'session.gitlabMrPicker.description': '選擇一個 Merge Request,將審查上下文附加到此訊息。', + 'session.gitlabMrPicker.searchPlaceholder': '按標題搜尋,或貼上 Merge Request URL', + 'session.gitlabMrPicker.includeDiffAria': '在附加上下文中包含 MR diff', + 'session.gitlabMrPicker.includeDiff': '包含 MR diff', + 'session.gitlabMrPicker.empty.noActiveProject': '未選擇活動專案。', + 'session.gitlabMrPicker.empty.runtimeUnavailable': 'GitLab 執行時 API 無法使用。', + 'session.gitlabMrPicker.empty.notConnected': 'GitLab 未連線。請在設定中連接 GitLab 帳號。', + 'session.gitlabMrPicker.empty.noMergeRequestsFound': '找不到 Merge Request', + 'session.gitlabMrPicker.empty.noOpenMergeRequestsFound': '沒有未關閉的 Merge Request', + 'session.gitlabMrPicker.loading.mergeRequests': '正在載入 Merge Request...', + 'session.gitlabMrPicker.loading.more': '載入中...', + 'session.gitlabMrPicker.actions.openSettings': '開啟設定', + 'session.gitlabMrPicker.actions.useMergeRequest': '使用 Merge Request !{number}', + 'session.gitlabMrPicker.actions.openInGitLabAria': '在 GitLab 中開啟', + 'session.gitlabMrPicker.actions.loadMore': '載入更多', 'session.newWorktree.title': '新增 Worktree', 'session.newWorktree.mode.newBranch': '新分支', 'session.newWorktree.mode.existingBranch': '現有分支', @@ -2152,6 +2209,8 @@ export const dict: Record = { 'chat.chatInput.actions.addAttachment': '加入附件', 'chat.chatInput.actions.linkGithubIssue': '關聯 GitHub Issue', 'chat.chatInput.actions.linkGithubPr': '關聯 GitHub PR', + 'chat.chatInput.actions.linkGitlabIssue': '關聯 GitLab Issue', + 'chat.chatInput.actions.linkGitlabMr': '關聯 GitLab MR', 'chat.chatInput.actions.modelAgentSettings': '模型與 Agent 設定', 'chat.chatInput.actions.sendMessageAria': '傳送訊息', 'chat.chatInput.actions.queueMessageAria': '將訊息加入佇列',