Skip to content

feat(ai-smart-file-tagger-qa): add AI Smart File Tagger#490

Open
LukasHirt wants to merge 16 commits into
mainfrom
ext/2026-06-24-ai-smart-file-tagger-qa
Open

feat(ai-smart-file-tagger-qa): add AI Smart File Tagger#490
LukasHirt wants to merge 16 commits into
mainfrom
ext/2026-06-24-ai-smart-file-tagger-qa

Conversation

@LukasHirt

Copy link
Copy Markdown
Collaborator

AI-generated · OSPO-50 · Gate: ✅ 1.00

Problem

Files accumulate without tags, making search and filter ineffective.
Manual tagging at scale doesn't happen — it requires opening every file individually.

Solution

A sparkle icon appears inline in each file row. Clicking it sends the file's
content (txt/md/pdf) or just its name and type (all others) to the configured
LLM, which returns 3–5 suggested tags shown in a lightweight confirmation
popover. The user accepts, deselects, or dismisses. With structured-output
models, tags include a confidence score; with basic text models, a flat list.
Without a configured LLM, the button shows a tooltip describing the required
admin setup.

Extension points

app.files.quick-actions

Why ship this now

As oCIS deployments scale, findability suffers first. This is the lowest-friction
path to enriched metadata, meeting users exactly where they already are.

What was built

packages/web-app-ai-smart-file-tagger-qa adds an inline "suggest tags" action to the files app, sending a file's content or name/MIME type to a configured LLM and returning suggested tags for the user to accept or dismiss. Because app.files.quick-actions doesn't exist in the installed @ownclouders/web-pkg (v12.3.2), the action is registered on global.files.context-actions instead — accessible from the file's context menu rather than as an inline row icon. src/index.ts reads applicationConfig.llm, builds the LLM config, and wires the ActionExtension registration.

The core logic lives in src/composables/useTagSuggestions.ts, which pulls file content over WebDAV for .txt/.md/.pdf (PDF text extraction via pdfjs-dist with a fake worker) and falls back to filename + MIME type for everything else, gated by src/utils/file-support.ts. It calls the existing useLLM.complete() composable with a structured JSON prompt and parses a { tags: [{ name, confidence }] } response, with a plain-text comma/newline fallback for models that don't support structured output — matching the two-tier degradation already established by web-app-ai-doc-summary. Accepted tags are applied via clientService.graphAuthenticated.tags.assignTags(). src/components/TagSuggestionModal.vue renders the suggestion UI: a loading spinner, an unconfigured-LLM notice, toggleable OcTag chips with confidence badges (selection state is tracked via a CSS class since this OcTag version has no native selected prop), an error banner, and its own Apply/Dismiss controls since the modal is launched with hideConfirmButton: true.

Test coverage includes 36 unit tests across the composable (prompt construction, structured/plain-text parsing, confidence normalization, tag application, and 401/429/network error handling) and the modal component (state rendering, chip toggling, confirm/dismiss behavior), plus a Playwright acceptance spec that mocks the LLM proxy, uploads a test file, triggers the action from the context menu, and verifies the modal's chip rendering, toggle, and confirm-close flow. The acceptance spec and its page object live inside the package rather than shared support/pages/, per the per-extension page-object convention. A known limitation, documented in the package README, is that tag suggestions surface from the file context menu rather than as an inline quick-action icon, since the originally spec'd extension point isn't available in this web-pkg version.

Gate

Check Result
Hygiene ✅ ok
Build ✅ ok
Lint ✅ ok
Unit tests ✅ ok
E2E tests ✅ ok
Score 1.00

Effort: S · 🤖 Generated by extctl

@kw-security

kw-security commented Jun 30, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@LukasHirt LukasHirt requested a review from dj4oC June 30, 2026 21:45
@LukasHirt LukasHirt self-assigned this Jun 30, 2026
LukasHirt and others added 16 commits June 30, 2026 23:47
Signed-off-by: Lukas Hirt <info@hirt.cz>
…mposables/useTagSuggestions.ts` (prompt construction, LLM fetch, JSON parse with confidence scores, plain-text fallback, WebDAV tag write via `useClientService`), `src/utils/file-support.ts` (content-extraction eligibility check, MIME helper), and wire `src/index.ts` to read `applicationConfig.llm`, build `llmConfig`, and register the `ActionExtension` on `app.files.quick-actions` (with fallback to `global.files.context-actions` if the extension point is absent). Verify the quick-actions extension point exists in `node_modules/@ownclouders/web-pkg` and confirm an unused dev-server port in the `97XX` range.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
…ts/TagSuggestionModal.vue`: loading spinner, selectable tag chips (OcTag or OcButton toggle fallback), confidence badges, error banner, confirm/dismiss buttons, and unconfigured-state info message. Wire the modal into the action handler via `useModals().dispatchModal`. Use only ODS components and ownCloud CSS custom properties; wrap all strings in `$pgettext`.

Signed-off-by: Lukas Hirt <info@hirt.cz>
…/composables/useTagSuggestions.spec.ts` covering prompt construction (content mode vs name-only mode), structured JSON parse, plain-text fallback, tag application via mocked WebDAV, and error branches (401, 429, network failure). Write `tests/unit/components/TagSuggestionModal.spec.ts` covering loading state, chip rendering, chip toggle, confirm calls `applyTags`, dismiss closes, error banner, and unconfigured tooltip.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
…on in `tests/e2e/acceptance.spec.ts` with a Playwright smoke test: upload a `.txt` file, click the sparkle quick-action, assert the `TagSuggestionModal` appears with at least one chip, toggle a chip off, confirm, and assert the modal closes.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
… state sync issues

- Fix tag chips rendering as raw JSON/numbered-list text when the LLM
  ignores the requested response format
- Fix selected/unselected chip colors having inverted contrast; tags now
  default to unselected (opt-in) instead of pre-selected
- Replace the modal's custom Dismiss/Apply tags button row with the
  framework's own modal actions (confirmText + exposed onConfirm)
- Add a hint above the chip list explaining the tags must be selected
- Sync the resources store after applying tags so the file list/sidebar
  reflect the new tags without a page refresh
- Fix a race in the e2e test that read the chip count before the async
  suggestion fetch resolved

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
…I matrix, and oCIS apps config

Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt LukasHirt force-pushed the ext/2026-06-24-ai-smart-file-tagger-qa branch from 8c0104a to 6d1c358 Compare June 30, 2026 21:47
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.

2 participants