27 Commits

Author SHA1 Message Date
LIlGG
d944ee546a release: Release v1.0.1 version 2025-10-11 18:57:17 +08:00
LIlGG
63cc0f878d release: Release v1.0.1 version 2025-10-11 18:33:59 +08:00
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
354 changed files with 1819 additions and 1295 deletions

View File

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

25
CHANGELOG.md Normal file
View File

@@ -0,0 +1,25 @@
## <small>[1.0.1](https://github.com/halo-dev/upage/compare/v1.0.0...v1.0.1) (2025-10-11)</small>
### Features
- allow using chat to modify page titles ([7acc494](https://github.com/halo-dev/upage/commit/7acc4949fb4d76a2c5429769ae3d1289ac07fcc5))
### Performance Improvements
- make the generated page names unique rather than consistent ([a93a679](https://github.com/halo-dev/upage/commit/a93a679c712182c5348593df4f1b6a1c8c83ebdd))
- reduce the frequency of saving empty pages ([3af1c30](https://github.com/halo-dev/upage/commit/3af1c30d49c37a69c442e03f2c0bea30a10716ef))
### Bug Fixes
- switching pages may cause page confusion ([a672fca](https://github.com/halo-dev/upage/commit/a672fcad1c6bda7b5ea927826d0218de5e1bc274))
- resolve logical issues when generating multi-page data ([884f518](https://github.com/halo-dev/upage/commit/884f5186a6ded44832555c03661683d19ad23201))
- resolve the issue of frequent triggering of replaceState ([5b8408d](https://github.com/halo-dev/upage/commit/5b8408d7da272f66ee7bce139d56d450158fc86b))
- allow rate limit trust proxy ([196a0c3](https://github.com/halo-dev/upage/commit/196a0c39e7f970c9dc161d046d2530da053f8004))
- addressing the issues with outdated prompt information in multi-turn dialogues ([5ff32f2](https://github.com/halo-dev/upage/commit/5ff32f2c987cf8b9fa3494a5b72f4b46c48abc90))
- resolve the issue of possible abnormal text generated during page creation. ([c5d47c6](https://github.com/halo-dev/upage/commit/c5d47c680ce736f54aa7b35974e2317be0d73146))
### Miscellaneous Chores
- remove tracking script from layout ([63636fe](https://github.com/halo-dev/upage/commit/63636fef1f32130079f7ce38bb21a25e37b80cb3))
### Code Refactoring
- repartition server-side and client-side code ([e9b573a](https://github.com/halo-dev/upage/commit/e9b573a2762e86c4c4df066baa1c33c93436bdc4))

View File

@@ -1,37 +1,26 @@
<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>
<h3 align="center">基于大模型的可视化网页构建平台</h3>
<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/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>
</p>
------------------------------
UPage 是一款基于大模型的可视化网页构建平台,支持多种 AI 提供商集成基于自然语言快速实现定制化网页。UPage 优势在于:
UPage 是一款基于大语言模型的可视化网页构建平台,支持接入主流大模型,只需通过自然语言描述需求,即可快速生成个性化、高颜值的网页,让创作更高效、更智能。
- **基于 LLM 的页面生成**:通过自然语言描述生成完整的网页
- **多种 LLM 提供商支持**:兼容 OpenAI、Anthropic Claude、Google Gemini 等多种 LLM 模型
- **可视化编辑器**:简洁直观的可视化编辑器界面,实时预览
- **多页面生成**:支持同时生成多个页面
- **代码导出**:生成标准的 HTML/CSS/JS 代码,方便集成到现有项目
- **响应式设计**:自动适应不同屏幕尺寸
- **部署集成**:支持一键部署到常见托管平台
------------------------------
特别感谢 [bolt.diy](https://github.com/stackblitz-labs/bolt.diy) 项目UPage 的实现基于该项目的代码结构。
------------------------------
- **可视化编辑,所见即所得**:简洁直观的可视化编辑器,支持实时预览,轻松调整布局与样式;
- **多页面一键生成**:支持同时生成多个关联页面,快速搭建完整网站结构;
- **标准代码自由导出**:自动生成规范的 HTML/CSS/JS 代码,便于集成至现有项目或二次开发;
- **响应式设计,全端适配**:自动适配桌面、平板、移动端等多种设备,确保跨平台完美呈现。
## 快速开始
UPage 提供基于 Docker 的部署方案,可以使用以下脚本进行快速部署
准备一台 Linux 服务器,安装好 Docker 后,执行以下一键安装脚本
```bash
docker run -d \
@@ -49,9 +38,9 @@ docker run -d \
halohub/upage:latest
```
其中参数说明如下:
参数说明如下:
- `-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 LLM_DEFAULT_MODEL=your-default-model`:设置默认的 LLM 模型,用于构建页面。
- `-e LLM_MINOR_MODEL=your-minor-model`:设置次要的 LLM 模型,用于执行其他任务。
@@ -61,6 +50,27 @@ docker run -d \
访问 `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) - 强大易用的开源建站工具
@@ -71,12 +81,13 @@ docker run -d \
- [Cordys CRM](https://github.com/cordys/cordys-crm) - 新一代的开源 AI CRM 系统
- [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 的 Logo 和版权信息;
二次开发后的衍生作品必须遵守 GPL V3 的开源义务。
如需商业授权,请联系 support@fit2cloud.com 。
- 不能替换和修改 UPage 的 Logo 和版权信息;
- 二次开发后的衍生作品必须遵守 GPL V3 的开源义务。
如需商业授权,请联系:`support@fit2cloud.com`

View File

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

View File

@@ -4,11 +4,11 @@ interface BridgeContext {
loaded: boolean;
}
export const bridgeContext: BridgeContext = import.meta.hot?.data.editorBridgeContext ?? {
export const bridgeContext: BridgeContext = import.meta.hot?.data?.editorBridgeContext ?? {
loaded: false,
};
if (import.meta.hot) {
if (import.meta.hot && import.meta.hot.data) {
import.meta.hot.data.editorBridgeContext = bridgeContext;
}
@@ -34,7 +34,7 @@ type SectionProps = {
};
/**
* node editor bridge
* editor bridge
* bridge 广
* 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) {
this.#pages.delete(pageName);
@@ -171,7 +191,7 @@ export let editorBridge: Promise<EditorBridge> = new Promise(() => {
if (!import.meta.env.SSR) {
editorBridge =
import.meta.hot?.data.editorBridge ??
import.meta.hot?.data?.editorBridge ??
Promise.resolve()
.then(() => {
return new EditorBridge();
@@ -181,7 +201,7 @@ if (!import.meta.env.SSR) {
return editorBridge;
});
if (import.meta.hot) {
if (import.meta.hot && import.meta.hot.data) {
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 { motion } from 'framer-motion';
import { useMemo } from 'react';
import { useAuth } from '~/lib/hooks/useAuth';
import { useAuth } from '~/.client/hooks/useAuth';
import type { TabType } from './types';
interface AvatarDropdownProps {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ import * as React from 'react';
import { useCallback, useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import { toast } from 'sonner';
import { tabConfigurationStore } from '~/lib/stores/settings';
import { tabConfigurationStore } from '~/.client/stores/settings';
// Register ChartJS components
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 type { TabType, TabVisibilityConfig } from '~/components/@settings/core/types';
import { DEFAULT_TAB_CONFIG } from '~/.client/components/@settings/core/constants';
import type { TabType, TabVisibilityConfig } from '~/.client/components/@settings/core/types';
export const getVisibleTabs = (
tabConfiguration: { userTabs: TabVisibilityConfig[]; developerTabs?: TabVisibilityConfig[] },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,12 +25,16 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
div: ({ className, children, node, ...props }) => {
if (className?.includes('__uPageArtifact__')) {
const messageId = node?.properties.dataMessageId as string;
const pageName = node?.properties.dataPageName as string;
if (!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__')) {

View File

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

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames';
import type { KeyboardEvent } 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';
interface ModelSelectorProps {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,8 +14,8 @@ import {
import classNames from 'classnames';
import { useMemo } from 'react';
import { Doughnut, Line, Pie } from 'react-chartjs-2';
import type { ChatUsageStats } from '~/lib/hooks/useChatUsage';
import { themeStore } from '~/lib/stores/theme';
import type { ChatUsageStats } from '~/.client/hooks/useChatUsage';
import { themeStore } from '~/stores/theme';
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 { memo, useCallback, useEffect, useRef, useState } from 'react';
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 { ConfirmationDialog, DialogDescription, DialogTitle } from '../../ui/Dialog';
import { IconButton } from '../../ui/IconButton';

View File

@@ -1,5 +1,5 @@
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 type { EditorProps } from './editors/EditorProps';
import { IconEditor } from './editors/IconEditor';

View File

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

View File

@@ -1,6 +1,6 @@
import type { RefObject } 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 { EditorController } from './EditorController';
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 { executeScript } from '~/utils/execute-scripts';
import { isScriptContent } from '~/utils/html-parse';
export class EditorController implements Editor {
private props: EditorControllerProps;

View File

@@ -1,9 +1,9 @@
import { motion, type Variants } from 'framer-motion';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
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 { executeScripts } from '~/utils/execute-scripts';
import { isMac } from '~/utils/os';
import { EditorOverlay } from './EditorOverlay';
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 { TooltipProvider } from '@radix-ui/react-tooltip';
import { useEffect } from 'react';
import WithTooltip from '~/components/ui/Tooltip';
import { useEditChatDescription } from '~/lib/hooks';
import { useChatHistory } from '~/lib/hooks/useChatHistory';
import { webBuilderStore } from '~/lib/stores/web-builder';
import WithTooltip from '~/.client/components/ui/Tooltip';
import { useEditChatDescription } from '~/.client/hooks';
import { useChatHistory } from '~/.client/hooks/useChatHistory';
import { webBuilderStore } from '~/.client/stores/web-builder';
export function ChatDescription() {
const { getChatLatestDescription } = useChatHistory();

View File

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

View File

@@ -2,11 +2,11 @@ import { useStore } from '@nanostores/react';
import * as Dialog from '@radix-ui/react-dialog';
import { motion } from 'framer-motion';
import React, { Suspense, useEffect, useState } from 'react';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment';
import { netlifyConnection } from '~/lib/stores/netlify';
import { useChatDeployment } from '~/.client/hooks/useChatDeployment';
import { netlifyConnection } from '~/.client/stores/netlify';
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 {
isOpen: boolean;

View File

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

View File

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

View File

@@ -4,11 +4,11 @@ import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { toast } from 'sonner';
import { NetlifyDeploymentLink } from '~/components/chat/NetlifyDeploymentLink.client';
import useViewport from '~/lib/hooks';
import { setLocalStorage } from '~/lib/persistence';
import { aiState, setShowChat } from '~/lib/stores/ai-state';
import { webBuilderStore } from '~/lib/stores/web-builder';
import { NetlifyDeploymentLink } from '~/.client/components/chat/NetlifyDeploymentLink.client';
import useViewport from '~/.client/hooks';
import { setLocalStorage } from '~/.client/persistence';
import { aiState, setShowChat } from '~/.client/stores/ai-state';
import { webBuilderStore } from '~/.client/stores/web-builder';
import type { _1PanelDeployResponse } from '~/types/1panel';
import { DeploymentPlatformEnum } from '~/types/deployment';
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 { motion } from 'framer-motion';
import { useMemo, useState } from 'react';
import { ChatUsageDialog } from '~/components/chat/usage/ChatUsageDialog';
import { DeploymentRecordsDialog } from '~/components/chat/usage/DeploymentRecordsDialog';
import { Button } from '~/components/ui/Button';
import { ConfirmationDialog } from '~/components/ui/Dialog';
import { useAuth } from '~/lib/hooks/useAuth';
import { useChatUsage } from '~/lib/hooks/useChatUsage';
import { ChatUsageDialog } from '~/.client/components/chat/usage/ChatUsageDialog';
import { DeploymentRecordsDialog } from '~/.client/components/chat/usage/DeploymentRecordsDialog';
import { Button } from '~/.client/components/ui/Button';
import { ConfirmationDialog } from '~/.client/components/ui/Dialog';
import { useAuth } from '~/.client/hooks/useAuth';
import { useChatUsage } from '~/.client/hooks/useChatUsage';
interface MinimalAvatarDropdownProps {}

View File

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

View File

@@ -5,10 +5,15 @@ import { formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale/zh-CN';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
import { Badge } from '~/components/ui/Badge';
import { Button } from '~/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible';
import { fetchNetlifyStats, isFetchingStats, netlifyConnection, updateNetlifyConnection } from '~/lib/stores/netlify';
import { Badge } from '~/.client/components/ui/Badge';
import { Button } from '~/.client/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/.client/components/ui/Collapsible';
import {
fetchNetlifyStats,
isFetchingStats,
netlifyConnection,
updateNetlifyConnection,
} from '~/.client/stores/netlify';
import type { ConnectionSettings } from '~/root';
import type { ApiResponse } from '~/types/global';
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 React, { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
import { logStore } from '~/lib/stores/logs';
import { fetchVercelStats, isFetchingStats, updateVercelConnection, vercelConnection } from '~/lib/stores/vercel';
import { fetchVercelStats, isFetchingStats, updateVercelConnection, vercelConnection } from '~/.client/stores/vercel';
import type { ConnectionSettings } from '~/root';
import { logStore } from '~/stores/logs';
import { logger } from '~/utils/logger';
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 React, { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
import { Badge } from '~/components/ui/Badge';
import { Button } from '~/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible';
import { _1PanelConnectionStore, fetch1PanelStats, isFetchingStats, update1PanelConnection } from '~/lib/stores/1panel';
import { getChatId } from '~/lib/stores/ai-state';
import { Badge } from '~/.client/components/ui/Badge';
import { Button } from '~/.client/components/ui/Button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/.client/components/ui/Collapsible';
import {
_1PanelConnectionStore,
fetch1PanelStats,
isFetchingStats,
update1PanelConnection,
} from '~/.client/stores/1panel';
import { getChatId } from '~/.client/stores/ai-state';
import type { ConnectionSettings } from '~/root';
import type { _1PanelWebsite } from '~/types/1panel';
import type { ApiResponse } from '~/types/global';

View File

@@ -4,14 +4,14 @@ import classNames from 'classnames';
import { motion } from 'framer-motion';
import React, { Suspense, useEffect, useState } from 'react';
import { toast } from 'sonner';
import { getLocalStorage } from '~/lib/persistence';
import { logStore } from '~/lib/stores/logs';
import { webBuilderStore } from '~/lib/stores/web-builder';
import { getLocalStorage } from '~/.client/persistence';
import { webBuilderStore } from '~/.client/stores/web-builder';
import { formatSize } from '~/.client/utils/format';
import { logStore } from '~/stores/logs';
import type { GitHubUserResponse } from '~/types/github';
import { formatSize } from '~/utils/format';
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 {
isOpen: boolean;

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import { format, isAfter, isThisWeek, isThisYear, isToday, isYesterday, subDays } from 'date-fns';
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[] };
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 bins: Array<Bin> = [];

View File

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

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