-
+ {messagesAndDiffs.map((msg, index) =>
+ msg.type === "message" ? (
+ msg.role === "user" ? (
+
+ ) : msg.role === "ai" ? (
+
-
- ) : msg.role === "ai" ? (
-
-
-
+ ) : (
+
+ {msg.content}
+
+ )
) : (
-
- {msg.content}
+
+ {/* pb-0だとmargin collapsingが起きて変な隙間が空く */}
+
+
+
+
+
+
)
)}
From 84e4980dbc77a1dc73ce8cccb8835ed5902c08a5 Mon Sep 17 00:00:00 2001
From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
Date: Tue, 17 Mar 2026 16:42:18 +0900
Subject: [PATCH 05/20] =?UTF-8?q?=E3=83=81=E3=83=A3=E3=83=83=E3=83=88?=
=?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB=E3=81=AE=E5=AE=9F=E8=A3=85?=
=?UTF-8?q?=E3=80=81=E5=89=8A=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3=E8=BF=BD?=
=?UTF-8?q?=E5=8A=A0=E3=81=AA=E3=81=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/(docs)/@chat/chat/[chatId]/chatArea.tsx | 185 ++++++
app/(docs)/@chat/chat/[chatId]/page.tsx | 97 +--
.../@docs/[lang]/[pageId]/pageContent.tsx | 63 +-
app/actions/chatActions.ts | 23 +-
app/lib/chatHistory.ts | 25 +-
app/markdown/markdown.tsx | 2 +-
app/schema/chat.ts | 1 +
app/sidebar.tsx | 2 +-
drizzle/0005_giant_kylun.sql | 18 +
drizzle/meta/0005_snapshot.json | 554 ++++++++++++++++++
drizzle/meta/_journal.json | 7 +
11 files changed, 847 insertions(+), 130 deletions(-)
create mode 100644 app/(docs)/@chat/chat/[chatId]/chatArea.tsx
create mode 100644 drizzle/0005_giant_kylun.sql
create mode 100644 drizzle/meta/0005_snapshot.json
diff --git a/app/(docs)/@chat/chat/[chatId]/chatArea.tsx b/app/(docs)/@chat/chat/[chatId]/chatArea.tsx
new file mode 100644
index 00000000..ffb2fc3d
--- /dev/null
+++ b/app/(docs)/@chat/chat/[chatId]/chatArea.tsx
@@ -0,0 +1,185 @@
+"use client";
+
+import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
+import { getChatOne } from "@/lib/chatHistory";
+import { LanguageEntry, MarkdownSection, PageEntry } from "@/lib/docs";
+import { Heading } from "@/markdown/heading";
+import { StyledMarkdown } from "@/markdown/markdown";
+import clsx from "clsx";
+import Link from "next/link";
+
+interface Props {
+ chatId: string;
+ chatData: Awaited
>;
+ targetLang: LanguageEntry | undefined;
+ targetPage: PageEntry | undefined;
+ targetSection: MarkdownSection | undefined;
+}
+export function ChatAreaContent(props: Props) {
+ const { chatId, chatData, targetLang, targetPage, targetSection } = props;
+
+ const messagesAndDiffs = [
+ ...chatData.messages.map((msg) => ({ type: "message" as const, ...msg })),
+ ...chatData.diff.map((diff) => ({ type: "diff" as const, ...diff })),
+ ];
+ messagesAndDiffs.sort(
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
+ );
+
+ return (
+
+ );
+}
diff --git a/app/(docs)/@chat/chat/[chatId]/page.tsx b/app/(docs)/@chat/chat/[chatId]/page.tsx
index bd25c026..33a58a04 100644
--- a/app/(docs)/@chat/chat/[chatId]/page.tsx
+++ b/app/(docs)/@chat/chat/[chatId]/page.tsx
@@ -1,9 +1,6 @@
-import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
import { getChatOne, initContext } from "@/lib/chatHistory";
import { getMarkdownSections, getPagesList } from "@/lib/docs";
-import { StyledMarkdown } from "@/markdown/markdown";
-import clsx from "clsx";
-import Link from "next/link";
+import { ChatAreaContent } from "./chatArea";
export default async function ChatPage({
params,
@@ -25,91 +22,13 @@ export default async function ChatPage({
const sections = await getMarkdownSections(targetLang!.id, targetPage!.slug);
const targetSection = sections.find((sec) => sec.id === chatData.sectionId);
- const messagesAndDiffs = [
- ...chatData.messages.map((msg) => ({ type: "message" as const, ...msg })),
- ...chatData.diff.map((diff) => ({ type: "diff" as const, ...diff })),
- ];
- messagesAndDiffs.sort(
- (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
- );
return (
-
+
);
}
diff --git a/app/(docs)/@docs/[lang]/[pageId]/pageContent.tsx b/app/(docs)/@docs/[lang]/[pageId]/pageContent.tsx
index 521742f5..7aba0c8a 100644
--- a/app/(docs)/@docs/[lang]/[pageId]/pageContent.tsx
+++ b/app/(docs)/@docs/[lang]/[pageId]/pageContent.tsx
@@ -256,15 +256,18 @@ function ChatListForSection(props: {
"border border-base-content/10 rounded-sm shadow-sm bg-base-200"
)}
>
- チャット
- {filteredChatHistories.map(({ chatId }) => (
+
+
+ AIへの質問
+
+ {filteredChatHistories.map(({ title, chatId }) => (
- {chatId}
+ {title}
))}
@@ -278,27 +281,7 @@ function ChatListForSection(props: {
)}
>
- {/**/}
-
+
{filteredChatHistories.length}
- {filteredChatHistories.map(({ chatId }) => (
+ {filteredChatHistories.map(({ title, chatId }) => (
-
- {chatId}
+ {title}
))}
@@ -324,3 +307,31 @@ function ChatListForSection(props: {
>
);
}
+
+function ChatIcon() {
+ return (
+ <>
+ {/**/}
+
+ >
+ );
+}
diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts
index 72603302..608accf8 100644
--- a/app/actions/chatActions.ts
+++ b/app/actions/chatActions.ts
@@ -155,6 +155,13 @@ export async function askAI(params: ChatParams): Promise {
" - ユーザーの質問がドキュメントのどのセクションとも直接的に関連しない場合は空白でも良いです。"
);
prompt.push("- 2行目は水平線 --- を出力してください。");
+ prompt.push(
+ "- 次の行に、この質問と回答を後から参照するためのわかりやすいタイトルをつけて記述してください。"
+ );
+ prompt.push(
+ "- 太字やコードブロックなどのMarkdownの記法は使わずテキストのみで出力してください。"
+ );
+ prompt.push("- その次の行は水平線 --- を出力してください。");
prompt.push(
"- それ以降の行に、ドキュメントの内容に基づいて、ユーザーに伝える回答をMarkdown形式で記述してください。"
);
@@ -202,20 +209,27 @@ export async function askAI(params: ChatParams): Promise {
.split(/\n-{3,}\n/)
.at(0)
?.trim() as SectionId | undefined;
- if (!targetSectionId) {
+ if (
+ !targetSectionId ||
+ !sectionContent.some((s) => s.id === targetSectionId)
+ ) {
targetSectionId = introSectionId(path);
}
+ const title = text.split(/\n-{3,}\n/).at(1);
+ if (!title) {
+ throw new Error("AIからの応答にタイトルが含まれていませんでした");
+ }
const responseMessage = text
.split(/\n-{3,}\n/)
- .at(1)
+ .at(2)
?.trim();
if (!responseMessage) {
- throw new Error("AIからの応答が空でした");
+ throw new Error("AIからの応答に本文が含まれていませんでした");
}
const diffRaw: CreateChatDiff[] = [];
for (const m of text
.split(/\n-{3,}\n/)
- .at(2)
+ .at(3)
?.matchAll(
/<{3,}\s*SEARCH\n([\s\S]*?)\n={3,}\n([\s\S]*?)\n>{3,}\s*REPLACE/g
) ?? []) {
@@ -236,6 +250,7 @@ export async function askAI(params: ChatParams): Promise {
const newChat = await addChat(
path,
targetSectionId,
+ title,
[
{ role: "user", content: userQuestion },
{ role: "ai", content: responseMessage },
diff --git a/app/lib/chatHistory.ts b/app/lib/chatHistory.ts
index 943342dc..4faff7fe 100644
--- a/app/lib/chatHistory.ts
+++ b/app/lib/chatHistory.ts
@@ -61,6 +61,7 @@ export async function initContext(ctx?: Partial): Promise {
export async function addChat(
path: PagePath,
sectionId: SectionId,
+ title: string,
messages: CreateChatMessage[],
diffRaw: CreateChatDiff[],
context: Context
@@ -74,6 +75,7 @@ export async function addChat(
.values({
userId,
sectionId,
+ title,
})
.returning();
@@ -88,15 +90,20 @@ export async function addChat(
)
.returning();
- const chatDiffs = await drizzle
- .insert(diff)
- .values(
- diffRaw.map((d) => ({
- chatId: newChat.chatId,
- ...d,
- }))
- )
- .returning();
+ let chatDiffs;
+ if (diffRaw.length > 0) {
+ chatDiffs = await drizzle
+ .insert(diff)
+ .values(
+ diffRaw.map((d) => ({
+ chatId: newChat.chatId,
+ ...d,
+ }))
+ )
+ .returning();
+ } else {
+ chatDiffs = [] as never[];
+ }
revalidateTag(cacheKeyForPage(path, userId));
if (isCloudflare()) {
diff --git a/app/markdown/markdown.tsx b/app/markdown/markdown.tsx
index 2f77ddf2..b41fe59e 100644
--- a/app/markdown/markdown.tsx
+++ b/app/markdown/markdown.tsx
@@ -54,7 +54,7 @@ const components: Components = {
),
- hr: ({ node, ...props }) =>
,
+ hr: ({ node, ...props }) => null,
pre: ({ node, ...props }) => props.children,
code: AutoCodeBlock,
ins: MultiHighlightTag,
diff --git a/app/schema/chat.ts b/app/schema/chat.ts
index cf264269..798b4479 100644
--- a/app/schema/chat.ts
+++ b/app/schema/chat.ts
@@ -6,6 +6,7 @@ export const chat = pgTable("chat", {
userId: text("userId").notNull(),
sectionId: text("sectionId").notNull(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
+ title: text("title").notNull().default("new chat"),
});
export const section = pgTable("section", {
diff --git a/app/sidebar.tsx b/app/sidebar.tsx
index c75c5bd3..e24121f0 100644
--- a/app/sidebar.tsx
+++ b/app/sidebar.tsx
@@ -147,7 +147,7 @@ export function Sidebar({ pagesList }: { pagesList: LanguageEntry[] }) {
strokeLinejoin="round"
/>
-
Close
+
閉じる
diff --git a/drizzle/0005_giant_kylun.sql b/drizzle/0005_giant_kylun.sql
new file mode 100644
index 00000000..05ae18a2
--- /dev/null
+++ b/drizzle/0005_giant_kylun.sql
@@ -0,0 +1,18 @@
+ALTER TABLE "chat" ADD COLUMN "title" text DEFAULT 'new chat' NOT NULL;
+
+-- ↓↓↓ ここからカスタム移行スクリプトを追記 ↓↓↓
+
+-- createdAtが最も古いメッセージを取得し、そのcontentでchatのtitleを更新する
+UPDATE "chat"
+SET "title" = sub."content"
+FROM (
+ SELECT
+ "chatId",
+ "content",
+ "role",
+ ROW_NUMBER() OVER (PARTITION BY "chatId", "role" ORDER BY "createdAt" ASC) as rn
+ FROM "message"
+) sub
+WHERE "chat"."chatId" = sub."chatId" AND sub."role" = 'user' AND sub."rn" = 1;
+
+-- ↑↑↑ ここまで ↑↑↑
diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json
new file mode 100644
index 00000000..343c192b
--- /dev/null
+++ b/drizzle/meta/0005_snapshot.json
@@ -0,0 +1,554 @@
+{
+ "id": "015d7396-ad26-488e-9b31-98399a89b690",
+ "prevId": "b738a961-0a1a-4898-8323-95b072abfca7",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_anonymous": {
+ "name": "is_anonymous",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat": {
+ "name": "chat",
+ "schema": "",
+ "columns": {
+ "chatId": {
+ "name": "chatId",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sectionId": {
+ "name": "sectionId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'new chat'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.diff": {
+ "name": "diff",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "search": {
+ "name": "search",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replace": {
+ "name": "replace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sectionId": {
+ "name": "sectionId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "targetMD5": {
+ "name": "targetMD5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.message": {
+ "name": "message",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.section": {
+ "name": "section",
+ "schema": "",
+ "columns": {
+ "sectionId": {
+ "name": "sectionId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "pagePath": {
+ "name": "pagePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 2e4b098c..bff66162 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -36,6 +36,13 @@
"when": 1773327252896,
"tag": "0004_busy_orphan",
"breakpoints": true
+ },
+ {
+ "idx": 5,
+ "version": "7",
+ "when": 1773727108826,
+ "tag": "0005_giant_kylun",
+ "breakpoints": true
}
]
}
\ No newline at end of file
From b4f8a521755822b034009198a1318bbd07397b1a Mon Sep 17 00:00:00 2001
From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
Date: Tue, 17 Mar 2026 16:58:35 +0900
Subject: [PATCH 06/20] =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=B3=E3=83=97?=
=?UTF-8?q?=E3=83=88=E8=AA=BF=E6=95=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/actions/chatActions.ts | 66 +++++++++++++++++---------------------
1 file changed, 29 insertions(+), 37 deletions(-)
diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts
index 608accf8..57fe9bf0 100644
--- a/app/actions/chatActions.ts
+++ b/app/actions/chatActions.ts
@@ -2,7 +2,6 @@
// import { z } from "zod";
import { generateContent } from "./gemini";
-import { DynamicMarkdownSection } from "../[lang]/[pageId]/pageContent";
import { ReplCommand, ReplOutput } from "@my-code/runtime/interface";
import {
addChat,
@@ -11,6 +10,7 @@ import {
initContext,
} from "@/lib/chatHistory";
import { getPagesList, introSectionId, PagePath, SectionId } from "@/lib/docs";
+import { DynamicMarkdownSection } from "@/(docs)/@docs/[lang]/[pageId]/pageContent";
type ChatResult =
| {
@@ -149,21 +149,22 @@ export async function askAI(params: ChatParams): Promise
{
prompt.push("# 指示");
prompt.push("");
prompt.push(
- `- 1行目に、ユーザーの質問ともっとも関連性の高いドキュメント内のセクションのidを回答してください。idのみを出力してください。`
+ `- 1行目に、ユーザーの質問ともっとも関連性の高いドキュメント内のセクションのidを回答してください。`
+ );
+ prompt.push(
+ " - idのみを出力してください。 セクションid: や括弧や引用符などは不要です。"
);
prompt.push(
" - ユーザーの質問がドキュメントのどのセクションとも直接的に関連しない場合は空白でも良いです。"
);
- prompt.push("- 2行目は水平線 --- を出力してください。");
prompt.push(
- "- 次の行に、この質問と回答を後から参照するためのわかりやすいタイトルをつけて記述してください。"
+ "- 2行目に、この質問と回答を後から参照するためのわかりやすいタイトルをつけて記述してください。"
);
prompt.push(
- "- 太字やコードブロックなどのMarkdownの記法は使わずテキストのみで出力してください。"
+ " - 太字やコードブロックなどのMarkdownの記法は使わずテキストのみで出力してください。"
);
- prompt.push("- その次の行は水平線 --- を出力してください。");
prompt.push(
- "- それ以降の行に、ドキュメントの内容に基づいて、ユーザーに伝える回答をMarkdown形式で記述してください。"
+ "- 3行目以降に、ドキュメントの内容に基づいて、ユーザーに伝える回答をMarkdown形式で記述してください。"
);
prompt.push(
" - ユーザーが入力したターミナルのコマンドやファイルの内容、実行結果を参考にして回答してください。"
@@ -176,12 +177,7 @@ export async function askAI(params: ChatParams): Promise {
prompt.push(
" - 水平線(---)はシステムが区切りとして認識するので、ユーザーへの回答中に水平線を使用することはできません。"
);
- prompt.push(
- "- ユーザーへのメッセージの最後の行の次には水平線 --- を出力してください。"
- );
- prompt.push(
- "- それ以降の行に、ドキュメントの一部を改訂したい場合はその差分を"
- );
+ prompt.push("- ドキュメントの一部を改訂したい場合はその差分を");
prompt.push("<<<<<<< SEARCH");
prompt.push("修正したい元の文章の塊(一字一句違わずに)");
prompt.push("=======");
@@ -197,6 +193,10 @@ export async function askAI(params: ChatParams): Promise {
prompt.push(
" - セクションid、セクション見出し、およびコードブロックの内側を編集することはできません。それ以外の文章のみを編集してください。"
);
+ prompt.push(
+ " - 改訂後のドキュメントと同じ内容はユーザーに伝える回答としては省略できます。(修正後のドキュメントを参照してください、など)"
+ );
+
console.log(prompt);
try {
@@ -205,48 +205,40 @@ export async function askAI(params: ChatParams): Promise {
if (!text) {
throw new Error("AIからの応答が空でした");
}
- let targetSectionId = text
- .split(/\n-{3,}\n/)
- .at(0)
- ?.trim() as SectionId | undefined;
+ console.log(JSON.stringify(text));
+ const textMatch = text.match(/^([^\n]*?)\n+([^\n]*?)\n+([\s\S]*)$/);
+ let targetSectionId = textMatch?.at(1)?.trim() as SectionId | undefined;
if (
!targetSectionId ||
!sectionContent.some((s) => s.id === targetSectionId)
) {
targetSectionId = introSectionId(path);
}
- const title = text.split(/\n-{3,}\n/).at(1);
+ const title = textMatch?.at(2)?.trim();
if (!title) {
throw new Error("AIからの応答にタイトルが含まれていませんでした");
}
- const responseMessage = text
- .split(/\n-{3,}\n/)
- .at(2)
- ?.trim();
+ let responseMessage = textMatch?.at(3)?.trim();
if (!responseMessage) {
throw new Error("AIからの応答に本文が含まれていませんでした");
}
+ const diffRegex =
+ /<{3,}\s*SEARCH\n([\s\S]*?)\n={3,}\n([\s\S]*?)\n>{3,}\s*REPLACE/g;
const diffRaw: CreateChatDiff[] = [];
- for (const m of text
- .split(/\n-{3,}\n/)
- .at(3)
- ?.matchAll(
- /<{3,}\s*SEARCH\n([\s\S]*?)\n={3,}\n([\s\S]*?)\n>{3,}\s*REPLACE/g
- ) ?? []) {
+ for (const m of responseMessage.matchAll(diffRegex) ?? []) {
const search = m[1];
const replace = m[2];
const targetSection = sectionContent.find((s) =>
- s.rawContent.includes(search)
+ s.replacedContent.includes(search)
);
- if (targetSection) {
- diffRaw.push({
- search,
- replace,
- sectionId: targetSection.id,
- targetMD5: targetSection.md5,
- });
- }
+ diffRaw.push({
+ search,
+ replace,
+ sectionId: targetSection?.id ?? ("" as SectionId),
+ targetMD5: targetSection?.md5 ?? "",
+ });
}
+ responseMessage = responseMessage.replace(diffRegex, "").trim();
const newChat = await addChat(
path,
targetSectionId,
From 49078970a9cd7bdd5819f46aaccecd6b85696b0d Mon Sep 17 00:00:00 2001
From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
Date: Tue, 17 Mar 2026 16:59:09 +0900
Subject: [PATCH 07/20] =?UTF-8?q?AI=E3=81=AE=E5=9B=9E=E7=AD=94=E6=99=82?=
=?UTF-8?q?=E3=81=AB=E3=81=9D=E3=81=AE=E3=83=81=E3=83=A3=E3=83=83=E3=83=88?=
=?UTF-8?q?=E3=81=AE=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=92=E9=96=8B=E3=81=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/(docs)/@docs/[lang]/[pageId]/chatForm.tsx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/(docs)/@docs/[lang]/[pageId]/chatForm.tsx b/app/(docs)/@docs/[lang]/[pageId]/chatForm.tsx
index 0231dc86..fca80300 100644
--- a/app/(docs)/@docs/[lang]/[pageId]/chatForm.tsx
+++ b/app/(docs)/@docs/[lang]/[pageId]/chatForm.tsx
@@ -12,6 +12,7 @@ import { useEmbedContext } from "@/terminal/embedContext";
import { useChatHistoryContext } from "./chatHistory";
import { askAI } from "@/actions/chatActions";
import { PagePath } from "@/lib/docs";
+import { useRouter } from "next/navigation";
interface ChatFormProps {
path: PagePath;
@@ -31,6 +32,8 @@ export function ChatForm({ path, sectionContent, close }: ChatFormProps) {
const { files, replOutputs, execResults } = useEmbedContext();
+ const router = useRouter();
+
// const documentContentInView = sectionContent
// .filter((s) => s.inView)
// .map((s) => s.rawContent)
@@ -91,6 +94,7 @@ export function ChatForm({ path, sectionContent, close }: ChatFormProps) {
document.getElementById(result.chat.sectionId)?.scrollIntoView({
behavior: "smooth",
});
+ router.push(`/chat/${result.chat.chatId}`, { scroll: false });
setInputValue("");
close();
}
From 398bb077c5d21d93f22db89e9f5326c725f4dfb1 Mon Sep 17 00:00:00 2001
From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
Date: Tue, 17 Mar 2026 17:12:28 +0900
Subject: [PATCH 08/20] =?UTF-8?q?=E3=83=81=E3=83=A3=E3=83=83=E3=83=88?=
=?UTF-8?q?=E5=89=8A=E9=99=A4=E3=82=92=E5=AE=9F=E8=A3=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/(docs)/@chat/chat/[chatId]/chatArea.tsx | 17 ++++++++++++---
app/(docs)/@chat/chat/[chatId]/page.tsx | 5 +++++
.../@docs/[lang]/[pageId]/chatHistory.tsx | 11 +++++++---
app/actions/deleteChat.ts | 21 +++++++++++++++++++
app/lib/chatHistory.ts | 6 +-----
5 files changed, 49 insertions(+), 11 deletions(-)
create mode 100644 app/actions/deleteChat.ts
diff --git a/app/(docs)/@chat/chat/[chatId]/chatArea.tsx b/app/(docs)/@chat/chat/[chatId]/chatArea.tsx
index ffb2fc3d..aaad898a 100644
--- a/app/(docs)/@chat/chat/[chatId]/chatArea.tsx
+++ b/app/(docs)/@chat/chat/[chatId]/chatArea.tsx
@@ -1,7 +1,9 @@
"use client";
+import { useChatHistoryContext } from "@/(docs)/@docs/[lang]/[pageId]/chatHistory";
import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
-import { getChatOne } from "@/lib/chatHistory";
+import { deleteChatAction } from "@/actions/deleteChat";
+import { ChatWithMessages } from "@/lib/chatHistory";
import { LanguageEntry, MarkdownSection, PageEntry } from "@/lib/docs";
import { Heading } from "@/markdown/heading";
import { StyledMarkdown } from "@/markdown/markdown";
@@ -10,7 +12,7 @@ import Link from "next/link";
interface Props {
chatId: string;
- chatData: Awaited>;
+ chatData: ChatWithMessages;
targetLang: LanguageEntry | undefined;
targetPage: PageEntry | undefined;
targetSection: MarkdownSection | undefined;
@@ -26,6 +28,10 @@ export function ChatAreaContent(props: Props) {
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
);
+ // useChatHistoryContext must be used within a ChatHistoryProvider
+ // const { deleteChat } = useChatHistoryContext();
+
+
return (
)
)}
-