Skip to content

Sparshr04/GitHub copilot auth UI#201

Open
Sparshr04 wants to merge 13 commits intodevelopfrom
sparshr04/github-copilot-auth-ui
Open

Sparshr04/GitHub copilot auth UI#201
Sparshr04 wants to merge 13 commits intodevelopfrom
sparshr04/github-copilot-auth-ui

Conversation

@Sparshr04
Copy link
Contributor

[Feature] Implement GitHub Copilot OAuth Device Flow UI

Description

This PR adds the frontend UI and data layer to handle the 3-step GitHub Copilot OAuth Device Flow. This connects the client to the /api/v1/auth/github endpoints.

Key Additions:

  • UI: Created the CopilotConnect.vue component and added it to the UserSettingsContent.vue page to handle the Disconnected, Pending (Polling), and Connected states.
  • Repository Layer: Added GitHubAuthRepository using the standard NuxtAxiosInstance to ensure Extralit JWTs are correctly passed for user isolation.
  • Domain Layer: Created GitHubAuthUseCase to orchestrate the login initiation, status checking, and the background polling loop (with proper onBeforeUnmount cleanup to prevent memory leaks).
  • Storage Layer: Added GitHubAuthStorage using the existing Pinia useStoreFor factory pattern to handle the reactive auth state.

Related Tickets & Documents

Depends on: #200

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Steps to QA

  1. Ensure both the extralit-frontend and extralit-server dev servers are running.
  2. Navigate to your profile -> My settings.
  3. Scroll down to the new GitHub Copilot section and click Connect Copilot.
  4. Copy the generated User Code and click the GitHub verification link.
  5. Enter the code on GitHub and click Authorize.
  6. Return to the Extralit settings page and validate that the UI automatically transitions to the green "GitHub Copilot Connected" badge.

Added/updated tests?

  • Yes
  • No, and this is why: Manual QA steps provided; UI components and use-case logic rely on active backend integration and the live GitHub OAuth flow.
  • I need help with writing tests

Added/updated documentations?

  • Yes
  • No, and this is why: This is a self-contained UI feature embedded in the existing user settings page.
  • I need help with writing docs

Checklist

Copilot AI and others added 13 commits December 7, 2025 23:21
Co-authored-by: JonnyTran <4750391+JonnyTran@users.noreply.github.com>
Co-authored-by: JonnyTran <4750391+JonnyTran@users.noreply.github.com>
- Update `BaseSimpleTable` adapter to pass the full column configuration (including editor params) instead of just name/type.
- Update `RenderTable` logic to respect custom column settings (like `editor: "list"`) by prioritizing schema config over defaults.
- Add watcher to `columnsConfig` in `RenderTable` to ensure dropdown options update reactively when new files are uploaded.
- Add missing `$emit('cell-edited')` in `RenderTable`'s tabulator handler.
- Ensures parent components are notified of changes to trigger state updates like the unmapped files counter.
Adds the CopilotConnect component to user settings, use-cases, and Pinia storage to handle the 3-step OAuth device flow.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements GitHub Copilot OAuth Device Flow connectivity and introduces supporting backend endpoints plus a new LiteLLM-based /chat API, alongside some packaging/runtime and import-flow updates.

Changes:

  • Added backend GitHub device-flow auth endpoints and a new streaming chat endpoint using LiteLLM (with optional RAG retrieval).
  • Added frontend Copilot “Connect” UI with repository/use-case/storage wiring in the Nuxt v1 architecture.
  • Updated Python packaging constraints/dependencies and introduced PDF-only import metadata table state + tests (UI wiring appears incomplete).

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
extralit/pyproject.toml Changes SDK Python requirement and dependency versions.
extralit-server/pyproject.toml Bumps server Python requirement, adds LiteLLM + local extralit dependency.
extralit-server/src/extralit_server/utils/litellm_context.py Adds per-user LiteLLM config isolation via XDG_CONFIG_HOME.
extralit-server/src/extralit_server/utils/auth_helpers.py Implements GitHub device flow helper + token persistence.
extralit-server/src/extralit_server/jobs/preload.py Enables OCR job preloading.
extralit-server/src/extralit_server/api/routes.py Registers new /auth/github/* and /chat routers.
extralit-server/src/extralit_server/api/handlers/v1/github_auth.py Adds GitHub auth API endpoints for device flow.
extralit-server/src/extralit_server/api/handlers/v1/chat.py Adds streaming chat endpoint using LiteLLM + optional RAG.
extralit-frontend/v1/infrastructure/storage/GitHubAuthStorage.ts Adds Pinia-backed reactive state for GitHub auth.
extralit-frontend/v1/infrastructure/repositories/index.ts Exports the new GitHub auth repository.
extralit-frontend/v1/infrastructure/repositories/GitHubAuthRepository.ts Adds API client for GitHub auth endpoints.
extralit-frontend/v1/domain/usecases/github-auth-use-case.ts Adds polling orchestration and store updates for the device flow.
extralit-frontend/v1/di/di.ts Registers new repository/use-case/storage in DI container.
extralit-frontend/components/features/user-settings/UserSettingsContent.vue Adds GitHub Copilot section to user settings.
extralit-frontend/components/features/user-settings/useCopilotConnectViewModel.ts View model for Copilot connect UI.
extralit-frontend/components/features/user-settings/CopilotConnect.vue UI component for disconnected/pending/connected states.
extralit-frontend/components/features/import/file-upload/useImportFileUploadViewModel.ts Adds state/logic for a PDF-only editable metadata table (UI not wired in template).
extralit-frontend/components/features/import/file-upload/useImportFileUploadViewModel.spec.ts Adds unit tests for new import view-model logic.
extralit-frontend/components/features/import/file-upload/ImportFileUpload.vue Imports/registers BaseSimpleTable but doesn’t render it.
copilot_changes.txt Contains a pasted patch/diff dump.

Comment on lines +6 to 9
# requires-python = ">=3.9.2, <3.14"
requires-python = ">=3.11,<3.14"
readme = "README.md"
license = { text = "Apache 2.0" }
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

requires-python was raised to >=3.11, but repo docs indicate the extralit Python SDK supports Python 3.9.2+; this is a breaking packaging change for existing users/environments. Either keep the lower bound (or justify the bump) and ensure CI/docs/tooling are updated consistently.

Suggested change
# requires-python = ">=3.9.2, <3.14"
requires-python = ">=3.11,<3.14"
readme = "README.md"
license = { text = "Apache 2.0" }
requires-python = ">=3.9.2, <3.14"
readme = "README.md"
license = { text = "Apache 2.0" }
license = { text = "Apache 2.0" }

Copilot uses AI. Check for mistakes.
Comment on lines +6 to 8
# requires-python = ">=3.11"
requires-python = ">=3.11,<3.14"
license = { text = "Apache-2.0" }
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The server now requires Python >=3.11, but the Ruff config still targets py310 (and comments suggest older support). Please align the declared Python requirement, Ruff target-version, and any CI/runtime expectations to avoid incorrect linting/unsupported syntax usage.

Copilot uses AI. Check for mistakes.
"pdf2image>=1.17.0",
"opencv-python-headless>=4.11.0.86",
# Local extralit package for OCR jobs
"extralit @ file:///${PROJECT_ROOT}/../extralit",
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

extralit @ file:///${PROJECT_ROOT}/../extralit is not a standard PEP 508 direct URL (env var interpolation is not generally supported by pip). This is likely to fail in non-dev installs/CI. Consider moving this to an optional/dev dependency, using a relative path supported by your package manager, or publishing/depending on a versioned extralit package instead.

Suggested change
"extralit @ file:///${PROJECT_ROOT}/../extralit",
"extralit @ ../extralit",

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +66
# Save original value
self.original_xdg_config_home = os.environ.get("XDG_CONFIG_HOME")

# Set to user-specific directory
os.environ["XDG_CONFIG_HOME"] = self.user_config_dir

# Ensure directory exists
Path(self.user_config_dir).mkdir(parents=True, exist_ok=True)

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

LiteLLMContext mutates the process-global os.environ["XDG_CONFIG_HOME"]. Under concurrent requests (multiple users), contexts can overlap and leak configuration across requests. Consider avoiding env mutation (preferred), or guard with a global async lock / per-request isolation mechanism that is concurrency-safe.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +67
def _get_config_dir(self) -> Path:
"""Get the user-specific config directory for token storage."""
config_path = Path(settings.home_path) / "data" / "users" / self.username / "config"
config_path.mkdir(parents=True, exist_ok=True)
return config_path
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

username is used directly as a path segment when building the config directory. Since username validation only enforces non-empty length, a crafted username containing path separators could escape the intended directory (path traversal). Please sanitize/normalize (e.g., restrict allowed chars) or use a safe identifier like user.id for filesystem paths.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,201 @@
import { ref } from "@nuxtjs/composition-api";
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

Unused import: ref is imported but never referenced. This can fail lint/build in stricter configs and adds noise to the test file.

Suggested change
import { ref } from "@nuxtjs/composition-api";

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +60
diff --git a/extralit-frontend/components/base/base-render-table/RenderTable.vue b/extralit-frontend/components/base/base-render-table/RenderTable.vue
index ff6874489..ab73e5afb 100644
--- a/extralit-frontend/components/base/base-render-table/RenderTable.vue
+++ b/extralit-frontend/components/base/base-render-table/RenderTable.vue
@@ -150,6 +150,7 @@ export default {
// }
// },
// },
+
validation: {
handler(newValidation, oldValidation) {
if (this.isLoaded) {
@@ -196,7 +197,7 @@ export default {
var configs = this.tableJSON.schema.fields.map((column: DataFrameField) => {
const commonConfig = this.generateColumnConfig(column.name);
const editableConfig = this.generateColumnEditableConfig(column.name);
- return { ...commonConfig, ...editableConfig };
+ return { ...commonConfig, ...editableConfig, ...column };
});

if (!this.editable) {
@@ -913,6 +914,7 @@ export default {

this.tabulator.on("cellEdited", (cell: CellComponent) => {
this.updateTableJsonData();
+ this.$emit("cell-edited", cell);
// const rowPos: number | boolean = cell.getRow().getPosition();
// if (typeof rowPos != 'number' || rowPos < 0 || rowPos > this.tableJSON.data.length) return;
// this.$set(this.tableJSON.data[rowPos-1], cell.getColumn().getField(), cell.getValue());
diff --git a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue
index 15a62bbc4..3a7e5435a 100644
--- a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue
+++ b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue
@@ -6,6 +6,7 @@
:editable="editable"
:hasValidValues="hasValidValues"
:questions="questions"
+ :validation="validation || validators"
@table-built="$emit('table-built')"
@row-click="(e, row) => $emit('row-click', e, row)"
@cell-edited="(cell) => $emit('cell-edited', cell)"
@@ -71,9 +72,11 @@ export default {
computed: {
// Convert simple data/columns to TableData format for RenderTable
computedTableJSON(): TableData {
+ // FIX 1: Use "...col" to preserve editor config (dropdowns), validators, and freezing
const fields = this.columns.map((col: any) => ({
name: col.field,
type: col.type || "string",
+ ...col // <--- THIS IS THE MAGIC FIX
}));

const schema = new DataFrameSchema(
@@ -89,7 +92,14 @@ export default {
null
);

- if (this.validation) {
+ // FIX 2: Handle both 'validation' and 'validators' props
+ // ImportFileUpload passes :validators, so we must prioritize that.
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This file appears to be a pasted diff/patch dump (diff --git ...) rather than application code or documentation used by the project. If it’s only for reference, it shouldn’t be committed to the repo; please remove it or convert it into proper applied changes / documentation.

Suggested change
diff --git a/extralit-frontend/components/base/base-render-table/RenderTable.vue b/extralit-frontend/components/base/base-render-table/RenderTable.vue
index ff6874489..ab73e5afb 100644
--- a/extralit-frontend/components/base/base-render-table/RenderTable.vue
+++ b/extralit-frontend/components/base/base-render-table/RenderTable.vue
@@ -150,6 +150,7 @@ export default {
// }
// },
// },
+
validation: {
handler(newValidation, oldValidation) {
if (this.isLoaded) {
@@ -196,7 +197,7 @@ export default {
var configs = this.tableJSON.schema.fields.map((column: DataFrameField) => {
const commonConfig = this.generateColumnConfig(column.name);
const editableConfig = this.generateColumnEditableConfig(column.name);
- return { ...commonConfig, ...editableConfig };
+ return { ...commonConfig, ...editableConfig, ...column };
});
if (!this.editable) {
@@ -913,6 +914,7 @@ export default {
this.tabulator.on("cellEdited", (cell: CellComponent) => {
this.updateTableJsonData();
+ this.$emit("cell-edited", cell);
// const rowPos: number | boolean = cell.getRow().getPosition();
// if (typeof rowPos != 'number' || rowPos < 0 || rowPos > this.tableJSON.data.length) return;
// this.$set(this.tableJSON.data[rowPos-1], cell.getColumn().getField(), cell.getValue());
diff --git a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue
index 15a62bbc4..3a7e5435a 100644
--- a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue
+++ b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue
@@ -6,6 +6,7 @@
:editable="editable"
:hasValidValues="hasValidValues"
:questions="questions"
+ :validation="validation || validators"
@table-built="$emit('table-built')"
@row-click="(e, row) => $emit('row-click', e, row)"
@cell-edited="(cell) => $emit('cell-edited', cell)"
@@ -71,9 +72,11 @@ export default {
computed: {
// Convert simple data/columns to TableData format for RenderTable
computedTableJSON(): TableData {
+ // FIX 1: Use "...col" to preserve editor config (dropdowns), validators, and freezing
const fields = this.columns.map((col: any) => ({
name: col.field,
type: col.type || "string",
+ ...col // <--- THIS IS THE MAGIC FIX
}));
const schema = new DataFrameSchema(
@@ -89,7 +92,14 @@ export default {
null
);
- if (this.validation) {
+ // FIX 2: Handle both 'validation' and 'validators' props
+ // ImportFileUpload passes :validators, so we must prioritize that.
# This file previously contained a temporary git diff/patch dump.
# The changes have been or should be applied to the actual source files,
# so the diff content has been removed to avoid keeping raw patches in the repo.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to +27
from extralit_server.api.handlers.v1 import authentication as authentication_v1
from extralit_server.api.handlers.v1 import (
chat as chat_v1,
)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

PR description frames this as a frontend-only OAuth Device Flow UI/data-layer change, but this diff also wires new backend routers (/auth/github/*, /chat) into the server API. Please either update the PR description/scope (and any linked tickets) to reflect backend + dependency/runtime changes, or split backend changes into a separate PR for easier review/release management.

Copilot uses AI. Check for mistakes.
<UserTokenComponent :userToken="user.apiKey" />
</div>
<div class="form-group">
<h2 class="--heading5 --medium description__title">GitHub Copilot</h2>
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This new section title is hardcoded (GitHub Copilot) while the rest of the settings page uses $t(...) for i18n. Please add the corresponding translation key and use $t here for consistency with the existing localization approach.

Suggested change
<h2 class="--heading5 --medium description__title">GitHub Copilot</h2>
<h2 class="--heading5 --medium description__title" v-text="$t('userSettings.copilot')" />

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +165
github_auth = GitHubDeviceFlowAuth(username=current_user.username)

try:
# Poll with a short timeout (single attempt)
token_data = await github_auth.poll_for_token(
device_code=request.device_code,
interval=5,
timeout=10, # Just one quick check
)

# Save the token
github_auth.save_token(token_data)

_LOGGER.info(f"Successfully authorized and saved token for {current_user.username}")

return PollTokenResponse(
status="authorized",
message="Successfully authenticated with GitHub",
)
except TimeoutError:
# User hasn't authorized yet
return PollTokenResponse(
status="pending",
message="Authorization pending",
)
except ValueError as e:
_LOGGER.error(f"Token polling error for {current_user.username}: {e}")
return PollTokenResponse(
status="error",
message=str(e),
)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

poll_github_token only catches TimeoutError and ValueError, but poll_for_token() can raise httpx.HTTPError (network/API failures) or JSON parsing exceptions. Those will currently surface as 500s. Consider catching httpx.HTTPError and returning an appropriate HTTPException/structured status="error" response.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants