Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions app/(docs)/@docs/[lang]/[pageId]/chatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import { useState, FormEvent, useEffect } from "react";
// QuestionExampleParams,
// } from "../actions/questionExample";
// import { getLanguageName } from "../pagesList";
import { DynamicMarkdownSection } from "./pageContent";
import { useEmbedContext } from "@/terminal/embedContext";
import { PagePath } from "@/lib/docs";
import { DynamicMarkdownSection, PagePath } from "@/lib/docs";
import { useRouter } from "next/navigation";
import { ChatStreamEvent } from "@/api/chat/route";
import { useStreamingChatContext } from "@/(docs)/streamingChatContext";
Expand Down
17 changes: 1 addition & 16 deletions app/(docs)/@docs/[lang]/[pageId]/pageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,18 @@ import { useSidebarMdContext } from "@/sidebar";
import clsx from "clsx";
import { PageTransition } from "./pageTransition";
import {
DynamicMarkdownSection,
LanguageEntry,
MarkdownSection,
PageEntry,
PagePath,
SectionId,
} from "@/lib/docs";
import { ReplacedRange } from "@/markdown/multiHighlight";
import { Heading } from "@/markdown/heading";
import Link from "next/link";
import { useChatId } from "@/(docs)/chatAreaState";
import { ChatWithMessages } from "@/lib/chatHistory";

/**
* MarkdownSectionに追加で、動的な情報を持たせる
*/
export interface DynamicMarkdownSection extends MarkdownSection {
/**
* ユーザーが今そのセクションを読んでいるかどうか
*/
inView: boolean;
/**
* チャットの会話を元にAIが書き換えた後の内容
*/
replacedContent: string;
replacedRange: ReplacedRange[];
}

interface PageContentProps {
splitMdContent: MarkdownSection[];
langEntry: LanguageEntry;
Expand Down
2 changes: 2 additions & 0 deletions app/actions/deleteChat.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"use server";

import { z } from "zod";
import { deleteChat, initContext } from "@/lib/chatHistory";

export async function deleteChatAction(chatId: string) {
chatId = z.uuid().parse(chatId);
const ctx = await initContext();
await deleteChat(chatId, ctx);
}
3 changes: 3 additions & 0 deletions app/actions/getRedirectFromChat.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"use server";

import { z } from "zod";
import { initContext } from "@/lib/chatHistory";
import { LangId, PageSlug } from "@/lib/docs";
import { chat, section } from "@/schema/chat";
import { and, eq } from "drizzle-orm";

export async function getRedirectFromChat(chatId: string): Promise<string> {
chatId = z.uuid().parse(chatId);

const { drizzle, userId } = await initContext();
if (!userId) {
throw new Error("Not authenticated");
Expand Down
52 changes: 34 additions & 18 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@ import {
CreateChatDiff,
initContext,
} from "@/lib/chatHistory";
import { getPagesList, introSectionId, PagePath, SectionId } from "@/lib/docs";
import { DynamicMarkdownSection } from "@/(docs)/@docs/[lang]/[pageId]/pageContent";
import { ReplCommand, ReplOutput } from "@my-code/runtime/interface";
import {
DynamicMarkdownSectionSchema,
getPagesList,
introSectionId,
PagePathSchema,
SectionId,
} from "@/lib/docs";
import {
ReplCommandSchema,
ReplOutputSchema,
} from "@my-code/runtime/interface";
import { z } from "zod";

type ChatParams = {
path: PagePath;
userQuestion: string;
sectionContent: DynamicMarkdownSection[];
replOutputs: Record<string, ReplCommand[]>;
files: Record<string, string>;
execResults: Record<string, ReplOutput[]>;
};
const ChatParamsSchema = z.object({
path: PagePathSchema,
userQuestion: z.string().min(1),
sectionContent: z.array(DynamicMarkdownSectionSchema),
replOutputs: z.record(z.string(), z.array(ReplCommandSchema)),
files: z.record(z.string(), z.string()),
execResults: z.record(z.string(), z.array(ReplOutputSchema)),
});

export type ChatStreamEvent =
| { type: "chat"; chatId: string; sectionId: string }
Expand All @@ -31,9 +40,18 @@ export async function POST(request: NextRequest) {
return new Response("Unauthorized", { status: 401 });
}

const params = (await request.json()) as ChatParams;
const { path, userQuestion, sectionContent, replOutputs, files, execResults } =
params;
const parseResult = ChatParamsSchema.safeParse(await request.json());
if (!parseResult.success) {
return new Response(JSON.stringify(parseResult.error), { status: 400 });
}
const {
path,
userQuestion,
sectionContent,
replOutputs,
files,
execResults,
} = parseResult.data;

const pagesList = await getPagesList();
const langName = pagesList.find((lang) => lang.id === path.lang)?.name;
Expand Down Expand Up @@ -202,7 +220,7 @@ export async function POST(request: NextRequest) {
prompt.join("\n")
)) {
console.log("Received chunk:", [chunk]);

fullText += chunk;

if (!headerParsed) {
Expand Down Expand Up @@ -294,9 +312,7 @@ export async function POST(request: NextRequest) {
await addMessagesAndDiffs(
chatId,
path,
[
{ role: "ai", content: cleanMessage },
],
[{ role: "ai", content: cleanMessage }],
diffRaw,
context
);
Expand Down
47 changes: 38 additions & 9 deletions app/lib/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import yaml from "js-yaml";
import { isCloudflare } from "./detectCloudflare";
import { notFound } from "next/navigation";
import crypto from "node:crypto";
import { z } from "zod";

/*
Branded Types
Expand All @@ -18,33 +19,61 @@ type Brand<K, T> = K & { readonly __brand: T };
export type LangId = Brand<string, "LangId">;
export type LangName = Brand<string, "LangName">;
export type PageSlug = Brand<string, "PageSlug">;
export type SectionId = Brand<string, "SectionId">;

export const PagePathSchema = z.object({
lang: z.string().transform((s) => s as LangId),
page: z.string().transform((s) => s as PageSlug),
});
export interface PagePath {
lang: LangId;
page: PageSlug;
}
export type SectionId = Brand<string, "SectionId">;

export interface MarkdownSection {
export const MarkdownSectionSchema = z.object({
/**
* セクションのmdファイル名
*/
file: string;
file: z.string(),
/**
* frontmatterに書くセクションid
* (データベース上の sectionId)
*/
id: SectionId;
level: number;
title: string;
id: z.string().transform((s) => s as SectionId),
level: z.number(),
title: z.string(),
/**
* frontmatterを除く、見出しも含めたもとのmarkdownの内容
*/
rawContent: string;
rawContent: z.string(),
/**
* rawContentのmd5ハッシュのbase64エンコード
*/
md5: string;
}
md5: z.string(),
});
export type MarkdownSection = z.output<typeof MarkdownSectionSchema>;

export const ReplacedRangeSchema = z.object({
start: z.number(),
end: z.number(),
id: z.string(),
});
export type ReplacedRange = z.output<typeof ReplacedRangeSchema>;

export const DynamicMarkdownSectionSchema = MarkdownSectionSchema.extend({
/**
* ユーザーが今そのセクションを読んでいるかどうか
*/
inView: z.boolean(),
/**
* チャットの会話を元にAIが書き換えた後の内容
*/
replacedContent: z.string(),
replacedRange: z.array(ReplacedRangeSchema),
});
export type DynamicMarkdownSection = z.output<
typeof DynamicMarkdownSectionSchema
>;

/**
* 各言語のindex.ymlから読み込んだデータにid,index等を追加したデータ型
Expand Down
2 changes: 1 addition & 1 deletion app/markdown/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import remarkCjkFriendly from "remark-cjk-friendly";
import {
MultiHighlightTag,
remarkMultiHighlight,
ReplacedRange,
} from "./multiHighlight";
import { Heading } from "./heading";
import { AutoCodeBlock } from "./codeBlock";
import { ReplacedRange } from "@/lib/docs";

export function StyledMarkdown(props: {
content: string;
Expand Down
6 changes: 1 addition & 5 deletions app/markdown/multiHighlight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@ import { ExtraProps } from "react-markdown";
import clsx from "clsx";
import { useChatId } from "@/(docs)/chatAreaState";
import Link from "next/link";
import type { ReplacedRange } from "@/lib/docs";

export interface ReplacedRange {
start: number;
end: number;
id: string;
}
export const remarkMultiHighlight: Plugin<[ReplacedRange[]], Root> = (
replacedRange?: ReplacedRange[]
) => {
Expand Down
3 changes: 1 addition & 2 deletions app/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { LangId, LanguageEntry, PagePath, PageSlug } from "@/lib/docs";
import { DynamicMarkdownSection, LangId, LanguageEntry, PagePath, PageSlug } from "@/lib/docs";
import { AccountMenu } from "./accountMenu";
import { ThemeToggle } from "./themeToggle";
import {
Expand All @@ -15,7 +15,6 @@ import {
import clsx from "clsx";
import { LanguageIcon } from "@/terminal/icons";
import { RuntimeLang } from "@my-code/runtime/languages";
import { DynamicMarkdownSection } from "./(docs)/@docs/[lang]/[pageId]/pageContent";

export interface ISidebarMdContext {
loadedPath: PagePath | null;
Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"react": "^19",
"react-dom": "^19",
"swr": "^2",
"typescript": "^5"
"typescript": "^5",
"zod": "^4.0.17"
},
"devDependencies": {
"@testing-library/react": "^16.3.2",
Expand Down
48 changes: 27 additions & 21 deletions packages/runtime/src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MutexInterface } from "async-mutex";
import { z } from "zod";

/**
* 各言語の実行環境のインタフェース
Expand Down Expand Up @@ -148,28 +149,33 @@ export interface RuntimeInfo {
version?: string;
}

export type ReplOutputType =
| "stdout"
| "stderr"
| "error"
| "return"
| "trace"
| "system";
export interface ReplOutput {
type: ReplOutputType; // 出力の種類
message: string; // 出力メッセージ
}
export interface UpdatedFile {
type: "file";
filename: string;
content: string;
}
export const ReplOutputTypeSchema = z.enum([
"stdout",
"stderr",
"error",
"return",
"trace",
"system",
]);
export type ReplOutputType = z.output<typeof ReplOutputTypeSchema>;
export const ReplOutputSchema = z.object({
type: ReplOutputTypeSchema, // 出力の種類
message: z.string(), // 出力メッセージ
});
export type ReplOutput = z.output<typeof ReplOutputSchema>;
export const UpdatedFileSchema = z.object({
type: z.literal("file"),
filename: z.string(),
content: z.string(),
});
export type UpdatedFile = z.output<typeof UpdatedFileSchema>;

export interface ReplCommand {
command: string;
output: ReplOutput[];
commandId?: string; // Optional for backward compatibility
}
export const ReplCommandSchema = z.object({
command: z.string(),
output: z.array(ReplOutputSchema),
commandId: z.string().optional(), // Optional for backward compatibility
});
export type ReplCommand = z.output<typeof ReplCommandSchema>;
export type SyntaxStatus = "complete" | "incomplete" | "invalid"; // 構文チェックの結果

export const emptyMutex: MutexInterface = {
Expand Down
Loading