どしたん話聞こうか?
-言わなくてもいいけど、言ってもいいよ。
++ どしたん話聞こうか? +
++ 言わなくてもいいけど、言ってもいいよ。 +
diff --git a/.dockerignore b/.dockerignore index da0b3191cc..a6018b6eaa 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,4 @@ node_modules .git *.log -application/server/seeds application/server/upload diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..914c7b66ef --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +application/server/database.sqlite !text !filter !merge !diff diff --git a/Dockerfile b/Dockerfile index 2c95811428..3fadcf204c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,8 @@ COPY ./application . RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm build +RUN pnpm --filter @web-speed-hackathon-2026/server seed:insert + RUN --mount=type=cache,target=/pnpm/store CI=true pnpm install --frozen-lockfile --prod --filter @web-speed-hackathon-2026/server FROM base diff --git a/README.md b/README.md index 1ddb13e8ba..0448cb6eb9 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,5 @@ https://github.com/CyberAgentHack/web-speed-hackathon-2026-scoring/issues/new?te - (Original Font) Source Han Serif JP: OFT 1.1 by Adobe http://www.adobe.com/ - Text - 太宰治『走れメロス』(1940年) + +deploy diff --git a/application/.gitignore b/application/.gitignore index 00b846e86b..c30953a31c 100644 --- a/application/.gitignore +++ b/application/.gitignore @@ -216,3 +216,6 @@ $RECYCLE.BIN/ *.lnk # End of https://www.toptal.com/developers/gitignore/api/node,macos,linux,windows + +.tanstack +.output diff --git a/application/client/index.html b/application/client/index.html new file mode 100644 index 0000000000..249815948b --- /dev/null +++ b/application/client/index.html @@ -0,0 +1,16 @@ + + + +
+ + +どしたん話聞こうか?
-言わなくてもいいけど、言ってもいいよ。
++ どしたん話聞こうか? +
++ 言わなくてもいいけど、言ってもいいよ。 +
-
+
利用規約
に同意して
diff --git a/application/client/src/components/crok/ChatInput.tsx b/application/client/src/components/crok/ChatInput.tsx
index 6f8c17796b..a629cc7fed 100644
--- a/application/client/src/components/crok/ChatInput.tsx
+++ b/application/client/src/components/crok/ChatInput.tsx
@@ -1,7 +1,4 @@
-import Bluebird from "bluebird";
-import kuromoji, { type Tokenizer, type IpadicFeatures } from "kuromoji";
import {
- useEffect,
useLayoutEffect,
useRef,
useState,
@@ -9,13 +6,8 @@ import {
type FormEvent,
type KeyboardEvent,
} from "react";
-
-import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon";
-import {
- extractTokens,
- filterSuggestionsBM25,
-} from "@web-speed-hackathon-2026/client/src/utils/bm25_search";
-import { fetchJSON } from "@web-speed-hackathon-2026/client/src/utils/fetchers";
+import { useDebounceEffect } from "../../hooks/use_debounce_effect";
+import { fetchSuggestions } from "../../utils/fetch_with_cache";
interface Props {
isStreaming: boolean;
@@ -23,7 +15,10 @@ interface Props {
}
// トークン単位でハイライト
-function highlightMatchByTokens(text: string, queryTokens: string[]): React.ReactNode {
+function highlightMatchByTokens(
+ text: string,
+ queryTokens: string[],
+): React.ReactNode {
if (queryTokens.length === 0) return text;
const lowerText = text.toLowerCase();
@@ -79,7 +74,6 @@ function highlightMatchByTokens(text: string, queryTokens: string[]): React.Reac
export const ChatInput = ({ isStreaming, onSendMessage }: Props) => {
const textareaRef = useRef
- {isStreaming ? "AIが応答を生成中..." : "Crok AIは間違いを起こす可能性があります。"} + {isStreaming + ? "AIが応答を生成中..." + : "Crok AIは間違いを起こす可能性があります。"}
{headline}
- {description !== "" ?{description}
: null} + {description !== "" ? ( +{description}
+ ) : null} )}{headline}
- {description !== "" ?{description}
: null} + {description !== "" ? ( +{description}
+ ) : null} @@ -61,7 +75,9 @@ export const DirectMessageListPage = ({ activeUser, newDmModalId }: Props) => { {error != null ? ( -DMの取得に失敗しました
++ DMの取得に失敗しました +
) : conversations.length === 0 ? (まだDMで会話した相手がいません。 @@ -69,20 +85,21 @@ export const DirectMessageListPage = ({ activeUser, newDmModalId }: Props) => { ) : (
{peer.name}
-@{peer.username}
++ @{peer.username} +
{lastMessage?.body}
++ {lastMessage?.body} +
{hasUnread ? ( 未読 diff --git a/application/client/src/components/direct_message/DirectMessagePage.tsx b/application/client/src/components/direct_message/DirectMessagePage.tsx index 098c7d2894..98cd7ccbf9 100644 --- a/application/client/src/components/direct_message/DirectMessagePage.tsx +++ b/application/client/src/components/direct_message/DirectMessagePage.tsx @@ -1,19 +1,18 @@ import classNames from "classnames"; -import moment from "moment"; import { - ChangeEvent, + type ChangeEvent, useCallback, useId, useRef, useState, - KeyboardEvent, - FormEvent, + type KeyboardEvent, + type FormEvent, useEffect, } from "react"; -import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon"; -import { DirectMessageFormData } from "@web-speed-hackathon-2026/client/src/direct_message/types"; +import type { DirectMessageFormData } from "@web-speed-hackathon-2026/client/src/direct_message/types"; import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path"; +import { formatDate } from "../../utils/format-date"; interface Props { conversationError: Error | null; @@ -38,7 +37,9 @@ export const DirectMessagePage = ({ const textAreaId = useId(); const peer = - conversation.initiator.id !== activeUser.id ? conversation.initiator : conversation.member; + conversation.initiator.id !== activeUser.id + ? conversation.initiator + : conversation.member; const [text, setText] = useState(""); const textAreaRows = Math.min((text || "").split("\n").length, 5); @@ -55,7 +56,11 @@ export const DirectMessagePage = ({ const handleKeyDown = useCallback( (event: KeyboardEventメッセージの取得に失敗しました
++ メッセージの取得に失敗しました +
{post.user.name} @@ -39,7 +46,11 @@ export const PostItem = ({ post }: Props) => {
@{post.user.username} @@ -52,12 +63,12 @@ export const PostItem = ({ post }: Props) => {
- -
diff --git a/application/client/src/components/post/TranslatableText.tsx b/application/client/src/components/post/TranslatableText.tsx index d772529d92..0661b4955f 100644 --- a/application/client/src/components/post/TranslatableText.tsx +++ b/application/client/src/components/post/TranslatableText.tsx @@ -1,6 +1,9 @@ import { useCallback, useState } from "react"; -import { createTranslator } from "@web-speed-hackathon-2026/client/src/utils/create_translator"; +import { + createNewTranslator, + translate, +} from "@web-speed-hackathon-2026/client/src/utils/create_translator"; type State = | { type: "idle"; text: string } @@ -20,18 +23,18 @@ export const TranslatableText = ({ text }: Props) => { (async () => { updateState({ type: "loading" }); try { - using translator = await createTranslator({ + const result = await translate(state.text, { sourceLanguage: "ja", targetLanguage: "en", }); - const result = await translator.translate(state.text); updateState({ type: "translated", text: result, original: state.text, }); - } catch { + } catch (e) { + console.error(e); updateState({ type: "translated", text: "翻訳に失敗しました", @@ -58,7 +61,9 @@ export const TranslatableText = ({ text }: Props) => { {state.type !== "loading" ? ( {state.text} ) : ( - {text} + + {text} + )} diff --git a/application/client/src/components/timeline/Timeline.tsx b/application/client/src/components/timeline/Timeline.tsx index 752a4d973b..e3ad406116 100644 --- a/application/client/src/components/timeline/Timeline.tsx +++ b/application/client/src/components/timeline/Timeline.tsx @@ -5,11 +5,34 @@ interface Props { } export const Timeline = ({ timeline }: Props) => { + const fvIndex = getFvIndex(timeline); + return (
{post.user.name}
@{post.user.username}
-
-
-
{user.description}
-