Skip to content

Develop #97

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2e819f8
いらない部分削除
yone-al Feb 18, 2025
f1cf071
Merge branch 'develop' into yone/develop
yone-al Feb 18, 2025
7aea367
appのpage.tsxを削除
RAIT-09 Feb 19, 2025
ecb6056
タイムラインをapp直下に移行,Yomuのリダイレクト先変更
RAIT-09 Feb 19, 2025
9bb4f65
初回取得10件の雅押下状況反映
RAIT-09 Feb 19, 2025
8ca7305
Merge pull request #87 from kc3hack/RAIT-09/develop
RAIT-09 Feb 19, 2025
1f0a336
タイムラインのコンポーネント化,特定ユーザの投稿のみ取得可能に
RAIT-09 Feb 19, 2025
d22b8ac
雅ランニングボタン追加
RAIT-09 Feb 19, 2025
b7237f5
JSONスキーマの指定方法を、できるだけドキュメントの書き方に近づけた
yone-al Feb 19, 2025
d5a8426
「ゃ」「ャ」「ゅ」「ュ」「ょ」「ョ」は読みとしてカウントしない
yone-al Feb 19, 2025
5a96cdd
FloatingActionButtonに字を渡せるよう変更
RAIT-09 Feb 19, 2025
11ddb35
日付の下に時間を表示するよう変更
RAIT-09 Feb 19, 2025
db84424
タイムラインの一番上まで戻るボタンを追加
RAIT-09 Feb 19, 2025
0170e3a
Profileの上部分
natanata25 Feb 19, 2025
afec2ab
アニメーション追加
RAIT-09 Feb 19, 2025
85c4c42
Merge pull request #90 from kc3hack/RAIT-09/develop
RAIT-09 Feb 19, 2025
b8d4f00
Merge branch 'develop' of https://github.com/kc3hack/2025_10 into Pro…
natanata25 Feb 19, 2025
3e8163d
infra
ABfry Feb 19, 2025
d0e3478
コメントアウト追加、細かいプロンプト調整
ABfry Feb 19, 2025
1de25ca
Merge pull request #89 from kc3hack/yone/develop
yone-al Feb 19, 2025
332d144
Merge pull request #91 from kc3hack/hayato/feature/infra
ABfry Feb 19, 2025
321807e
printLine()を読みやすく
yone-al Feb 19, 2025
4d36513
Merge branch 'yone/develop' of https://github.com/kc3hack/2025_10 int…
yone-al Feb 19, 2025
2091787
Merge branch 'develop' into yone/develop
yone-al Feb 19, 2025
6eca7e6
Merge branch 'develop' into hayato/develop
ABfry Feb 19, 2025
d6a7d7c
環境変数の設定
yone-al Feb 19, 2025
bcb55de
Merge pull request #92 from kc3hack/yone/develop
yone-al Feb 19, 2025
af1acc4
カスタムログインページ
ABfry Feb 19, 2025
5817b51
fix dynamic api
ABfry Feb 19, 2025
8430b81
Merge pull request #93 from kc3hack/hayato/develop
ABfry Feb 19, 2025
74c7fa6
profil画面の調整
natanata25 Feb 19, 2025
5c8ba0a
自分のタイムラインのみ表示
natanata25 Feb 19, 2025
e6926ee
Merge pull request #96 from kc3hack/Profilr_UI
natanata25 Feb 19, 2025
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
38 changes: 38 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# 開発用
front:
cd frontend && npm run dev

Expand All @@ -19,3 +20,40 @@ down:

sb:
cd frontend && npm run storybook


# ECR


# ECR URLの定義
FRONT_ECR_URL := $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(FRONT_IMAGE_NAME)
BACK_ECR_URL := $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(BACK_IMAGE_NAME)
UUID := $(shell uuidgen)

login-ecr:
aws ecr get-login-password --region $(AWS_REGION) | docker login --username AWS --password-stdin $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com

build-ecr-front:
docker build --platform linux/amd64 -t $(FRONT_IMAGE_NAME) -f ./frontend/Dockerfile ./frontend

build-ecr-back:
docker build --platform linux/amd64 -t $(BACK_IMAGE_NAME) -f ./backend/Dockerfile ./backend

tag-ecr: build-ecr-front build-ecr-back
docker tag $(FRONT_IMAGE_NAME):$(TAG) $(FRONT_ECR_URL):$(TAG)
docker tag $(BACK_IMAGE_NAME):$(TAG) $(BACK_ECR_URL):$(TAG)

push-ecr: tag-ecr
docker push $(FRONT_ECR_URL):$(TAG)
docker push $(BACK_ECR_URL):$(TAG)

tag-ecr-uuid: build-ecr-front build-ecr-back
docker tag $(FRONT_IMAGE_NAME):$(TAG) $(FRONT_ECR_URL):$(UUID)
docker tag $(BACK_IMAGE_NAME):$(TAG) $(BACK_ECR_URL):$(UUID)

push-ecr-uuid: tag-ecr-uuid
docker push $(FRONT_ECR_URL):$(UUID)
docker push $(BACK_ECR_URL):$(UUID)

ecr: login-ecr build-ecr-front build-ecr-back tag-ecr tag-ecr-uuid push-ecr push-ecr-uuid

259 changes: 110 additions & 149 deletions backend/src/lib/gemini.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { Context } from 'hono';
import { env } from '../config/env.js';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai';

const printLine = (): void => {
console.log('--------------------------------');
};

const generateTanka = async (originalText: string): Promise<string[]> => {
// Geminiで短歌生成
Expand All @@ -16,121 +20,152 @@ const generateTanka = async (originalText: string): Promise<string[]> => {
}

const genAI = new GoogleGenerativeAI(apiKey);

const schema = {
description: '生成される短歌のオブジェクト',
type: SchemaType.OBJECT,
properties: {
line0: {
type: SchemaType.STRING,
description: '短歌の1句目, 日本語で5音節程度',
nullable: false,
},
line1: {
type: SchemaType.STRING,
description: '短歌の2句目, 日本語で7音節程度',
nullable: false,
},
line2: {
type: SchemaType.STRING,
description: '短歌の3句目, 日本語で5音節程度',
nullable: false,
},
line3: {
type: SchemaType.STRING,
description: '短歌の4句目, 日本語で7音節程度',
nullable: false,
},
line4: {
type: SchemaType.STRING,
description: '短歌の5句目, 日本語で7音節程度',
nullable: false,
},
yomi0: {
type: SchemaType.STRING,
description: '短歌の1句目のふりがな',
nullable: false,
},
yomi1: {
type: SchemaType.STRING,
description: '短歌の2句目のふりがな',
nullable: false,
},
yomi2: {
type: SchemaType.STRING,
description: '短歌の3句目のふりがな',
nullable: false,
},
yomi3: {
type: SchemaType.STRING,
description: '短歌の4句目のふりがな',
nullable: false,
},
yomi4: {
type: SchemaType.STRING,
description: '短歌の5句目のふりがな',
nullable: false,
},
},
required: [
'line0',
'line1',
'line2',
'line3',
'line4',
'yomi0',
'yomi1',
'yomi2',
'yomi3',
'yomi4',
],
};

const model = genAI.getGenerativeModel({
model: 'gemini-2.0-flash',
systemInstruction: `SNSの投稿を短歌にしてください。原文の特徴的な要素はそのままに、比喩表現を使った趣深い短歌にしてください。出力の形式は以下のJSONスキーマに従ってください。各値は日本語で出力してください。
{
"type": "object",
"description": "変換した短歌",
"properties": {
"response": {
"type": "array",
"description": "ユーザーアカウント作成のレスポンスデータの配列",
"items": {
"type": "object",
"properties": {
"line1": {
"type": "string",
"description": "短歌の1句目, 5文字程度"
},
"line2": {
"type": "string",
"description": "短歌の2句目, 7文字程度"
},
"line3": {
"type": "string",
"description": "短歌の3句目, 5文字程度"
},
"line4": {
"type": "string",
"description": "短歌の4句目, 7文字程度"
},
"line5": {
"type": "string",
"description": "短歌の5句目, 7文字程度"
},
"yomi1": {
"type": "string",
"description": "短歌の1句目のふりがな"
},
"yomi2": {
"type": "string",
"description": "短歌の2句目のふりがな"
},
"yomi3": {
"type": "string",
"description": "短歌の1句目のふりがな"
},
"yomi4": {
"type": "string",
"description": "短歌の2句目のふりがな"
},
"yomi5": {
"type": "string",
"description": "短歌の1句目のふりがな"
},
},
"required": ["line1", "line2", "line3", "line4", "line5", "yomi1", "yomi2", "yomi3", "yomi4", "yomi5"]
systemInstruction:
'SNSの投稿を短歌にしてください。原文の特徴的な要素はそのままに、比喩表現を使った趣深い短歌にしてください。出力の形式は指定したJSONスキーマに従ってください。各値は日本語で出力してください。',
generationConfig: {
responseMimeType: 'application/json',
responseSchema: schema,
},
"required": ["response"]
}
}`,
generationConfig: { responseMimeType: 'application/json' },
});

// 短歌の各句の文字数をチェックする関数
const isValidTanka = (lines: string[]): boolean => {
const expectedCharaCount = [5, 7, 5, 7, 7];
return lines.every((line, index) => {
// everyは配列のすべての要素が条件を満たしていればtrueを返す
//console.log(line);
console.log(line);
// アルファベット(全角、半角)、ひらがな、カタカナ、漢字を1文字としてカウント
const regex = /[A-Za-z\uFF21-\uFF3A\uFF41-\uFF5A\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/g;
// console.log(line.match(regex));
const count = line.match(regex)?.length || 0;
const matchedChars = line.match(regex) || [];
// console.log(matchedChars);
// 「ゃ」「ャ」「ゅ」「ュ」「ょ」「ョ」はカウントしない
const excludeChars = ['ゃ', 'ャ', 'ゅ', 'ュ', 'ょ', 'ョ'];
const validChars = matchedChars.filter((char) => !excludeChars.includes(char));
const count = validChars.length;
// console.log('count: ', count);
// console.log('-----------------------------');

// 文字数をカウント(アルファベット(全角、半角)、ひらがな、カタカナ、漢字を1文字としてカウント)
return Math.abs(count - expectedCharaCount[index]) <= 1; // 1文字分までの誤差は許容
});
};

// 生成後、型のチェック(3回まで)
for (let i = 0; i < 3; i++) {
printLine();
console.log(`短歌生成${i + 1}回目`);

const result = await model.generateContent(originalText);

// console.log(result.response.text());

const jsonResponse = JSON.parse(result.response.text());

console.log(`短歌生成${i + 1}回目`);
console.log(jsonResponse);

const tanka = [
jsonResponse.response[0].line1.replace('ッ', 'ツ'),
jsonResponse.response[0].line2.replace('ッ', 'ツ'),
jsonResponse.response[0].line3.replace('ッ', 'ツ'),
jsonResponse.response[0].line4.replace('ッ', 'ツ'),
jsonResponse.response[0].line5.replace('ッ', 'ツ'),
jsonResponse.line0.replace('ッ', 'ツ'),
jsonResponse.line1.replace('ッ', 'ツ'),
jsonResponse.line2.replace('ッ', 'ツ'),
jsonResponse.line3.replace('ッ', 'ツ'),
jsonResponse.line4.replace('ッ', 'ツ'),
];

const tankaYomi = [
jsonResponse.response[0].yomi1,
jsonResponse.response[0].yomi2,
jsonResponse.response[0].yomi3,
jsonResponse.response[0].yomi4,
jsonResponse.response[0].yomi5,
jsonResponse.yomi0,
jsonResponse.yomi1,
jsonResponse.yomi2,
jsonResponse.yomi3,
jsonResponse.yomi4,
];

// console.log(tanka);

if (isValidTanka(tankaYomi)) {
printLine();
console.log('短歌の形式が正しいので結果を返却');
// ["短歌の1行目", "短歌の2行目", "短歌の3行目", "短歌の4行目", "短歌の5行目"]
return tanka;
} else if (i < 2) {
printLine();
console.log(tanka);
console.log(tankaYomi);
console.log('短歌の形式が不正のため再生成');
}
}

printLine();
console.log('短歌を生成できませんでした');
throw new Error('短歌を生成できませんでした');
} catch (error) {
Expand All @@ -139,90 +174,16 @@ const generateTanka = async (originalText: string): Promise<string[]> => {
}
};

/*
const getGeminiText = async (c: Context) => {
const getNews = async (originalText: string): Promise<JSON> => {
try {
// POSTメソッド以外は拒否
if (c.req.method !== 'POST') {
return c.json(
{
success: 'false',
message: {
status: 405,
message: 'Bad Request',
description: 'POSTメソッドでリクエストしてください。',
},
},
405
);
}

// リクエストボディから prompt を取得
const { prompt } = await c.req.json();
if (!prompt) {
return c.json({ error: 'Prompt is required' }, 400);
}

const apiKey = env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error('APIが設定されていません。');
}

const genAI = new GoogleGenerativeAI(apiKey);
const model = genAI.getGenerativeModel({
model: 'gemini-2.0-pro-exp-02-05',
systemInstruction: `SNSの投稿を短歌にしてください。原文の特徴的な要素はそのままに、比喩表現を使った趣深い短歌にしてください。出力の形式は以下のJSONスキーマに従ってください。各値は日本語で出力してください。
{
"type": "object",
"description": "変換した短歌",
"properties": {
"response": {
"type": "array",
"description": "ユーザーアカウント作成のレスポンスデータの配列",
"items": {
"type": "object",
"properties": {
"line1": {
"type": "string",
"description": "短歌の1行目"
},
"line2": {
"type": "string",
"description": "短歌の2行目"
},
"line3": {
"type": "string",
"description": "短歌の3行目"
},
"line4": {
"type": "string",
"description": "短歌の4行目"
},
"line5": {
"type": "string",
"description": "短歌の5行目"
}
},
"required": ["line1", "line2", "line3", "line4", "line5"]
}
},
"required": ["response"]
}
}`,
generationConfig: { responseMimeType: 'application/json' },
});

const result = await model.generateContent(prompt);
const jsonResponse = JSON.parse(result.response.text());
console.log(jsonResponse);
console.log('-----------------------------');

return c.json(jsonResponse);
} catch (error) {
console.error('APIエラー:', error);
return c.json({ error: 'Internal Server Error' }, 500);
return {};
}
};
*/

export default generateTanka;
3 changes: 3 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ services:
env_file:
- ./frontend/.env
environment:
- TZ=Asia/Tokyo
- BACKEND_URL=http://backend:8080
backend:
build:
Expand All @@ -27,3 +28,5 @@ services:
- /app/node_modules
env_file:
- ./backend/.env
environment:
- TZ=Asia/Tokyo
3 changes: 2 additions & 1 deletion frontend/.env.exapmle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NEXT_PUBLIC_ADOBE_FONTS_KIT_ID=
AUTH_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECREAT=
GITHUB_CLIENT_SECREAT=
AUTH_URL=
Loading