diff --git a/frontend/src/components/shared/buttons/github-issues-prs-button.tsx b/frontend/src/components/shared/buttons/github-issues-prs-button.tsx
new file mode 100644
index 0000000000..890f7cc208
--- /dev/null
+++ b/frontend/src/components/shared/buttons/github-issues-prs-button.tsx
@@ -0,0 +1,28 @@
+import { useTranslation } from "react-i18next";
+import { I18nKey } from "#/i18n/declaration";
+import { TooltipButton } from "./tooltip-button";
+import PRIcon from "#/icons/u-pr.svg?react";
+
+interface GitHubIssuesPRsButtonProps {
+ disabled?: boolean;
+}
+
+export function GitHubIssuesPRsButton({
+ disabled = false,
+}: GitHubIssuesPRsButtonProps) {
+ const { t } = useTranslation();
+
+ const tooltip = t(I18nKey.SIDEBAR$GITHUB_ISSUES_PRS);
+
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/hooks/query/use-github-issues-prs.ts b/frontend/src/hooks/query/use-github-issues-prs.ts
new file mode 100644
index 0000000000..5f8df055c1
--- /dev/null
+++ b/frontend/src/hooks/query/use-github-issues-prs.ts
@@ -0,0 +1,125 @@
+import { useQuery, useQueryClient } from "@tanstack/react-query";
+import React from "react";
+import GitHubIssuesPRsService, {
+ GitHubItemsFilter,
+ GitHubItemsResponse,
+} from "#/api/github-service/github-issues-prs.api";
+import { useShouldShowUserFeatures } from "../use-should-show-user-features";
+
+const CACHE_KEY = "github-issues-prs-cache";
+const CACHE_DURATION_MS = 60 * 1000; // 1 minute
+
+interface CachedData {
+ data: GitHubItemsResponse;
+ timestamp: number;
+}
+
+/**
+ * Get cached data from localStorage
+ */
+function getCachedData(): CachedData | null {
+ try {
+ const cached = localStorage.getItem(CACHE_KEY);
+ if (cached) {
+ const parsed = JSON.parse(cached) as CachedData;
+ return parsed;
+ }
+ } catch {
+ // Ignore parse errors
+ }
+ return null;
+}
+
+/**
+ * Save data to localStorage cache
+ */
+function setCachedData(data: GitHubItemsResponse): void {
+ try {
+ const cacheEntry: CachedData = {
+ data,
+ timestamp: Date.now(),
+ };
+ localStorage.setItem(CACHE_KEY, JSON.stringify(cacheEntry));
+ } catch {
+ // Ignore storage errors
+ }
+}
+
+/**
+ * Check if cached data is still valid
+ */
+function isCacheValid(cached: CachedData | null): boolean {
+ if (!cached) return false;
+ return Date.now() - cached.timestamp < CACHE_DURATION_MS;
+}
+
+/**
+ * Hook to fetch GitHub issues and PRs with local storage caching
+ */
+export const useGitHubIssuesPRs = (filter?: GitHubItemsFilter) => {
+ const shouldShowUserFeatures = useShouldShowUserFeatures();
+ const queryClient = useQueryClient();
+
+ // Set up auto-refresh interval
+ React.useEffect(() => {
+ if (!shouldShowUserFeatures) return undefined;
+
+ const interval = setInterval(() => {
+ queryClient.invalidateQueries({ queryKey: ["github-issues-prs"] });
+ }, CACHE_DURATION_MS);
+
+ return () => clearInterval(interval);
+ }, [shouldShowUserFeatures, queryClient]);
+
+ return useQuery({
+ queryKey: ["github-issues-prs", filter],
+ queryFn: async () => {
+ // Check localStorage cache first
+ const cached = getCachedData();
+ if (isCacheValid(cached)) {
+ // Return cached data but still fetch in background
+ return cached!.data;
+ }
+
+ // Fetch fresh data
+ const response = await GitHubIssuesPRsService.getGitHubItems(filter);
+
+ // Save to localStorage
+ setCachedData(response);
+
+ return response;
+ },
+ enabled: shouldShowUserFeatures,
+ staleTime: CACHE_DURATION_MS,
+ gcTime: CACHE_DURATION_MS * 5,
+ // Use cached data as initial data for faster loading
+ initialData: () => {
+ const cached = getCachedData();
+ if (cached) {
+ return cached.data;
+ }
+ return undefined;
+ },
+ initialDataUpdatedAt: () => {
+ const cached = getCachedData();
+ if (cached) {
+ return cached.timestamp;
+ }
+ return undefined;
+ },
+ });
+};
+
+/**
+ * Hook to manually refresh GitHub issues and PRs data
+ */
+export const useRefreshGitHubIssuesPRs = () => {
+ const queryClient = useQueryClient();
+
+ return React.useCallback(() => {
+ // Clear localStorage cache
+ localStorage.removeItem(CACHE_KEY);
+ // Invalidate React Query cache
+ queryClient.invalidateQueries({ queryKey: ["github-issues-prs"] });
+ }, [queryClient]);
+};
diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts
index 1b330730d9..f8b33a2af7 100644
--- a/frontend/src/i18n/declaration.ts
+++ b/frontend/src/i18n/declaration.ts
@@ -960,4 +960,25 @@ export enum I18nKey {
OBSERVATION_MESSAGE$SKILL_READY = "OBSERVATION_MESSAGE$SKILL_READY",
CONVERSATION$SHOW_SKILLS = "CONVERSATION$SHOW_SKILLS",
SKILLS_MODAL$TITLE = "SKILLS_MODAL$TITLE",
+ GITHUB_ISSUES_PRS$TITLE = "GITHUB_ISSUES_PRS$TITLE",
+ GITHUB_ISSUES_PRS$REFRESH = "GITHUB_ISSUES_PRS$REFRESH",
+ GITHUB_ISSUES_PRS$VIEW = "GITHUB_ISSUES_PRS$VIEW",
+ GITHUB_ISSUES_PRS$ALL = "GITHUB_ISSUES_PRS$ALL",
+ GITHUB_ISSUES_PRS$ISSUES = "GITHUB_ISSUES_PRS$ISSUES",
+ GITHUB_ISSUES_PRS$PRS = "GITHUB_ISSUES_PRS$PRS",
+ GITHUB_ISSUES_PRS$ASSIGNED_TO_ME = "GITHUB_ISSUES_PRS$ASSIGNED_TO_ME",
+ GITHUB_ISSUES_PRS$AUTHORED_BY_ME = "GITHUB_ISSUES_PRS$AUTHORED_BY_ME",
+ GITHUB_ISSUES_PRS$LAST_UPDATED = "GITHUB_ISSUES_PRS$LAST_UPDATED",
+ GITHUB_ISSUES_PRS$ERROR_LOADING = "GITHUB_ISSUES_PRS$ERROR_LOADING",
+ GITHUB_ISSUES_PRS$TRY_AGAIN = "GITHUB_ISSUES_PRS$TRY_AGAIN",
+ GITHUB_ISSUES_PRS$NO_ITEMS = "GITHUB_ISSUES_PRS$NO_ITEMS",
+ GITHUB_ISSUES_PRS$MERGE_CONFLICTS = "GITHUB_ISSUES_PRS$MERGE_CONFLICTS",
+ GITHUB_ISSUES_PRS$FAILING_CHECKS = "GITHUB_ISSUES_PRS$FAILING_CHECKS",
+ GITHUB_ISSUES_PRS$UNRESOLVED_COMMENTS = "GITHUB_ISSUES_PRS$UNRESOLVED_COMMENTS",
+ GITHUB_ISSUES_PRS$OPEN_ISSUE = "GITHUB_ISSUES_PRS$OPEN_ISSUE",
+ GITHUB_ISSUES_PRS$OPEN_PR = "GITHUB_ISSUES_PRS$OPEN_PR",
+ GITHUB_ISSUES_PRS$VIEW_ON_GITHUB = "GITHUB_ISSUES_PRS$VIEW_ON_GITHUB",
+ GITHUB_ISSUES_PRS$START_SESSION = "GITHUB_ISSUES_PRS$START_SESSION",
+ GITHUB_ISSUES_PRS$RESUME_SESSION = "GITHUB_ISSUES_PRS$RESUME_SESSION",
+ SIDEBAR$GITHUB_ISSUES_PRS = "SIDEBAR$GITHUB_ISSUES_PRS",
}
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index a421de5ddf..761ee73f31 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -15358,5 +15358,341 @@
"es": "Habilidades disponibles",
"tr": "Kullanılabilir yetenekler",
"uk": "Доступні навички"
+ },
+ "GITHUB_ISSUES_PRS$TITLE": {
+ "en": "GitHub Issues & Pull Requests",
+ "ja": "GitHub Issues & Pull Requests",
+ "zh-CN": "GitHub Issues & Pull Requests",
+ "zh-TW": "GitHub Issues & Pull Requests",
+ "ko-KR": "GitHub Issues & Pull Requests",
+ "no": "GitHub Issues & Pull Requests",
+ "it": "GitHub Issues & Pull Requests",
+ "pt": "GitHub Issues & Pull Requests",
+ "es": "GitHub Issues & Pull Requests",
+ "ar": "GitHub Issues & Pull Requests",
+ "fr": "GitHub Issues & Pull Requests",
+ "tr": "GitHub Issues & Pull Requests",
+ "de": "GitHub Issues & Pull Requests",
+ "uk": "GitHub Issues & Pull Requests"
+ },
+ "GITHUB_ISSUES_PRS$REFRESH": {
+ "en": "Refresh",
+ "ja": "更新",
+ "zh-CN": "刷新",
+ "zh-TW": "重新整理",
+ "ko-KR": "새로고침",
+ "no": "Oppdater",
+ "it": "Aggiorna",
+ "pt": "Atualizar",
+ "es": "Actualizar",
+ "ar": "تحديث",
+ "fr": "Actualiser",
+ "tr": "Yenile",
+ "de": "Aktualisieren",
+ "uk": "Оновити"
+ },
+ "GITHUB_ISSUES_PRS$VIEW": {
+ "en": "View",
+ "ja": "表示",
+ "zh-CN": "查看",
+ "zh-TW": "檢視",
+ "ko-KR": "보기",
+ "no": "Vis",
+ "it": "Visualizza",
+ "pt": "Ver",
+ "es": "Ver",
+ "ar": "عرض",
+ "fr": "Voir",
+ "tr": "Görüntüle",
+ "de": "Ansicht",
+ "uk": "Перегляд"
+ },
+ "GITHUB_ISSUES_PRS$ALL": {
+ "en": "All",
+ "ja": "すべて",
+ "zh-CN": "全部",
+ "zh-TW": "全部",
+ "ko-KR": "전체",
+ "no": "Alle",
+ "it": "Tutti",
+ "pt": "Todos",
+ "es": "Todos",
+ "ar": "الكل",
+ "fr": "Tous",
+ "tr": "Tümü",
+ "de": "Alle",
+ "uk": "Всі"
+ },
+ "GITHUB_ISSUES_PRS$ISSUES": {
+ "en": "Issues",
+ "ja": "Issues",
+ "zh-CN": "Issues",
+ "zh-TW": "Issues",
+ "ko-KR": "Issues",
+ "no": "Issues",
+ "it": "Issues",
+ "pt": "Issues",
+ "es": "Issues",
+ "ar": "Issues",
+ "fr": "Issues",
+ "tr": "Issues",
+ "de": "Issues",
+ "uk": "Issues"
+ },
+ "GITHUB_ISSUES_PRS$PRS": {
+ "en": "Pull Requests",
+ "ja": "Pull Requests",
+ "zh-CN": "Pull Requests",
+ "zh-TW": "Pull Requests",
+ "ko-KR": "Pull Requests",
+ "no": "Pull Requests",
+ "it": "Pull Requests",
+ "pt": "Pull Requests",
+ "es": "Pull Requests",
+ "ar": "Pull Requests",
+ "fr": "Pull Requests",
+ "tr": "Pull Requests",
+ "de": "Pull Requests",
+ "uk": "Pull Requests"
+ },
+ "GITHUB_ISSUES_PRS$ASSIGNED_TO_ME": {
+ "en": "Assigned to me",
+ "ja": "自分に割り当て",
+ "zh-CN": "分配给我",
+ "zh-TW": "指派給我",
+ "ko-KR": "나에게 할당됨",
+ "no": "Tildelt meg",
+ "it": "Assegnato a me",
+ "pt": "Atribuído a mim",
+ "es": "Asignado a mí",
+ "ar": "مُعيَّن لي",
+ "fr": "Assigné à moi",
+ "tr": "Bana atanan",
+ "de": "Mir zugewiesen",
+ "uk": "Призначено мені"
+ },
+ "GITHUB_ISSUES_PRS$AUTHORED_BY_ME": {
+ "en": "Authored by me",
+ "ja": "自分が作成",
+ "zh-CN": "我创建的",
+ "zh-TW": "我建立的",
+ "ko-KR": "내가 작성함",
+ "no": "Opprettet av meg",
+ "it": "Creato da me",
+ "pt": "Criado por mim",
+ "es": "Creado por mí",
+ "ar": "من إنشائي",
+ "fr": "Créé par moi",
+ "tr": "Benim tarafımdan oluşturulan",
+ "de": "Von mir erstellt",
+ "uk": "Створено мною"
+ },
+ "GITHUB_ISSUES_PRS$LAST_UPDATED": {
+ "en": "Last updated",
+ "ja": "最終更新",
+ "zh-CN": "最后更新",
+ "zh-TW": "最後更新",
+ "ko-KR": "마지막 업데이트",
+ "no": "Sist oppdatert",
+ "it": "Ultimo aggiornamento",
+ "pt": "Última atualização",
+ "es": "Última actualización",
+ "ar": "آخر تحديث",
+ "fr": "Dernière mise à jour",
+ "tr": "Son güncelleme",
+ "de": "Zuletzt aktualisiert",
+ "uk": "Останнє оновлення"
+ },
+ "GITHUB_ISSUES_PRS$ERROR_LOADING": {
+ "en": "Failed to load GitHub items",
+ "ja": "GitHubアイテムの読み込みに失敗しました",
+ "zh-CN": "加载GitHub项目失败",
+ "zh-TW": "載入GitHub項目失敗",
+ "ko-KR": "GitHub 항목 로드 실패",
+ "no": "Kunne ikke laste GitHub-elementer",
+ "it": "Impossibile caricare gli elementi GitHub",
+ "pt": "Falha ao carregar itens do GitHub",
+ "es": "Error al cargar elementos de GitHub",
+ "ar": "فشل في تحميل عناصر GitHub",
+ "fr": "Échec du chargement des éléments GitHub",
+ "tr": "GitHub öğeleri yüklenemedi",
+ "de": "GitHub-Elemente konnten nicht geladen werden",
+ "uk": "Не вдалося завантажити елементи GitHub"
+ },
+ "GITHUB_ISSUES_PRS$TRY_AGAIN": {
+ "en": "Try Again",
+ "ja": "再試行",
+ "zh-CN": "重试",
+ "zh-TW": "重試",
+ "ko-KR": "다시 시도",
+ "no": "Prøv igjen",
+ "it": "Riprova",
+ "pt": "Tentar novamente",
+ "es": "Intentar de nuevo",
+ "ar": "حاول مرة أخرى",
+ "fr": "Réessayer",
+ "tr": "Tekrar dene",
+ "de": "Erneut versuchen",
+ "uk": "Спробувати знову"
+ },
+ "GITHUB_ISSUES_PRS$NO_ITEMS": {
+ "en": "No issues or pull requests found",
+ "ja": "IssueまたはPull Requestが見つかりません",
+ "zh-CN": "未找到Issue或Pull Request",
+ "zh-TW": "未找到Issue或Pull Request",
+ "ko-KR": "Issue 또는 Pull Request를 찾을 수 없습니다",
+ "no": "Ingen issues eller pull requests funnet",
+ "it": "Nessun issue o pull request trovato",
+ "pt": "Nenhum issue ou pull request encontrado",
+ "es": "No se encontraron issues ni pull requests",
+ "ar": "لم يتم العثور على issues أو pull requests",
+ "fr": "Aucun issue ou pull request trouvé",
+ "tr": "Issue veya pull request bulunamadı",
+ "de": "Keine Issues oder Pull Requests gefunden",
+ "uk": "Issues або Pull Requests не знайдено"
+ },
+ "GITHUB_ISSUES_PRS$MERGE_CONFLICTS": {
+ "en": "Merge Conflicts",
+ "ja": "マージコンフリクト",
+ "zh-CN": "合并冲突",
+ "zh-TW": "合併衝突",
+ "ko-KR": "병합 충돌",
+ "no": "Flettekonflikter",
+ "it": "Conflitti di merge",
+ "pt": "Conflitos de merge",
+ "es": "Conflictos de merge",
+ "ar": "تعارضات الدمج",
+ "fr": "Conflits de fusion",
+ "tr": "Birleştirme çakışmaları",
+ "de": "Merge-Konflikte",
+ "uk": "Конфлікти злиття"
+ },
+ "GITHUB_ISSUES_PRS$FAILING_CHECKS": {
+ "en": "Failing Checks",
+ "ja": "失敗したチェック",
+ "zh-CN": "检查失败",
+ "zh-TW": "檢查失敗",
+ "ko-KR": "실패한 검사",
+ "no": "Mislykkede sjekker",
+ "it": "Controlli falliti",
+ "pt": "Verificações falhando",
+ "es": "Verificaciones fallidas",
+ "ar": "فحوصات فاشلة",
+ "fr": "Vérifications échouées",
+ "tr": "Başarısız kontroller",
+ "de": "Fehlgeschlagene Prüfungen",
+ "uk": "Невдалі перевірки"
+ },
+ "GITHUB_ISSUES_PRS$UNRESOLVED_COMMENTS": {
+ "en": "Unresolved Comments",
+ "ja": "未解決のコメント",
+ "zh-CN": "未解决的评论",
+ "zh-TW": "未解決的評論",
+ "ko-KR": "해결되지 않은 댓글",
+ "no": "Uløste kommentarer",
+ "it": "Commenti non risolti",
+ "pt": "Comentários não resolvidos",
+ "es": "Comentarios sin resolver",
+ "ar": "تعليقات غير محلولة",
+ "fr": "Commentaires non résolus",
+ "tr": "Çözülmemiş yorumlar",
+ "de": "Ungelöste Kommentare",
+ "uk": "Невирішені коментарі"
+ },
+ "GITHUB_ISSUES_PRS$OPEN_ISSUE": {
+ "en": "Open Issue",
+ "ja": "オープンIssue",
+ "zh-CN": "开放Issue",
+ "zh-TW": "開放Issue",
+ "ko-KR": "열린 Issue",
+ "no": "Åpen issue",
+ "it": "Issue aperto",
+ "pt": "Issue aberto",
+ "es": "Issue abierto",
+ "ar": "Issue مفتوح",
+ "fr": "Issue ouvert",
+ "tr": "Açık issue",
+ "de": "Offenes Issue",
+ "uk": "Відкритий Issue"
+ },
+ "GITHUB_ISSUES_PRS$OPEN_PR": {
+ "en": "Open PR",
+ "ja": "オープンPR",
+ "zh-CN": "开放PR",
+ "zh-TW": "開放PR",
+ "ko-KR": "열린 PR",
+ "no": "Åpen PR",
+ "it": "PR aperta",
+ "pt": "PR aberto",
+ "es": "PR abierto",
+ "ar": "PR مفتوح",
+ "fr": "PR ouverte",
+ "tr": "Açık PR",
+ "de": "Offener PR",
+ "uk": "Відкритий PR"
+ },
+ "GITHUB_ISSUES_PRS$VIEW_ON_GITHUB": {
+ "en": "View on GitHub",
+ "ja": "GitHubで表示",
+ "zh-CN": "在GitHub上查看",
+ "zh-TW": "在GitHub上檢視",
+ "ko-KR": "GitHub에서 보기",
+ "no": "Vis på GitHub",
+ "it": "Visualizza su GitHub",
+ "pt": "Ver no GitHub",
+ "es": "Ver en GitHub",
+ "ar": "عرض على GitHub",
+ "fr": "Voir sur GitHub",
+ "tr": "GitHub'da görüntüle",
+ "de": "Auf GitHub ansehen",
+ "uk": "Переглянути на GitHub"
+ },
+ "GITHUB_ISSUES_PRS$START_SESSION": {
+ "en": "Start Session",
+ "ja": "セッションを開始",
+ "zh-CN": "开始会话",
+ "zh-TW": "開始會話",
+ "ko-KR": "세션 시작",
+ "no": "Start økt",
+ "it": "Avvia sessione",
+ "pt": "Iniciar sessão",
+ "es": "Iniciar sesión",
+ "ar": "بدء الجلسة",
+ "fr": "Démarrer la session",
+ "tr": "Oturumu başlat",
+ "de": "Sitzung starten",
+ "uk": "Почати сеанс"
+ },
+ "GITHUB_ISSUES_PRS$RESUME_SESSION": {
+ "en": "Resume Session",
+ "ja": "セッションを再開",
+ "zh-CN": "恢复会话",
+ "zh-TW": "恢復會話",
+ "ko-KR": "세션 재개",
+ "no": "Gjenoppta økt",
+ "it": "Riprendi sessione",
+ "pt": "Retomar sessão",
+ "es": "Reanudar sesión",
+ "ar": "استئناف الجلسة",
+ "fr": "Reprendre la session",
+ "tr": "Oturumu devam ettir",
+ "de": "Sitzung fortsetzen",
+ "uk": "Відновити сеанс"
+ },
+ "SIDEBAR$GITHUB_ISSUES_PRS": {
+ "en": "Issues & PRs",
+ "ja": "Issues & PRs",
+ "zh-CN": "Issues & PRs",
+ "zh-TW": "Issues & PRs",
+ "ko-KR": "Issues & PRs",
+ "no": "Issues & PRs",
+ "it": "Issues & PRs",
+ "pt": "Issues & PRs",
+ "es": "Issues & PRs",
+ "ar": "Issues & PRs",
+ "fr": "Issues & PRs",
+ "tr": "Issues & PRs",
+ "de": "Issues & PRs",
+ "uk": "Issues & PRs"
}
}
diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts
index ecee511688..d2d6ec7cbd 100644
--- a/frontend/src/routes.ts
+++ b/frontend/src/routes.ts
@@ -21,6 +21,7 @@ export default [
]),
route("conversations/:conversationId", "routes/conversation.tsx"),
route("microagent-management", "routes/microagent-management.tsx"),
+ route("github-issues-prs", "routes/github-issues-prs.tsx"),
route("oauth/device/verify", "routes/device-verify.tsx"),
]),
] satisfies RouteConfig;
diff --git a/frontend/src/routes/github-issues-prs.tsx b/frontend/src/routes/github-issues-prs.tsx
new file mode 100644
index 0000000000..aa494533ea
--- /dev/null
+++ b/frontend/src/routes/github-issues-prs.tsx
@@ -0,0 +1,404 @@
+import React from "react";
+import { useNavigate } from "react-router";
+import { useTranslation } from "react-i18next";
+import { I18nKey } from "#/i18n/declaration";
+import {
+ useGitHubIssuesPRs,
+ useRefreshGitHubIssuesPRs,
+} from "#/hooks/query/use-github-issues-prs";
+import { useSearchConversations } from "#/hooks/query/use-search-conversations";
+import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
+import { LoadingSpinner } from "#/components/shared/loading-spinner";
+import {
+ GitHubItem,
+ GitHubItemsFilter,
+} from "#/api/github-service/github-issues-prs.api";
+import { Conversation } from "#/api/open-hands.types";
+import { cn } from "#/utils/utils";
+
+type ViewType = "all" | "issues" | "prs";
+
+interface GitHubItemCardProps {
+ item: GitHubItem;
+ relatedConversation?: Conversation;
+ onStartSession: () => void;
+ onResumeSession: () => void;
+ isStarting: boolean;
+}
+
+function GitHubItemCard({
+ item,
+ relatedConversation,
+ onStartSession,
+ onResumeSession,
+ isStarting,
+}: GitHubItemCardProps) {
+ const { t } = useTranslation();
+
+ const getStatusBadge = () => {
+ switch (item.status) {
+ case "MERGE_CONFLICTS":
+ return (
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$MERGE_CONFLICTS)}
+
+ );
+ case "FAILING_CHECKS":
+ return (
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$FAILING_CHECKS)}
+
+ );
+ case "UNRESOLVED_COMMENTS":
+ return (
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$UNRESOLVED_COMMENTS)}
+
+ );
+ case "OPEN_ISSUE":
+ return (
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$OPEN_ISSUE)}
+
+ );
+ case "OPEN_PR":
+ return (
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$OPEN_PR)}
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+
+
+ {relatedConversation ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+function GitHubIssuesPRsPage() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ // Filter state
+ const [viewType, setViewType] = React.useState
("all");
+ const [assignedToMe, setAssignedToMe] = React.useState(true);
+ const [authoredByMe, setAuthoredByMe] = React.useState(true);
+
+ // Build filter object
+ const filter: GitHubItemsFilter = React.useMemo(
+ () => ({
+ itemType: viewType,
+ assignedToMe,
+ authoredByMe,
+ }),
+ [viewType, assignedToMe, authoredByMe],
+ );
+
+ // Fetch GitHub items
+ const {
+ data: githubData,
+ isLoading,
+ error,
+ isFetching,
+ } = useGitHubIssuesPRs(filter);
+ const refreshData = useRefreshGitHubIssuesPRs();
+
+ // Fetch conversations to find related ones
+ const { data: conversations } = useSearchConversations(
+ undefined,
+ undefined,
+ 100,
+ );
+
+ // Create conversation mutation
+ const { mutate: createConversation, isPending: isCreating } =
+ useCreateConversation();
+
+ // Track which item is being started
+ const [startingItemKey, setStartingItemKey] = React.useState(
+ null,
+ );
+
+ // Find conversation related to a GitHub item
+ const findRelatedConversation = React.useCallback(
+ (item: GitHubItem): Conversation | undefined => {
+ if (!conversations) return undefined;
+
+ // Look for conversations that match the repository and have the PR number
+ return conversations.find((conv) => {
+ // Check if the conversation is for the same repository
+ if (conv.selected_repository !== item.repo) return false;
+
+ // Check if the conversation has the same PR/issue number in metadata
+ if (conv.pr_number?.includes(item.number)) {
+ return true;
+ }
+
+ // Check if the conversation title contains the issue/PR number
+ if (conv.title.includes(`#${item.number}`)) {
+ return true;
+ }
+
+ return false;
+ });
+ },
+ [conversations],
+ );
+
+ // Handle starting a new session
+ const handleStartSession = React.useCallback(
+ (item: GitHubItem) => {
+ const itemKey = `${item.repo}-${item.number}`;
+ setStartingItemKey(itemKey);
+
+ // Build the initial message based on item type
+ let initialMessage: string;
+ if (item.item_type === "issue") {
+ initialMessage = `Please help me resolve issue #${item.number} in the ${item.repo} repository.
+
+First, understand the issue context by reading the issue description and any comments. Then, work on resolving the issue. If you successfully resolve it, please open a draft PR with the fix.
+
+Issue: ${item.url}`;
+ } else {
+ initialMessage = `Please help me with PR #${item.number} in the ${item.repo} repository.
+
+First, read the PR description, comments, and check the CI results. Then, address any issues found:
+- Fix failing CI checks
+- Resolve merge conflicts if any
+- Address review comments
+
+PR: ${item.url}`;
+ }
+
+ createConversation(
+ {
+ query: initialMessage,
+ repository: {
+ name: item.repo,
+ gitProvider: item.git_provider,
+ },
+ },
+ {
+ onSuccess: (response) => {
+ setStartingItemKey(null);
+ navigate(`/conversations/${response.conversation_id}`);
+ },
+ onError: () => {
+ setStartingItemKey(null);
+ },
+ },
+ );
+ },
+ [createConversation, navigate],
+ );
+
+ // Handle resuming an existing session
+ const handleResumeSession = React.useCallback(
+ (conversation: Conversation) => {
+ navigate(`/conversations/${conversation.conversation_id}`);
+ },
+ [navigate],
+ );
+
+ // Filter items based on view type
+ const filteredItems = React.useMemo(() => {
+ if (!githubData?.items) return [];
+
+ let { items } = githubData;
+
+ if (viewType === "issues") {
+ items = items.filter((item) => item.item_type === "issue");
+ } else if (viewType === "prs") {
+ items = items.filter((item) => item.item_type === "pr");
+ }
+
+ return items;
+ }, [githubData?.items, viewType]);
+
+ return (
+
+ {/* Header */}
+
+
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$TITLE)}
+
+
+
+
+ {/* Filters */}
+
+
+ {/* Cache info */}
+ {githubData?.cached_at && (
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$LAST_UPDATED)}:{" "}
+ {new Date(githubData.cached_at).toLocaleTimeString()}
+
+ )}
+
+
+ {/* Content */}
+
+ {isLoading && !githubData && (
+
+
+
+ )}
+ {!isLoading && error && (
+
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$ERROR_LOADING)}
+
+
+
+ )}
+ {!isLoading && !error && filteredItems.length === 0 && (
+
+
+ {t(I18nKey.GITHUB_ISSUES_PRS$NO_ITEMS)}
+
+
+ )}
+ {!isLoading && !error && filteredItems.length > 0 && (
+
+ {filteredItems.map((item) => {
+ const itemKey = `${item.repo}-${item.number}`;
+ const relatedConversation = findRelatedConversation(item);
+
+ return (
+ handleStartSession(item)}
+ onResumeSession={() =>
+ relatedConversation &&
+ handleResumeSession(relatedConversation)
+ }
+ isStarting={isCreating && startingItemKey === itemKey}
+ />
+ );
+ })}
+
+ )}
+
+
+ );
+}
+
+export default GitHubIssuesPRsPage;