mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
🔨 chore: add desktop pre-code to validate build process (#7261)
* add code * fix lint * fix tests
This commit is contained in:
@@ -6,14 +6,22 @@ import ReactComponentName from 'react-scan/react-component-name/webpack';
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const buildWithDocker = process.env.DOCKER === 'true';
|
||||
const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
const enableReactScan = !!process.env.REACT_SCAN_MONITOR_API_KEY;
|
||||
const isUsePglite = process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite';
|
||||
|
||||
// if you need to proxy the api endpoint to remote server
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH;
|
||||
const isStandaloneMode = buildWithDocker || isDesktop;
|
||||
|
||||
const standaloneConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
outputFileTracingIncludes: { '*': ['public/**/*', '.next/static/**/*'] },
|
||||
};
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
...(isStandaloneMode ? standaloneConfig : {}),
|
||||
basePath,
|
||||
compress: isProd,
|
||||
experimental: {
|
||||
@@ -110,10 +118,6 @@ const nextConfig: NextConfig = {
|
||||
hmrRefreshes: true,
|
||||
},
|
||||
},
|
||||
output: buildWithDocker ? 'standalone' : undefined,
|
||||
outputFileTracingIncludes: buildWithDocker
|
||||
? { '*': ['public/**/*', '.next/static/**/*'] }
|
||||
: undefined,
|
||||
reactStrictMode: true,
|
||||
redirects: async () => [
|
||||
{
|
||||
@@ -231,13 +235,14 @@ const noWrapper = (config: NextConfig) => config;
|
||||
|
||||
const withBundleAnalyzer = process.env.ANALYZE === 'true' ? analyzer() : noWrapper;
|
||||
|
||||
const withPWA = isProd
|
||||
? withSerwistInit({
|
||||
register: false,
|
||||
swDest: 'public/sw.js',
|
||||
swSrc: 'src/app/sw.ts',
|
||||
})
|
||||
: noWrapper;
|
||||
const withPWA =
|
||||
isProd && !isDesktop
|
||||
? withSerwistInit({
|
||||
register: false,
|
||||
swDest: 'public/sw.js',
|
||||
swSrc: 'src/app/sw.ts',
|
||||
})
|
||||
: noWrapper;
|
||||
|
||||
const hasSentry = !!process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
const withSentry =
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
|
||||
"build:analyze": "ANALYZE=true next build",
|
||||
"build:docker": "DOCKER=true next build && npm run build-sitemap",
|
||||
"build:electron": "NODE_OPTIONS=--max-old-space-size=6144 NEXT_PUBLIC_IS_DESKTOP_APP=1 next build ",
|
||||
"db:generate": "drizzle-kit generate && npm run db:generate-client && npm run workflow:dbml",
|
||||
"db:generate-client": "tsx ./scripts/migrateClientDB/compile-migrations.ts",
|
||||
"db:migrate": "MIGRATION_DB=1 tsx ./scripts/migrateServerDB/index.ts",
|
||||
@@ -129,6 +130,8 @@
|
||||
"@icons-pack/react-simple-icons": "9.6.0",
|
||||
"@khmyznikov/pwa-install": "0.3.9",
|
||||
"@langchain/community": "^0.3.37",
|
||||
"@lobechat/electron-client-ipc": "workspace:*",
|
||||
"@lobechat/electron-server-ipc": "workspace:*",
|
||||
"@lobechat/web-crawler": "workspace:*",
|
||||
"@lobehub/charts": "^1.12.0",
|
||||
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
||||
|
||||
48
packages/electron-client-ipc/README.md
Normal file
48
packages/electron-client-ipc/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# @lobechat/electron-client-ipc
|
||||
|
||||
这个包是 LobeChat 在 Electron 环境中用于处理 IPC(进程间通信)的客户端工具包。
|
||||
|
||||
## 介绍
|
||||
|
||||
在 Electron 应用中,IPC(进程间通信)是连接主进程(Main Process)、渲染进程(Renderer Process)以及 NextJS 进程的桥梁。为了更好地组织和管理这些通信,我们将 IPC 相关的代码分成了两个包:
|
||||
|
||||
- `@lobechat/electron-client-ipc`:**客户端 IPC 包**
|
||||
- `@lobechat/electron-server-ipc`:**服务端 IPC 包**
|
||||
|
||||
## 主要区别
|
||||
|
||||
### electron-client-ipc(本包)
|
||||
|
||||
- 运行环境:在渲染进程(Renderer Process)中运行
|
||||
- 主要职责:
|
||||
- 提供渲染进程调用主进程方法的接口定义
|
||||
- 封装 `ipcRenderer.invoke` 相关方法
|
||||
- 处理与主进程的通信请求
|
||||
|
||||
### electron-server-ipc
|
||||
|
||||
- 运行环境:在 Electron 主进程和 Next.js 服务端进程中运行
|
||||
- 主要职责:
|
||||
- 提供基于 Socket 的 IPC 通信机制
|
||||
- 实现服务端(ElectronIPCServer)和客户端(ElectronIpcClient)通信组件
|
||||
- 处理跨进程的请求和响应
|
||||
- 提供自动重连和错误处理机制
|
||||
- 确保类型安全的 API 调用
|
||||
|
||||
## 使用场景
|
||||
|
||||
当渲染进程需要:
|
||||
|
||||
- 访问系统 API
|
||||
- 进行文件操作
|
||||
- 调用主进程特定功能
|
||||
|
||||
时,都需要通过 `electron-client-ipc` 包提供的方法来发起请求。
|
||||
|
||||
## 技术说明
|
||||
|
||||
这种分包设计遵循了关注点分离原则,使得:
|
||||
|
||||
- IPC 通信接口清晰可维护
|
||||
- 客户端和服务端代码解耦
|
||||
- TypeScript 类型定义共享,确保类型安全
|
||||
7
packages/electron-client-ipc/package.json
Normal file
7
packages/electron-client-ipc/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@lobechat/electron-client-ipc",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts"
|
||||
}
|
||||
6
packages/electron-client-ipc/src/events/devtools.ts
Normal file
6
packages/electron-client-ipc/src/events/devtools.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface DevtoolsDispatchEvents {
|
||||
/**
|
||||
* open the LobeHub Devtools
|
||||
*/
|
||||
openDevtools: () => void;
|
||||
}
|
||||
13
packages/electron-client-ipc/src/events/index.ts
Normal file
13
packages/electron-client-ipc/src/events/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { DevtoolsDispatchEvents } from './devtools';
|
||||
|
||||
/**
|
||||
* renderer -> main dispatch events
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ClientDispatchEvents extends DevtoolsDispatchEvents {}
|
||||
|
||||
export type ClientDispatchEventKey = keyof ClientDispatchEvents;
|
||||
|
||||
export type ClientEventReturnType<T extends ClientDispatchEventKey> = ReturnType<
|
||||
ClientDispatchEvents[T]
|
||||
>;
|
||||
2
packages/electron-client-ipc/src/index.ts
Normal file
2
packages/electron-client-ipc/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './events';
|
||||
export * from './types';
|
||||
10
packages/electron-client-ipc/src/types/dispatch.ts
Normal file
10
packages/electron-client-ipc/src/types/dispatch.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type {
|
||||
ClientDispatchEventKey,
|
||||
ClientDispatchEvents,
|
||||
ClientEventReturnType,
|
||||
} from '../events';
|
||||
|
||||
export type DispatchInvoke = <T extends ClientDispatchEventKey>(
|
||||
event: T,
|
||||
...data: Parameters<ClientDispatchEvents[T]>
|
||||
) => Promise<ClientEventReturnType<T>>;
|
||||
1
packages/electron-client-ipc/src/types/index.ts
Normal file
1
packages/electron-client-ipc/src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dispatch';
|
||||
@@ -4,7 +4,7 @@ LobeHub 的 Electron 应用与服务端之间的 IPC(进程间通信)模块
|
||||
|
||||
## 📝 简介
|
||||
|
||||
`@lobechat/electron-server-ipc` 是 LobeHub 桌面应用的核心组件,负责处理 Electron 进程与 nextjs 服务端之间的通信。它提供了一套简单而健壮的 API,用于在不同进程间传递数据和执行远程方法调用。
|
||||
`@lobechat/electron-server-ipc` 是 LobeHub 桌面应用的核心组件,负责处理 Electron 主进程与 nextjs 服务端之间的通信。它提供了一套简单而健壮的 API,用于在不同进程间传递数据和执行远程方法调用。
|
||||
|
||||
## 🛠️ 核心功能
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
packages:
|
||||
- 'packages/**'
|
||||
- '.'
|
||||
- '!apps/**'
|
||||
|
||||
89
src/app/desktop/devtools/page.tsx
Normal file
89
src/app/desktop/devtools/page.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
'use client';
|
||||
|
||||
import { ActionIcon, FluentEmoji, SideNav } from '@lobehub/ui';
|
||||
import { Cog, DatabaseIcon } from 'lucide-react';
|
||||
import { memo, useState } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { BRANDING_NAME } from '@/const/branding';
|
||||
import PostgresViewer from '@/features/DevPanel/PostgresViewer';
|
||||
import SystemInspector from '@/features/DevPanel/SystemInspector';
|
||||
import { useStyles } from '@/features/DevPanel/features/FloatPanel';
|
||||
import { electronStylish } from '@/styles/electron';
|
||||
|
||||
const DevTools = memo(() => {
|
||||
const { styles, theme, cx } = useStyles();
|
||||
|
||||
const items = [
|
||||
{
|
||||
children: <PostgresViewer />,
|
||||
icon: <DatabaseIcon size={16} />,
|
||||
key: 'Postgres Viewer',
|
||||
},
|
||||
{
|
||||
children: <SystemInspector />,
|
||||
icon: <Cog size={16} />,
|
||||
key: 'System Status',
|
||||
},
|
||||
];
|
||||
|
||||
const [tab, setTab] = useState<string>(items[0].key);
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
height={'100%'}
|
||||
horizontal
|
||||
style={{ overflow: 'hidden', position: 'relative' }}
|
||||
width={'100%'}
|
||||
>
|
||||
<SideNav
|
||||
avatar={<FluentEmoji emoji={'🧰'} size={24} />}
|
||||
bottomActions={[]}
|
||||
style={{
|
||||
paddingBlock: 32,
|
||||
width: 48,
|
||||
}}
|
||||
topActions={items.map((item) => (
|
||||
<ActionIcon
|
||||
active={tab === item.key}
|
||||
key={item.key}
|
||||
onClick={() => setTab(item.key)}
|
||||
placement={'right'}
|
||||
title={item.key}
|
||||
>
|
||||
{item.icon}
|
||||
</ActionIcon>
|
||||
))}
|
||||
/>
|
||||
<Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }} width={'100%'}>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(`panel-drag-handle`, styles.header, electronStylish.draggable)}
|
||||
horizontal
|
||||
justify={'center'}
|
||||
>
|
||||
<Flexbox align={'baseline'} gap={6} horizontal>
|
||||
<b>{BRANDING_NAME} Dev Tools</b>
|
||||
<span style={{ color: theme.colorTextDescription }}>/</span>
|
||||
<span style={{ color: theme.colorTextDescription }}>{tab}</span>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
{items.map((item) => (
|
||||
<Flexbox
|
||||
flex={1}
|
||||
height={'100%'}
|
||||
key={item.key}
|
||||
style={{
|
||||
display: tab === item.key ? 'flex' : 'none',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{item.children}
|
||||
</Flexbox>
|
||||
))}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default DevTools;
|
||||
31
src/app/desktop/layout.tsx
Normal file
31
src/app/desktop/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { NuqsAdapter } from 'nuqs/adapters/next/app';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { isDesktop } from '@/const/version';
|
||||
import GlobalLayout from '@/layout/GlobalProvider';
|
||||
import { ServerConfigStoreProvider } from '@/store/serverConfig/Provider';
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const RootLayout = async ({ children }: RootLayoutProps) => {
|
||||
if (!isDesktop) return notFound();
|
||||
|
||||
return (
|
||||
<html dir="ltr" suppressHydrationWarning>
|
||||
<body>
|
||||
<NuqsAdapter>
|
||||
<ServerConfigStoreProvider>
|
||||
<GlobalLayout appearance={'auto'} isMobile={false} locale={''}>
|
||||
{children}
|
||||
</GlobalLayout>
|
||||
</ServerConfigStoreProvider>
|
||||
</NuqsAdapter>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootLayout;
|
||||
11
src/app/layout.tsx
Normal file
11
src/app/layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
const Layout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
1
src/app/not-found.tsx
Normal file
1
src/app/not-found.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@/components/404';
|
||||
1
src/const/desktop.ts
Normal file
1
src/const/desktop.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const DESKTOP_USER_ID = 'DEFAULT_DESKTOP_USER';
|
||||
@@ -7,6 +7,8 @@ export const CURRENT_VERSION = pkg.version;
|
||||
export const isServerMode = process.env.NEXT_PUBLIC_SERVICE_MODE === 'server';
|
||||
export const isUsePgliteDB = process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite';
|
||||
|
||||
export const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
|
||||
export const isDeprecatedEdition = !isServerMode && !isUsePgliteDB;
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -167,17 +167,10 @@ export class DatabaseManager {
|
||||
// if hash is the same, no need to migrate
|
||||
if (hash === cacheHash) {
|
||||
try {
|
||||
// 检查数据库中是否存在表
|
||||
// 这里使用 pg_tables 系统表查询用户表数量
|
||||
const tablesResult = await this.db.execute(
|
||||
sql`
|
||||
SELECT COUNT(*) as table_count
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
`,
|
||||
);
|
||||
const drizzleMigration = new DrizzleMigrationModel(this.db as any);
|
||||
|
||||
const tableCount = parseInt((tablesResult.rows[0] as any).table_count || '0', 10);
|
||||
// 检查数据库中是否存在表
|
||||
const tableCount = await drizzleMigration.getTableCounts();
|
||||
|
||||
// 如果表数量大于0,则认为数据库已正确初始化
|
||||
if (tableCount > 0) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
import { LobeChatDatabase } from '@/database/type';
|
||||
import { MigrationTableItem } from '@/types/clientDB';
|
||||
|
||||
@@ -8,6 +10,19 @@ export class DrizzleMigrationModel {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
getTableCounts = async () => {
|
||||
// 这里使用 pg_tables 系统表查询用户表数量
|
||||
const result = await this.db.execute(
|
||||
sql`
|
||||
SELECT COUNT(*) as table_count
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
`,
|
||||
);
|
||||
|
||||
return parseInt((result.rows[0] as any).table_count || '0');
|
||||
};
|
||||
|
||||
getMigrationList = async () => {
|
||||
const res = await this.db.execute(
|
||||
'SELECT * FROM "drizzle"."__drizzle_migrations" ORDER BY "created_at" DESC;',
|
||||
|
||||
@@ -177,6 +177,9 @@ export class UserModel {
|
||||
};
|
||||
|
||||
// Static method
|
||||
static makeSureUserExist = async (db: LobeChatDatabase, userId: string) => {
|
||||
await db.insert(users).values({ id: userId }).onConflictDoNothing();
|
||||
};
|
||||
|
||||
static createUser = async (db: LobeChatDatabase, params: NewUser) => {
|
||||
// if user already exists, skip creation
|
||||
|
||||
@@ -4,14 +4,16 @@ import { ActionIcon, FluentEmoji, Icon, SideNav } from '@lobehub/ui';
|
||||
import { FloatButton } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { BugIcon, BugOff, XIcon } from 'lucide-react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { ReactNode, memo, useEffect, useState } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { Rnd } from 'react-rnd';
|
||||
|
||||
import { BRANDING_NAME } from '@/const/branding';
|
||||
import { isDesktop } from '@/const/version';
|
||||
|
||||
// 定义样式
|
||||
const useStyles = createStyles(({ token, css, prefixCls }) => {
|
||||
export const useStyles = createStyles(({ token, css, prefixCls }) => {
|
||||
return {
|
||||
collapsed: css`
|
||||
pointer-events: none;
|
||||
@@ -86,6 +88,7 @@ const CollapsibleFloatPanel = memo<CollapsibleFloatPanelProps>(({ items }) => {
|
||||
const [position, setPosition] = useState({ x: 100, y: 100 });
|
||||
const [size, setSize] = useState({ height: minHeight, width: minWidth });
|
||||
|
||||
const pathname = usePathname();
|
||||
useEffect(() => {
|
||||
try {
|
||||
const localStoragePosition = localStorage.getItem('debug-panel-position');
|
||||
@@ -108,11 +111,25 @@ const CollapsibleFloatPanel = memo<CollapsibleFloatPanelProps>(({ items }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<FloatButton
|
||||
className={styles.floatButton}
|
||||
icon={<Icon icon={isExpanded ? BugOff : BugIcon} />}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
/>
|
||||
{
|
||||
// desktop devtools 下隐藏
|
||||
pathname !== '/desktop/devtools' && (
|
||||
<FloatButton
|
||||
className={styles.floatButton}
|
||||
icon={<Icon icon={isExpanded ? BugOff : BugIcon} />}
|
||||
onClick={async () => {
|
||||
if (isDesktop) {
|
||||
const { electronDevtoolsService } = await import('@/services/electron/devtools');
|
||||
|
||||
await electronDevtoolsService.openDevtools();
|
||||
|
||||
return;
|
||||
}
|
||||
setIsExpanded(!isExpanded);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{isExpanded && (
|
||||
<Rnd
|
||||
bounds="window"
|
||||
|
||||
@@ -4,16 +4,20 @@ import { Popover } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { PropsWithChildren, memo, useState } from 'react';
|
||||
|
||||
import { isDesktop } from '@/const/version';
|
||||
|
||||
import PanelContent from './PanelContent';
|
||||
import UpgradeBadge from './UpgradeBadge';
|
||||
import { useNewVersion } from './useNewVersion';
|
||||
|
||||
const useStyles = createStyles(({ css }) => ({
|
||||
popover: css`
|
||||
inset-block-start: 8px !important;
|
||||
inset-inline-start: 8px !important;
|
||||
`,
|
||||
}));
|
||||
const useStyles = createStyles(({ css }) => {
|
||||
return {
|
||||
popover: css`
|
||||
inset-block-start: ${isDesktop ? 24 : 8}px !important;
|
||||
inset-inline-start: 8px !important;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const UserPanel = memo<PropsWithChildren>(({ children }) => {
|
||||
const hasNewVersion = useNewVersion();
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { enableClerk } from '@/const/auth';
|
||||
import { DESKTOP_USER_ID } from '@/const/desktop';
|
||||
import { isDesktop } from '@/const/version';
|
||||
|
||||
import { trpc } from '../init';
|
||||
|
||||
export const userAuth = trpc.middleware(async (opts) => {
|
||||
const { ctx } = opts;
|
||||
|
||||
if (isDesktop) {
|
||||
return opts.next({
|
||||
ctx: {
|
||||
userId: DESKTOP_USER_ID,
|
||||
},
|
||||
});
|
||||
}
|
||||
// `ctx.user` is nullable
|
||||
if (!ctx.userId) {
|
||||
if (enableClerk) {
|
||||
|
||||
@@ -21,6 +21,7 @@ vi.mock('@/config/tools', () => ({
|
||||
|
||||
vi.mock('@/const/version', () => ({
|
||||
isServerMode: true,
|
||||
isDesktop: false,
|
||||
}));
|
||||
|
||||
const createCaller = createCallerFactory(searchRouter);
|
||||
|
||||
9
src/services/electron/devtools.ts
Normal file
9
src/services/electron/devtools.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { dispatch } from '@/utils/electron/dispatch';
|
||||
|
||||
class DevtoolsService {
|
||||
async openDevtools(): Promise<void> {
|
||||
return dispatch('openDevtools');
|
||||
}
|
||||
}
|
||||
|
||||
export const electronDevtoolsService = new DevtoolsService();
|
||||
14
src/styles/electron.ts
Normal file
14
src/styles/electron.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { css, cx } from 'antd-style';
|
||||
|
||||
export const draggable = cx(css`
|
||||
-webkit-app-region: drag;
|
||||
`);
|
||||
|
||||
export const nodrag = cx(css`
|
||||
-webkit-app-region: no-drag;
|
||||
`);
|
||||
|
||||
export const electronStylish = {
|
||||
draggable,
|
||||
nodrag,
|
||||
};
|
||||
11
src/types/electron.ts
Normal file
11
src/types/electron.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { DispatchInvoke } from '@lobechat/electron-client-ipc';
|
||||
|
||||
export interface IElectronAPI {
|
||||
invoke: DispatchInvoke;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: IElectronAPI;
|
||||
}
|
||||
}
|
||||
10
src/utils/electron/dispatch.ts
Normal file
10
src/utils/electron/dispatch.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DispatchInvoke } from '@lobechat/electron-client-ipc';
|
||||
|
||||
/**
|
||||
* client 端请求 sketch 端 event 数据的方法
|
||||
*/
|
||||
export const dispatch: DispatchInvoke = async (event, ...data) => {
|
||||
if (!window.electronAPI) throw new Error('electronAPI not found');
|
||||
|
||||
return window.electronAPI.invoke(event, ...data);
|
||||
};
|
||||
@@ -27,16 +27,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"exclude": ["node_modules", "public/sw.js"],
|
||||
"exclude": ["node_modules", "public/sw.js", "apps/desktop"],
|
||||
"include": [
|
||||
"**/*.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"next-env.d.ts",
|
||||
"vitest.config.ts",
|
||||
"src",
|
||||
"tests",
|
||||
"**/*.ts",
|
||||
"**/*.d.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
"vitest.config.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
|
||||
Reference in New Issue
Block a user