Skip to content

Claude/hardcore bassi 1697a9#5251

Closed
youngzs wants to merge 7 commits into
1Panel-dev:v2from
youngzs:claude/hardcore-bassi-1697a9
Closed

Claude/hardcore bassi 1697a9#5251
youngzs wants to merge 7 commits into
1Panel-dev:v2from
youngzs:claude/hardcore-bassi-1697a9

Conversation

@youngzs
Copy link
Copy Markdown

@youngzs youngzs commented May 13, 2026

What this PR does / why we need it?

Summary of your change

Please indicate you've done the following:

  • Made sure tests are passing and test coverage is added if needed.
  • Made sure commit message follow the rule of Conventional Commits specification.
  • Considered the docs impact and opened a new docs issue or PR with docs changes if needed.

youngzs and others added 2 commits May 13, 2026 07:37
Document the dual-process Django/Vue setup, app boundaries (chat vs.
workflow engines under application/, models_provider abstraction, etc.),
canonical entry point (main.py), and boot quirks worth knowing
(SERVER_NAME settings split, embedded Postgres/Redis 127.0.0.1 gate,
migrate retry loop, hmac_signed_serializer Celery tasks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement the white-label display (theme) settings API in the
community edition, so CE users can rebrand the platform (logo,
favicon, login page background, title, slogan, theme color, header
links) from the existing admin UI at /system/setting/theme without
needing X-PACK.

The frontend page and ThemeApi client already ship in CE
(ui/src/views/system-setting/theme/index.vue,
 ui/src/api/system-settings/theme.ts) but the backend endpoints
they hit (GET /display/info, PUT /display/update) only existed
in the closed-source X-PACK. This commit adds them, matching the
frontend contract field-for-field.

Backend changes:

- SystemSetting model: add SettingType.THEME = 3 with a single-row
  migration (0006) that only widens the choices enum. JSON config
  lives in SystemSetting(type=THEME).meta.

- New DisplayInfoSerializer with one() + Create.update_or_save()
  mirroring the EmailSettingSerializer pattern. DEFAULTS dict serves
  first-install / missing-record fallback. Image fields are multi-
  typed (UploadedFile / kept-old URL / empty) and handled via
  initial_data because DRF Serializer cannot express that polymorphism
  cleanly. Old images are best-effort deleted on replacement.

- Image uploads land in the existing knowledge.File model
  (source_type=SYSTEM, source_id='THEME') as PostgreSQL Large Objects,
  and the public-by-design oss.FileRetrievalView serves them via the
  './oss/file/<uuid>' relative URL convention used elsewhere in the
  app. This means the login page (unauthenticated) can render the
  logo and background without an extra custom asset endpoint.

- DisplayInfo.get is intentionally authentication_classes=[] +
  permission_classes=[] because the login screen, which is shown
  pre-auth, has to load these values.

- DisplayUpdate.put requires TokenAuth + has_permissions(
  APPEARANCE_SETTINGS_EDIT, ADMIN). It accepts both multipart/form-
  data (for file uploads) and application/json. The @log audit
  decorator scrubs file bytes from the body to keep operation_log
  rows small.

- Routes wired at /admin/api/display/info and
  /admin/api/display/update under the existing system_manage URL
  include.

Frontend gating (router edition check + user.ts CE branch that
skips theme.theme()) will be unlocked in a follow-up commit; the
backend can be smoke-tested today via curl against the two new
endpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@f2c-ci-robot
Copy link
Copy Markdown

f2c-ci-robot Bot commented May 13, 2026

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@f2c-ci-robot
Copy link
Copy Markdown

f2c-ci-robot Bot commented May 13, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

youngzs and others added 5 commits May 13, 2026 08:38
DisplayInfo.get was explicitly setting authentication_classes = []
and permission_classes = [], intending to make the endpoint public.

But MaxKB does NOT include django.contrib.auth in INSTALLED_APPS, and
DRF's default behavior for an anonymous request hitting an empty
authentication_classes is to fall back to api_settings.UNAUTHENTICATED_USER,
which lazily imports django.contrib.auth.models.AnonymousUser — and that
import then tries to register the Permission model, which fails with:

  RuntimeError: Model class django.contrib.auth.models.Permission
  doesn't declare an explicit app_label and isn't in an application
  in INSTALLED_APPS.

The fix: omit authentication_classes / permission_classes entirely and
inherit REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES, which MaxKB sets
to common.auth.authenticate.AnonymousAuthentication — the same pattern
oss.FileRetrievalView uses to expose public file URLs.

Verified end-to-end against a 1panel/maxkb:v2.9.0 deployment:
  GET  /admin/api/display/info  -> HTTP 200, returns DEFAULTS dict
  PUT  /admin/api/display/update (no token) -> HTTP 401 未登录

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… page

Now that /display/info and /display/update are open-source backend
endpoints (previous commits), let the community edition actually reach
them:

1. **Router unlock** (`ui/src/router/modules/system.ts`)
   Drop `[EditionConst.IS_EE, EditionConst.IS_PE]` from the two
   `ComplexPermission` blocks that gate the **system → 系统设置 →
   外观设置** menu and its `/system/setting/theme` child route.
   Now ADMIN role + APPEARANCE_SETTINGS_READ perm is sufficient,
   regardless of edition.

2. **User store CE branch removal** (`ui/src/stores/modules/user.ts`)
   `asyncGetProfile` was hardcoding `themeInfo = defaultPlatformSetting`
   for CE and only calling `theme.theme()` (the API) for EE/PE.
   Replace that branch with an unconditional `await theme.theme()`,
   so the CE backend's `/display/info` response is actually used.
   Drop the now-unused `defaultPlatformSetting` import.

3. **Login page preload** (`ui/src/layout/login-layout/LoginLayout.vue`)
   The login screen is shown pre-authentication and previously had
   no way to fetch custom theme/logo until after login. Add an
   `onMounted` that calls `theme.theme()` when `themeInfo` is not
   yet populated. The backend GET is public for exactly this reason;
   on failure we silently fall back to defaults so the login page
   stays usable.

Verified end-to-end against 1panel/maxkb:v2.9.0 + public domain:
- GET https://maxkb.yangyubo.cn/admin/api/display/info → 200 + defaults
- Admin HTML now references the rebuilt bundle (admin-B5sCJGak.js)
- PUT /display/update without token → 401 未登录

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the markdown-table chunking for .xlsx / .xls / .csv with
per-row key-value paragraphs that include filename + sheet + row index
+ header→value pairs. Vector retrieval against business questions
("张三的订单金额是多少?") can now hit specific rows, while the
previous output ("| 张三 | SO-0042 | 12000 |") lost semantic context
once chunks were split and headers stopped repeating.

New paragraph shape (xlsx example):

  TITLE: 订单明细.xlsx / Q1销售 / 第47行
  CONTENT:
    文件:订单明细.xlsx
    工作表:Q1销售
    行号:第 47 行
    ---
    客户名称:张三
    订单编号:SO-0042
    金额:12000
    下单时间:2026-03-15

Design notes:

- New `apps/common/handle/impl/text/excel_kv_common.py` factors the
  serialization, so xlsx / xls / csv share `normalize_value`,
  `normalize_headers`, `make_kv_paragraph`. This also keeps date /
  float-int / image-cell / empty-row / empty-cell handling consistent.

- `normalize_value` collapses `12000.0` → `"12000"`, renders midnight
  datetimes as date-only, and preserves the existing image-cell
  behavior (`![](./oss/file/<id>)`).

- Merged cells in xlsx are now properly resolved on the data path
  (previously `handle()` did not call `fill_merged_cells`; only the
  unrelated `get_content()` workflow path did).

- Multi-sheet workbooks still split into one document per sheet (old
  behavior preserved), but each paragraph's "文件:xxx" line now shows
  the real file name regardless of which sheet became the doc name.

- xls cell type is honored via xlrd's `xldate_as_datetime` so dates
  stop appearing as float serials.

- CSV omits the 工作表 line (no such concept), uses 1-based row index
  matching the file's actual row numbering.

- Empty rows are skipped entirely; empty cells skip just that one key
  to avoid "客户名称:" noise that would hurt embedding quality.

- `get_content()` (used by workflow document-extract nodes etc.) is
  untouched — it still produces the original markdown-table output,
  because changing it would have unintended ripple effects elsewhere.

- The existing QA-flavored parsers under apps/common/handle/impl/qa/
  are not touched; standard "question | answer" Excel sheets continue
  to flow through that dedicated pipeline.

Verified end-to-end on 1panel/maxkb:v2.9.0 via Django shell:
- Multi-sheet xlsx splits correctly into one doc per sheet
- All 12 design points (file/sheet/row label, dates, floats, empty
  rows, empty cells, CSV-no-sheet) confirmed by smoke test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an OCR pipeline for the knowledge ingestion path so that:
- Image files (.png/.jpg/.jpeg/.bmp/.tif/.tiff/.webp/.heif/.heic) are
  auto-OCR'd and indexed as text instead of being rejected.
- Scanned (image-only) pages inside PDFs fall back to OCR when pypdf
  can't extract enough text. Mixed PDFs (text + scan) work correctly —
  only the empty pages get OCR'd.

Two OCR backends, switchable in the new "系统设置 → OCR 设置" page:

1. **Vision LLM mode (default, recommended)** —
   Reuses any IMAGE-type model already registered in models_provider
   (gpt-4o, claude-3-5-sonnet, gemini-1.5-pro, qwen-vl, glm-4v, …).
   Zero new Python deps for this mode; the langchain ChatModel
   instance is loaded via the existing
   `get_model_instance_by_model_workspace_id` factory. Prompt is
   customizable; default tells the model to "faithfully transcribe,
   not summarize".

2. **Local OCR mode (offline)** —
   Backed by rapidocr-onnxruntime, lazy-imported. Not added to
   pyproject.toml (CPU build is ~150 MB) — admin installs it manually
   inside the container when this mode is selected. The "Test" button
   on the settings page surfaces a clean error if the dependency is
   missing.

Code layout:

  apps/common/handle/impl/ocr/
    provider.py          — OcrProvider ABC + factory + DEFAULT_PROMPT
    vision_llm_provider.py
    local_provider.py    — lazy-import rapidocr_onnxruntime

  apps/common/handle/impl/text/image_ocr_split_handle.py
    Routes image extensions through the OCR provider, then through
    the same SplitModel as text files so user chunk-size/pattern
    config still applies. Registered in
    apps/knowledge/serializers/document.py:split_handles BEFORE the
    default text handler (which would otherwise reject the extensions).

  apps/common/handle/impl/text/pdf_split_handle.py
    Adds a _try_ocr_empty_pages helper. After pypdf has visited every
    page, pages whose extracted text is below _OCR_PAGE_TEXT_THRESHOLD
    (10 chars) are rendered to PNG via pymupdf and sent to the OCR
    provider. The pipeline degrades gracefully:
      - OCR not configured → log info, skip OCR, keep the empty pages
      - pymupdf missing    → log warning, skip
      - Per-page failure   → log error, keep going

System settings:
  - SettingType.OCR = 4 (migration 0007, choices-only AlterField,
    no schema change)
  - OcrSettingSerializer with one() + Create.update_or_save() +
    is_valid_config() mirroring EmailSettingSerializer.
  - View permission reuses APPEARANCE_SETTINGS_READ/EDIT — no new
    permission required.
  - Endpoints:
      GET  /admin/api/ocr_setting   — read current config
      PUT  /admin/api/ocr_setting   — write
      POST /admin/api/ocr_setting   — test (initializes provider
                                       without recognizing an image)

Frontend:
  - New page ui/src/views/system-setting/ocr/index.vue with a mode
    radio (vision_model / local), an IMAGE-type model picker for the
    vision mode, a custom prompt textarea, and a language picker +
    install hint for the local mode.
  - API client ui/src/api/system-settings/ocr-setting.ts.
  - Route registered under /system/setting/ocr with the same
    APPEARANCE_SETTINGS_READ permission that unlocks "外观设置" for CE.

Runtime dependency note:
  pymupdf is required for scanned-PDF OCR. MaxKB's pip is configured
  with PIP_TARGET=/opt/maxkb/python-packages (sandbox path) which is
  NOT on the worker's sys.path. To install for the main worker, use:
      docker exec <ct> bash -c "PIP_TARGET= pip install \
          --target /opt/py3/lib/python3.11/site-packages pymupdf"
  For local-OCR mode, the same form applies with
  rapidocr-onnxruntime + onnxruntime.

Verified end-to-end on 1panel/maxkb:v2.9.0:
- GET /admin/api/ocr_setting → 401 without token (rest auth works)
- Image upload with no OCR config → handler returns a friendly hint
  paragraph instead of crashing
- pymupdf importable from worker
- get_ocr_provider({}) raises clean OcrConfigError
- New /system/setting/ocr menu visible to admin in CE

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem this solves:
Today, starting a chat with an agent requires: top menu "应用" →
agent list → find the card → click the small chat icon. That's 3-4
clicks and not discoverable as the product's main interaction. New
users frequently miss the chat button entirely.

Solution:
A new top-level menu placed at the very front of the navigation
(order: 0), titled "对话" / "Chat", that aggregates every published
agent in one card grid where clicking any card opens the chat
directly in a new tab.

Changes:

1. New route module `ui/src/router/modules/chat-entry.ts` registered
   at /chat-entry with the same permission set as /application
   (USER role + APPLICATION_READ permission, no edition gating).
   Icon: app-user-chat / app-user-chat-active (already registered).
   order: 0 so it lands at the leftmost menu position.

2. New page `ui/src/views/chat-entry/index.vue`:
   - Pulls the workspace's application list via getAllApplication
   - Filters to is_publish === true (only chat-ready agents)
   - Renders a responsive card grid (1/2/3/4 cols by breakpoint)
   - Each card: icon + name + nick_name + simple/workflow tag
     + description + updated time + "开始对话" button
   - The card itself is a button (cursor + hover + keyboard) so the
     whole tile is one big click target
   - Click → calls getApplicationDetail (for workflow apps) +
     getAccessToken (same flow as application/index.vue's toChat),
     opens application.location + access_token in a new tab
   - Empty state with a "去创建智能体" CTA that routes to /application

3. i18n in three locales (zh-CN, en-US, zh-Hant) under views/chat-entry,
   registered in each views/index.ts barrel.

Not in this PR (follow-ups, if needed):
- "Quick chat" mode that lets users pick model + knowledge base and
  spawn an ephemeral SIMPLE application on the fly. This needs a
  backend POST flow + UX decisions about how to clean up ephemeral
  apps; deferred.
- Filter chips for agent type (simple / workflow / by tag).

Verified end-to-end:
- Built admin (admin-BM2GjUoq.js) and chat bundles
- Deployed to 1panel/maxkb:v2.9.0
- Bundle contains chat-entry route definition
- Public URL /admin/ now serves the new bundle

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@liqiang-fit2cloud
Copy link
Copy Markdown
Member

感谢贡献,没有计划支持本功能。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants