Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 99 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ on:
pull_request:

permissions:
contents: read
contents: write

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 35

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}

- name: Set up Java 8
uses: actions/setup-java@v4
Expand All @@ -28,7 +30,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20.19.0'
node-version: '22'
cache: npm
cache-dependency-path: frontend/package.json

Expand All @@ -49,3 +51,97 @@ jobs:

- name: Validate Docker Compose
run: docker compose config --quiet

- name: Start the full playground stack
run: docker compose up --build --detach

- name: Wait for browser and API readiness
run: |
for attempt in $(seq 1 90); do
if curl --fail --silent --show-error http://127.0.0.1:3000/api/health > /tmp/playground-health.json; then
cat /tmp/playground-health.json
exit 0
fi
sleep 2
done
echo 'Playground did not become ready.' >&2
docker compose logs --no-color
exit 1

- name: Install Playwright Chromium
working-directory: frontend
run: npx playwright install --with-deps chromium

- name: Run browser acceptance test and capture screenshot
working-directory: frontend
run: npm run e2e

- name: Commit accepted screenshot and README showcase to the PR branch
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
python3 - <<'PY'
from pathlib import Path

updates = [
(
Path('README.md'),
'## Why Java-RAG?',
'''<!-- playground-screenshot -->
## Visual Playground

![Java-RAG visual playground with knowledge base, grounded chat, and retrieval evidence](docs/assets/playground.png)

This screenshot is generated by the Playwright acceptance test after Docker Compose starts the real application, the browser loads the sample document, asks a grounded question, and verifies the matching retrieval evidence.''',
),
(
Path('README_ch.md'),
'## 为什么值得关注?',
'''<!-- playground-screenshot -->
## 可视化 Playground

![Java-RAG 可视化知识库、证据化问答与检索证据界面](docs/assets/playground.png)

这张截图由 Playwright 验收测试自动生成:Docker Compose 启动真实应用后,浏览器加载示例文档、完成一次证据化问答,并校验右侧检索证据与回答引用一致。''',
),
]

for path, anchor, block in updates:
text = path.read_text(encoding='utf-8')
if '<!-- playground-screenshot -->' in text:
continue
if anchor not in text:
raise SystemExit('README anchor not found in %s' % path)
path.write_text(text.replace(anchor, block + '\n\n' + anchor, 1), encoding='utf-8')
PY

git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add docs/assets/playground.png README.md README_ch.md
if git diff --cached --quiet; then
echo 'Accepted screenshot and README showcase are already current.'
exit 0
fi
git commit -m 'docs: publish accepted playground screenshot [skip ci]'
git push origin HEAD:"${HEAD_REF}"

- name: Upload browser evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: playground-browser-evidence
if-no-files-found: ignore
retention-days: 30
path: |
docs/assets/playground.png
frontend/playwright-report
frontend/test-results

- name: Show container logs after failure
if: failure()
run: docker compose logs --no-color

- name: Stop playground stack
if: always()
run: docker compose down --volumes --remove-orphans
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ RAG_PLAYGROUND_MODE=ollama docker compose --profile ollama up --build

See [the playground guide](docs/PLAYGROUND.md) for supported files, environment variables, API contracts and local development.

<!-- playground-screenshot -->
## Visual Playground

![Java-RAG visual playground with knowledge base, grounded chat, and retrieval evidence](docs/assets/playground.png)

This screenshot is generated by the Playwright acceptance test after Docker Compose starts the real application, the browser loads the sample document, asks a grounded question, and verifies the matching retrieval evidence.

## Why Java-RAG?

- **Visual and inspectable** — the React playground displays knowledge-base state, conversation and retrieval evidence side by side.
Expand Down
7 changes: 7 additions & 0 deletions README_ch.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ RAG_PLAYGROUND_MODE=ollama docker compose --profile ollama up --build

详细说明见 [Playground 使用与 API 文档](docs/PLAYGROUND.md)。

<!-- playground-screenshot -->
## 可视化 Playground

![Java-RAG 可视化知识库、证据化问答与检索证据界面](docs/assets/playground.png)

这张截图由 Playwright 验收测试自动生成:Docker Compose 启动真实应用后,浏览器加载示例文档、完成一次证据化问答,并校验右侧检索证据与回答引用一致。

## 为什么值得关注?

- **有真正的前端产品界面**:知识库、问答和证据并排展示,而不是只有后端类和接口;
Expand Down
Binary file added docs/assets/playground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions frontend/e2e/playground.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect, test } from '@playwright/test'
import { mkdirSync } from 'node:fs'
import { resolve } from 'node:path'

test('uploads sample, asks a grounded question, and captures real evidence', async ({ page }) => {
await page.goto('/')

await expect(page.getByRole('heading', { name: 'Java-RAG Playground' })).toBeVisible()
await expect(page.getByText('local · offline-extractive')).toBeVisible()

await page.getByRole('button', { name: /加载内置示例/ }).click()

const documentCard = page.locator('.document-card').filter({ hasText: 'java-rag-playground-guide.md' })
await expect(documentCard).toBeVisible()
await expect(documentCard).toContainText('chunks')

const question = page.getByPlaceholder('向已上传文档提问…')
await expect(question).toBeEnabled()
await question.fill('如何启动可视化 Playground?')
await page.getByRole('button', { name: /^发送/ }).click()

const latestAssistantMessage = page.locator('.message-row.assistant').last()
await expect(latestAssistantMessage).toContainText('docker compose up --build')
await expect(latestAssistantMessage).toContainText('[1]')

const evidenceCard = page.locator('.evidence-card')
await expect(evidenceCard).toBeVisible()
await expect(evidenceCard).toContainText('java-rag-playground-guide.md')
await expect(evidenceCard).toContainText('Chunk #')
await expect(evidenceCard).toContainText('相似度')
await expect(evidenceCard).toContainText('docker compose up --build')

const screenshotDirectory = resolve(process.cwd(), '../docs/assets')
mkdirSync(screenshotDirectory, { recursive: true })
await page.screenshot({
path: resolve(screenshotDirectory, 'playground.png'),
fullPage: true,
})
})
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"e2e": "playwright test"
},
"dependencies": {
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
"devDependencies": {
"@playwright/test": "1.61.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.4",
Expand Down
32 changes: 32 additions & 0 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
testDir: './e2e',
timeout: 90_000,
expect: {
timeout: 15_000,
},
fullyParallel: false,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
workers: 1,
reporter: process.env.CI
? [['line'], ['html', { outputFolder: 'playwright-report', open: 'never' }]]
: 'list',
outputDir: 'test-results',
use: {
baseURL: process.env.PLAYGROUND_BASE_URL || 'http://127.0.0.1:3000',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1440, height: 960 },
},
},
],
})