mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 399440c84d | |||
| e68ae8e79a | |||
| f8032adf3f |
@@ -275,8 +275,12 @@ async function buildApp(): Promise<void> {
|
||||
|
||||
async function isServerRunning(port: number): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/chat`, { method: 'HEAD' });
|
||||
return response.ok;
|
||||
const response = await fetch(`http://localhost:${port}/`, {
|
||||
method: 'HEAD',
|
||||
redirect: 'manual',
|
||||
});
|
||||
// Any HTTP response (including redirects) means the server is running
|
||||
return response.status > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ let createdDocumentId: string | null = null;
|
||||
// Given Steps
|
||||
// ============================================
|
||||
|
||||
Given('用户在 Home 页面', async function (this: CustomWorld) {
|
||||
Given('用户在 Home 页面', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 设置 LLM mock...');
|
||||
// Setup LLM mock before navigation (for agent/group/page builder message)
|
||||
llmMockManager.setResponse('E2E Test Agent', presetResponses.greeting);
|
||||
@@ -51,7 +51,7 @@ Given('用户在 Home 页面', async function (this: CustomWorld) {
|
||||
// When Steps
|
||||
// ============================================
|
||||
|
||||
When('用户点击创建 Agent 按钮', async function (this: CustomWorld) {
|
||||
When('用户点击创建 Agent 按钮', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 点击创建 Agent 按钮...');
|
||||
|
||||
// Find the "Create Agent" button by text (supports both English and Chinese)
|
||||
@@ -66,7 +66,7 @@ When('用户点击创建 Agent 按钮', async function (this: CustomWorld) {
|
||||
console.log(' ✅ 已点击创建 Agent 按钮');
|
||||
});
|
||||
|
||||
When('用户点击创建 Group 按钮', async function (this: CustomWorld) {
|
||||
When('用户点击创建 Group 按钮', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 点击创建 Group 按钮...');
|
||||
|
||||
// Find the "Create Group" button by text (supports both English and Chinese)
|
||||
@@ -81,7 +81,7 @@ When('用户点击创建 Group 按钮', async function (this: CustomWorld) {
|
||||
console.log(' ✅ 已点击创建 Group 按钮');
|
||||
});
|
||||
|
||||
When('用户点击写作按钮', async function (this: CustomWorld) {
|
||||
When('用户点击写作按钮', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 点击写作按钮...');
|
||||
|
||||
// Find the "Write" button by text (supports both English and Chinese)
|
||||
@@ -94,49 +94,139 @@ When('用户点击写作按钮', async function (this: CustomWorld) {
|
||||
console.log(' ✅ 已点击写作按钮');
|
||||
});
|
||||
|
||||
When('用户在输入框中输入 {string}', async function (this: CustomWorld, message: string) {
|
||||
console.log(` 📍 Step: 在输入框中输入 "${message}"...`);
|
||||
When(
|
||||
'用户在输入框中输入 {string}',
|
||||
{ timeout: 30_000 },
|
||||
async function (this: CustomWorld, message: string) {
|
||||
console.log(` 📍 Step: 在输入框中输入 "${message}"...`);
|
||||
|
||||
// The chat input is a contenteditable editor, need to click first then type
|
||||
const chatInputContainer = this.page.locator('[data-testid="chat-input"]').first();
|
||||
// The chat input is a contenteditable editor, need to click first then type
|
||||
const chatInputContainer = this.page.locator('[data-testid="chat-input"]').first();
|
||||
|
||||
// If data-testid not found, try alternative selectors
|
||||
let inputFound = false;
|
||||
if ((await chatInputContainer.count()) > 0) {
|
||||
await chatInputContainer.click();
|
||||
inputFound = true;
|
||||
} else {
|
||||
// Try to find the editor by its contenteditable attribute
|
||||
const editor = this.page.locator('[contenteditable="true"]').first();
|
||||
if ((await editor.count()) > 0) {
|
||||
await editor.click();
|
||||
// If data-testid not found, try alternative selectors
|
||||
let inputFound = false;
|
||||
if ((await chatInputContainer.count()) > 0) {
|
||||
await chatInputContainer.click();
|
||||
inputFound = true;
|
||||
} else {
|
||||
// Try to find the editor by its contenteditable attribute
|
||||
const editor = this.page.locator('[contenteditable="true"]').first();
|
||||
if ((await editor.count()) > 0) {
|
||||
await editor.click();
|
||||
inputFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!inputFound) {
|
||||
throw new Error('Could not find chat input');
|
||||
}
|
||||
if (!inputFound) {
|
||||
throw new Error('Could not find chat input');
|
||||
}
|
||||
|
||||
await this.page.waitForTimeout(300);
|
||||
await this.page.keyboard.type(message, { delay: 30 });
|
||||
await this.page.waitForTimeout(300);
|
||||
await this.page.keyboard.type(message, { delay: 30 });
|
||||
|
||||
console.log(` ✅ 已输入 "${message}"`);
|
||||
});
|
||||
// Verify text appeared in the editor
|
||||
const editorEl = this.page
|
||||
.locator('[data-testid="chat-input"] [contenteditable="true"]')
|
||||
.first();
|
||||
const fallbackEditor = this.page.locator('[contenteditable="true"]').first();
|
||||
const targetEditor = (await editorEl.count()) > 0 ? editorEl : fallbackEditor;
|
||||
await expect(targetEditor).toContainText(message.slice(0, 10), { timeout: 5000 });
|
||||
|
||||
When('用户按 Enter 发送', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
// Wait for the editor's debounced onChange (100ms) to fire and sync inputMessage to store.
|
||||
// The @lobehub/editor debounces onChange using useMemo which may lose timers on re-renders,
|
||||
// so we wait generously and then type+delete a character to ensure a fresh debounce fires.
|
||||
await this.page.waitForTimeout(300);
|
||||
|
||||
console.log(` ✅ 已输入 "${message}"`);
|
||||
},
|
||||
);
|
||||
|
||||
When('用户按 Enter 发送', { timeout: 60_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 按 Enter 发送...');
|
||||
|
||||
// Wait for editor's debounced onChange (100ms default) to sync inputMessage to store
|
||||
// Without this, inputMessage is empty and send() silently returns
|
||||
await this.page.waitForTimeout(200);
|
||||
// Diagnostic: check editor state before sending
|
||||
const editorState = await this.page.evaluate(() => {
|
||||
const el = document.querySelector('[contenteditable="true"]');
|
||||
return {
|
||||
activeTag: document.activeElement?.tagName || 'none',
|
||||
editorExists: !!el,
|
||||
focused: document.activeElement === el || (el?.contains(document.activeElement) ?? false),
|
||||
text: el?.textContent?.trim() || '',
|
||||
};
|
||||
});
|
||||
console.log(
|
||||
` 📍 Editor: text="${editorState.text.slice(0, 30)}", focused=${editorState.focused}, active=${editorState.activeTag}`,
|
||||
);
|
||||
|
||||
// Ensure focus is on the editor before pressing Enter
|
||||
if (!editorState.focused) {
|
||||
console.log(' ⚠️ Editor not focused, clicking to focus...');
|
||||
const editor = this.page.locator('[contenteditable="true"]').first();
|
||||
await editor.click();
|
||||
await this.page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
// Type a space and delete it to trigger a fresh onChange debounce cycle.
|
||||
// The @lobehub/editor uses useMemo to create debounced onChange, which can lose
|
||||
// pending timers when the component re-renders (useEditorState triggers re-renders
|
||||
// on every keystroke). By typing after re-renders have settled, we ensure the
|
||||
// debounce timer fires and syncs inputMessage to the store.
|
||||
await this.page.keyboard.press('Space');
|
||||
await this.page.waitForTimeout(50);
|
||||
await this.page.keyboard.press('Backspace');
|
||||
|
||||
// Wait for the debounce (100ms) to fire and sync inputMessage
|
||||
await this.page.waitForTimeout(300);
|
||||
|
||||
// Listen for navigation to capture the agent/group ID
|
||||
const navigationPromise = this.page.waitForURL(/\/(agent|group)\/.*\/profile/, {
|
||||
timeout: 30_000,
|
||||
timeout: 45_000,
|
||||
});
|
||||
|
||||
// Monitor TRPC response to confirm the request reaches the server
|
||||
const trpcResponsePromise = this.page
|
||||
.waitForResponse(
|
||||
(response) => response.url().includes('/trpc/lambda') && response.status() === 200,
|
||||
{ timeout: 10_000 },
|
||||
)
|
||||
.catch(() => null);
|
||||
|
||||
await this.page.keyboard.press('Enter');
|
||||
console.log(' 📍 Enter pressed, waiting for response...');
|
||||
|
||||
// Wait for TRPC response (short timeout) to confirm the request was sent
|
||||
const trpcResponse = await trpcResponsePromise;
|
||||
if (trpcResponse) {
|
||||
console.log(` 📍 TRPC response: ${trpcResponse.url()}`);
|
||||
} else {
|
||||
// TRPC request was not sent. Diagnose and retry.
|
||||
const postEnterState = await this.page.evaluate(() => {
|
||||
const el = document.querySelector('[contenteditable="true"]');
|
||||
return {
|
||||
text: el?.textContent?.trim() || '',
|
||||
url: window.location.href,
|
||||
};
|
||||
});
|
||||
console.log(
|
||||
` ⚠️ No TRPC response. Editor text="${postEnterState.text.slice(0, 30)}", URL=${postEnterState.url}`,
|
||||
);
|
||||
|
||||
// Retry: click the send button if visible
|
||||
console.log(' ⚠️ Retrying via send button...');
|
||||
const sendBtn = this.page.locator('[data-testid="chat-input"] button:has(svg)').last();
|
||||
const sendBtnVisible = await sendBtn.isVisible().catch(() => false);
|
||||
if (sendBtnVisible) {
|
||||
await sendBtn.click();
|
||||
console.log(' 📍 Clicked send button');
|
||||
} else {
|
||||
// Last resort: re-focus editor and press Enter again
|
||||
console.log(' ⚠️ No send button, re-pressing Enter...');
|
||||
const editor = this.page.locator('[contenteditable="true"]').first();
|
||||
await editor.click();
|
||||
await this.page.waitForTimeout(300);
|
||||
await this.page.keyboard.press('Enter');
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for navigation to profile page
|
||||
await navigationPromise;
|
||||
@@ -188,7 +278,7 @@ When('用户按 Enter 发送创建文档', { timeout: 30_000 }, async function (
|
||||
console.log(' ✅ 已发送并创建文档');
|
||||
});
|
||||
|
||||
When('用户返回 Home 页面', async function (this: CustomWorld) {
|
||||
When('用户返回 Home 页面', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 返回 Home 页面...');
|
||||
|
||||
await this.page.goto('/');
|
||||
@@ -202,27 +292,35 @@ When('用户返回 Home 页面', async function (this: CustomWorld) {
|
||||
// Then Steps
|
||||
// ============================================
|
||||
|
||||
Then('页面应该跳转到 Agent 的 profile 页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到 Agent profile 页面...');
|
||||
Then(
|
||||
'页面应该跳转到 Agent 的 profile 页面',
|
||||
{ timeout: 30_000 },
|
||||
async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到 Agent profile 页面...');
|
||||
|
||||
// Check current URL matches /agent/{id}/profile pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/agent\/[^/]+\/profile/);
|
||||
// Check current URL matches /agent/{id}/profile pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/agent\/[^/]+\/profile/);
|
||||
|
||||
console.log(' ✅ 已跳转到 Agent profile 页面');
|
||||
});
|
||||
console.log(' ✅ 已跳转到 Agent profile 页面');
|
||||
},
|
||||
);
|
||||
|
||||
Then('页面应该跳转到 Group 的 profile 页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到 Group profile 页面...');
|
||||
Then(
|
||||
'页面应该跳转到 Group 的 profile 页面',
|
||||
{ timeout: 30_000 },
|
||||
async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到 Group profile 页面...');
|
||||
|
||||
// Check current URL matches /group/{id}/profile pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/group\/[^/]+\/profile/);
|
||||
// Check current URL matches /group/{id}/profile pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/group\/[^/]+\/profile/);
|
||||
|
||||
console.log(' ✅ 已跳转到 Group profile 页面');
|
||||
});
|
||||
console.log(' ✅ 已跳转到 Group profile 页面');
|
||||
},
|
||||
);
|
||||
|
||||
Then('新创建的 Agent 应该在侧边栏中显示', async function (this: CustomWorld) {
|
||||
Then('新创建的 Agent 应该在侧边栏中显示', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证 Agent 在侧边栏中显示...');
|
||||
|
||||
// Wait for sidebar to be visible and data to load
|
||||
@@ -245,7 +343,7 @@ Then('新创建的 Agent 应该在侧边栏中显示', async function (this: Cus
|
||||
console.log(' ✅ Agent 已在侧边栏中显示');
|
||||
});
|
||||
|
||||
Then('新创建的 Group 应该在侧边栏中显示', async function (this: CustomWorld) {
|
||||
Then('新创建的 Group 应该在侧边栏中显示', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证 Group 在侧边栏中显示...');
|
||||
|
||||
// Wait for sidebar to be visible and data to load
|
||||
@@ -268,7 +366,7 @@ Then('新创建的 Group 应该在侧边栏中显示', async function (this: Cus
|
||||
console.log(' ✅ Group 已在侧边栏中显示');
|
||||
});
|
||||
|
||||
Then('页面应该跳转到文档编辑页面', async function (this: CustomWorld) {
|
||||
Then('页面应该跳转到文档编辑页面', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到文档编辑页面...');
|
||||
|
||||
// Check current URL matches /page/{id} pattern
|
||||
@@ -282,7 +380,7 @@ Then('页面应该跳转到文档编辑页面', async function (this: CustomWorl
|
||||
console.log(` ✅ 已跳转到文档编辑页面: /page/${createdDocumentId}`);
|
||||
});
|
||||
|
||||
Then('Page Agent 应该收到用户的提示词', async function (this: CustomWorld) {
|
||||
Then('Page Agent 应该收到用户的提示词', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证 Page Agent 收到用户的提示词...');
|
||||
|
||||
// Wait for the page to fully load and Page Agent panel to appear
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { After, AfterAll, Before, BeforeAll, Status, setDefaultTimeout } from '@cucumber/cucumber';
|
||||
import { type Cookie, chromium } from 'playwright';
|
||||
import { After, AfterAll, Before, BeforeAll, setDefaultTimeout,Status } from '@cucumber/cucumber';
|
||||
import { chromium,type Cookie } from 'playwright';
|
||||
|
||||
import { TEST_USER, seedTestUser } from '../support/seedTestUser';
|
||||
import { seedTestUser,TEST_USER } from '../support/seedTestUser';
|
||||
import { startWebServer, stopWebServer } from '../support/webServer';
|
||||
import { CustomWorld } from '../support/world';
|
||||
import { type CustomWorld } from '../support/world';
|
||||
|
||||
process.env['E2E'] = '1';
|
||||
// Set default timeout for all steps to 10 seconds
|
||||
setDefaultTimeout(10_000);
|
||||
// Set default timeout for all steps to 30 seconds (CI runners are slower than local)
|
||||
setDefaultTimeout(30_000);
|
||||
|
||||
// Store base URL and cached session cookies
|
||||
let baseUrl: string;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { IWorldOptions, World, setWorldConstructor } from '@cucumber/cucumber';
|
||||
import { Browser, BrowserContext, Page, Response, chromium } from '@playwright/test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { type IWorldOptions, setWorldConstructor, World } from '@cucumber/cucumber';
|
||||
import {
|
||||
type Browser,
|
||||
type BrowserContext,
|
||||
chromium,
|
||||
type Page,
|
||||
type Response,
|
||||
} from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Default timeout for waiting operations (e.g., waitForURL, toBeVisible)
|
||||
*/
|
||||
@@ -73,6 +80,13 @@ export class CustomWorld extends World {
|
||||
});
|
||||
|
||||
this.page.setDefaultTimeout(30_000);
|
||||
|
||||
// Capture unhandled promise rejections (not caught by pageerror)
|
||||
await this.page.addInitScript(() => {
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('[E2E] Unhandled rejection:', String(event.reason));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
|
||||
@@ -4,39 +4,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishButton.tsx": {
|
||||
"import-x/consistent-type-specifier-style": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/community/(detail)/agent/features/Details/Capabilities/PluginItem.tsx": {
|
||||
"simple-import-sort/imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/community/(detail)/agent/features/Header.tsx": {
|
||||
"simple-import-sort/imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/community/components/VirtuosoGridList/index.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/home/_layout/Body/Agent/Modals/ConfigGroupModal/GroupItem.tsx": {
|
||||
"import-x/consistent-type-specifier-style": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/home/_layout/Body/Agent/Modals/ConfigGroupModal/index.tsx": {
|
||||
"@typescript-eslint/consistent-type-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"simple-import-sort/imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/home/features/RecentPage/Item.tsx": {
|
||||
"regexp/no-super-linear-backtracking": {
|
||||
"count": 1
|
||||
@@ -114,9 +86,6 @@
|
||||
}
|
||||
},
|
||||
"src/envs/auth.ts": {
|
||||
"perfectionist/sort-interfaces": {
|
||||
"count": 19
|
||||
},
|
||||
"sort-keys-fix/sort-keys-fix": {
|
||||
"count": 1
|
||||
},
|
||||
@@ -158,16 +127,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/features/Conversation/Messages/CompressedGroup/index.tsx": {
|
||||
"import-x/consistent-type-specifier-style": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx": {
|
||||
"simple-import-sort/imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/features/Conversation/types/hooks.ts": {
|
||||
"typescript-sort-keys/interface": {
|
||||
"count": 1
|
||||
@@ -312,22 +271,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/server/routers/lambda/userMemory.ts": {
|
||||
"@typescript-eslint/consistent-type-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"import-x/no-duplicates": {
|
||||
"count": 2
|
||||
},
|
||||
"simple-import-sort/imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/server/routers/tools/_helpers/scheduleToolCallReport.test.ts": {
|
||||
"simple-import-sort/imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/server/routers/tools/mcp.ts": {
|
||||
"sort-keys-fix/sort-keys-fix": {
|
||||
"count": 1
|
||||
@@ -485,14 +428,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/store/chat/slices/aiChat/actions/__tests__/StreamingHandler.test.ts": {
|
||||
"import-x/consistent-type-specifier-style": {
|
||||
"count": 1
|
||||
},
|
||||
"unused-imports/no-unused-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/store/chat/slices/aiChat/actions/conversationControl.ts": {
|
||||
"no-unused-private-class-members": {
|
||||
"count": 1
|
||||
@@ -746,11 +681,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/store/tool/slices/builtin/executors/index.ts": {
|
||||
"import-x/consistent-type-specifier-style": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/store/tool/slices/oldStore/initialState.ts": {
|
||||
"typescript-sort-keys/string-enum": {
|
||||
"count": 1
|
||||
|
||||
@@ -46,26 +46,26 @@ const Nav = memo(() => {
|
||||
<Flexbox gap={1} paddingInline={4}>
|
||||
<NavItem
|
||||
icon={MessageSquarePlusIcon}
|
||||
onClick={handleNewTopic}
|
||||
title={tTopic('actions.addNewTopic')}
|
||||
onClick={handleNewTopic}
|
||||
/>
|
||||
{!hideProfile && (
|
||||
<NavItem
|
||||
active={isProfileActive}
|
||||
icon={BotPromptIcon}
|
||||
title={t('tab.profile')}
|
||||
onClick={() => {
|
||||
switchTopic(null, { skipRefreshMessage: true });
|
||||
router.push(urlJoin('/agent', agentId!, 'profile'));
|
||||
}}
|
||||
title={t('tab.profile')}
|
||||
/>
|
||||
)}
|
||||
<NavItem
|
||||
icon={SearchIcon}
|
||||
title={t('tab.search')}
|
||||
onClick={() => {
|
||||
toggleCommandMenu(true);
|
||||
}}
|
||||
title={t('tab.search')}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -33,14 +33,14 @@ const MainChatInput = memo(() => {
|
||||
|
||||
return (
|
||||
<ChatInput
|
||||
skipScrollMarginWithList
|
||||
leftActions={leftActions}
|
||||
rightActions={rightActions}
|
||||
sendMenu={{ items: sendMenuItems }}
|
||||
onEditorReady={(instance) => {
|
||||
// Sync to global ChatStore for compatibility with other features
|
||||
useChatStore.setState({ mainInputEditor: instance });
|
||||
}}
|
||||
rightActions={rightActions}
|
||||
sendMenu={{ items: sendMenuItems }}
|
||||
skipScrollMarginWithList
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
+9
-8
@@ -13,7 +13,7 @@ import { agentSelectors } from '@/store/agent/selectors';
|
||||
|
||||
import { useVersionReviewStatus } from '../AgentVersionReviewTag';
|
||||
import ForkConfirmModal from './ForkConfirmModal';
|
||||
import type { MarketPublishAction } from './types';
|
||||
import { type MarketPublishAction } from './types';
|
||||
import { type OriginalAgentInfo, useMarketPublish } from './useMarketPublish';
|
||||
|
||||
interface MarketPublishButtonProps {
|
||||
@@ -80,7 +80,8 @@ const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess
|
||||
if (isUnderReview) {
|
||||
message.warning({
|
||||
content: t('marketPublish.validation.underReview', {
|
||||
defaultValue: 'Your new version is currently under review. Please wait for approval before publishing a new version.',
|
||||
defaultValue:
|
||||
'Your new version is currently under review. Please wait for approval before publishing a new version.',
|
||||
}),
|
||||
});
|
||||
return;
|
||||
@@ -144,6 +145,9 @@ const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess
|
||||
<Popconfirm
|
||||
arrow={false}
|
||||
okButtonProps={{ type: 'primary' }}
|
||||
open={confirmOpened}
|
||||
placement="bottomRight"
|
||||
title={t('marketPublish.validation.confirmPublish')}
|
||||
onCancel={() => setConfirmOpened(false)}
|
||||
onConfirm={handleConfirmPublish}
|
||||
onOpenChange={(open) => {
|
||||
@@ -151,25 +155,22 @@ const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess
|
||||
setConfirmOpened(false);
|
||||
}
|
||||
}}
|
||||
open={confirmOpened}
|
||||
placement="bottomRight"
|
||||
title={t('marketPublish.validation.confirmPublish')}
|
||||
>
|
||||
<Button
|
||||
icon={ShapesUploadIcon}
|
||||
loading={loading}
|
||||
onClick={handleButtonClick}
|
||||
title={buttonTitle}
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
{t('publishToCommunity')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<ForkConfirmModal
|
||||
loading={isPublishing}
|
||||
onCancel={handleForkCancel}
|
||||
onConfirm={handleForkConfirm}
|
||||
open={showForkModal}
|
||||
originalAgent={originalAgentInfo}
|
||||
onCancel={handleForkCancel}
|
||||
onConfirm={handleForkConfirm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+13
-13
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
KLAVIS_SERVER_TYPES,
|
||||
getLobehubSkillProviderById,
|
||||
KLAVIS_SERVER_TYPES,
|
||||
type KlavisServerType,
|
||||
type LobehubSkillProviderType,
|
||||
} from '@lobechat/const';
|
||||
@@ -20,16 +20,16 @@ import { builtinTools } from '@/tools';
|
||||
* For string type icon, use Image component to render
|
||||
* For IconType type icon, use Icon component to render with theme fill color
|
||||
*/
|
||||
const BuiltinToolIcon = memo<
|
||||
Pick<KlavisServerType | LobehubSkillProviderType, 'icon' | 'label'>
|
||||
>(({ icon, label }) => {
|
||||
if (typeof icon === 'string') {
|
||||
return <Image alt={label} height={40} src={icon} style={{ flex: 'none' }} width={40} />;
|
||||
}
|
||||
const BuiltinToolIcon = memo<Pick<KlavisServerType | LobehubSkillProviderType, 'icon' | 'label'>>(
|
||||
({ icon, label }) => {
|
||||
if (typeof icon === 'string') {
|
||||
return <Image alt={label} height={40} src={icon} style={{ flex: 'none' }} width={40} />;
|
||||
}
|
||||
|
||||
// Use theme color fill, automatically adapts in dark mode
|
||||
return <Icon fill={cssVar.colorText} icon={icon} size={40} />;
|
||||
});
|
||||
// Use theme color fill, automatically adapts in dark mode
|
||||
return <Icon fill={cssVar.colorText} icon={icon} size={40} />;
|
||||
},
|
||||
);
|
||||
|
||||
BuiltinToolIcon.displayName = 'BuiltinToolIcon';
|
||||
|
||||
@@ -195,7 +195,7 @@ const PluginItem = memo<PluginItemProps>(({ identifier }) => {
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Block gap={12} horizontal key={identifier} padding={12} variant={'outlined'}>
|
||||
<Block horizontal gap={12} key={identifier} padding={12} variant={'outlined'}>
|
||||
<Skeleton paragraph={{ rows: 1 }} title={false} />
|
||||
</Block>
|
||||
);
|
||||
@@ -216,9 +216,9 @@ const PluginItem = memo<PluginItemProps>(({ identifier }) => {
|
||||
|
||||
const content = (
|
||||
<Block
|
||||
horizontal
|
||||
className={cx(sourceConfig.clickable ? styles.clickable : styles.noLink)}
|
||||
gap={12}
|
||||
horizontal
|
||||
key={identifier}
|
||||
padding={12}
|
||||
variant={'outlined'}
|
||||
@@ -232,7 +232,7 @@ const PluginItem = memo<PluginItemProps>(({ identifier }) => {
|
||||
}}
|
||||
>
|
||||
<div className={styles.titleRow}>
|
||||
<Text as={'h2'} className={cx(styles.title, 'plugin-title')} ellipsis>
|
||||
<Text ellipsis as={'h2'} className={cx(styles.title, 'plugin-title')}>
|
||||
{data.title}
|
||||
</Text>
|
||||
{sourceConfig.tagText && (
|
||||
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cssVar, useResponsive } from 'antd-style';
|
||||
import {
|
||||
BookTextIcon,
|
||||
BookmarkCheckIcon,
|
||||
BookmarkIcon,
|
||||
BookTextIcon,
|
||||
CoinsIcon,
|
||||
DotIcon,
|
||||
GitBranchIcon,
|
||||
@@ -33,8 +33,8 @@ import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
|
||||
import { socialService } from '@/services/social';
|
||||
import { formatIntergerNumber } from '@/utils/format';
|
||||
|
||||
import { useCategory } from '../../../(list)/agent/features/Category/useCategory';
|
||||
import PublishedTime from '../../../../../../../components/PublishedTime';
|
||||
import { useCategory } from '../../../(list)/agent/features/Category/useCategory';
|
||||
import AgentForkTag from './AgentForkTag';
|
||||
import { useDetailContext } from './DetailProvider';
|
||||
|
||||
@@ -123,7 +123,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
|
||||
return (
|
||||
<Flexbox gap={12}>
|
||||
<Flexbox align={'flex-start'} gap={16} horizontal width={'100%'}>
|
||||
<Flexbox horizontal align={'flex-start'} gap={16} width={'100%'}>
|
||||
<Avatar avatar={avatar} shape={'square'} size={mobile ? 48 : 64} />
|
||||
<Flexbox
|
||||
flex={1}
|
||||
@@ -133,9 +133,9 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
gap={8}
|
||||
horizontal
|
||||
justify={'space-between'}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
@@ -143,18 +143,18 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
flex={1}
|
||||
gap={12}
|
||||
horizontal
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
as={'h1'}
|
||||
ellipsis
|
||||
as={'h1'}
|
||||
style={{ fontSize: mobile ? 18 : 24, margin: 0 }}
|
||||
title={identifier}
|
||||
>
|
||||
@@ -165,12 +165,12 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
<ActionIcon
|
||||
icon={isFavorited ? BookmarkCheckIcon : BookmarkIcon}
|
||||
loading={favoriteLoading}
|
||||
onClick={handleFavoriteClick}
|
||||
variant={isFavorited ? 'outlined' : undefined}
|
||||
onClick={handleFavoriteClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flexbox>
|
||||
<Flexbox align={'center'} gap={8} horizontal wrap={'wrap'}>
|
||||
<Flexbox horizontal align={'center'} gap={8} wrap={'wrap'}>
|
||||
{author && userName ? (
|
||||
<Link style={{ color: 'inherit' }} to={urlJoin('/community/user', userName)}>
|
||||
{author}
|
||||
@@ -195,9 +195,9 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
</Flexbox>
|
||||
<TooltipGroup>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
gap={mobile ? 12 : 24}
|
||||
horizontal
|
||||
style={{
|
||||
color: cssVar.colorTextSecondary,
|
||||
}}
|
||||
@@ -208,7 +208,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
styles={{ root: { pointerEvents: 'none' } }}
|
||||
title={t('assistants.tokenUsage')}
|
||||
>
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={CoinsIcon} />
|
||||
{formatIntergerNumber(tokenUsage)}
|
||||
</Flexbox>
|
||||
@@ -219,7 +219,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
styles={{ root: { pointerEvents: 'none' } }}
|
||||
title={t('assistants.withPlugin')}
|
||||
>
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon fill={cssVar.colorTextSecondary} icon={MCP} />
|
||||
{pluginCount}
|
||||
</Flexbox>
|
||||
@@ -230,7 +230,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
styles={{ root: { pointerEvents: 'none' } }}
|
||||
title={t('assistants.withKnowledge')}
|
||||
>
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={BookTextIcon} />
|
||||
{knowledgeCount}
|
||||
</Flexbox>
|
||||
|
||||
@@ -13,13 +13,7 @@ import {
|
||||
} from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cssVar, useResponsive } from 'antd-style';
|
||||
import {
|
||||
BookmarkCheckIcon,
|
||||
BookmarkIcon,
|
||||
DotIcon,
|
||||
GitBranchIcon,
|
||||
UsersIcon,
|
||||
} from 'lucide-react';
|
||||
import { BookmarkCheckIcon, BookmarkIcon, DotIcon, GitBranchIcon, UsersIcon } from 'lucide-react';
|
||||
import qs from 'query-string';
|
||||
import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -119,7 +113,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
|
||||
return (
|
||||
<Flexbox gap={12}>
|
||||
<Flexbox align={'flex-start'} gap={16} horizontal width={'100%'}>
|
||||
<Flexbox horizontal align={'flex-start'} gap={16} width={'100%'}>
|
||||
<Avatar avatar={displayAvatar} shape={'square'} size={mobile ? 48 : 64} />
|
||||
<Flexbox
|
||||
flex={1}
|
||||
@@ -129,9 +123,9 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
gap={8}
|
||||
horizontal
|
||||
justify={'space-between'}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
@@ -139,18 +133,18 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
flex={1}
|
||||
gap={12}
|
||||
horizontal
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
as={'h1'}
|
||||
ellipsis
|
||||
as={'h1'}
|
||||
style={{ fontSize: mobile ? 18 : 24, margin: 0 }}
|
||||
title={identifier}
|
||||
>
|
||||
@@ -161,12 +155,12 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
<ActionIcon
|
||||
icon={isFavorited ? BookmarkCheckIcon : BookmarkIcon}
|
||||
loading={favoriteLoading}
|
||||
onClick={handleFavoriteClick}
|
||||
variant={isFavorited ? 'outlined' : undefined}
|
||||
onClick={handleFavoriteClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flexbox>
|
||||
<Flexbox align={'center'} gap={8} horizontal wrap={'wrap'}>
|
||||
<Flexbox horizontal align={'center'} gap={8} wrap={'wrap'}>
|
||||
{(() => {
|
||||
// API returns author as object {avatar, name, userName}, but type definition says string
|
||||
const authorObj =
|
||||
@@ -198,9 +192,9 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
</Flexbox>
|
||||
<TooltipGroup>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
gap={mobile ? 12 : 24}
|
||||
horizontal
|
||||
style={{
|
||||
color: cssVar.colorTextSecondary,
|
||||
}}
|
||||
@@ -211,7 +205,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
styles={{ root: { pointerEvents: 'none' } }}
|
||||
title={t('groupAgents.memberCount', { defaultValue: 'Members' })}
|
||||
>
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={UsersIcon} />
|
||||
{memberCount}
|
||||
</Flexbox>
|
||||
|
||||
@@ -4,7 +4,13 @@ import { Select } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type StatusFilterValue = 'published' | 'unpublished' | 'deprecated' | 'archived' | 'forked' | 'favorite';
|
||||
export type StatusFilterValue =
|
||||
| 'published'
|
||||
| 'unpublished'
|
||||
| 'deprecated'
|
||||
| 'archived'
|
||||
| 'forked'
|
||||
| 'favorite';
|
||||
|
||||
interface StatusFilterProps {
|
||||
onChange: (value: StatusFilterValue) => void;
|
||||
@@ -23,14 +29,7 @@ const StatusFilter = memo<StatusFilterProps>(({ value, onChange }) => {
|
||||
{ label: t('user.statusFilter.favorite'), value: 'favorite' as const },
|
||||
];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
style={{ minWidth: 120 }}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
return <Select options={options} style={{ minWidth: 120 }} value={value} onChange={onChange} />;
|
||||
});
|
||||
|
||||
export default StatusFilter;
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
DropdownMenu,
|
||||
Flexbox,
|
||||
Icon,
|
||||
stopPropagation,
|
||||
Tag as AntTag,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
TooltipGroup,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
|
||||
@@ -17,7 +17,13 @@ interface UserAgentListProps {
|
||||
|
||||
const UserAgentList = memo<UserAgentListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
const { t } = useTranslation('discover');
|
||||
const { agents, agentCount, forkedAgents = [], favoriteAgents = [], isOwner } = useUserDetailContext();
|
||||
const {
|
||||
agents,
|
||||
agentCount,
|
||||
forkedAgents = [],
|
||||
favoriteAgents = [],
|
||||
isOwner,
|
||||
} = useUserDetailContext();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilterValue>('published');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -69,26 +75,23 @@ const UserAgentList = memo<UserAgentListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8} justify={'space-between'}>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<Text fontSize={16} weight={500}>
|
||||
{t('user.publishedAgents')}
|
||||
</Text>
|
||||
{agentCount > 0 && <Tag>{filteredAgents.length}</Tag>}
|
||||
</Flexbox>
|
||||
{isOwner && (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<Input.Search
|
||||
allowClear
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t('user.searchPlaceholder')}
|
||||
style={{ width: 200 }}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
<StatusFilter
|
||||
onChange={(value) => setStatusFilter(value)}
|
||||
value={statusFilter}
|
||||
/>
|
||||
<StatusFilter value={statusFilter} onChange={(value) => setStatusFilter(value)} />
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
@@ -101,10 +104,10 @@ const UserAgentList = memo<UserAgentListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
<Flexbox align={'center'} justify={'center'}>
|
||||
<Pagination
|
||||
current={currentPage}
|
||||
onChange={(page) => setCurrentPage(page)}
|
||||
pageSize={pageSize}
|
||||
showSizeChanger={false}
|
||||
total={filteredAgents.length}
|
||||
onChange={(page) => setCurrentPage(page)}
|
||||
/>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
@@ -6,10 +6,10 @@ import {
|
||||
Flexbox,
|
||||
Grid,
|
||||
Icon,
|
||||
stopPropagation,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
|
||||
@@ -6,10 +6,10 @@ import {
|
||||
Flexbox,
|
||||
Grid,
|
||||
Icon,
|
||||
stopPropagation,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
DropdownMenu,
|
||||
Flexbox,
|
||||
Icon,
|
||||
stopPropagation,
|
||||
Tag as AntTag,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
TooltipGroup,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import {
|
||||
|
||||
@@ -16,7 +16,13 @@ interface UserGroupListProps {
|
||||
|
||||
const UserGroupList = memo<UserGroupListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
const { t } = useTranslation('discover');
|
||||
const { agentGroups = [], groupCount, forkedAgentGroups = [], favoriteAgentGroups = [], isOwner } = useUserDetailContext();
|
||||
const {
|
||||
agentGroups = [],
|
||||
groupCount,
|
||||
forkedAgentGroups = [],
|
||||
favoriteAgentGroups = [],
|
||||
isOwner,
|
||||
} = useUserDetailContext();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilterValue>('published');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -67,26 +73,23 @@ const UserGroupList = memo<UserGroupListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8} justify={'space-between'}>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<Text fontSize={16} weight={500}>
|
||||
{t('user.publishedGroups', { defaultValue: '创作的群组' })}
|
||||
</Text>
|
||||
{groupCount > 0 && <Tag>{filteredGroups.length}</Tag>}
|
||||
</Flexbox>
|
||||
{isOwner && (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<Input.Search
|
||||
allowClear
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t('user.searchPlaceholder')}
|
||||
style={{ width: 200 }}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
<StatusFilter
|
||||
onChange={(value) => setStatusFilter(value)}
|
||||
value={statusFilter}
|
||||
/>
|
||||
<StatusFilter value={statusFilter} onChange={(value) => setStatusFilter(value)} />
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
@@ -99,10 +102,10 @@ const UserGroupList = memo<UserGroupListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
<Flexbox align={'center'} justify={'center'}>
|
||||
<Pagination
|
||||
current={currentPage}
|
||||
onChange={(page) => setCurrentPage(page)}
|
||||
pageSize={pageSize}
|
||||
showSizeChanger={false}
|
||||
total={filteredGroups.length}
|
||||
onChange={(page) => setCurrentPage(page)}
|
||||
/>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
Block,
|
||||
Flexbox,
|
||||
Icon,
|
||||
stopPropagation,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { Spotlight } from '@lobehub/ui/awesome';
|
||||
import { createStaticStyles, cssVar } from 'antd-style';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Github, ModelTag, ProviderCombine } from '@lobehub/icons';
|
||||
import { ActionIcon, Block, Flexbox, MaskShadow, Text, stopPropagation } from '@lobehub/ui';
|
||||
import { ActionIcon, Block, Flexbox, MaskShadow, stopPropagation, Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar } from 'antd-style';
|
||||
import { GlobeIcon } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
|
||||
@@ -33,14 +33,14 @@ const MainChatInput = memo(() => {
|
||||
|
||||
return (
|
||||
<ChatInput
|
||||
skipScrollMarginWithList
|
||||
leftActions={leftActions}
|
||||
rightActions={rightActions}
|
||||
sendMenu={{ items: sendMenuItems }}
|
||||
onEditorReady={(instance) => {
|
||||
// Sync to global ChatStore for compatibility with other features
|
||||
useChatStore.setState({ mainInputEditor: instance });
|
||||
}}
|
||||
rightActions={rightActions}
|
||||
sendMenu={{ items: sendMenuItems }}
|
||||
skipScrollMarginWithList
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
+6
-6
@@ -145,6 +145,9 @@ const PublishButton = memo<GroupPublishButtonProps>(({ action, onPublishSuccess
|
||||
<Popconfirm
|
||||
arrow={false}
|
||||
okButtonProps={{ type: 'primary' }}
|
||||
open={confirmOpened}
|
||||
placement="bottomRight"
|
||||
title={t('marketPublish.validation.confirmPublish')}
|
||||
onCancel={() => setConfirmOpened(false)}
|
||||
onConfirm={handleConfirmPublish}
|
||||
onOpenChange={(open) => {
|
||||
@@ -152,25 +155,22 @@ const PublishButton = memo<GroupPublishButtonProps>(({ action, onPublishSuccess
|
||||
setConfirmOpened(false);
|
||||
}
|
||||
}}
|
||||
open={confirmOpened}
|
||||
placement="bottomRight"
|
||||
title={t('marketPublish.validation.confirmPublish')}
|
||||
>
|
||||
<Button
|
||||
icon={ShapesUploadIcon}
|
||||
loading={loading}
|
||||
onClick={handleButtonClick}
|
||||
title={buttonTitle}
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
{t('publishToCommunity')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<GroupForkConfirmModal
|
||||
loading={isPublishing}
|
||||
onCancel={handleForkCancel}
|
||||
onConfirm={handleForkConfirm}
|
||||
open={showForkModal}
|
||||
originalGroup={originalGroupInfo}
|
||||
onCancel={handleForkCancel}
|
||||
onConfirm={handleForkConfirm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MenuProps } from '@lobehub/ui';
|
||||
import { type MenuProps } from '@lobehub/ui';
|
||||
import { ActionIcon, DropdownMenu } from '@lobehub/ui';
|
||||
import { MoreHorizontalIcon } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
|
||||
+7
-7
@@ -6,7 +6,7 @@ import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useHomeStore } from '@/store/home';
|
||||
import type { SessionGroupItemBase } from '@/types/session';
|
||||
import { type SessionGroupItemBase } from '@/types/session';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
content: css`
|
||||
@@ -35,9 +35,10 @@ const GroupItem = memo<SessionGroupItemBase>(({ id, name }) => {
|
||||
{!editing ? (
|
||||
<>
|
||||
<span className={styles.title}>{name}</span>
|
||||
<ActionIcon icon={PencilLine} onClick={() => setEditing(true)} size={'small'} />
|
||||
<ActionIcon icon={PencilLine} size={'small'} onClick={() => setEditing(true)} />
|
||||
<ActionIcon
|
||||
icon={Trash}
|
||||
size={'small'}
|
||||
onClick={() => {
|
||||
modal.confirm({
|
||||
centered: true,
|
||||
@@ -51,12 +52,15 @@ const GroupItem = memo<SessionGroupItemBase>(({ id, name }) => {
|
||||
title: t('sessionGroup.confirmRemoveGroupAlert'),
|
||||
});
|
||||
}}
|
||||
size={'small'}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<EditableText
|
||||
editing={editing}
|
||||
showEditIcon={false}
|
||||
style={{ height: 28 }}
|
||||
value={name}
|
||||
onEditingChange={(e) => setEditing(e)}
|
||||
onChangeEnd={async (input) => {
|
||||
if (name !== input) {
|
||||
if (!input) return;
|
||||
@@ -68,10 +72,6 @@ const GroupItem = memo<SessionGroupItemBase>(({ id, name }) => {
|
||||
}
|
||||
setEditing(false);
|
||||
}}
|
||||
onEditingChange={(e) => setEditing(e)}
|
||||
showEditIcon={false}
|
||||
style={{ height: 28 }}
|
||||
value={name}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -8,9 +8,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useHomeStore } from '@/store/home';
|
||||
import { homeAgentListSelectors } from '@/store/home/selectors';
|
||||
import { type SessionGroupItemBase } from '@/types/session';
|
||||
|
||||
import GroupItem from './GroupItem';
|
||||
import { SessionGroupItemBase } from '@/types/session';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
@@ -44,29 +44,29 @@ const ConfigGroupModal = memo<ModalProps>(({ open, onCancel }) => {
|
||||
<Modal
|
||||
allowFullscreen
|
||||
footer={null}
|
||||
onCancel={onCancel}
|
||||
open={open}
|
||||
title={t('sessionGroup.config')}
|
||||
width={400}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<Flexbox>
|
||||
<SortableList
|
||||
items={sessionGroupItems}
|
||||
onChange={(items: SessionGroupItemBase[]) => {
|
||||
updateGroupSort(items);
|
||||
}}
|
||||
renderItem={(item: SessionGroupItemBase) => (
|
||||
<SortableList.Item
|
||||
horizontal
|
||||
align={'center'}
|
||||
className={styles.container}
|
||||
gap={4}
|
||||
horizontal
|
||||
id={item.id}
|
||||
justify={'space-between'}
|
||||
>
|
||||
<GroupItem {...item} />
|
||||
</SortableList.Item>
|
||||
)}
|
||||
onChange={(items: SessionGroupItemBase[]) => {
|
||||
updateGroupSort(items);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
Center,
|
||||
DropdownMenu,
|
||||
Skeleton,
|
||||
Text,
|
||||
stopPropagation,
|
||||
Text,
|
||||
} from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { ChevronsUpDownIcon } from 'lucide-react';
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
ActionIcon,
|
||||
ContextMenuTrigger,
|
||||
Flexbox,
|
||||
Text,
|
||||
stopPropagation,
|
||||
Text,
|
||||
} from '@lobehub/ui';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { ArrowDownUpIcon } from 'lucide-react';
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
Form,
|
||||
Icon,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
stopPropagation,
|
||||
Tooltip,
|
||||
} from '@lobehub/ui';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { Form as AntdForm, Switch } from 'antd';
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
List,
|
||||
Modal,
|
||||
SearchBar,
|
||||
stopPropagation,
|
||||
Text,
|
||||
Tooltip,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { Switch } from 'antd';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
|
||||
+49
-49
@@ -6,92 +6,83 @@ declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
// ===== Better Auth ===== //
|
||||
AUTH_SECRET?: string;
|
||||
AUTH_EMAIL_VERIFICATION?: string;
|
||||
AUTH_ENABLE_MAGIC_LINK?: string;
|
||||
AUTH_SSO_PROVIDERS?: string;
|
||||
AUTH_TRUSTED_ORIGINS?: string;
|
||||
AUTH_ALLOWED_EMAILS?: string;
|
||||
AUTH_DISABLE_EMAIL_PASSWORD?: string;
|
||||
|
||||
// ===== Auth Provider Credentials ===== //
|
||||
AUTH_GOOGLE_ID?: string;
|
||||
AUTH_GOOGLE_SECRET?: string;
|
||||
|
||||
AUTH_APPLE_APP_BUNDLE_IDENTIFIER?: string;
|
||||
AUTH_APPLE_CLIENT_ID?: string;
|
||||
AUTH_APPLE_CLIENT_SECRET?: string;
|
||||
AUTH_APPLE_APP_BUNDLE_IDENTIFIER?: string;
|
||||
|
||||
AUTH_GITHUB_ID?: string;
|
||||
AUTH_GITHUB_SECRET?: string;
|
||||
|
||||
AUTH_COGNITO_ID?: string;
|
||||
AUTH_COGNITO_SECRET?: string;
|
||||
AUTH_COGNITO_ISSUER?: string;
|
||||
AUTH_COGNITO_DOMAIN?: string;
|
||||
AUTH_COGNITO_REGION?: string;
|
||||
AUTH_COGNITO_USERPOOL_ID?: string;
|
||||
|
||||
AUTH_MICROSOFT_AUTHORITY_URL?: string;
|
||||
AUTH_MICROSOFT_ID?: string;
|
||||
AUTH_MICROSOFT_SECRET?: string;
|
||||
AUTH_MICROSOFT_TENANT_ID?: string;
|
||||
|
||||
AUTH_AUTH0_ID?: string;
|
||||
AUTH_AUTH0_SECRET?: string;
|
||||
AUTH_AUTH0_ISSUER?: string;
|
||||
AUTH_AUTH0_SECRET?: string;
|
||||
|
||||
AUTH_AUTHELIA_ID?: string;
|
||||
AUTH_AUTHELIA_SECRET?: string;
|
||||
AUTH_AUTHELIA_ISSUER?: string;
|
||||
|
||||
AUTH_AUTHELIA_SECRET?: string;
|
||||
AUTH_AUTHENTIK_ID?: string;
|
||||
AUTH_AUTHENTIK_SECRET?: string;
|
||||
AUTH_AUTHENTIK_ISSUER?: string;
|
||||
|
||||
AUTH_AUTHENTIK_SECRET?: string;
|
||||
AUTH_CASDOOR_ID?: string;
|
||||
AUTH_CASDOOR_SECRET?: string;
|
||||
|
||||
AUTH_CASDOOR_ISSUER?: string;
|
||||
|
||||
AUTH_CASDOOR_SECRET?: string;
|
||||
AUTH_CLOUDFLARE_ZERO_TRUST_ID?: string;
|
||||
AUTH_CLOUDFLARE_ZERO_TRUST_SECRET?: string;
|
||||
AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER?: string;
|
||||
AUTH_CLOUDFLARE_ZERO_TRUST_SECRET?: string;
|
||||
AUTH_COGNITO_DOMAIN?: string;
|
||||
|
||||
AUTH_COGNITO_ID?: string;
|
||||
AUTH_COGNITO_ISSUER?: string;
|
||||
AUTH_COGNITO_REGION?: string;
|
||||
AUTH_COGNITO_SECRET?: string;
|
||||
|
||||
AUTH_COGNITO_USERPOOL_ID?: string;
|
||||
AUTH_DISABLE_EMAIL_PASSWORD?: string;
|
||||
AUTH_EMAIL_VERIFICATION?: string;
|
||||
|
||||
AUTH_ENABLE_MAGIC_LINK?: string;
|
||||
AUTH_FEISHU_APP_ID?: string;
|
||||
AUTH_FEISHU_APP_SECRET?: string;
|
||||
|
||||
AUTH_GENERIC_OIDC_ID?: string;
|
||||
AUTH_GENERIC_OIDC_SECRET?: string;
|
||||
AUTH_GENERIC_OIDC_ISSUER?: string;
|
||||
AUTH_GENERIC_OIDC_SECRET?: string;
|
||||
|
||||
AUTH_GITHUB_ID?: string;
|
||||
AUTH_GITHUB_SECRET?: string;
|
||||
// ===== Auth Provider Credentials ===== //
|
||||
AUTH_GOOGLE_ID?: string;
|
||||
|
||||
AUTH_GOOGLE_SECRET?: string;
|
||||
AUTH_KEYCLOAK_ID?: string;
|
||||
AUTH_KEYCLOAK_SECRET?: string;
|
||||
AUTH_KEYCLOAK_ISSUER?: string;
|
||||
|
||||
AUTH_KEYCLOAK_SECRET?: string;
|
||||
AUTH_LOGTO_ID?: string;
|
||||
AUTH_LOGTO_SECRET?: string;
|
||||
|
||||
AUTH_LOGTO_ISSUER?: string;
|
||||
AUTH_LOGTO_SECRET?: string;
|
||||
AUTH_MICROSOFT_AUTHORITY_URL?: string;
|
||||
|
||||
AUTH_MICROSOFT_ID?: string;
|
||||
AUTH_MICROSOFT_SECRET?: string;
|
||||
AUTH_MICROSOFT_TENANT_ID?: string;
|
||||
|
||||
AUTH_OKTA_ID?: string;
|
||||
AUTH_OKTA_SECRET?: string;
|
||||
AUTH_OKTA_ISSUER?: string;
|
||||
AUTH_OKTA_SECRET?: string;
|
||||
|
||||
// ===== Better Auth ===== //
|
||||
AUTH_SECRET?: string;
|
||||
AUTH_SSO_PROVIDERS?: string;
|
||||
AUTH_TRUSTED_ORIGINS?: string;
|
||||
|
||||
AUTH_WECHAT_ID?: string;
|
||||
AUTH_WECHAT_SECRET?: string;
|
||||
|
||||
AUTH_ZITADEL_ID?: string;
|
||||
AUTH_ZITADEL_SECRET?: string;
|
||||
AUTH_ZITADEL_ISSUER?: string;
|
||||
|
||||
// ===== JWKS Key ===== //
|
||||
/**
|
||||
* Generic JWKS key for signing/verifying JWTs.
|
||||
* Used for internal service authentication and other cryptographic operations.
|
||||
* Must be a JWKS JSON string containing an RS256 RSA key pair.
|
||||
* Can be generated using `node scripts/generate-oidc-jwk.mjs`.
|
||||
*/
|
||||
JWKS_KEY?: string;
|
||||
AUTH_ZITADEL_SECRET?: string;
|
||||
|
||||
/**
|
||||
* Internal JWT expiration time for lambda → async calls.
|
||||
@@ -101,6 +92,15 @@ declare global {
|
||||
* @default '30s'
|
||||
*/
|
||||
INTERNAL_JWT_EXPIRATION?: string;
|
||||
|
||||
// ===== JWKS Key ===== //
|
||||
/**
|
||||
* Generic JWKS key for signing/verifying JWTs.
|
||||
* Used for internal service authentication and other cryptographic operations.
|
||||
* Must be a JWKS JSON string containing an RS256 RSA key pair.
|
||||
* Can be generated using `node scripts/generate-oidc-jwk.mjs`.
|
||||
*/
|
||||
JWKS_KEY?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,12 @@ const ModelSwitch = memo(() => {
|
||||
provider={provider}
|
||||
onModelChange={handleModelChange}
|
||||
>
|
||||
<Center className={styles.model} height={36} style={borderRadius ? { borderRadius } : undefined} width={36} >
|
||||
<Center
|
||||
className={styles.model}
|
||||
height={36}
|
||||
style={borderRadius ? { borderRadius } : undefined}
|
||||
width={36}
|
||||
>
|
||||
<div className={styles.icon}>
|
||||
<ModelIcon model={model} size={22} />
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import PluginTag from '@/components/Plugins/PluginTag';
|
||||
import { useToolStore } from '@/store/tool';
|
||||
import { customPluginSelectors } from '@/store/tool/selectors';
|
||||
|
||||
import type { CheckboxItemProps } from '../components/CheckboxWithLoading';
|
||||
import { type CheckboxItemProps } from '../components/CheckboxWithLoading';
|
||||
import CheckboxItem from '../components/CheckboxWithLoading';
|
||||
|
||||
const ToolItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { ItemType } from '@lobehub/ui';
|
||||
import { type ItemType } from '@lobehub/ui';
|
||||
import { Flexbox, Icon, Text } from '@lobehub/ui';
|
||||
import { Divider } from 'antd';
|
||||
import { createStaticStyles, cssVar } from 'antd-style';
|
||||
import type { ReactNode } from 'react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { Fragment, isValidElement, memo } from 'react';
|
||||
|
||||
export const toolsListStyles = createStaticStyles(({ css }) => ({
|
||||
groupLabel: css`
|
||||
padding-block-start: 12px;
|
||||
padding-block-end: 4px;
|
||||
padding-block: 12px 4px;
|
||||
padding-inline: 12px;
|
||||
`,
|
||||
item: css`
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { type UIChatMessage } from '@lobechat/types';
|
||||
import { ActionIconGroup, Flexbox, createRawModal } from '@lobehub/ui';
|
||||
import type { ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
|
||||
import { type ActionIconGroupEvent, type ActionIconGroupItemType } from '@lobehub/ui';
|
||||
import { ActionIconGroup, createRawModal, Flexbox } from '@lobehub/ui';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
import { ReactionPicker } from '../../../components/Reaction';
|
||||
import ShareMessageModal, { type ShareModalProps } from '../../../components/ShareMessageModal';
|
||||
import {
|
||||
Provider,
|
||||
createStore,
|
||||
messageStateSelectors,
|
||||
Provider,
|
||||
useConversationStore,
|
||||
useConversationStoreApi,
|
||||
} from '../../../store';
|
||||
import type {
|
||||
MessageActionItem,
|
||||
MessageActionItemOrDivider,
|
||||
MessageActionsConfig,
|
||||
import {
|
||||
type MessageActionItem,
|
||||
type MessageActionItemOrDivider,
|
||||
type MessageActionsConfig,
|
||||
} from '../../../types';
|
||||
import { ErrorActionsBar } from './Error';
|
||||
import { useAssistantActions } from './useAssistantActions';
|
||||
@@ -211,7 +211,7 @@ export const AssistantActionsBar = memo<AssistantActionsBarProps>(
|
||||
if (error) return <ErrorActionsBar actions={defaultActions} onActionClick={handleAction} />;
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<ReactionPicker messageId={id} />
|
||||
<ActionIconGroup items={items} menu={menu} onActionClick={handleAction} />
|
||||
</Flexbox>
|
||||
|
||||
@@ -85,8 +85,8 @@ const MessageContent = memo<UIChatMessage>(
|
||||
<ReactionDisplay
|
||||
isActive={isActive}
|
||||
messageId={id}
|
||||
onReactionClick={handleReactionClick}
|
||||
reactions={reactions}
|
||||
onReactionClick={handleReactionClick}
|
||||
/>
|
||||
)}
|
||||
</Flexbox>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { type AssistantContentBlock, type UIChatMessage } from '@lobechat/types';
|
||||
import { ActionIconGroup, Flexbox, createRawModal } from '@lobehub/ui';
|
||||
import type { ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
|
||||
import { type ActionIconGroupEvent, type ActionIconGroupItemType } from '@lobehub/ui';
|
||||
import { ActionIconGroup, createRawModal, Flexbox } from '@lobehub/ui';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
import { ReactionPicker } from '../../../components/Reaction';
|
||||
import ShareMessageModal, { type ShareModalProps } from '../../../components/ShareMessageModal';
|
||||
import {
|
||||
Provider,
|
||||
createStore,
|
||||
messageStateSelectors,
|
||||
Provider,
|
||||
useConversationStore,
|
||||
useConversationStoreApi,
|
||||
} from '../../../store';
|
||||
import type {
|
||||
MessageActionItem,
|
||||
MessageActionItemOrDivider,
|
||||
MessageActionsConfig,
|
||||
import {
|
||||
type MessageActionItem,
|
||||
type MessageActionItemOrDivider,
|
||||
type MessageActionsConfig,
|
||||
} from '../../../types';
|
||||
import { useGroupActions } from './useGroupActions';
|
||||
|
||||
@@ -164,7 +164,7 @@ const WithContentId = memo<GroupActionsProps>(({ actionsConfig, id, data, conten
|
||||
);
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<ReactionPicker messageId={id} />
|
||||
<ActionIconGroup items={items} menu={menu} onActionClick={handleAction} />
|
||||
</Flexbox>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import type { AssistantContentBlock, EmojiReaction } from '@lobechat/types';
|
||||
import { type AssistantContentBlock, type EmojiReaction } from '@lobechat/types';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import type {MouseEventHandler} from 'react';
|
||||
import { memo, Suspense, useCallback, useMemo } from 'react';
|
||||
import { type MouseEventHandler } from 'react';
|
||||
import { memo, Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
import { MESSAGE_ACTION_BAR_PORTAL_ATTRIBUTES } from '@/const/messageActionPortal';
|
||||
import { ChatItem } from '@/features/Conversation/ChatItem';
|
||||
@@ -177,8 +177,8 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
|
||||
<ReactionDisplay
|
||||
isActive={isReactionActive}
|
||||
messageId={id}
|
||||
onReactionClick={handleReactionClick}
|
||||
reactions={reactions}
|
||||
onReactionClick={handleReactionClick}
|
||||
/>
|
||||
)}
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { CompressionGroupMetadata, UIChatMessage } from '@lobechat/types';
|
||||
import { type CompressionGroupMetadata, type UIChatMessage } from '@lobechat/types';
|
||||
import {
|
||||
ActionIcon,
|
||||
Flexbox,
|
||||
@@ -148,26 +148,26 @@ const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
|
||||
<StreamingMarkdown>{content}</StreamingMarkdown>
|
||||
</>
|
||||
) : (
|
||||
<Flexbox align={'center'} distribution={'space-between'} horizontal width={'100%'}>
|
||||
<Flexbox horizontal align={'center'} distribution={'space-between'} width={'100%'}>
|
||||
<Tabs
|
||||
compact
|
||||
activeKey={isGeneratingSummary ? 'summary' : activeTab}
|
||||
className={styles.header}
|
||||
compact
|
||||
items={tabItems}
|
||||
onChange={handleTabChange}
|
||||
variant={'rounded'}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
<Flexbox gap={4} horizontal>
|
||||
<Flexbox horizontal gap={4}>
|
||||
<ActionIcon
|
||||
icon={Undo2}
|
||||
onClick={handleCancelCompression}
|
||||
size={'small'}
|
||||
title={t('compression.cancel')}
|
||||
onClick={handleCancelCompression}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon={expanded ? ChevronUp : ChevronDown}
|
||||
onClick={() => toggleCompressedGroupExpanded(id)}
|
||||
size={'small'}
|
||||
onClick={() => toggleCompressedGroupExpanded(id)}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type {AssistantContentBlock, UIChatMessage} from '@lobechat/types';
|
||||
import type { ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
|
||||
import { ActionIconGroup, createRawModal , Flexbox} from '@lobehub/ui';
|
||||
import { type AssistantContentBlock, type UIChatMessage } from '@lobechat/types';
|
||||
import { type ActionIconGroupEvent, type ActionIconGroupItemType } from '@lobehub/ui';
|
||||
import { ActionIconGroup, createRawModal, Flexbox } from '@lobehub/ui';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
import { ReactionPicker } from '../../../components/Reaction';
|
||||
import ShareMessageModal, { type ShareModalProps } from '../../../components/ShareMessageModal';
|
||||
import {
|
||||
Provider,
|
||||
createStore,
|
||||
messageStateSelectors,
|
||||
Provider,
|
||||
useConversationStore,
|
||||
useConversationStoreApi,
|
||||
} from '../../../store';
|
||||
import type {
|
||||
MessageActionItem,
|
||||
MessageActionItemOrDivider,
|
||||
MessageActionsConfig,
|
||||
import {
|
||||
type MessageActionItem,
|
||||
type MessageActionItemOrDivider,
|
||||
type MessageActionsConfig,
|
||||
} from '../../../types';
|
||||
import { useGroupActions } from './useGroupActions';
|
||||
|
||||
@@ -156,7 +156,7 @@ const WithContentId = memo<GroupActionsProps>(({ actionsConfig, id, data, conten
|
||||
);
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<ReactionPicker messageId={id} />
|
||||
<ActionIconGroup items={items} menu={menu} onActionClick={handleAction} />
|
||||
</Flexbox>
|
||||
|
||||
@@ -7,8 +7,8 @@ import { type AssistantContentBlock } from '@/types/index';
|
||||
|
||||
import ErrorContent from '../../../ChatItem/components/ErrorContent';
|
||||
import { messageStateSelectors, useConversationStore } from '../../../store';
|
||||
import { Tools } from '../../AssistantGroup/Tools';
|
||||
import MessageContent from '../../AssistantGroup/components/MessageContent';
|
||||
import { Tools } from '../../AssistantGroup/Tools';
|
||||
import Reasoning from '../../components/Reasoning';
|
||||
|
||||
interface ContentBlockProps extends AssistantContentBlock {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { EmojiReaction } from '@lobechat/types';
|
||||
import { type EmojiReaction } from '@lobechat/types';
|
||||
import { Tag } from '@lobehub/ui';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { type MouseEventHandler } from 'react';
|
||||
@@ -156,8 +156,8 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
|
||||
<ReactionDisplay
|
||||
isActive={isReactionActive}
|
||||
messageId={id}
|
||||
onReactionClick={handleReactionClick}
|
||||
reactions={reactions}
|
||||
onReactionClick={handleReactionClick}
|
||||
/>
|
||||
)}
|
||||
</ChatItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { EmojiReaction } from '@lobechat/types';
|
||||
import { type EmojiReaction } from '@lobechat/types';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
@@ -71,7 +71,7 @@ const ReactionDisplay = memo<ReactionDisplayProps>(
|
||||
if (reactions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} className={styles.container} horizontal>
|
||||
<Flexbox horizontal align={'center'} className={styles.container}>
|
||||
{reactions.map((reaction) => (
|
||||
<div
|
||||
className={cx(styles.reactionTag, isActive?.(reaction.emoji) && styles.active)}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ActionIcon, Flexbox, Tooltip } from '@lobehub/ui';
|
||||
import { Popover } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
import { PlusIcon, SmilePlus } from 'lucide-react';
|
||||
import { type FC, type ReactNode, memo, useState } from 'react';
|
||||
import { type FC, memo, type ReactNode, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
@@ -91,13 +91,13 @@ const ReactionPicker: FC<ReactionPickerProps> = memo(({ messageId, trigger }) =>
|
||||
<Picker
|
||||
data={data}
|
||||
locale={locale?.split('-')[0] || 'en'}
|
||||
onEmojiSelect={(emoji: any) => handleSelect(emoji.native)}
|
||||
previewPosition="none"
|
||||
skinTonePosition="none"
|
||||
theme={theme.appearance === 'dark' ? 'dark' : 'light'}
|
||||
onEmojiSelect={(emoji: any) => handleSelect(emoji.native)}
|
||||
/>
|
||||
) : (
|
||||
<Flexbox className={styles.pickerContainer} gap={4} horizontal wrap="wrap">
|
||||
<Flexbox horizontal className={styles.pickerContainer} gap={4} wrap="wrap">
|
||||
{QUICK_REACTIONS.map((emoji) => (
|
||||
<div className={styles.emojiButton} key={emoji} onClick={() => handleSelect(emoji)}>
|
||||
{emoji}
|
||||
@@ -113,11 +113,11 @@ const ReactionPicker: FC<ReactionPickerProps> = memo(({ messageId, trigger }) =>
|
||||
<Popover
|
||||
arrow={false}
|
||||
content={content}
|
||||
onOpenChange={handleOpenChange}
|
||||
open={open}
|
||||
overlayInnerStyle={{ padding: 0 }}
|
||||
placement="top"
|
||||
trigger="click"
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
{trigger || (
|
||||
<span {...(open ? { 'data-popup-open': '' } : {})}>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { StateCreator } from 'zustand';
|
||||
import { type StateCreator } from 'zustand';
|
||||
|
||||
import type { Store as ConversationStore } from '../../../action';
|
||||
import { type Store as ConversationStore } from '../../../action';
|
||||
import { type MessageCRUDAction, messageCRUDSlice } from './crud';
|
||||
import { type MessageReactionAction, messageReactionSlice } from './reaction';
|
||||
import { sendMessage } from './sendMessage';
|
||||
import type {MessageStateAction} from './state';
|
||||
import { messageStateSlice } from './state';
|
||||
import { type MessageStateAction } from './state';
|
||||
import { messageStateSlice } from './state';
|
||||
|
||||
/**
|
||||
* Message Actions
|
||||
@@ -16,7 +16,8 @@ import { messageStateSlice } from './state';
|
||||
* - State management (loading, collapsed, editing)
|
||||
* - Sending messages
|
||||
*/
|
||||
export interface MessageAction extends MessageCRUDAction, MessageReactionAction, MessageStateAction {
|
||||
export interface MessageAction
|
||||
extends MessageCRUDAction, MessageReactionAction, MessageStateAction {
|
||||
/**
|
||||
* Add an AI message (convenience method)
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { EmojiReaction } from '@lobechat/types';
|
||||
import type { StateCreator } from 'zustand';
|
||||
import { type EmojiReaction } from '@lobechat/types';
|
||||
import { type StateCreator } from 'zustand';
|
||||
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { userProfileSelectors } from '@/store/user/selectors';
|
||||
|
||||
import type { Store as ConversationStore } from '../../../action';
|
||||
import { type Store as ConversationStore } from '../../../action';
|
||||
import { dataSelectors } from '../../data/selectors';
|
||||
|
||||
export interface MessageReactionAction {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { IEditor } from '@lobehub/editor';
|
||||
import { type IEditor } from '@lobehub/editor';
|
||||
import {
|
||||
ReactCodemirrorPlugin,
|
||||
ReactCodePlugin,
|
||||
@@ -17,7 +17,7 @@ import { Editor, useEditorState } from '@lobehub/editor/react';
|
||||
import { memo, useEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { EditorCanvasProps } from './EditorCanvas';
|
||||
import { type EditorCanvasProps } from './EditorCanvas';
|
||||
import InlineToolbar from './InlineToolbar';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Center, Flexbox, Icon, Tooltip, stopPropagation } from '@lobehub/ui';
|
||||
import { Center, Flexbox, Icon, stopPropagation, Tooltip } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { CircleDashedIcon, HammerIcon, LayersIcon, MessageSquareQuoteIcon } from 'lucide-react';
|
||||
import qs from 'query-string';
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
Button,
|
||||
Flexbox,
|
||||
Icon,
|
||||
stopPropagation,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
stopPropagation,
|
||||
} from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cssVar, useResponsive } from 'antd-style';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BUILTIN_AGENT_SLUGS } from '@lobechat/builtin-agents';
|
||||
import { EditorProvider } from '@lobehub/editor/react';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { cssVar } from 'antd-style';
|
||||
import type { FC } from 'react';
|
||||
import { type FC } from 'react';
|
||||
import { memo, useEffect } from 'react';
|
||||
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
|
||||
@@ -491,8 +491,8 @@ const FileListItem = memo<FileListItemProps>(
|
||||
align={'center'}
|
||||
gap={8}
|
||||
paddingInline={8}
|
||||
onPointerDown={stopPropagation}
|
||||
onClick={stopPropagation}
|
||||
onPointerDown={stopPropagation}
|
||||
>
|
||||
{!isFolder &&
|
||||
!isPage &&
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Button, Flexbox, Tooltip, stopPropagation } from '@lobehub/ui';
|
||||
import { Button, Flexbox, stopPropagation, Tooltip } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { isNull } from 'es-toolkit/compat';
|
||||
import { FileBoxIcon, Folder } from 'lucide-react';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Button, Flexbox, Tooltip, stopPropagation } from '@lobehub/ui';
|
||||
import { Button, Flexbox, stopPropagation, Tooltip } from '@lobehub/ui';
|
||||
import { Image } from 'antd';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { isNull } from 'es-toolkit/compat';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Button, Tooltip, stopPropagation } from '@lobehub/ui';
|
||||
import { Button, stopPropagation, Tooltip } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { isNull } from 'es-toolkit/compat';
|
||||
import { FileBoxIcon } from 'lucide-react';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Button, Tooltip, stopPropagation } from '@lobehub/ui';
|
||||
import { Button, stopPropagation, Tooltip } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { isNull } from 'es-toolkit/compat';
|
||||
import { FileBoxIcon } from 'lucide-react';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { ActionIcon, Block, DropdownMenu, Flexbox, Icon, stopPropagation } from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { cssVar } from 'antd-style';
|
||||
import type { Klavis } from 'klavis';
|
||||
import { type Klavis } from 'klavis';
|
||||
import { Loader2, MoreVerticalIcon, Plus, Unplug } from 'lucide-react';
|
||||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -6,29 +6,29 @@ import {
|
||||
CreateUserMemoryIdentitySchema,
|
||||
MemorySourceType,
|
||||
UpdateUserMemoryIdentitySchema,
|
||||
UserMemoryExtractionMetadata,
|
||||
type UserMemoryExtractionMetadata,
|
||||
} from '@lobechat/types';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AsyncTaskModel, initUserMemoryExtractionMetadata } from '@/database/models/asyncTask';
|
||||
import { TopicModel } from '@/database/models/topic';
|
||||
import { UserMemoryModel } from '@/database/models/userMemory';
|
||||
import {
|
||||
UserMemoryActivityModel,
|
||||
UserMemoryContextModel,
|
||||
UserMemoryExperienceModel,
|
||||
UserMemoryIdentityModel,
|
||||
UserMemoryModel,
|
||||
UserMemoryPreferenceModel,
|
||||
} from '@/database/models/userMemory/index';
|
||||
} from '@/database/models/userMemory';
|
||||
import { UserPersonaModel } from '@/database/models/userMemory/persona';
|
||||
import { appEnv } from '@/envs/app';
|
||||
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
||||
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
||||
import { parseMemoryExtractionConfig } from '@/server/globalConfig/parseMemoryExtractionConfig';
|
||||
import {
|
||||
MemoryExtractionWorkflowService,
|
||||
buildWorkflowPayloadInput,
|
||||
MemoryExtractionWorkflowService,
|
||||
normalizeMemoryExtractionPayload,
|
||||
} from '@/server/services/memory/userMemory/extract';
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { DiscoverService } from '@/server/services/discover';
|
||||
|
||||
import {
|
||||
type ScheduleToolCallReportParams,
|
||||
scheduleToolCallReport,
|
||||
type ScheduleToolCallReportParams,
|
||||
} from './scheduleToolCallReport';
|
||||
|
||||
// Mock Next.js after() function
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { StreamingHandler } from '../StreamingHandler';
|
||||
import type { StreamChunk, StreamingCallbacks, StreamingContext } from '../types/streaming';
|
||||
import { type StreamingCallbacks, type StreamingContext } from '../types/streaming';
|
||||
|
||||
// Helper to create a mock streaming context
|
||||
const createContext = (overrides: Partial<StreamingContext> = {}): StreamingContext => ({
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import type { IEditor } from '@lobehub/editor/es/types';
|
||||
import type { EditorState as LobehubEditorState } from '@lobehub/editor/react';
|
||||
import { type IEditor } from '@lobehub/editor/es/types';
|
||||
import { type EditorState as LobehubEditorState } from '@lobehub/editor/react';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
|
||||
import { documentService } from '@/services/document';
|
||||
import type { StoreSetter } from '@/store/types';
|
||||
import { type StoreSetter } from '@/store/types';
|
||||
import { setNamespace } from '@/utils/storeDebug';
|
||||
|
||||
import type { DocumentStore } from '../../store';
|
||||
import type { DocumentDispatch } from './reducer';
|
||||
import { type DocumentStore } from '../../store';
|
||||
import { type DocumentDispatch } from './reducer';
|
||||
import { documentReducer } from './reducer';
|
||||
|
||||
const n = setNamespace('document/editor');
|
||||
|
||||
@@ -13,7 +13,7 @@ import { knowledgeBaseExecutor } from '@lobechat/builtin-tool-knowledge-base/exe
|
||||
import { localSystemExecutor } from '@lobechat/builtin-tool-local-system/executor';
|
||||
import { memoryExecutor } from '@lobechat/builtin-tool-memory/executor';
|
||||
|
||||
import type { IBuiltinToolExecutor } from '../types';
|
||||
import { type IBuiltinToolExecutor } from '../types';
|
||||
import { notebookExecutor } from './lobe-notebook';
|
||||
import { pageAgentExecutor } from './lobe-page-agent';
|
||||
import { webBrowsing } from './lobe-web-browsing';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CLASSNAMES } from '@lobehub/ui';
|
||||
import type { Theme } from 'antd-style';
|
||||
import { type Theme } from 'antd-style';
|
||||
import { css } from 'antd-style';
|
||||
|
||||
// fix ios input keyboard
|
||||
|
||||
Reference in New Issue
Block a user