feat: 集成 KieAI 服务,移除 models-integration 子项目

- 添加 Gemini 2.5 Flash 对话接口(流式+非流式)
- 添加 NanoBanana 图像生成/编辑接口
- 添加 Sora2 视频生成接口(文生视频、图生视频、去水印)
- 移除 models-integration 子项目(功能已迁移至主后端)
- 新增测试文档和 Playwright E2E 配置
- 更新前端页面和 API 接口
- 更新后端配置和日志处理
This commit is contained in:
2026-03-03 15:33:50 +08:00
parent 1ddb051977
commit 4be53dcd1b
586 changed files with 21142 additions and 25130 deletions

View File

@@ -0,0 +1,517 @@
/**
* E2E Bug Regression Tests
*
* Covers manual test issues:
* BUG-001 打卡积分显示与累加逻辑 (TC-B01a, TC-B01b)
* BUG-002 食谱计算器 Tab 选中样式 (TC-B02)
* BUG-003 食物百科列表缺图片与简介 (TC-B03)
* BUG-004 食物百科详情页数据加载失败 (TC-B04)
* BUG-005 AI营养师固定默认答复 (TC-B05)
* BUG-006 健康知识/营养知识名称不统一 (TC-B06)
* BUG-007 饮食指南/科普文章详情页无内容 (TC-B07)
* BUG-008 打卡社区帖子营养统计数据 (TC-B08)
* BUG-009 打卡社区帖子类型中文命名 (TC-B09)
*/
import { test, expect, Page, APIRequestContext } from '@playwright/test';
// @ts-expect-error - Node built-in, types from @types/node
import * as fs from 'fs';
// ─── 常量 ───────────────────────────────────────────────────────────────────
const BASE = 'http://localhost:8080';
const API_BASE = 'http://127.0.0.1:20822';
const TOOL_MAIN = 'pages/tool_main/index';
const PHONE = '18621813282';
const PASSWORD = 'A123456';
const SCREENSHOT_DIR = 'tests/e2e/screenshots';
const EXPIRE_SUFFIX = '_expire_2019_12_17_18_44';
const FAR_FUTURE = '4102444800';
let AUTH_TOKEN = '';
let AUTH_UID = 0;
// ─── 工具函数 ─────────────────────────────────────────────────────────────────
async function apiLogin(request: APIRequestContext): Promise<{ token: string; uid: number }> {
const res = await request.post(`${API_BASE}/api/front/login`, {
data: { account: PHONE, password: PASSWORD },
headers: { 'Content-Type': 'application/json' },
});
const body = await res.json();
if (body.code !== 200 || !body.data?.token) {
throw new Error(`API login failed: ${JSON.stringify(body)}`);
}
return { token: body.data.token, uid: body.data.uid };
}
async function injectAuth(page: Page, token: string, uid: number): Promise<void> {
await page.addInitScript(
({ t, u, suffix, farFuture }) => {
localStorage.setItem('LOGIN_STATUS_TOKEN', t);
localStorage.setItem(`LOGIN_STATUS_TOKEN${suffix}`, farFuture);
localStorage.setItem('UID', u.toString());
localStorage.setItem(`UID${suffix}`, farFuture);
},
{ t: token, u: uid, suffix: EXPIRE_SUFFIX, farFuture: FAR_FUTURE },
);
}
async function goto(page: Page, route: string, waitMs = 2500): Promise<void> {
await page.goto(`${BASE}/#/${route}`);
await page.waitForTimeout(waitMs);
}
function ensureScreenshotDir(): void {
try {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
} catch {
// ignore if exists or permission
}
}
async function screenshot(page: Page, name: string): Promise<void> {
await page.screenshot({
path: `${SCREENSHOT_DIR}/${name}.png`,
fullPage: false,
}).catch(() => {});
}
/** Parse points number from ".points-text" content like "522 积分" */
function parsePointsFromText(text: string | null): number {
const s = text ?? '';
const m = s.match(/(\d+)\s*积分/);
return m ? parseInt(m[1], 10) : 0;
}
// ─── 全局 beforeAll / beforeEach ─────────────────────────────────────────────
test.beforeAll(async ({ request }) => {
ensureScreenshotDir();
try {
const { token, uid } = await apiLogin(request);
AUTH_TOKEN = token;
AUTH_UID = uid;
} catch (e) {
console.error('[Auth] API login failed:', e);
}
});
test.beforeEach(async ({ page }) => {
page.on('pageerror', (err) => {
if (!err.message.includes('sockjs') && !err.message.includes('Unexpected end of stream')) {
console.warn(`[PageError] ${err.message}`);
}
});
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-001 打卡:未登录跳转登录页,打卡页一天仅允许一次
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B01a 未登录点击打卡跳转登录页', async ({ page }) => {
await goto(page, TOOL_MAIN, 4000);
const urlAfterLoad = page.url();
if (urlAfterLoad.includes('users/login') || urlAfterLoad.includes('login/index')) {
await screenshot(page, 'tc-b01a-checkin-to-login');
return;
}
const userCard = page.locator('.user-card').first();
await userCard.waitFor({ state: 'visible', timeout: 10_000 }).catch(() => {});
const checkinBtn = page.locator('.checkin-btn').first();
if (await checkinBtn.isVisible().catch(() => false)) {
await checkinBtn.click();
await page.waitForTimeout(2500);
}
const url = page.url();
const onLoginPage = url.includes('users/login') || url.includes('login/index');
expect(onLoginPage, '未登录点击打卡应跳转到登录页').toBe(true);
await screenshot(page, 'tc-b01a-checkin-to-login');
});
test('TC-B01b 登录后第一次打卡成功且同一天第二次仅允许一次', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool/checkin', 3000);
const checkinPage = page.locator('.checkin-page').first();
await checkinPage.waitFor({ state: 'visible', timeout: 12_000 });
const pointsEl = page.locator('.points-text').first();
await expect(pointsEl).toBeVisible({ timeout: 10_000 });
const initialText = await pointsEl.textContent();
const initialPoints = parsePointsFromText(initialText || '');
const punchBtn = page.locator('.checkin-card .checkin-btn').first();
const btnText = await punchBtn.textContent().catch(() => '');
if (btnText && btnText.includes('立即打卡')) {
await punchBtn.click();
await page.waitForTimeout(5000);
await goto(page, 'pages/tool/checkin', 3500);
const afterEl = page.locator('.points-text').first();
await expect(afterEl).toBeVisible({ timeout: 10_000 });
const afterText = await afterEl.textContent();
const afterPoints = parsePointsFromText(afterText || '');
expect(afterPoints, '首次打卡成功后积分应增加').toBeGreaterThanOrEqual(initialPoints);
}
await goto(page, 'pages/tool/checkin', 4000);
const punchBtnSecond = page.locator('.checkin-card .checkin-btn').first();
await expect(punchBtnSecond).toBeVisible({ timeout: 10_000 });
await page.waitForTimeout(1500);
let secondText = (await punchBtnSecond.textContent().catch(() => '')) || '';
let hasDisabledClass = await punchBtnSecond.evaluate((el) => el.classList.contains('checkin-btn-disabled')).catch(() => false);
let hasAlreadySignedText = await page.locator('.checkin-card').getByText(/今日已打卡|已签到|今日已/).isVisible().catch(() => false);
let alreadySigned =
secondText.includes('今日已打卡') ||
secondText.includes('已签到') ||
secondText.includes('今日已') ||
hasDisabledClass ||
hasAlreadySignedText;
if (!alreadySigned && secondText.includes('立即打卡')) {
await punchBtnSecond.click();
await page.waitForTimeout(3500);
const btnAfter = await page.locator('.checkin-card .checkin-btn').first().textContent().catch(() => '');
const pageHasToast = await page.getByText(/今日已签到|今日已打卡|不可重复/).isVisible().catch(() => false);
alreadySigned = (btnAfter && (btnAfter.includes('今日已打卡') || btnAfter.includes('已签到'))) || pageHasToast;
}
expect(alreadySigned, '同一天第二次进入打卡页应显示今日已打卡或点击后提示今日已签到').toBe(true);
await screenshot(page, 'tc-b01b-checkin-once-per-day');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-002 食谱计算器 Tab 选中样式辨识度
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B02 计算结果页 Tab 切换选中状态清晰', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
await page.locator('.calculator').click();
await page.waitForTimeout(3000);
const formContainer = page.locator('.calculator-page').first();
await formContainer.waitFor({ state: 'visible', timeout: 10_000 });
const maleOption = page.locator('.radio-item').filter({ hasText: '男' }).first();
await maleOption.click();
await page.waitForTimeout(500);
const numberInputs = page.locator('input[type="number"], input.uni-input-input');
const ageI = numberInputs.nth(0);
const htI = numberInputs.nth(1);
const wtI = numberInputs.nth(2);
if (await ageI.count() > 0) {
await ageI.fill('45');
await htI.fill('170');
await wtI.fill('65');
await page.waitForTimeout(500);
}
const calcBtn = page.locator('.submit-btn, .calc-btn, button').filter({ hasText: /计算|确定|提交/ }).first();
await calcBtn.click();
await page.waitForTimeout(3500);
const resultPage = page.locator('.result-page').first();
await resultPage.waitFor({ state: 'visible', timeout: 10_000 });
const tabItems = page.locator('.tab-item');
await expect(tabItems).toHaveCount(2);
const overviewTab = page.locator('.tab-item').filter({ hasText: '健康概览' }).first();
const mealTab = page.locator('.tab-item').filter({ hasText: '营养配餐' }).first();
await overviewTab.click();
await page.waitForTimeout(500);
await expect(overviewTab).toHaveClass(/active/);
await expect(mealTab).not.toHaveClass(/active/);
await mealTab.click();
await page.waitForTimeout(500);
await expect(mealTab).toHaveClass(/active/);
await expect(overviewTab).not.toHaveClass(/active/);
const activeColor = await page.locator('.tab-item.active .tab-text').first().evaluate((el) => {
const s = window.getComputedStyle(el);
return s.color + '|' + s.fontWeight;
});
const inactiveColor = await page.locator('.tab-item').filter({ hasText: '健康概览' }).first().evaluate((el) => {
const s = window.getComputedStyle(el);
return s.color + '|' + s.fontWeight;
});
expect(activeColor).toBeTruthy();
expect(inactiveColor).toBeTruthy();
await screenshot(page, 'tc-b02-calculator-tabs');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-003 食物百科列表缺图片与简介
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B03 食物列表每条目展示图片与营养信息', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool/food-encyclopedia', 3000);
const foodPage = page.locator('.food-page, .search-container, .food-list').first();
await foodPage.waitFor({ state: 'visible', timeout: 12_000 });
const foodItems = page.locator('.food-item');
await expect(foodItems.first()).toBeVisible({ timeout: 10_000 });
const count = await foodItems.count();
expect(count, '食物列表应有至少一条').toBeGreaterThan(0);
const toCheck = Math.min(3, count);
for (let i = 0; i < toCheck; i++) {
const item = foodItems.nth(i);
await expect(item.locator('.food-name').first()).toBeVisible();
const nameText = await item.locator('.food-name').first().textContent();
expect((nameText || '').trim().length).toBeGreaterThan(0);
const img = item.locator('.food-image').first();
if (await img.count() > 0) {
const src = await img.getAttribute('src');
expect(src, `食物条目 ${i + 1} 应有配图 src`).toBeTruthy();
expect((src || '').trim().length).toBeGreaterThan(0);
}
const nutritionItems = item.locator('.nutrition-item');
const nutritionCount = await nutritionItems.count();
expect(nutritionCount, `食物条目 ${i + 1} 应至少有一条营养信息`).toBeGreaterThanOrEqual(1);
}
await screenshot(page, 'tc-b03-food-list');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-004 食物百科详情页数据加载失败
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B04 食物详情页正常加载内容', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool/food-encyclopedia', 3000);
const firstFood = page.locator('.food-item').first();
await firstFood.waitFor({ state: 'visible', timeout: 10_000 });
await firstFood.click();
await page.waitForTimeout(3000);
const detailPage = page.locator('.food-detail-page').first();
await detailPage.waitFor({ state: 'visible', timeout: 8_000 }).catch(() => {});
await expect(page.getByText('数据加载失败')).toHaveCount(0);
const nameOverlay = page.locator('.food-name-overlay').first();
await expect(nameOverlay).toBeVisible({ timeout: 6_000 });
const nameText = await nameOverlay.textContent();
expect((nameText || '').trim().length).toBeGreaterThan(0);
const nutrientCards = page.locator('.nutrient-card');
const cardCount = await nutrientCards.count();
expect(cardCount, '详情页应展示营养数据').toBeGreaterThan(0);
const nutritionRows = page.locator('.nutrition-row');
const rowCount = await nutritionRows.count();
expect(rowCount, '详情页应有营养表格').toBeGreaterThan(0);
await screenshot(page, 'tc-b04-food-detail');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-005 AI营养师对话使用 KieAI Gemini chat针对问题返回差异化回复
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B05 AI针对不同问题返回差异化回复', async ({ page }) => {
test.setTimeout(60_000);
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
await page.locator('.ai-nutritionist').click();
await page.waitForTimeout(3500);
const chatContainer = page.locator('.chat-container, .ai-chat-page').first();
await chatContainer.waitFor({ state: 'visible', timeout: 12_000 });
const textInput = page.locator('input[type="text"], textarea, .chat-input input').first();
await textInput.waitFor({ state: 'visible', timeout: 8_000 });
await textInput.fill('什么食物富含蛋白质?');
const sendBtn = page.locator('.send-btn').first();
await sendBtn.click();
await page.waitForTimeout(8000);
const aiMessages1 = page.locator('.message-item.ai-message .message-text');
await expect(aiMessages1.last()).toBeVisible({ timeout: 15_000 });
const response1 = await aiMessages1.last().textContent();
expect((response1 || '').length).toBeGreaterThan(20);
await textInput.fill('糖尿病患者饮食需要注意什么?');
await sendBtn.click();
await page.waitForTimeout(8000);
const aiMessages2 = page.locator('.message-item.ai-message .message-text');
await expect(aiMessages2.last()).toBeVisible({ timeout: 10_000 });
const response2 = await aiMessages2.last().textContent();
expect((response2 || '').length).toBeGreaterThan(20);
expect(response1, '两个不同问题应得到不同回复').not.toBe(response2);
await screenshot(page, 'tc-b05-ai-responses');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-006 健康知识模块名称不统一
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B06 健康知识与营养知识名称统一性', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
const sectionHeader = page.locator('.section-header').filter({ has: page.locator('.section-title') }).filter({ hasText: /健康知识|营养知识/ }).first();
await expect(sectionHeader).toBeVisible({ timeout: 10_000 });
const mainTitle = await sectionHeader.locator('.section-title').textContent();
const mainName = (mainTitle || '').trim();
await page.locator('.nutrition-knowledge').click();
await page.waitForTimeout(3000);
const navTitle = page.locator('.uni-nav-bar__content__title, .nav-bar-title, .page-title').first();
const pageTitle = await navTitle.textContent().catch(() => '');
const detailName = (pageTitle || '').trim();
const mainIsHealth = mainName.includes('健康知识');
const mainIsNutrition = mainName.includes('营养知识');
const detailIsHealth = detailName.includes('健康知识');
const detailIsNutrition = detailName.includes('营养知识');
expect(mainIsHealth || mainIsNutrition).toBe(true);
expect(detailIsHealth || detailIsNutrition).toBe(true);
expect(mainName === detailName || (mainIsHealth && detailIsHealth) || (mainIsNutrition && detailIsNutrition),
'主页区块名称与营养知识页标题应统一(均为健康知识或均为营养知识)').toBe(true);
await screenshot(page, 'tc-b06-name-consistency');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-007 饮食指南/科普文章详情页无内容
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B07 饮食指南和科普文章详情页有正常内容', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool/nutrition-knowledge', 3000);
const tabContainer = page.locator('.tab-container').first();
await tabContainer.waitFor({ state: 'visible', timeout: 10_000 });
const guideTab = page.locator('.tab-item').filter({ hasText: '饮食指南' }).first();
await guideTab.click();
await page.waitForTimeout(2000);
const guideList = page.locator('.knowledge-item');
const guideCount = await guideList.count();
if (guideCount > 0) {
await guideList.first().click();
await page.waitForTimeout(3000);
const contentArea = page.locator('.conter, .article-content, .content-scroll').first();
await expect(contentArea).toBeVisible({ timeout: 8_000 });
const contentText = await contentArea.textContent();
expect((contentText || '').trim().length, '饮食指南详情应有正文').toBeGreaterThan(50);
await expect(page.locator('.empty-placeholder').filter({ hasText: '暂无' })).toHaveCount(0);
await page.goBack();
await page.waitForTimeout(2000);
}
const articlesTab = page.locator('.tab-item').filter({ hasText: '科普文章' }).first();
await articlesTab.click();
await page.waitForTimeout(2000);
const articleList = page.locator('.knowledge-item');
const articleCount = await articleList.count();
if (articleCount > 0) {
await articleList.first().click();
await page.waitForTimeout(3000);
const contentArea2 = page.locator('.conter, .article-content, .content-scroll, .newsDetail').first();
await expect(contentArea2).toBeVisible({ timeout: 8_000 });
const contentText2 = await contentArea2.textContent();
expect((contentText2 || '').trim().length, '科普文章详情应有正文').toBeGreaterThan(50);
}
await screenshot(page, 'tc-b07-article-detail');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-008 打卡社区帖子营养统计数据
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-B08 帖子详情页展示营养统计数据', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool_main/community', 3000);
const communityPage = page.locator('.community-page').first();
await communityPage.waitFor({ state: 'visible', timeout: 12_000 });
const hasPosts = await page.locator('.post-grid .post-card').first().isVisible().catch(() => false);
if (!hasPosts) {
test.skip();
return;
}
await page.locator('.post-card').first().click();
await page.waitForTimeout(3000);
const statsCard = page.locator('.nutrition-stats-card').first();
await expect(statsCard).toBeVisible({ timeout: 8_000 });
const statItems = page.locator('.stat-item');
await expect(statItems.first()).toBeVisible();
const count = await statItems.count();
expect(count).toBeGreaterThan(0);
const firstValue = await page.locator('.stat-value').first().textContent();
expect((firstValue || '').trim().length).toBeGreaterThan(0);
await screenshot(page, 'tc-b08-post-nutrition-stats');
});
// ═══════════════════════════════════════════════════════════════════════════════
// BUG-009 打卡社区帖子类型中文命名
// ═══════════════════════════════════════════════════════════════════════════════
const CHINESE_ONLY = /^[\u4e00-\u9fa5]+$/;
test('TC-B09 社区 Tab 标签和帖子类型均使用中文', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool_main/community', 3000);
const tabNav = page.locator('.tab-nav').first();
await tabNav.waitFor({ state: 'visible', timeout: 12_000 });
const tabTexts = page.locator('.tab-item .tab-text, .tab-item');
const tabCount = await tabTexts.count();
for (let i = 0; i < tabCount; i++) {
const text = await tabTexts.nth(i).textContent();
const label = (text || '').trim();
if (label.length > 0) {
const isChinese = CHINESE_ONLY.test(label) || /[\u4e00-\u9fa5]/.test(label);
expect(isChinese, `Tab 标签 "${label}" 应为中文`).toBe(true);
}
}
const typeTags = page.locator('.type-tag, .meal-tag');
const tagCount = await typeTags.count();
for (let i = 0; i < Math.min(tagCount, 10); i++) {
const text = await typeTags.nth(i).textContent();
const label = (text || '').trim();
if (label.length > 0) {
const hasChinese = /[\u4e00-\u9fa5]/.test(label);
expect(hasChinese, `帖子类型 "${label}" 应含中文表述`).toBe(true);
}
}
await screenshot(page, 'tc-b09-community-chinese');
});

View File

@@ -0,0 +1,77 @@
import { Page, FrameLocator, Locator } from '@playwright/test';
/** App base URL (H5 dev server) */
export const BASE_URL = 'http://localhost:8080';
/** Credentials for demo account */
export const TEST_CREDENTIALS = {
phone: '18621813282',
password: 'A123456',
};
/**
* Returns the frame or page to interact with.
* If the page has a #iframe element (pc.html wrapper), use the frame context;
* otherwise use the page directly.
*/
export async function getAppContext(page: Page): Promise<Page | FrameLocator> {
const hasIframe = await page.locator('#iframe').count();
if (hasIframe > 0) {
return page.frameLocator('#iframe');
}
return page;
}
/**
* Navigate to an internal UniApp hash route.
* Works whether we are inside an iframe or directly on root.
*/
export async function goToPage(page: Page, route: string): Promise<void> {
await page.goto(`${BASE_URL}/#/${route}`);
// Brief wait for Vue router & components to hydrate
await page.waitForTimeout(1500);
}
/**
* Perform login flow using phone + password.
* Expects to already be on the login page or navigates there.
*/
export async function login(
page: Page,
phone = TEST_CREDENTIALS.phone,
password = TEST_CREDENTIALS.password,
): Promise<void> {
await goToPage(page, 'pages/users/login/index');
// UniApp H5 renders <input> as native HTML input elements
const phoneInput = page.locator('input[type="number"], input[placeholder*="手机号"]').first();
const passwordInput = page.locator('input[type="password"], input[placeholder*="密码"]').first();
await phoneInput.waitFor({ state: 'visible', timeout: 10_000 });
await phoneInput.fill(phone);
await passwordInput.fill(password);
// Agree to terms (checkbox-group)
const checkbox = page.locator('uni-checkbox, .checkbox').first();
const isChecked = await checkbox.getAttribute('checked').catch(() => null);
if (!isChecked || isChecked === 'false') {
await checkbox.click().catch(() => {
// Fallback: click the parent checkbox-group
return page.locator('uni-checkbox-group, .checkgroup').first().click();
});
}
// Click login button (current === 0 → "账号登录" button)
const loginBtn = page.locator('.logon').first();
await loginBtn.click();
// Wait for navigation away from login page
await page.waitForTimeout(3000);
}
/**
* Wait until the UniApp page finishes mounting (title or key element appears).
*/
export async function waitForPageReady(page: Page, selector: string, timeout = 10_000): Promise<void> {
await page.locator(selector).waitFor({ state: 'visible', timeout });
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

View File

@@ -0,0 +1,70 @@
# Page snapshot
```yaml
- generic [ref=e3]:
- generic [ref=e5]:
- generic [ref=e8] [cursor=pointer]:
- generic [ref=e10]: 营养知识
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e17]: 营养素
- generic [ref=e19]: 饮食指南
- generic [ref=e21]: 科普文章
- generic [ref=e26]:
- generic [ref=e27]: 了解关键营养素,科学管理慢性肾病饮食
- generic [ref=e28]:
- generic [ref=e29]:
- generic [ref=e31]: 🥩
- generic [ref=e32]:
- generic [ref=e33]:
- generic [ref=e34]: 蛋白质
- generic [ref=e35]: Protein
- generic [ref=e36]: 构成人体组织的重要营养素
- generic [ref=e38]: 需控制
- generic [ref=e40]:
- generic [ref=e41]:
- generic [ref=e43]: 🍌
- generic [ref=e44]:
- generic [ref=e45]:
- generic [ref=e46]:
- generic [ref=e47]: Potassium (K)
- generic [ref=e48]: 维持神经肌肉功能的重要元素
- generic [ref=e50]: 严格控制
- generic [ref=e52]:
- generic [ref=e53]:
- generic [ref=e55]: 🥜
- generic [ref=e56]:
- generic [ref=e57]:
- generic [ref=e58]:
- generic [ref=e59]: Phosphorus (P)
- generic [ref=e60]: 骨骼健康的重要矿物质
- generic [ref=e62]: 严格控制
- generic [ref=e64]:
- generic [ref=e65]:
- generic [ref=e67]: 🧂
- generic [ref=e68]:
- generic [ref=e69]:
- generic [ref=e70]:
- generic [ref=e71]: Sodium (Na)
- generic [ref=e72]: 调节体液平衡的电解质
- generic [ref=e74]: 适量控制
- generic [ref=e76]:
- generic [ref=e77]:
- generic [ref=e79]: 🥛
- generic [ref=e80]:
- generic [ref=e81]:
- generic [ref=e82]:
- generic [ref=e83]: Calcium (Ca)
- generic [ref=e84]: 骨骼和牙齿的主要成分
- generic [ref=e86]: 适量补充
- generic [ref=e88]:
- generic [ref=e89]:
- generic [ref=e91]: 💧
- generic [ref=e92]:
- generic [ref=e93]:
- generic [ref=e94]: 水分
- generic [ref=e95]: Water
- generic [ref=e96]: 生命活动必需的基础物质
- generic [ref=e98]: 需控制
- generic [ref=e100]:
```

View File

@@ -0,0 +1,46 @@
# Page snapshot
```yaml
- generic [ref=e3]:
- generic [ref=e5]:
- generic [ref=e8] [cursor=pointer]:
- generic [ref=e10]: 食谱计算器
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e16]:
- generic [ref=e17]: 性别:
- generic [ref=e18]:
- generic [ref=e21]:
- generic [ref=e24]:
- generic [ref=e25]:
- generic [ref=e26]: 年龄:
- generic [ref=e27]:
- spinbutton [ref=e30]: "45"
- generic:
- generic [ref=e31]:
- generic [ref=e32]: 身高:
- generic [ref=e33]:
- spinbutton [ref=e36]: "170"
- generic: cm
- generic [ref=e37]:
- generic [ref=e38]: 是否透析:
- generic [ref=e39]:
- generic [ref=e42]:
- generic [ref=e45]:
- generic [ref=e46]:
- generic [ref=e47]: 干体重:
- generic [ref=e48]:
- spinbutton [active] [ref=e51]: "65"
- generic: kg
- generic [ref=e52]: 透析患者请输入透析后的干体重
- generic [ref=e53]:
- generic [ref=e54]: 血肌酐:
- generic [ref=e55]:
- generic [ref=e57]:
- generic: 请输入血肌酐值
- spinbutton [ref=e58]
- generic: μmol/L
- generic [ref=e59]:
- generic:
- generic: 开始计算
```

View File

@@ -0,0 +1,40 @@
# Page snapshot
```yaml
- generic [ref=e3]:
- generic [ref=e5]:
- generic [ref=e8] [cursor=pointer]:
- generic [ref=e10]: AI营养师
- generic [ref=e14]:
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]:
- generic [ref=e19]: 慢生活守护健康
- generic [ref=e20]:
- generic [ref=e21]: 营养师专家入驻,在线答疑
- generic [ref=e22]:
- generic [ref=e23]:
- generic [ref=e24]: 🗑️
- generic [ref=e25]: 清空
- img [ref=e28]
- generic [ref=e34]:
- generic [ref=e36]: 🤖
- generic [ref=e37]:
- generic [ref=e39]: AI营养师
- generic [ref=e42]:
- text: 👋您好我是您的AI营养师助手。
- text: 我可以帮您:
- text: • 解答饮食疑问
- text: • 评估食物适宜性
- text: • 提供烹饪建议
- text: • 解读检验报告
- text: 有什么想问的吗?
- generic [ref=e43]:
- generic [ref=e45]: 透析患者可以喝牛奶吗?
- generic [ref=e47]: 什么食物含磷比较低?
- generic [ref=e49]:
- img [ref=e53]
- img [ref=e57]
- textbox [active] [ref=e60]: 什么食物富含蛋白质?
- img [ref=e64]
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -0,0 +1,97 @@
# Page snapshot
```yaml
- generic [ref=e2]:
- generic [ref=e3]:
- generic [ref=e7]: 慢生活管理甄品商城
- generic [ref=e11]:
- generic [ref=e13]:
- img [ref=e17] [cursor=pointer]
- generic [ref=e18]:
- generic [ref=e19]: 数字化+Scott潘
- generic [ref=e20]: 尚未完成健康档案
- generic [ref=e22]: 打卡
- generic [ref=e23]:
- generic [ref=e25]:
- generic [ref=e26]:
- generic [ref=e27]: 食谱计算器
- generic [ref=e28]: 个性化营养方案
- generic [ref=e30]: 📊
- generic [ref=e32]:
- generic [ref=e33]:
- generic [ref=e34]: AI营养师
- generic [ref=e35]: 智慧知肾健康
- generic [ref=e37]: 💬
- generic [ref=e39]:
- generic [ref=e40]:
- generic [ref=e41]: 食物百科
- generic [ref=e42]: 营养成分查询
- generic [ref=e44]: 🔍
- generic [ref=e46]:
- generic [ref=e47]:
- generic [ref=e48]: 营养知识
- generic [ref=e49]: 专业营养指导
- generic [ref=e51]: 💡
- generic [ref=e52]:
- generic [ref=e53]:
- generic [ref=e54]: 精选食谱
- generic [ref=e56] [cursor=pointer]:
- generic [ref=e57]:
- generic [ref=e58]:
- generic [ref=e59]:
- img [ref=e62]
- generic [ref=e63]: 我的配餐
- generic [ref=e64]:
- generic [ref=e65]: 每日营养配餐 - CKD 2期
- generic [ref=e66]: 蛋白质 56.0g/天 | 能量 2450kcal/天
- generic [ref=e67]:
- generic [ref=e68]:
- generic [ref=e69]: 🥩
- generic [ref=e70]: 蛋白质 56.0g
- generic [ref=e71]:
- generic [ref=e72]: 🔥
- generic [ref=e73]: 2450kcal
- generic [ref=e74]:
- generic [ref=e75]:
- img [ref=e78]
- generic [ref=e79]: 我的配餐
- generic [ref=e80]:
- generic [ref=e81]: 每日营养配餐 - 透析期
- generic [ref=e82]: 蛋白质 82.3g/天 | 能量 2401kcal/天
- generic [ref=e83]:
- generic [ref=e84]:
- generic [ref=e85]: 🥩
- generic [ref=e86]: 蛋白质 82.3g
- generic [ref=e87]:
- generic [ref=e88]: 🔥
- generic [ref=e89]: 2401kcal
- generic [ref=e91]:
- generic [ref=e92]:
- generic [ref=e93]: 慢生活营养专家
- generic [ref=e94]: 专业个性化营养方案
- generic [ref=e96]: 立即领取福利
- generic [ref=e97]:
- generic [ref=e98]:
- generic [ref=e99]: 健康知识
- generic [ref=e101] [cursor=pointer]:
- generic [ref=e102]:
- generic [ref=e105]:
- generic [ref=e106]: 碳水化合物食物热效应是多少?
- generic [ref=e108]: ·
- generic [ref=e111]:
- generic [ref=e112]: 一天需要多少能量
- generic [ref=e114]: ·
- generic [ref=e117]:
- generic [ref=e120] [cursor=pointer]:
- img [ref=e122]
- generic [ref=e123]: 首页
- generic [ref=e125] [cursor=pointer]:
- img [ref=e127]
- generic [ref=e128]: 社区
- generic [ref=e130] [cursor=pointer]:
- img [ref=e132]
- generic [ref=e133]: 商城
- generic [ref=e135] [cursor=pointer]:
- img [ref=e137]
- generic [ref=e138]: 我的
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.drop-target{display:flex;align-items:center;justify-content:center;flex:auto;flex-direction:column;background-color:var(--vscode-editor-background);position:absolute;top:0;right:0;bottom:0;left:0;z-index:100;line-height:24px}body .drop-target{background:#fffc}:root.dark-mode .drop-target{background:#000c}.drop-target .title{font-size:24px;font-weight:700;margin-bottom:30px}.drop-target .info{max-width:400px;text-align:center}.drop-target .processing-error{font-size:24px;color:#e74c3c;font-weight:700;text-align:center;margin:30px;white-space:pre-line}.drop-target input{margin-top:50px}.drop-target button{color:#fff;background-color:#007acc;padding:8px 12px;border:none;margin:30px 0;cursor:pointer}.drop-target .version{color:var(--vscode-disabledForeground);margin-top:8px}.progress-dialog{width:400px;top:0;right:0;bottom:0;left:0;border:none;outline:none;background-color:var(--vscode-sideBar-background)}.progress-dialog::backdrop{background-color:#0006}.progress-content{padding:16px}.progress-content .title{background-color:unset;font-size:18px;font-weight:700;padding:0}.progress-wrapper{background-color:var(--vscode-commandCenter-activeBackground);width:100%;margin-top:16px;margin-bottom:8px}.inner-progress{background-color:var(--vscode-progressBar-background);height:4px}.header{display:flex;background-color:#000;flex:none;flex-basis:48px;line-height:48px;font-size:16px;color:#ccc}.workbench-loader{contain:size}.workbench-loader .header .toolbar-button{margin:12px;padding:8px 4px}.workbench-loader .logo{margin-left:16px;display:flex;align-items:center}.workbench-loader .logo img{height:32px;width:32px;pointer-events:none;flex:none}.workbench-loader .product{font-weight:600;margin-left:16px;flex:none}.workbench-loader .header .title{margin-left:16px;overflow:hidden;text-overflow:ellipsis;text-wrap:nowrap}html,body{min-width:550px;min-height:450px;overflow:auto}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en" translate="no">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="./playwright-logo.svg" type="image/svg+xml">
<link rel="manifest" href="./manifest.webmanifest">
<title>Playwright Trace Viewer</title>
<script type="module" crossorigin src="./index.BDwrLSGN.js"></script>
<link rel="modulepreload" crossorigin href="./assets/defaultSettingsView-CJSZINFr.js">
<link rel="stylesheet" crossorigin href="./defaultSettingsView.7ch9cixO.css">
<link rel="stylesheet" crossorigin href="./index.BVu7tZDe.css">
</head>
<body>
<div id="root"></div>
<dialog id="fallback-error">
<p>The Playwright Trace Viewer must be loaded over the <code>http://</code> or <code>https://</code> protocols.</p>
<p>For more information, please see the <a href="https://aka.ms/playwright/trace-viewer-file-protocol">docs</a>.</p>
</dialog>
<script>
if (!/^https?:/.test(window.location.protocol)) {
const fallbackErrorDialog = document.getElementById('fallback-error');
const isTraceViewerInsidePlaywrightReport = window.location.protocol === 'file:' && window.location.pathname.endsWith('/trace/index.html');
// Best-effort to show the report path in the dialog.
if (isTraceViewerInsidePlaywrightReport) {
const reportPath = (() => {
const base = decodeURIComponent(window.location.pathname).replace(/\/trace\/index\.html$/, '');
if (navigator.platform === 'Win32')
return base.replace(/^\//, '').replace(/\//g, '\\\\');
return base;
})();
const reportLink = document.createElement('div');
const command = `npx playwright show-report "${reportPath}"`;
reportLink.innerHTML = `You can open the report via <code>${command}</code> from your Playwright project. <button type="button">Copy Command</button>`;
fallbackErrorDialog.insertBefore(reportLink, fallbackErrorDialog.children[1]);
reportLink.querySelector('button').addEventListener('click', () => navigator.clipboard.writeText(command));
}
fallbackErrorDialog.show();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
{
"theme_color": "#000",
"background_color": "#fff",
"display": "standalone",
"start_url": "index.html",
"name": "Playwright Trace Viewer",
"short_name": "Trace Viewer",
"icons": [
{
"src": "playwright-logo.svg",
"sizes": "48x48 72x72 96x96 128x128 150x150 256x256 512x512 1024x1024",
"type": "image/svg+xml",
"purpose": "any"
}
]
}

View File

@@ -0,0 +1,9 @@
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M136.444 221.556C123.558 225.213 115.104 231.625 109.535 238.032C114.869 233.364 122.014 229.08 131.652 226.348C141.51 223.554 149.92 223.574 156.869 224.915V219.481C150.941 218.939 144.145 219.371 136.444 221.556ZM108.946 175.876L61.0895 188.484C61.0895 188.484 61.9617 189.716 63.5767 191.36L104.153 180.668C104.153 180.668 103.578 188.077 98.5847 194.705C108.03 187.559 108.946 175.876 108.946 175.876ZM149.005 288.347C81.6582 306.486 46.0272 228.438 35.2396 187.928C30.2556 169.229 28.0799 155.067 27.5 145.928C27.4377 144.979 27.4665 144.179 27.5336 143.446C24.04 143.657 22.3674 145.473 22.7077 150.721C23.2876 159.855 25.4633 174.016 30.4473 192.721C41.2301 233.225 76.8659 311.273 144.213 293.134C158.872 289.185 169.885 281.992 178.152 272.81C170.532 279.692 160.995 285.112 149.005 288.347ZM161.661 128.11V132.903H188.077C187.535 131.206 186.989 129.677 186.447 128.11H161.661Z" fill="#2D4552"/>
<path d="M193.981 167.584C205.861 170.958 212.144 179.287 215.465 186.658L228.711 190.42C228.711 190.42 226.904 164.623 203.57 157.995C181.741 151.793 168.308 170.124 166.674 172.496C173.024 167.972 182.297 164.268 193.981 167.584ZM299.422 186.777C277.573 180.547 264.145 198.916 262.535 201.255C268.89 196.736 278.158 193.031 289.837 196.362C301.698 199.741 307.976 208.06 311.307 215.436L324.572 219.212C324.572 219.212 322.736 193.41 299.422 186.777ZM286.262 254.795L176.072 223.99C176.072 223.99 177.265 230.038 181.842 237.869L274.617 263.805C282.255 259.386 286.262 254.795 286.262 254.795ZM209.867 321.102C122.618 297.71 133.166 186.543 147.284 133.865C153.097 112.156 159.073 96.0203 164.029 85.204C161.072 84.5953 158.623 86.1529 156.203 91.0746C150.941 101.747 144.212 119.124 137.7 143.45C123.586 196.127 113.038 307.29 200.283 330.682C241.406 341.699 273.442 324.955 297.323 298.659C274.655 319.19 245.714 330.701 209.867 321.102Z" fill="#2D4552"/>
<path d="M161.661 262.296V239.863L99.3324 257.537C99.3324 257.537 103.938 230.777 136.444 221.556C146.302 218.762 154.713 218.781 161.661 220.123V128.11H192.869C189.471 117.61 186.184 109.526 183.423 103.909C178.856 94.612 174.174 100.775 163.545 109.665C156.059 115.919 137.139 129.261 108.668 136.933C80.1966 144.61 57.179 142.574 47.5752 140.911C33.9601 138.562 26.8387 135.572 27.5049 145.928C28.0847 155.062 30.2605 169.224 35.2445 187.928C46.0272 228.433 81.663 306.481 149.01 288.342C166.602 283.602 179.019 274.233 187.626 262.291H161.661V262.296ZM61.0848 188.484L108.946 175.876C108.946 175.876 107.551 194.288 89.6087 199.018C71.6614 203.743 61.0848 188.484 61.0848 188.484Z" fill="#E2574C"/>
<path d="M341.786 129.174C329.345 131.355 299.498 134.072 262.612 124.185C225.716 114.304 201.236 97.0224 191.537 88.8994C177.788 77.3834 171.74 69.3802 165.788 81.4857C160.526 92.163 153.797 109.54 147.284 133.866C133.171 186.543 122.623 297.706 209.867 321.098C297.093 344.47 343.53 242.92 357.644 190.238C364.157 165.917 367.013 147.5 367.799 135.625C368.695 122.173 359.455 126.078 341.786 129.174ZM166.497 172.756C166.497 172.756 180.246 151.372 203.565 158C226.899 164.628 228.706 190.425 228.706 190.425L166.497 172.756ZM223.42 268.713C182.403 256.698 176.077 223.99 176.077 223.99L286.262 254.796C286.262 254.791 264.021 280.578 223.42 268.713ZM262.377 201.495C262.377 201.495 276.107 180.126 299.422 186.773C322.736 193.411 324.572 219.208 324.572 219.208L262.377 201.495Z" fill="#2EAD33"/>
<path d="M139.88 246.04L99.3324 257.532C99.3324 257.532 103.737 232.44 133.607 222.496L110.647 136.33L108.663 136.933C80.1918 144.611 57.1742 142.574 47.5704 140.911C33.9554 138.563 26.834 135.572 27.5001 145.929C28.08 155.063 30.2557 169.224 35.2397 187.929C46.0225 228.433 81.6583 306.481 149.005 288.342L150.989 287.719L139.88 246.04ZM61.0848 188.485L108.946 175.876C108.946 175.876 107.551 194.288 89.6087 199.018C71.6615 203.743 61.0848 188.485 61.0848 188.485Z" fill="#D65348"/>
<path d="M225.27 269.163L223.415 268.712C182.398 256.698 176.072 223.99 176.072 223.99L232.89 239.872L262.971 124.281L262.607 124.185C225.711 114.304 201.232 97.0224 191.532 88.8994C177.783 77.3834 171.735 69.3802 165.783 81.4857C160.526 92.163 153.797 109.54 147.284 133.866C133.171 186.543 122.623 297.706 209.867 321.097L211.655 321.5L225.27 269.163ZM166.497 172.756C166.497 172.756 180.246 151.372 203.565 158C226.899 164.628 228.706 190.425 228.706 190.425L166.497 172.756Z" fill="#1D8D22"/>
<path d="M141.946 245.451L131.072 248.537C133.641 263.019 138.169 276.917 145.276 289.195C146.513 288.922 147.74 288.687 149 288.342C152.302 287.451 155.364 286.348 158.312 285.145C150.371 273.361 145.118 259.789 141.946 245.451ZM137.7 143.451C132.112 164.307 127.113 194.326 128.489 224.436C130.952 223.367 133.554 222.371 136.444 221.551L138.457 221.101C136.003 188.939 141.308 156.165 147.284 133.866C148.799 128.225 150.318 122.978 151.832 118.085C149.393 119.637 146.767 121.228 143.776 122.867C141.759 129.093 139.722 135.898 137.7 143.451Z" fill="#C04B41"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<body>
<iframe src="about:blank" style="position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;border:none;"></iframe>
<script>
(async () => {
if (!navigator.serviceWorker)
throw new Error(`Service workers are not supported.\nMake sure to serve the Trace Viewer (${window.location}) via HTTPS or localhost.`);
navigator.serviceWorker.register('sw.bundle.js');
if (!navigator.serviceWorker.controller)
await new Promise(f => navigator.serviceWorker.oncontrollerchange = f);
const traceUrl = new URL(location.href).searchParams.get('trace');
const params = new URLSearchParams();
params.set('trace', traceUrl);
await fetch('contexts?' + params.toString());
document.querySelector('iframe').src = new URLSearchParams(location.search).get('r');
})();
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en" translate="no">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="./playwright-logo.svg" type="image/svg+xml">
<title>Playwright Test</title>
<script type="module" crossorigin src="./uiMode.CQJ9SCIQ.js"></script>
<link rel="modulepreload" crossorigin href="./assets/defaultSettingsView-CJSZINFr.js">
<link rel="stylesheet" crossorigin href="./defaultSettingsView.7ch9cixO.css">
<link rel="stylesheet" crossorigin href="./uiMode.Btcz36p_.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Some files were not shown because too many files have changed in this diff Show More