Skip to content

Commit 0792753

Browse files
committed
チャットにloadingを追加、チャットを閉じた時と初期ロードのリダイレクト追加
1 parent 842caa5 commit 0792753

8 files changed

Lines changed: 147 additions & 63 deletions

File tree

app/(docs)/@chat/chat/[chatId]/chatArea.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
34
import { deleteChatAction } from "@/actions/deleteChat";
45
import { ChatWithMessages } from "@/lib/chatHistory";
56
import { LanguageEntry, MarkdownSection, PageEntry } from "@/lib/docs";
@@ -8,6 +9,48 @@ import { StyledMarkdown } from "@/markdown/markdown";
89
import clsx from "clsx";
910
import Link from "next/link";
1011
import { useRouter } from "next/navigation";
12+
import { ReactNode } from "react";
13+
14+
export function ChatAreaContainer(props: { chatId: string; children: ReactNode }) {
15+
return (
16+
<aside
17+
className={clsx(
18+
// モバイルでは全画面表示する
19+
"fixed inset-0 pt-20 bg-base-100",
20+
// PCではスクロールで動かない右サイドバー
21+
"lg:sticky lg:top-0 lg:pt-4 lg:w-1/3 lg:h-screen lg:shadow-md lg:bg-base-200 ",
22+
"p-4",
23+
"flex flex-col",
24+
"overflow-y-auto"
25+
)}
26+
>
27+
<ChatAreaStateUpdater chatId={props.chatId} />
28+
<div className="flex flex-row items-center">
29+
<span className="flex-1 text-base font-bold opacity-40">
30+
AIへの質問
31+
</span>
32+
<Link className="btn btn-ghost" href="/chat" scroll={false}>
33+
<svg
34+
className="w-8 h-8 -scale-x-100"
35+
viewBox="0 0 24 24"
36+
fill="none"
37+
xmlns="http://www.w3.org/2000/svg"
38+
>
39+
<path
40+
d="M18 17L13 12L18 7M11 17L6 12L11 7"
41+
stroke="currentColor"
42+
strokeWidth="1.5"
43+
strokeLinecap="round"
44+
strokeLinejoin="round"
45+
/>
46+
</svg>
47+
<span className="text-lg">閉じる</span>
48+
</Link>
49+
</div>
50+
{props.children}
51+
</aside>
52+
);
53+
}
1154

1255
interface Props {
1356
chatId: string;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ChatAreaContainer } from "./chatArea";
2+
3+
export default function Loading() {
4+
return (
5+
<ChatAreaContainer chatId={"loading"}>
6+
<div className="skeleton h-7 w-full mt-2 mb-3">{/* heading2 */}</div>
7+
<div className="skeleton h-5 w-2/4 my-2">{/* breadcrumbs */}</div>
8+
<div className="skeleton h-5 w-35 my-1.5">{/* date */}</div>
9+
<div className="divider" />
10+
<div className="skeleton h-15 ml-auto w-2/3 my-1">{/* chat */}</div>
11+
<div className="skeleton h-40 w-full my-2">{/* <p> */}</div>
12+
<div className="skeleton h-15 ml-auto w-2/3 my-1">{/* chat */}</div>
13+
<div className="skeleton h-40 w-full my-2">{/* <p> */}</div>
14+
</ChatAreaContainer>
15+
);
16+
}
Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import { getChatOne, initContext } from "@/lib/chatHistory";
22
import { getMarkdownSections, getPagesList } from "@/lib/docs";
3-
import { ChatAreaContent } from "./chatArea";
4-
import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
5-
import { ReactNode } from "react";
6-
import clsx from "clsx";
7-
import Link from "next/link";
3+
import { ChatAreaContainer, ChatAreaContent } from "./chatArea";
84

95
export default async function ChatPage({
106
params,
@@ -47,44 +43,3 @@ export default async function ChatPage({
4743
</ChatAreaContainer>
4844
);
4945
}
50-
51-
function ChatAreaContainer(props: { chatId: string; children: ReactNode }) {
52-
return (
53-
<aside
54-
className={clsx(
55-
// モバイルでは全画面表示する
56-
"fixed inset-0 pt-20 bg-base-100",
57-
// PCではスクロールで動かない右サイドバー
58-
"lg:sticky lg:top-0 lg:pt-4 lg:w-1/3 lg:h-screen lg:shadow-md lg:bg-base-200 ",
59-
"p-4",
60-
"flex flex-col",
61-
"overflow-y-auto"
62-
)}
63-
>
64-
<ChatAreaStateUpdater chatId={props.chatId} />
65-
<div className="flex flex-row items-center">
66-
<span className="flex-1 text-base font-bold opacity-40">
67-
AIへの質問
68-
</span>
69-
<Link className="btn btn-ghost" href="/chat" scroll={false}>
70-
<svg
71-
className="w-8 h-8 -scale-x-100"
72-
viewBox="0 0 24 24"
73-
fill="none"
74-
xmlns="http://www.w3.org/2000/svg"
75-
>
76-
<path
77-
d="M18 17L13 12L18 7M11 17L6 12L11 7"
78-
stroke="currentColor"
79-
strokeWidth="1.5"
80-
strokeLinecap="round"
81-
strokeLinejoin="round"
82-
/>
83-
</svg>
84-
<span className="text-lg">閉じる</span>
85-
</Link>
86-
</div>
87-
{props.children}
88-
</aside>
89-
);
90-
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use client";
2+
3+
import { PagePath } from "@/lib/docs";
4+
import { usePathname, useRouter } from "next/navigation";
5+
import { useEffect } from "react";
6+
7+
export function DocsAutoRedirect(props: { path: PagePath }) {
8+
const pathname = usePathname();
9+
const router = useRouter();
10+
useEffect(() => {
11+
if (pathname === `/chat`) {
12+
router.replace(`/${props.path.lang}/${props.path.page}`, {
13+
scroll: false,
14+
});
15+
}
16+
}, [pathname, router, props.path.lang, props.path.page]);
17+
18+
return null;
19+
}

app/(docs)/@docs/[lang]/[pageId]/page.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "@/lib/docs";
1717
import { unstable_cacheLife, unstable_cacheTag } from "next/cache";
1818
import { isCloudflare } from "@/lib/detectCloudflare";
19+
import { DocsAutoRedirect } from "./autoRedirect";
1920

2021
export async function generateMetadata({
2122
params,
@@ -61,15 +62,18 @@ export default async function Page({
6162
const chatHistories = await getChatFromCache(path, context.userId);
6263

6364
return (
64-
<PageContent
65-
chatHistories={chatHistories}
66-
splitMdContent={sections}
67-
langEntry={langEntry}
68-
pageEntry={pageEntry}
69-
prevPage={prevPage}
70-
nextPage={nextPage}
71-
path={path}
72-
/>
65+
<>
66+
<PageContent
67+
chatHistories={chatHistories}
68+
splitMdContent={sections}
69+
langEntry={langEntry}
70+
pageEntry={pageEntry}
71+
prevPage={prevPage}
72+
nextPage={nextPage}
73+
path={path}
74+
/>
75+
<DocsAutoRedirect path={path} />
76+
</>
7377
);
7478
}
7579

app/(docs)/@docs/default.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1-
export default function EmptyPage() {
2-
return (
3-
<div className="flex-1">
4-
TODO: /chat/チャットid
5-
に直接アクセスした場合には、チャットからそれに対応するページidを取得し、そのドキュメントに自動でリダイレクトする。
6-
</div>
7-
);
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
import Loading from "./[lang]/[pageId]/loading";
5+
import { getRedirectFromChat } from "@/actions/getRedirectFromChat";
6+
import { usePathname, useRouter } from "next/navigation";
7+
8+
// /chat/チャットid に直接アクセスした場合には、
9+
// チャットからそれに対応するページidを取得し、そのドキュメントに自動でリダイレクトする。
10+
export default function DocsDefaultPage() {
11+
const pathname = usePathname();
12+
const router = useRouter();
13+
useEffect(() => {
14+
if (pathname.startsWith("/chat/")) {
15+
const chatId = pathname.split("/chat/")[1];
16+
getRedirectFromChat(chatId).then((path) => {
17+
router.replace(path);
18+
});
19+
}
20+
}, [pathname, router]);
21+
22+
return <Loading />;
823
}

app/actions/getRedirectFromChat.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use server";
2+
3+
import { initContext } from "@/lib/chatHistory";
4+
import { LangId, PageSlug } from "@/lib/docs";
5+
import { chat, section } from "@/schema/chat";
6+
import { and, eq } from "drizzle-orm";
7+
8+
export async function getRedirectFromChat(chatId: string): Promise<string> {
9+
const { drizzle, userId } = await initContext();
10+
if (!userId) {
11+
throw new Error("Not authenticated");
12+
}
13+
14+
const chatData = (await drizzle.query.chat.findFirst({
15+
where: and(eq(chat.chatId, chatId), eq(chat.userId, userId)),
16+
with: {
17+
section: true,
18+
},
19+
})) as
20+
| (typeof chat.$inferSelect & {
21+
section: typeof section.$inferSelect;
22+
})
23+
| undefined;
24+
if (!chatData?.section) {
25+
throw new Error("Chat or section not found");
26+
}
27+
const [lang, page] = (chatData.section.pagePath.split("/") ?? []) as [
28+
LangId,
29+
PageSlug,
30+
];
31+
return `/${lang}/${page}#${chatData.sectionId}`;
32+
}

app/sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212
useEffect,
1313
useState,
1414
} from "react";
15-
import { DynamicMarkdownSection } from "./[lang]/[pageId]/pageContent";
1615
import clsx from "clsx";
1716
import { LanguageIcon } from "@/terminal/icons";
1817
import { RuntimeLang } from "@my-code/runtime/languages";
18+
import { DynamicMarkdownSection } from "./(docs)/@docs/[lang]/[pageId]/pageContent";
1919

2020
export interface ISidebarMdContext {
2121
loadedPath: PagePath | null;

0 commit comments

Comments
 (0)