Skip to content
Open
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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
.next
.env
.git
.gitignore
Dockerfile
docker-compose.yml
npm-debug.log
README.md
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:24-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000

COPY --from=build /app/package.json /app/package-lock.json ./
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/public ./public
COPY --from=build /app/.next ./.next

EXPOSE 3000
CMD ["npm", "run", "start"]
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,23 @@ npm run dev
```

ブラウザで [http://localhost:3000](http://localhost:3000) を開いてください。

## Dockerで起動

Dockerで動かすには、最初に `.env` を用意します。中身は通常の起動と同じで、`GOOGLE_API_KEY` を入れてください。次に、Dockerでビルドして起動します。

```bash
# ビルド
docker build -t anamnesis-webui .

# 起動
docker run --env-file .env -p 3000:3000 anamnesis-webui
```

ブラウザで [http://localhost:3000](http://localhost:3000) を開いてください。

docker-compose を使う場合は次の2行で動きます。

```bash
docker compose up --build
```
6 changes: 5 additions & 1 deletion app/personality-profiler/components/InterviewPhase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function InterviewPhase({
const [messages, setMessages] = useState<Message[]>(initialMessages);
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const isComposingRef = useRef(false);

const hasStarted = useRef(false);

Expand Down Expand Up @@ -85,8 +86,9 @@ export default function InterviewPhase({
}
};

const handleKeyDown = (e: React.KeyboardEvent) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
if (isComposingRef.current || e.nativeEvent.isComposing) return;
e.preventDefault();
handleSendMessage(input);
}
Expand Down Expand Up @@ -165,6 +167,8 @@ export default function InterviewPhase({
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
onCompositionStart={() => { isComposingRef.current = true; }}
onCompositionEnd={() => { isComposingRef.current = false; }}
className="w-full bg-transparent border-none py-6 text-white placeholder-gray-800 focus:ring-0 resize-none font-mono text-sm leading-relaxed h-20"
placeholder="ENTER_RESPONSE..."
/>
Expand Down
2 changes: 2 additions & 0 deletions app/personality-profiler/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import ProfilerClient from './ProfilerClient';

export const dynamic = 'force-dynamic';

export default function DeepProfilerPage() {
const envApiKey = process.env.GOOGLE_API_KEY;

Expand Down
12 changes: 11 additions & 1 deletion app/talk/TalkClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function TalkClient({ envApiKey }: TalkClientProps) {
const [characterName, setCharacterName] = useState<string>('');

const scrollRef = useRef<HTMLDivElement>(null);
const isComposingRef = useRef(false);

useEffect(() => {
// Load sessions that have a final profile
Expand Down Expand Up @@ -89,6 +90,13 @@ export default function TalkClient({ envApiKey }: TalkClientProps) {
}
};

const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key !== 'Enter') return;
if (isComposingRef.current || e.nativeEvent.isComposing) return;
e.preventDefault();
handleSend();
};

if (sessions.length === 0) {
return (
<div className="min-h-screen bg-[#050505] text-white flex items-center justify-center font-mono">
Expand Down Expand Up @@ -182,7 +190,9 @@ export default function TalkClient({ envApiKey }: TalkClientProps) {
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
onCompositionStart={() => { isComposingRef.current = true; }}
onCompositionEnd={() => { isComposingRef.current = false; }}
onKeyDown={handleInputKeyDown}
placeholder="Type your message..."
className="flex-1 bg-transparent border-b border-white/20 py-2 focus:border-white outline-none transition-colors"
/>
Expand Down
2 changes: 2 additions & 0 deletions app/talk/multi/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import MultiClient from './MultiClient';

export const dynamic = 'force-dynamic';

export default function MultiTalkPage() {
const envApiKey = process.env.GOOGLE_API_KEY;

Expand Down
2 changes: 2 additions & 0 deletions app/talk/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import TalkClient from './TalkClient';

export const dynamic = 'force-dynamic';

export default function TalkPage() {
const envApiKey = process.env.GOOGLE_API_KEY;

Expand Down
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
web:
build: .
ports:
- "3000:3000"
environment:
GOOGLE_API_KEY: ${GOOGLE_API_KEY}