25 Commits

Author SHA1 Message Date
LIlGG
e9b573a276 refactor: repartition server-side and client-side code 2025-10-11 18:26:07 +08:00
LIlGG
7acc4949fb feat: allow using chat to modify page titles 2025-10-11 16:36:20 +08:00
LIlGG
a672fcad1c fix: switching pages may cause page confusion
Eliminated unnecessary call to webBuilderStore.setActiveSectionByPageName in openArtifactInWebBuilder, as selected page is already set and scrolling to element is handled.
2025-10-11 16:35:02 +08:00
Takagi
1a4ee99ff0 Merge pull request #8 from LIlGG/fix/multi-page-render
fix: resolve logical issues when generating multi-page data
2025-10-11 16:29:24 +08:00
LIlGG
884f5186a6 fix: resolve logical issues when generating multi-page data 2025-10-10 18:48:40 +08:00
Takagi
e96c2da9e5 Merge pull request #7 from LIlGG/fix/loop-call-replace-state
fix: resolve the issue of frequent triggering of replaceState
2025-10-10 14:49:08 +08:00
LIlGG
5b8408d7da fix: resolve the issue of frequent triggering of replaceState 2025-10-10 12:23:42 +08:00
LIlGG
63636fef1f chore: remove tracking script from layout
Deleted the inclusion of the external tracking script in the Layout component, likely to improve privacy or simplify the page head.
2025-10-10 11:32:36 +08:00
LIlGG
3af1c30d49 fix: Reduce the frequency of saving empty pages 2025-10-10 11:29:19 +08:00
LIlGG
c5d47c680c fix: Resolve the issue of possible abnormal text generated during page creation. 2025-10-09 17:48:18 +08:00
LIlGG
a93a679c71 pref: Make the generated page names unique rather than consistent 2025-10-09 14:54:18 +08:00
LIlGG
5ff32f2c98 fix: addressing the issues with outdated prompt information in multi-turn dialogues 2025-10-09 12:02:20 +08:00
LIlGG
196a0c39e7 fix: allow rate limit trust proxy 2025-10-09 11:28:29 +08:00
LIlGG
30aba9dfff docs: correct the quick deployment Docker image name 2025-09-30 18:14:08 +08:00
LIlGG
846444c3a2 docs: update README.md 2025-09-30 18:02:20 +08:00
LIlGG
1d8f634a3f chore: compress document images 2025-09-30 17:40:54 +08:00
LIlGG
9c76fcaa3e Merge branch 'main' of github.com:halo-dev/upage 2025-09-30 17:25:46 +08:00
LIlGG
bb661a7737 docs: supplemental user guide documents 2025-09-30 17:25:35 +08:00
maninhill
759dc777e1 Remove Workflow Status badge from README
Removed GitHub Workflow Status badge from README.
2025-09-30 15:42:04 +08:00
LIlGG
c0197de5c7 docs: add 1panel deployment 2025-09-30 11:12:14 +08:00
maninhill
48faae8988 Update README with 1Panel installation instructions
Added installation instructions for UPage via 1Panel app store.
2025-09-30 10:10:30 +08:00
Takagi
dc815c1595 Update README.md 2025-09-29 22:40:52 +08:00
LIlGG
0c6797c6a9 update README.md 2025-09-29 19:19:28 +08:00
maninhill
c68b479c4d chore: Update README.md 2025-09-29 18:32:03 +08:00
maninhill
f969408c83 chore: Update README.md 2025-09-29 18:20:30 +08:00
352 changed files with 1793 additions and 1294 deletions

View File

@@ -6,6 +6,7 @@ on:
- main - main
paths: paths:
- "**" - "**"
- "!img/**"
- "!**.md" - "!**.md"
- '!docs/**' - '!docs/**'
release: release:

View File

@@ -1,37 +1,26 @@
<p align="center"> <p align="center">
<img alt="UPage 主界面" src="./public/logo.png" style="width: 240px; height: auto;" /> <img alt="UPage logo" src="./public/logo.png" style="width: 240px; height: auto;" />
</p> </p>
<h3 align="center">基于大模型的可视化网页构建平台</h3> <h3 align="center">基于大模型的可视化网页构建平台</h3>
<p align="center"> <p align="center">
<a href="https://github.com/halo-dev/upage/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/halo-dev/upage.svg?style=flat-square&include_prereleases" /></a> <a href="https://github.com/halo-dev/upage/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/halo-dev/upage.svg?style=flat-square&include_prereleases" /></a>
<a href="https://github.com/halo-dev/upage/commits"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/halo-dev/upage.svg?style=flat-square" /></a> <a href="https://github.com/halo-dev/upage/commits"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/halo-dev/upage.svg?style=flat-square" /></a>
<a href="https://github.com/halo-dev/upage/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/halo-dev/upage/halo.yaml?branch=main&style=flat-square" /></a>
<a href="https://halo-dev.github.io/upage/"><img alt="Documentation" src="https://img.shields.io/badge/docs-latest-blue?style=flat-square" /></a> <a href="https://halo-dev.github.io/upage/"><img alt="Documentation" src="https://img.shields.io/badge/docs-latest-blue?style=flat-square" /></a>
</p> </p>
------------------------------ ------------------------------
UPage 是一款基于大模型的可视化网页构建平台,支持多种 AI 提供商集成基于自然语言快速实现定制化网页。UPage 优势在于: UPage 是一款基于大语言模型的可视化网页构建平台,支持接入主流大模型,只需通过自然语言描述需求,即可快速生成个性化、高颜值的网页,让创作更高效、更智能。
- **基于 LLM 的页面生成**:通过自然语言描述生成完整的网页 - **可视化编辑,所见即所得**:简洁直观的可视化编辑器,支持实时预览,轻松调整布局与样式;
- **多种 LLM 提供商支持**:兼容 OpenAI、Anthropic Claude、Google Gemini 等多种 LLM 模型 - **多页面一键生成**:支持同时生成多个关联页面,快速搭建完整网站结构;
- **可视化编辑器**:简洁直观的可视化编辑器界面,实时预览 - **标准代码自由导出**:自动生成规范的 HTML/CSS/JS 代码,便于集成至现有项目或二次开发;
- **多页面生成**:支持同时生成多个页面 - **响应式设计,全端适配**:自动适配桌面、平板、移动端等多种设备,确保跨平台完美呈现。
- **代码导出**:生成标准的 HTML/CSS/JS 代码,方便集成到现有项目
- **响应式设计**:自动适应不同屏幕尺寸
- **部署集成**:支持一键部署到常见托管平台
------------------------------
特别感谢 [bolt.diy](https://github.com/stackblitz-labs/bolt.diy) 项目UPage 的实现基于该项目的代码结构。
------------------------------
## 快速开始 ## 快速开始
UPage 提供基于 Docker 的部署方案,可以使用以下脚本进行快速部署 准备一台 Linux 服务器,安装好 Docker 后,执行以下一键安装脚本
```bash ```bash
docker run -d \ docker run -d \
@@ -49,9 +38,9 @@ docker run -d \
halohub/upage:latest halohub/upage:latest
``` ```
其中参数说明如下: 参数说明如下:
- `-e LLM_PROVIDER=OpenAI`:设置默认的 LLM 提供商为 OpenAI同时兼容支持 OpenAI 规范的 API 接口。 - `-e LLM_PROVIDER=OpenAI`:设置默认的 LLM 提供商为 OpenAI同时兼容支持 OpenAI 规范的 API 接口。
- `-e PROVIDER_BASE_URL=your-provider-base-url`:设置 LLM 提供商的 API 基础 URL部分提供商需要设置此项例如 Ollama, LMStudioOpenAI 提供商可选此项。 - `-e PROVIDER_BASE_URL=your-provider-base-url`:设置 LLM 提供商的 API 基础 URL部分提供商需要设置此项例如 OllamaLMStudioOpenAI 提供商可选此项。例如 `https://api.openai.com/v1`
- `-e PROVIDER_API_KEY=your-openai-api-key`:设置 LLM 提供商的 API 密钥,大部分提供商需要设置此项。 - `-e PROVIDER_API_KEY=your-openai-api-key`:设置 LLM 提供商的 API 密钥,大部分提供商需要设置此项。
- `-e LLM_DEFAULT_MODEL=your-default-model`:设置默认的 LLM 模型,用于构建页面。 - `-e LLM_DEFAULT_MODEL=your-default-model`:设置默认的 LLM 模型,用于构建页面。
- `-e LLM_MINOR_MODEL=your-minor-model`:设置次要的 LLM 模型,用于执行其他任务。 - `-e LLM_MINOR_MODEL=your-minor-model`:设置次要的 LLM 模型,用于执行其他任务。
@@ -61,6 +50,27 @@ docker run -d \
访问 `http://localhost:3000` 即可访问 UPage 的界面。 访问 `http://localhost:3000` 即可访问 UPage 的界面。
你也可以通过 [1Panel 应用商店](https://1panel.cn/) 来安装部署 UPage。
详细使用指南请参考:[UPage 在线文档](https://docs.upage.ai/quick-start)
### 联系我们
如你有更多问题,可以加入我们的技术交流群与我们交流。
<img width="180" height="180" alt="contact_me_qr" src="./img/wecom.png">
## UI 展示
| | |
| --- | --- |
| ![](./img/preview-4.png) | ![](./img/preview-1.png) |
| ![](./img/preview-2.png) | ![](./img/preview-3.png) |
## 致谢
UPage 基于 [bolt.diy](https://github.com/stackblitz-labs/bolt.diy) 的代码结构构建,特此致谢该项目带来的启发与贡献。
## 飞致云旗下的其他明星项目 ## 飞致云旗下的其他明星项目
- [Halo](https://github.com/halo-dev/halo) - 强大易用的开源建站工具 - [Halo](https://github.com/halo-dev/halo) - 强大易用的开源建站工具
@@ -71,12 +81,13 @@ docker run -d \
- [Cordys CRM](https://github.com/cordys/cordys-crm) - 新一代的开源 AI CRM 系统 - [Cordys CRM](https://github.com/cordys/cordys-crm) - 新一代的开源 AI CRM 系统
- [MeterSphere](https://github.com/metersphere/metersphere) - 新一代的开源持续测试工具 - [MeterSphere](https://github.com/metersphere/metersphere) - 新一代的开源持续测试工具
## 许可证 ## License
本仓库遵循 [FIT2CLOUD Open Source License](https://github.com/halo-dev/upage/blob/main/LICENSE.txt) 开源协议,该许可证本质上是 GPLv3但有一些额外的限制。 本仓库遵循 [FIT2CLOUD Open Source License](LICENSE) 开源协议,该许可证本质上是 GPLv3但有一些额外的限制。
你可以基于 UPage 的源代码进行二次开发,但是需要遵守以下规定: 你可以基于 UPage 的源代码进行二次开发,但是需要遵守以下规定:
不能替换和修改 UPage 的 Logo 和版权信息; - 不能替换和修改 UPage 的 Logo 和版权信息;
二次开发后的衍生作品必须遵守 GPL V3 的开源义务。 - 二次开发后的衍生作品必须遵守 GPL V3 的开源义务。
如需商业授权,请联系 support@fit2cloud.com 。
如需商业授权,请联系:`support@fit2cloud.com`

View File

@@ -1,5 +1,5 @@
import type { LogEntry } from '~/lib/stores/logs'; import type { LogEntry } from '~/stores/logs';
import { logStore } from '~/lib/stores/logs'; import { logStore } from '~/stores/logs';
export interface Notification { export interface Notification {
id: string; id: string;

View File

@@ -4,11 +4,11 @@ interface BridgeContext {
loaded: boolean; loaded: boolean;
} }
export const bridgeContext: BridgeContext = import.meta.hot?.data.editorBridgeContext ?? { export const bridgeContext: BridgeContext = import.meta.hot?.data?.editorBridgeContext ?? {
loaded: false, loaded: false,
}; };
if (import.meta.hot) { if (import.meta.hot && import.meta.hot.data) {
import.meta.hot.data.editorBridgeContext = bridgeContext; import.meta.hot.data.editorBridgeContext = bridgeContext;
} }
@@ -34,7 +34,7 @@ type SectionProps = {
}; };
/** /**
* node editor bridge * editor bridge
* bridge 广 * bridge 广
* pages sections editor * pages sections editor
*/ */
@@ -101,6 +101,26 @@ export class EditorBridge {
}); });
} }
/**
*
*
* @param pageName
* @param param1
* @returns
*/
async updatePageAttributes(pageName: string, { title }: { title?: string } = {}) {
const page = this.#pages.get(pageName);
if (!page) {
return;
}
const actionIds = page.actionIds;
this.#emit('upsert_page', {
pageName,
title,
actionIds,
});
}
async removePage(pageName: string) { async removePage(pageName: string) {
this.#pages.delete(pageName); this.#pages.delete(pageName);
@@ -171,7 +191,7 @@ export let editorBridge: Promise<EditorBridge> = new Promise(() => {
if (!import.meta.env.SSR) { if (!import.meta.env.SSR) {
editorBridge = editorBridge =
import.meta.hot?.data.editorBridge ?? import.meta.hot?.data?.editorBridge ??
Promise.resolve() Promise.resolve()
.then(() => { .then(() => {
return new EditorBridge(); return new EditorBridge();
@@ -181,7 +201,7 @@ if (!import.meta.env.SSR) {
return editorBridge; return editorBridge;
}); });
if (import.meta.hot) { if (import.meta.hot && import.meta.hot.data) {
import.meta.hot.data.editorBridge = editorBridge; import.meta.hot.data.editorBridge = editorBridge;
} }
} }

View File

@@ -2,7 +2,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import classNames from 'classnames'; import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useAuth } from '~/lib/hooks/useAuth'; import { useAuth } from '~/.client/hooks/useAuth';
import type { TabType } from './types'; import type { TabType } from './types';
interface AvatarDropdownProps { interface AvatarDropdownProps {

View File

@@ -3,22 +3,24 @@ import * as RadixDialog from '@radix-ui/react-dialog';
import classNames from 'classnames'; import classNames from 'classnames';
import { AnimatePresence, motion, type Variants } from 'framer-motion'; import { AnimatePresence, motion, type Variants } from 'framer-motion';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { TabTile } from '~/components/@settings/core/TabTile'; import { TabTile } from '~/.client/components/@settings/core/TabTile';
import DebugTab from '~/components/@settings/tabs/debug/DebugTab'; import DebugTab from '~/.client/components/@settings/tabs/debug/DebugTab';
import { EventLogsTab } from '~/components/@settings/tabs/event-logs/EventLogsTab'; import { EventLogsTab } from '~/.client/components/@settings/tabs/event-logs/EventLogsTab';
import NotificationsTab from '~/components/@settings/tabs/notifications/NotificationsTab'; import NotificationsTab from '~/.client/components/@settings/tabs/notifications/NotificationsTab';
import SettingsTab from '~/components/@settings/tabs/settings/SettingsTab'; import SettingsTab from '~/.client/components/@settings/tabs/settings/SettingsTab';
import TaskManagerTab from '~/components/@settings/tabs/task-manager/TaskManagerTab'; import TaskManagerTab from '~/.client/components/@settings/tabs/task-manager/TaskManagerTab';
import BackgroundRays from '~/components/ui/BackgroundRays'; import BackgroundRays from '~/.client/components/ui/BackgroundRays';
import { useDebugStatus } from '~/lib/hooks/useDebugStatus'; import { useDebugStatus } from '~/.client/hooks/useDebugStatus';
import { useNotifications } from '~/lib/hooks/useNotifications'; import { useNotifications } from '~/.client/hooks/useNotifications';
import { profileStore } from '~/lib/stores/profile'; import { profileStore } from '~/.client/stores/profile';
import { resetTabConfiguration, tabConfigurationStore } from '~/lib/stores/settings'; import { resetTabConfiguration, tabConfigurationStore } from '~/.client/stores/settings';
import { logger } from '~/utils/logger'; import { createScopedLogger } from '~/utils/logger';
import { AvatarDropdown } from './AvatarDropdown'; import { AvatarDropdown } from './AvatarDropdown';
import { DEFAULT_TAB_CONFIG, TAB_DESCRIPTIONS } from './constants'; import { DEFAULT_TAB_CONFIG, TAB_DESCRIPTIONS } from './constants';
import type { Profile, TabType, TabVisibilityConfig } from './types'; import type { Profile, TabType, TabVisibilityConfig } from './types';
const logger = createScopedLogger('ControlPanel');
interface ControlPanelProps { interface ControlPanelProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -83,8 +85,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
}; };
// Process tabs in priority order // Process tabs in priority order
tabConfiguration.developerTabs?.forEach((tab) => processTab(tab as BaseTabConfig)); tabConfiguration.developerTabs?.forEach((tab: any) => processTab(tab as BaseTabConfig));
tabConfiguration.userTabs.forEach((tab) => processTab(tab as BaseTabConfig)); tabConfiguration.userTabs.forEach((tab: any) => processTab(tab as BaseTabConfig));
DEFAULT_TAB_CONFIG.forEach((tab) => processTab(tab as BaseTabConfig)); DEFAULT_TAB_CONFIG.forEach((tab) => processTab(tab as BaseTabConfig));
return devTabs.sort((a, b) => a.order - b.order); return devTabs.sort((a, b) => a.order - b.order);

View File

@@ -1,8 +1,8 @@
import * as Tooltip from '@radix-ui/react-tooltip'; import * as Tooltip from '@radix-ui/react-tooltip';
import classNames from 'classnames'; import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { TAB_ICONS, TAB_LABELS } from '~/components/@settings/core/constants'; import { TAB_ICONS, TAB_LABELS } from '~/.client/components/@settings/core/constants';
import type { TabVisibilityConfig } from '~/components/@settings/core/types'; import type { TabVisibilityConfig } from '~/.client/components/@settings/core/types';
interface TabTileProps { interface TabTileProps {
tab: TabVisibilityConfig; tab: TabVisibilityConfig;

View File

@@ -3,12 +3,12 @@ import classNames from 'classnames';
import { jsPDF } from 'jspdf'; import { jsPDF } from 'jspdf';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Badge } from '~/components/ui/Badge'; import { Badge } from '~/.client/components/ui/Badge';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/.client/components/ui/Collapsible';
import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; import { Dialog, DialogRoot, DialogTitle } from '~/.client/components/ui/Dialog';
import { Progress } from '~/components/ui/Progress'; import { Progress } from '~/.client/components/ui/Progress';
import { ScrollArea } from '~/components/ui/ScrollArea'; import { ScrollArea } from '~/.client/components/ui/ScrollArea';
import { type LogEntry, logStore } from '~/lib/stores/logs'; import { type LogEntry, logStore } from '~/stores/logs';
interface SystemInfo { interface SystemInfo {
os: string; os: string;

View File

@@ -5,9 +5,9 @@ import { motion } from 'framer-motion';
import { jsPDF } from 'jspdf'; import { jsPDF } from 'jspdf';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; import { Dialog, DialogRoot, DialogTitle } from '~/.client/components/ui/Dialog';
import { Switch } from '~/components/ui/Switch'; import { Switch } from '~/.client/components/ui/Switch';
import { type LogEntry, logStore } from '~/lib/stores/logs'; import { type LogEntry, logStore } from '~/stores/logs';
interface SelectOption { interface SelectOption {
value: string; value: string;

View File

@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { logStore } from '~/lib/stores/logs'; import { logStore } from '~/stores/logs';
interface NotificationDetails { interface NotificationDetails {
type?: string; type?: string;

View File

@@ -2,9 +2,9 @@ import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { UserProfile } from '~/components/@settings/core/types'; import type { UserProfile } from '~/.client/components/@settings/core/types';
import { Switch } from '~/components/ui/Switch'; import { Switch } from '~/.client/components/ui/Switch';
import { isMac } from '~/utils/os'; import { isMac } from '~/.client/utils/os';
// Helper to get modifier key symbols/text // Helper to get modifier key symbols/text
const getModifierSymbol = (modifier: string): string => { const getModifierSymbol = (modifier: string): string => {

View File

@@ -15,7 +15,7 @@ import * as React from 'react';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { tabConfigurationStore } from '~/lib/stores/settings'; import { tabConfigurationStore } from '~/.client/stores/settings';
// Register ChartJS components // Register ChartJS components
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);

View File

@@ -1,5 +1,5 @@
import { DEFAULT_TAB_CONFIG } from '~/components/@settings/core/constants'; import { DEFAULT_TAB_CONFIG } from '~/.client/components/@settings/core/constants';
import type { TabType, TabVisibilityConfig } from '~/components/@settings/core/types'; import type { TabType, TabVisibilityConfig } from '~/.client/components/@settings/core/types';
export const getVisibleTabs = ( export const getVisibleTabs = (
tabConfiguration: { userTabs: TabVisibilityConfig[]; developerTabs?: TabVisibilityConfig[] }, tabConfiguration: { userTabs: TabVisibilityConfig[]; developerTabs?: TabVisibilityConfig[] },

View File

@@ -1,7 +1,7 @@
import { useNavigate } from '@remix-run/react'; import { useNavigate } from '@remix-run/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Button } from '~/components/ui/Button'; import { Button } from '~/.client/components/ui/Button';
import { useAuth } from '~/lib/hooks/useAuth'; import { useAuth } from '~/.client/hooks/useAuth';
export function SignInButton({ className, children = '登录' }: { className?: string; children?: React.ReactNode }) { export function SignInButton({ className, children = '登录' }: { className?: string; children?: React.ReactNode }) {
const { signIn, isAuthenticated } = useAuth(); const { signIn, isAuthenticated } = useAuth();

View File

@@ -1,4 +1,4 @@
import { useAuth } from '~/lib/hooks/useAuth'; import { useAuth } from '~/.client/hooks/useAuth';
export function UserProfile({ className }: { className?: string }) { export function UserProfile({ className }: { className?: string }) {
const { isAuthenticated, userInfo, isLoading } = useAuth(); const { isAuthenticated, userInfo, isLoading } = useAuth();

View File

@@ -2,11 +2,11 @@ import { useStore } from '@nanostores/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { computed } from 'nanostores'; import { computed } from 'nanostores';
import { memo, useEffect, useRef, useState } from 'react'; import { memo, useEffect, useMemo, useRef, useState } from 'react';
import { type BundledLanguage, type BundledTheme, createHighlighter, type HighlighterGeneric } from 'shiki'; import { type BundledLanguage, type BundledTheme, createHighlighter, type HighlighterGeneric } from 'shiki';
import type { ActionState } from '~/lib/runtime/action-runner'; import type { ActionState } from '~/.client/runtime/action-runner';
import { webBuilderStore } from '~/lib/stores/web-builder'; import { webBuilderStore } from '~/.client/stores/web-builder';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/.client/utils/easings';
const highlighterOptions = { const highlighterOptions = {
langs: ['shell'], langs: ['shell'],
@@ -14,26 +14,34 @@ const highlighterOptions = {
}; };
const shellHighlighter: HighlighterGeneric<BundledLanguage, BundledTheme> = const shellHighlighter: HighlighterGeneric<BundledLanguage, BundledTheme> =
import.meta.hot?.data.shellHighlighter ?? (await createHighlighter(highlighterOptions)); import.meta.hot?.data?.shellHighlighter ?? (await createHighlighter(highlighterOptions));
if (import.meta.hot) { if (import.meta.hot && import.meta.hot.data) {
import.meta.hot.data.shellHighlighter = shellHighlighter; import.meta.hot.data.shellHighlighter = shellHighlighter;
} }
interface ArtifactProps { interface ArtifactProps {
messageId: string; messageId: string;
pageName: string;
} }
export const Artifact = memo(({ messageId }: ArtifactProps) => { export const Artifact = memo(({ messageId, pageName }: ArtifactProps) => {
const userToggledActions = useRef(false); const userToggledActions = useRef(false);
const [showActions, setShowActions] = useState(false); const [showActions, setShowActions] = useState(false);
const [allActionFinished, setAllActionFinished] = useState(false); const [allActionFinished, setAllActionFinished] = useState(false);
const artifacts = useStore(webBuilderStore.chatStore.artifacts); const artifacts = useStore(webBuilderStore.chatStore.artifacts);
const artifact = artifacts[messageId]; const artifact = useMemo(() => {
const artifactsByPageName = artifacts.get(messageId);
if (!artifactsByPageName) {
return undefined;
}
return artifactsByPageName.get(pageName);
}, [artifacts, messageId, pageName]);
const actions = useStore( const actions = useStore(
computed(artifact.runner.actions, (actions) => { computed(artifact?.runner.actions!, (actions) => {
return Object.values(actions); return Object.values(actions);
}), }),
); );
@@ -44,11 +52,14 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
}; };
useEffect(() => { useEffect(() => {
const actionsMap = artifact.runner.actions.get(); const actionsMap = artifact?.runner.actions.get();
if (!actionsMap) {
return;
}
Object.entries(actionsMap).forEach(([actionId, action]) => { Object.entries(actionsMap).forEach(([actionId, action]) => {
if (action.status === 'running' || action.status === 'pending') { if (action.status === 'running' || action.status === 'pending') {
artifact.runner.actions.setKey(actionId, { artifact?.runner.actions.setKey(actionId, {
...action, ...action,
status: 'aborted', status: 'aborted',
}); });
@@ -61,7 +72,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
setShowActions(true); setShowActions(true);
} }
if (actions.length !== 0 && artifact.type === 'bundled') { if (actions.length !== 0 && artifact?.type === 'bundled') {
const finished = !actions.find((action) => action.status !== 'complete'); const finished = !actions.find((action) => action.status !== 'complete');
if (allActionFinished !== finished) { if (allActionFinished !== finished) {
@@ -80,7 +91,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
webBuilderStore.showWorkbench.set(!showWorkbench); webBuilderStore.showWorkbench.set(!showWorkbench);
}} }}
> >
{artifact.type == 'bundled' && ( {artifact?.type == 'bundled' && (
<> <>
<div className="p-4"> <div className="p-4">
{allActionFinished ? ( {allActionFinished ? (
@@ -101,7 +112,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
</button> </button>
<div className="bg-upage-elements-artifacts-borderColor w-[1px]" /> <div className="bg-upage-elements-artifacts-borderColor w-[1px]" />
<AnimatePresence> <AnimatePresence>
{actions.length && artifact.type !== 'bundled' && ( {actions.length && artifact?.type !== 'bundled' && (
<motion.button <motion.button
initial={{ width: 0 }} initial={{ width: 0 }}
animate={{ width: 'auto' }} animate={{ width: 'auto' }}
@@ -118,7 +129,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
</AnimatePresence> </AnimatePresence>
</div> </div>
<AnimatePresence> <AnimatePresence>
{artifact.type !== 'bundled' && showActions && actions.length > 0 && ( {artifact?.type !== 'bundled' && showActions && actions.length > 0 && (
<motion.div <motion.div
className="actions" className="actions"
initial={{ height: 0 }} initial={{ height: 0 }}

View File

@@ -1,7 +1,7 @@
import { memo } from 'react'; import { memo } from 'react';
import Popover from '~/components/ui/Popover'; import Popover from '~/.client/components/ui/Popover';
import Tooltip from '~/components/ui/Tooltip'; import Tooltip from '~/.client/components/ui/Tooltip';
import type { ParsedUIMessage } from '~/lib/stores/ai-state'; import type { ParsedUIMessage } from '~/.client/stores/ai-state';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
export const AssistantMessage = memo(({ message }: { message: ParsedUIMessage }) => { export const AssistantMessage = memo(({ message }: { message: ParsedUIMessage }) => {

View File

@@ -5,10 +5,10 @@ import classNames from 'classnames';
import { useAnimate } from 'framer-motion'; import { useAnimate } from 'framer-motion';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only'; import { ClientOnly } from 'remix-utils/client-only';
import { useShortcuts, useSnapScroll } from '~/lib/hooks'; import { useShortcuts, useSnapScroll } from '~/.client/hooks';
import { useChatMessage } from '~/lib/hooks/useChatMessage'; import { useChatMessage } from '~/.client/hooks/useChatMessage';
import { aiState, setChatId, setChatStarted } from '~/lib/stores/ai-state'; import { aiState, setChatId, setChatStarted } from '~/.client/stores/ai-state';
import { webBuilderStore } from '~/lib/stores/web-builder'; import { webBuilderStore } from '~/.client/stores/web-builder';
import type { ChatMessage, ChatWithMessages } from '~/types/chat'; import type { ChatMessage, ChatWithMessages } from '~/types/chat';
import { renderLogger } from '~/utils/logger'; import { renderLogger } from '~/utils/logger';
import { Menu } from '../sidebar/Menu.client'; import { Menu } from '../sidebar/Menu.client';

View File

@@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { webBuilderStore } from '~/lib/stores/web-builder'; import { webBuilderStore } from '~/.client/stores/web-builder';
interface Props { interface Props {
postMessage: (message: string) => void; postMessage: (message: string) => void;

View File

@@ -2,8 +2,8 @@ import { useStore } from '@nanostores/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only'; import { ClientOnly } from 'remix-utils/client-only';
import { useAuth, usePromptEnhancer } from '~/lib/hooks'; import { useAuth, usePromptEnhancer } from '~/.client/hooks';
import { aiState } from '~/lib/stores/ai-state'; import { aiState } from '~/.client/stores/ai-state';
import { IconButton } from '../ui/IconButton'; import { IconButton } from '../ui/IconButton';
import { SendButton } from './SendButton.client'; import { SendButton } from './SendButton.client';

View File

@@ -1,7 +1,7 @@
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { cubicEasingFn } from '~/.client/utils/easings';
import type { ElementInfoMetadata } from '~/types/message'; import type { ElementInfoMetadata } from '~/types/message';
import { cubicEasingFn } from '~/utils/easings';
import { ElementPreview } from './ElementPreview'; import { ElementPreview } from './ElementPreview';
interface ElementEditPreviewProps { interface ElementEditPreviewProps {

View File

@@ -25,12 +25,16 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
div: ({ className, children, node, ...props }) => { div: ({ className, children, node, ...props }) => {
if (className?.includes('__uPageArtifact__')) { if (className?.includes('__uPageArtifact__')) {
const messageId = node?.properties.dataMessageId as string; const messageId = node?.properties.dataMessageId as string;
const pageName = node?.properties.dataPageName as string;
if (!messageId) { if (!messageId) {
logger.error(`Invalid message id ${messageId}`); logger.error(`Invalid message id ${messageId}`);
} }
if (!pageName) {
logger.error(`Invalid page name ${pageName}`);
}
return <Artifact messageId={messageId} />; return <Artifact messageId={messageId} pageName={pageName} />;
} }
if (className?.includes('__uPageThought__')) { if (className?.includes('__uPageThought__')) {

View File

@@ -4,11 +4,11 @@ import classNames from 'classnames';
import type { ForwardedRef } from 'react'; import type { ForwardedRef } from 'react';
import { Fragment, forwardRef, memo, useEffect, useMemo, useRef } from 'react'; import { Fragment, forwardRef, memo, useEffect, useMemo, useRef } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import WithTooltip from '~/components/ui/Tooltip'; import WithTooltip from '~/.client/components/ui/Tooltip';
import { useAuth } from '~/lib/hooks/useAuth'; import { useAuth } from '~/.client/hooks/useAuth';
import { useChatOperate } from '~/lib/hooks/useChatOperate'; import { useChatOperate } from '~/.client/hooks/useChatOperate';
import { useSnapScroll } from '~/lib/hooks/useSnapScroll'; import { useSnapScroll } from '~/.client/hooks/useSnapScroll';
import { aiState, type ParsedUIMessage } from '~/lib/stores/ai-state'; import { aiState, type ParsedUIMessage } from '~/.client/stores/ai-state';
import { AssistantMessage } from './AssistantMessage'; import { AssistantMessage } from './AssistantMessage';
import styles from './Messages.module.scss'; import styles from './Messages.module.scss';
import { UserMessage } from './UserMessage'; import { UserMessage } from './UserMessage';

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { KeyboardEvent } from 'react'; import type { KeyboardEvent } from 'react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import type { ModelInfo } from '~/lib/modules/llm/types'; import type { ModelInfo } from '~/.server/modules/llm/types';
import type { ProviderInfo } from '~/types/model'; import type { ProviderInfo } from '~/types/model';
interface ModelSelectorProps { interface ModelSelectorProps {

View File

@@ -1,6 +1,6 @@
import * as Tooltip from '@radix-ui/react-tooltip'; import * as Tooltip from '@radix-ui/react-tooltip';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment'; import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
export function NetlifyDeploymentLink() { export function NetlifyDeploymentLink() {

View File

@@ -1,8 +1,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { cubicEasingFn } from '~/.client/utils/easings';
import type { ProgressAnnotation } from '~/types/message'; import type { ProgressAnnotation } from '~/types/message';
import { cubicEasingFn } from '~/utils/easings';
export default function ProgressCompilation({ data }: { data?: ProgressAnnotation[] }) { export default function ProgressCompilation({ data }: { data?: ProgressAnnotation[] }) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);

View File

@@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { IconButton } from '~/components/ui/IconButton'; import { IconButton } from '~/.client/components/ui/IconButton';
export const SpeechRecognitionButton = ({ export const SpeechRecognitionButton = ({
isListening, isListening,

View File

@@ -1,6 +1,6 @@
import type { FileUIPart } from 'ai'; import type { FileUIPart } from 'ai';
import { MODEL_REGEX, PROVIDER_REGEX } from '~/.client/utils/constants';
import type { UPageUIMessage } from '~/types/message'; import type { UPageUIMessage } from '~/types/message';
import { MODEL_REGEX, PROVIDER_REGEX } from '~/utils/constants';
import { ElementEditPreview } from './ElementEditPreview'; import { ElementEditPreview } from './ElementEditPreview';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';

View File

@@ -1,6 +1,6 @@
import * as Tooltip from '@radix-ui/react-tooltip'; import * as Tooltip from '@radix-ui/react-tooltip';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment'; import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
export function VercelDeploymentLink() { export function VercelDeploymentLink() {

View File

@@ -1,6 +1,6 @@
import * as Tooltip from '@radix-ui/react-tooltip'; import * as Tooltip from '@radix-ui/react-tooltip';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment'; import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
export function _1PanelDeploymentLink() { export function _1PanelDeploymentLink() {

View File

@@ -1,5 +1,5 @@
import { IconButton } from '~/components/ui/IconButton'; import { IconButton } from '~/.client/components/ui/IconButton';
import WithTooltip from '~/components/ui/Tooltip'; import WithTooltip from '~/.client/components/ui/Tooltip';
export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => { export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => {
return ( return (

View File

@@ -2,7 +2,7 @@ import * as RadixDialog from '@radix-ui/react-dialog';
import classNames from 'classnames'; import classNames from 'classnames';
import { motion, type Transition, type Variants } from 'framer-motion'; import { motion, type Transition, type Variants } from 'framer-motion';
import { memo } from 'react'; import { memo } from 'react';
import { useChatUsage } from '~/lib/hooks/useChatUsage'; import { useChatUsage } from '~/.client/hooks/useChatUsage';
import { DialogDescription, DialogTitle } from '../../ui/Dialog'; import { DialogDescription, DialogTitle } from '../../ui/Dialog';
import { IconButton } from '../../ui/IconButton'; import { IconButton } from '../../ui/IconButton';
import { ChatUsageVisualization } from './ChatUsageVisualization'; import { ChatUsageVisualization } from './ChatUsageVisualization';

View File

@@ -14,8 +14,8 @@ import {
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Doughnut, Line, Pie } from 'react-chartjs-2'; import { Doughnut, Line, Pie } from 'react-chartjs-2';
import type { ChatUsageStats } from '~/lib/hooks/useChatUsage'; import type { ChatUsageStats } from '~/.client/hooks/useChatUsage';
import { themeStore } from '~/lib/stores/theme'; import { themeStore } from '~/stores/theme';
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, PointElement, LineElement); ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, PointElement, LineElement);

View File

@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { motion, type Transition, type Variants } from 'framer-motion'; import { motion, type Transition, type Variants } from 'framer-motion';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { type DeploymentRecord, useDeploymentRecords } from '~/lib/hooks/useDeploymentRecords'; import { type DeploymentRecord, useDeploymentRecords } from '~/.client/hooks/useDeploymentRecords';
import { DeploymentPlatformEnum, DeploymentStatusEnum } from '~/types/deployment'; import { DeploymentPlatformEnum, DeploymentStatusEnum } from '~/types/deployment';
import { ConfirmationDialog, DialogDescription, DialogTitle } from '../../ui/Dialog'; import { ConfirmationDialog, DialogDescription, DialogTitle } from '../../ui/Dialog';
import { IconButton } from '../../ui/IconButton'; import { IconButton } from '../../ui/IconButton';

View File

@@ -1,5 +1,5 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { sendChatMessageStore } from '~/lib/stores/chat-message'; import { sendChatMessageStore } from '~/.client/stores/chat-message';
import { DefaultEditor } from './editors/DefaultEditor'; import { DefaultEditor } from './editors/DefaultEditor';
import type { EditorProps } from './editors/EditorProps'; import type { EditorProps } from './editors/EditorProps';
import { IconEditor } from './editors/IconEditor'; import { IconEditor } from './editors/IconEditor';

View File

@@ -1,10 +1,10 @@
import { memo, useCallback, useEffect, useRef } from 'react'; import { memo, useCallback, useEffect, useRef } from 'react';
import { useChatHistory } from '~/lib/persistence'; import { useChatHistory } from '~/.client/persistence';
import { isValidContent } from '~/.client/utils/html-parse';
import { throttleWithTrailing } from '~/.client/utils/throttle';
import type { Section } from '~/types/actions'; import type { Section } from '~/types/actions';
import type { DocumentProperties, Editor } from '~/types/editor'; import type { DocumentProperties, Editor } from '~/types/editor';
import { isValidContent } from '~/utils/html-parse';
import { logger } from '~/utils/logger'; import { logger } from '~/utils/logger';
import { throttleWithTrailing } from '~/utils/throttle';
import { EditorComponent } from './EditorComponent'; import { EditorComponent } from './EditorComponent';
export interface ScrollPosition { export interface ScrollPosition {
@@ -28,7 +28,6 @@ interface Props {
editable?: boolean; editable?: boolean;
debounceChange?: number; debounceChange?: number;
debounceScroll?: number; debounceScroll?: number;
autoFocusOnDocumentChange?: boolean;
onChange?: OnChangeCallback; onChange?: OnChangeCallback;
onReset?: () => void; onReset?: () => void;
onSave?: OnSaveCallback; onSave?: OnSaveCallback;
@@ -39,7 +38,7 @@ interface Props {
} }
export const EditorStudio = memo( export const EditorStudio = memo(
({ documents, currentPage, currentSection, autoFocusOnDocumentChange, onChange, onSave, onLoad, onReady }: Props) => { ({ documents, currentPage, currentSection, onChange, onSave, onLoad, onReady }: Props) => {
const editorRef = useRef<Editor | null>(null); const editorRef = useRef<Editor | null>(null);
const pendingSectionRef = useRef<Section | null>(null); const pendingSectionRef = useRef<Section | null>(null);
@@ -134,7 +133,7 @@ export const EditorStudio = memo(
// 保存最新的页面属性,确保在节流期间如果有新的更新进来,会使用最新的数据 // 保存最新的页面属性,确保在节流期间如果有新的更新进来,会使用最新的数据
pendingSectionRef.current = currentSection; pendingSectionRef.current = currentSection;
setEditorDocument(editor, currentSection); setEditorDocument(editor, currentSection);
}, [currentSection, autoFocusOnDocumentChange]); }, [currentSection]);
// 确保在组件卸载前应用最后一次更新 // 确保在组件卸载前应用最后一次更新
useEffect(() => { useEffect(() => {

View File

@@ -1,6 +1,6 @@
import type { RefObject } from 'react'; import type { RefObject } from 'react';
import { createRef, useCallback, useEffect, useRef } from 'react'; import { createRef, useCallback, useEffect, useRef } from 'react';
import { useEditorCommands } from '~/lib/hooks'; import { useEditorCommands } from '~/.client/hooks';
import type { DocumentProperties, Editor } from '~/types/editor'; import type { DocumentProperties, Editor } from '~/types/editor';
import { EditorController } from './EditorController'; import { EditorController } from './EditorController';
import { EditorRender } from './EditorRender'; import { EditorRender } from './EditorRender';

View File

@@ -1,6 +1,6 @@
import { executeScript } from '~/.client/utils/execute-scripts';
import { isScriptContent } from '~/.client/utils/html-parse';
import type { Editor, EditorControllerProps } from '~/types/editor'; import type { Editor, EditorControllerProps } from '~/types/editor';
import { executeScript } from '~/utils/execute-scripts';
import { isScriptContent } from '~/utils/html-parse';
export class EditorController implements Editor { export class EditorController implements Editor {
private props: EditorControllerProps; private props: EditorControllerProps;

View File

@@ -1,9 +1,9 @@
import { motion, type Variants } from 'framer-motion'; import { motion, type Variants } from 'framer-motion';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import Frame from 'react-frame-component'; import Frame from 'react-frame-component';
import { executeScripts } from '~/.client/utils/execute-scripts';
import { isMac } from '~/.client/utils/os';
import type { DocumentProperties } from '~/types/editor'; import type { DocumentProperties } from '~/types/editor';
import { executeScripts } from '~/utils/execute-scripts';
import { isMac } from '~/utils/os';
import { EditorOverlay } from './EditorOverlay'; import { EditorOverlay } from './EditorOverlay';
export interface PageRenderRef { export interface PageRenderRef {

View File

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 227 B

View File

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

View File

Before

Width:  |  Height:  |  Size: 287 B

After

Width:  |  Height:  |  Size: 287 B

View File

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 601 B

View File

@@ -1,10 +1,10 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { TooltipProvider } from '@radix-ui/react-tooltip'; import { TooltipProvider } from '@radix-ui/react-tooltip';
import { useEffect } from 'react'; import { useEffect } from 'react';
import WithTooltip from '~/components/ui/Tooltip'; import WithTooltip from '~/.client/components/ui/Tooltip';
import { useEditChatDescription } from '~/lib/hooks'; import { useEditChatDescription } from '~/.client/hooks';
import { useChatHistory } from '~/lib/hooks/useChatHistory'; import { useChatHistory } from '~/.client/hooks/useChatHistory';
import { webBuilderStore } from '~/lib/stores/web-builder'; import { webBuilderStore } from '~/.client/stores/web-builder';
export function ChatDescription() { export function ChatDescription() {
const { getChatLatestDescription } = useChatHistory(); const { getChatLatestDescription } = useChatHistory();

View File

@@ -2,9 +2,9 @@ import { useStore } from '@nanostores/react';
import * as Dialog from '@radix-ui/react-dialog'; import * as Dialog from '@radix-ui/react-dialog';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import _1PanelConnection from '~/components/header/connections/_1PanelConnection'; import _1PanelConnection from '~/.client/components/header/connections/_1PanelConnection';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment'; import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { _1PanelConnectionStore } from '~/lib/stores/1panel'; import { _1PanelConnectionStore } from '~/.client/stores/1panel';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
interface DeployTo1PanelDialogProps { interface DeployTo1PanelDialogProps {

View File

@@ -2,11 +2,11 @@ import { useStore } from '@nanostores/react';
import * as Dialog from '@radix-ui/react-dialog'; import * as Dialog from '@radix-ui/react-dialog';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React, { Suspense, useEffect, useState } from 'react'; import React, { Suspense, useEffect, useState } from 'react';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment'; import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { netlifyConnection } from '~/lib/stores/netlify'; import { netlifyConnection } from '~/.client/stores/netlify';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
const NetlifyConnection = React.lazy(() => import('~/components/header/connections/NetlifyConnection')); const NetlifyConnection = React.lazy(() => import('~/.client/components/header/connections/NetlifyConnection'));
interface DeployToNetlifyDialogProps { interface DeployToNetlifyDialogProps {
isOpen: boolean; isOpen: boolean;

View File

@@ -2,9 +2,9 @@ import { useStore } from '@nanostores/react';
import * as Dialog from '@radix-ui/react-dialog'; import * as Dialog from '@radix-ui/react-dialog';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import VercelConnection from '~/components/header/connections/VercelConnection'; import VercelConnection from '~/.client/components/header/connections/VercelConnection';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment'; import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { vercelConnection } from '~/lib/stores/vercel'; import { vercelConnection } from '~/.client/stores/vercel';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
interface DeployToVercelDialogProps { interface DeployToVercelDialogProps {

View File

@@ -2,9 +2,9 @@ import { useStore } from '@nanostores/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ClientOnly } from 'remix-utils/client-only'; import { ClientOnly } from 'remix-utils/client-only';
import { useAuth } from '~/lib/hooks'; import { useAuth } from '~/.client/hooks';
import { aiState } from '~/lib/stores/ai-state'; import { aiState } from '~/.client/stores/ai-state';
import { themeStore } from '~/lib/stores/theme'; import { themeStore } from '~/stores/theme';
import { HistorySwitch } from '../sidebar/HistorySwitch'; import { HistorySwitch } from '../sidebar/HistorySwitch';
import { ThemeSwitch } from '../ui/ThemeSwitch'; import { ThemeSwitch } from '../ui/ThemeSwitch';
import { ChatDescription } from './ChatDescription.client'; import { ChatDescription } from './ChatDescription.client';

View File

@@ -4,11 +4,11 @@ import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { renderToStaticMarkup } from 'react-dom/server'; import { renderToStaticMarkup } from 'react-dom/server';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { NetlifyDeploymentLink } from '~/components/chat/NetlifyDeploymentLink.client'; import { NetlifyDeploymentLink } from '~/.client/components/chat/NetlifyDeploymentLink.client';
import useViewport from '~/lib/hooks'; import useViewport from '~/.client/hooks';
import { setLocalStorage } from '~/lib/persistence'; import { setLocalStorage } from '~/.client/persistence';
import { aiState, setShowChat } from '~/lib/stores/ai-state'; import { aiState, setShowChat } from '~/.client/stores/ai-state';
import { webBuilderStore } from '~/lib/stores/web-builder'; import { webBuilderStore } from '~/.client/stores/web-builder';
import type { _1PanelDeployResponse } from '~/types/1panel'; import type { _1PanelDeployResponse } from '~/types/1panel';
import { DeploymentPlatformEnum } from '~/types/deployment'; import { DeploymentPlatformEnum } from '~/types/deployment';
import type { ApiResponse } from '~/types/global'; import type { ApiResponse } from '~/types/global';

View File

@@ -2,12 +2,12 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import classNames from 'classnames'; import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { ChatUsageDialog } from '~/components/chat/usage/ChatUsageDialog'; import { ChatUsageDialog } from '~/.client/components/chat/usage/ChatUsageDialog';
import { DeploymentRecordsDialog } from '~/components/chat/usage/DeploymentRecordsDialog'; import { DeploymentRecordsDialog } from '~/.client/components/chat/usage/DeploymentRecordsDialog';
import { Button } from '~/components/ui/Button'; import { Button } from '~/.client/components/ui/Button';
import { ConfirmationDialog } from '~/components/ui/Dialog'; import { ConfirmationDialog } from '~/.client/components/ui/Dialog';
import { useAuth } from '~/lib/hooks/useAuth'; import { useAuth } from '~/.client/hooks/useAuth';
import { useChatUsage } from '~/lib/hooks/useChatUsage'; import { useChatUsage } from '~/.client/hooks/useChatUsage';
interface MinimalAvatarDropdownProps {} interface MinimalAvatarDropdownProps {}

View File

@@ -2,9 +2,9 @@ import classNames from 'classnames';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Button } from '~/components/ui/Button'; import { Button } from '~/.client/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/.client/components/ui/Collapsible';
import { logStore } from '~/lib/stores/logs'; import { logStore } from '~/stores/logs';
import ConnectionBorder from './components/ConnectionBorder'; import ConnectionBorder from './components/ConnectionBorder';
interface GitHubUserResponse { interface GitHubUserResponse {

View File

@@ -5,10 +5,15 @@ import { formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale/zh-CN'; import { zhCN } from 'date-fns/locale/zh-CN';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Badge } from '~/components/ui/Badge'; import { Badge } from '~/.client/components/ui/Badge';
import { Button } from '~/components/ui/Button'; import { Button } from '~/.client/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/.client/components/ui/Collapsible';
import { fetchNetlifyStats, isFetchingStats, netlifyConnection, updateNetlifyConnection } from '~/lib/stores/netlify'; import {
fetchNetlifyStats,
isFetchingStats,
netlifyConnection,
updateNetlifyConnection,
} from '~/.client/stores/netlify';
import type { ConnectionSettings } from '~/root'; import type { ConnectionSettings } from '~/root';
import type { ApiResponse } from '~/types/global'; import type { ApiResponse } from '~/types/global';
import type { NetlifyBuild, NetlifyDeploy, NetlifySite } from '~/types/netlify'; import type { NetlifyBuild, NetlifyDeploy, NetlifySite } from '~/types/netlify';

View File

@@ -3,9 +3,9 @@ import { useFetcher, useRouteLoaderData } from '@remix-run/react';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { logStore } from '~/lib/stores/logs'; import { fetchVercelStats, isFetchingStats, updateVercelConnection, vercelConnection } from '~/.client/stores/vercel';
import { fetchVercelStats, isFetchingStats, updateVercelConnection, vercelConnection } from '~/lib/stores/vercel';
import type { ConnectionSettings } from '~/root'; import type { ConnectionSettings } from '~/root';
import { logStore } from '~/stores/logs';
import { logger } from '~/utils/logger'; import { logger } from '~/utils/logger';
import ConnectionBorder from './components/ConnectionBorder'; import ConnectionBorder from './components/ConnectionBorder';

View File

@@ -6,11 +6,16 @@ import { zhCN } from 'date-fns/locale/zh-CN';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Badge } from '~/components/ui/Badge'; import { Badge } from '~/.client/components/ui/Badge';
import { Button } from '~/components/ui/Button'; import { Button } from '~/.client/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/.client/components/ui/Collapsible';
import { _1PanelConnectionStore, fetch1PanelStats, isFetchingStats, update1PanelConnection } from '~/lib/stores/1panel'; import {
import { getChatId } from '~/lib/stores/ai-state'; _1PanelConnectionStore,
fetch1PanelStats,
isFetchingStats,
update1PanelConnection,
} from '~/.client/stores/1panel';
import { getChatId } from '~/.client/stores/ai-state';
import type { ConnectionSettings } from '~/root'; import type { ConnectionSettings } from '~/root';
import type { _1PanelWebsite } from '~/types/1panel'; import type { _1PanelWebsite } from '~/types/1panel';
import type { ApiResponse } from '~/types/global'; import type { ApiResponse } from '~/types/global';

View File

@@ -4,14 +4,14 @@ import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React, { Suspense, useEffect, useState } from 'react'; import React, { Suspense, useEffect, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { getLocalStorage } from '~/lib/persistence'; import { getLocalStorage } from '~/.client/persistence';
import { logStore } from '~/lib/stores/logs'; import { webBuilderStore } from '~/.client/stores/web-builder';
import { webBuilderStore } from '~/lib/stores/web-builder'; import { formatSize } from '~/.client/utils/format';
import { logStore } from '~/stores/logs';
import type { GitHubUserResponse } from '~/types/github'; import type { GitHubUserResponse } from '~/types/github';
import { formatSize } from '~/utils/format';
import { logger } from '~/utils/logger'; import { logger } from '~/utils/logger';
const GitHubConnection = React.lazy(() => import('~/components/header/connections/GithubConnection')); const GitHubConnection = React.lazy(() => import('~/.client/components/header/connections/GithubConnection'));
interface PushToGitHubDialogProps { interface PushToGitHubDialogProps {
isOpen: boolean; isOpen: boolean;

View File

@@ -1,10 +1,10 @@
import { useParams } from '@remix-run/react'; import { useParams } from '@remix-run/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { type ForwardedRef, forwardRef, useCallback } from 'react'; import { type ForwardedRef, forwardRef, useCallback } from 'react';
import { Checkbox } from '~/components/ui/Checkbox'; import { Checkbox } from '~/.client/components/ui/Checkbox';
import WithTooltip from '~/components/ui/Tooltip'; import WithTooltip from '~/.client/components/ui/Tooltip';
import { useEditChatDescription } from '~/lib/hooks'; import { useEditChatDescription } from '~/.client/hooks';
import type { ServerChatItem } from '~/lib/hooks/useChatEntries'; import type { ServerChatItem } from '~/.client/hooks/useChatEntries';
interface HistoryItemProps { interface HistoryItemProps {
item: ServerChatItem; item: ServerChatItem;

View File

@@ -1,6 +1,6 @@
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
import { IconButton } from '~/components/ui/IconButton'; import { IconButton } from '~/.client/components/ui/IconButton';
import { toggleSidebar } from '~/lib/stores/sidebar'; import { toggleSidebar } from '~/.client/stores/sidebar';
interface HistorySwitchProps { interface HistorySwitchProps {
className?: string; className?: string;

View File

@@ -3,15 +3,15 @@ import classNames from 'classnames';
import { motion, type Variants } from 'framer-motion'; import { motion, type Variants } from 'framer-motion';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { ControlPanel } from '~/components/@settings/core/ControlPanel'; import { ControlPanel } from '~/.client/components/@settings/core/ControlPanel';
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/.client/components/ui/Dialog';
import { SettingsButton } from '~/components/ui/SettingsButton'; import { SettingsButton } from '~/.client/components/ui/SettingsButton';
import { useAuth } from '~/lib/hooks'; import { useAuth } from '~/.client/hooks';
import { type ServerChatItem, useChatEntries } from '~/lib/hooks/useChatEntries'; import { type ServerChatItem, useChatEntries } from '~/.client/hooks/useChatEntries';
import { useChatOperate } from '~/lib/hooks/useChatOperate'; import { useChatOperate } from '~/.client/hooks/useChatOperate';
import { aiState } from '~/lib/stores/ai-state'; import { aiState } from '~/.client/stores/ai-state';
import { sidebarStore } from '~/lib/stores/sidebar'; import { sidebarStore } from '~/.client/stores/sidebar';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/.client/utils/easings';
import WithTooltip from '../ui/Tooltip'; import WithTooltip from '../ui/Tooltip';
import { binDates } from './date-binning'; import { binDates } from './date-binning';
import { HistoryItem } from './HistoryItem.client'; import { HistoryItem } from './HistoryItem.client';

View File

@@ -1,11 +1,11 @@
import { format, isAfter, isThisWeek, isThisYear, isToday, isYesterday, subDays } from 'date-fns'; import { format, isAfter, isThisWeek, isThisYear, isToday, isYesterday, subDays } from 'date-fns';
import { zhCN } from 'date-fns/locale/zh-CN'; import { zhCN } from 'date-fns/locale/zh-CN';
import type { ServerChatItem } from '~/lib/hooks/useChatEntries'; import type { ServerChatItem } from '~/.client/hooks/useChatEntries';
type Bin = { category: string; items: ServerChatItem[] }; type Bin = { category: string; items: ServerChatItem[] };
export function binDates(_list: ServerChatItem[]) { export function binDates(_list: ServerChatItem[]) {
const list = _list.toSorted((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)); const list = _list.slice().sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
const binLookup: Record<string, Bin> = {}; const binLookup: Record<string, Bin> = {};
const bins: Array<Bin> = []; const bins: Array<Bin> = [];

View File

@@ -3,7 +3,7 @@ import classNames from 'classnames';
import { motion, type Variants } from 'framer-motion'; import { motion, type Variants } from 'framer-motion';
import React, { memo, type ReactNode, useEffect, useMemo, useState } from 'react'; import React, { memo, type ReactNode, useEffect, useMemo, useState } from 'react';
import { List, type RowComponentProps } from 'react-window'; import { List, type RowComponentProps } from 'react-window';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/.client/utils/easings';
import { Button } from './Button'; import { Button } from './Button';
import { Checkbox } from './Checkbox'; import { Checkbox } from './Checkbox';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';

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