柿崎透真
commited on
Commit
·
66a50c8
1
Parent(s):
1b0b08e
feat: 動画教材レコメンド機能 & ストリーム出力
Browse files- app.py +3 -1
- neollm/llm/llm/google_generativeai/google_generativeai.py +12 -2
- src/backend/movie_recommender/movie_recommender.py +66 -0
- src/backend/movie_summarizer/movie_summarizer.py +53 -0
- src/backend/query_generator/query_generator.py +82 -0
- src/backend/tutor/cg/cg_image_tutor/cg_image_tutor.py +6 -0
- src/backend/tutor/cg/cg_image_tutor/prompt.py +7 -3
- src/backend/tutor/cg/cg_video_tutor/prompt.py +7 -3
- src/backend/tutor/graphic/idea_tutor/graphic_idea_tutor.py +5 -0
- src/backend/tutor/graphic/poster_tutor/graphic_poster_tutor.py +6 -0
- src/backend/tutor/graphic/poster_tutor/prompt.py +7 -1
- src/gradio_interface/cg/image.py +15 -5
- src/gradio_interface/cg/image_with_movie_recommendation.py +207 -0
- src/gradio_interface/graphic/idea.py +16 -5
- src/gradio_interface/graphic/poster.py +14 -10
- src/gradio_interface/utils.py +86 -20
app.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import gradio as gr
|
2 |
|
3 |
from src.gradio_interface.cg.image import create_cg_image_tab
|
|
|
4 |
from src.gradio_interface.cg.video import create_cg_video_tab
|
5 |
from src.gradio_interface.graphic.idea import create_idea_tab
|
6 |
from src.gradio_interface.graphic.poster import create_poster_tab
|
@@ -15,11 +16,12 @@ def main():
|
|
15 |
|
16 |
with gr.Tabs():
|
17 |
create_cg_image_tab()
|
|
|
18 |
create_cg_video_tab()
|
19 |
create_poster_tab()
|
20 |
create_idea_tab()
|
21 |
|
22 |
-
demo.launch()
|
23 |
|
24 |
|
25 |
if __name__ == "__main__":
|
|
|
1 |
import gradio as gr
|
2 |
|
3 |
from src.gradio_interface.cg.image import create_cg_image_tab
|
4 |
+
from src.gradio_interface.cg.image_with_movie_recommendation import create_cg_image_with_movie_recommendation_tab
|
5 |
from src.gradio_interface.cg.video import create_cg_video_tab
|
6 |
from src.gradio_interface.graphic.idea import create_idea_tab
|
7 |
from src.gradio_interface.graphic.poster import create_poster_tab
|
|
|
16 |
|
17 |
with gr.Tabs():
|
18 |
create_cg_image_tab()
|
19 |
+
create_cg_image_with_movie_recommendation_tab()
|
20 |
create_cg_video_tab()
|
21 |
create_poster_tab()
|
22 |
create_idea_tab()
|
23 |
|
24 |
+
demo.launch(enable_queue=True)
|
25 |
|
26 |
|
27 |
if __name__ == "__main__":
|
neollm/llm/llm/google_generativeai/google_generativeai.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from neollm.llm.llm.gpt.openai_gpt import OpenAILLM
|
2 |
from neollm.types import (
|
3 |
ChatCompletion,
|
@@ -11,6 +12,7 @@ from neollm.types import (
|
|
11 |
)
|
12 |
from neollm.types.info import APIPricing
|
13 |
from neollm.types.mytypes import ClientSettings
|
|
|
14 |
|
15 |
|
16 |
class _GoogleGenerativeLLM(OpenAILLM):
|
@@ -26,11 +28,19 @@ class _GoogleGenerativeLLM(OpenAILLM):
|
|
26 |
|
27 |
def encode(self, text: str) -> list[int]:
|
28 |
print("Tokens are different from actual tokens because tiktoken for gpt is used.")
|
29 |
-
|
|
|
|
|
|
|
|
|
30 |
|
31 |
def decode(self, encoded: list[int]) -> str:
|
32 |
print("Tokens are different from actual tokens because tiktoken for gpt is used.")
|
33 |
-
|
|
|
|
|
|
|
|
|
34 |
|
35 |
def _convert_to_response(self, platform_response: OpenAIResponse) -> Response:
|
36 |
platform_response.id = str(platform_response.id) # IDがnullで出てくるため、strに変換
|
|
|
1 |
+
import tiktoken
|
2 |
from neollm.llm.llm.gpt.openai_gpt import OpenAILLM
|
3 |
from neollm.types import (
|
4 |
ChatCompletion,
|
|
|
12 |
)
|
13 |
from neollm.types.info import APIPricing
|
14 |
from neollm.types.mytypes import ClientSettings
|
15 |
+
ALTERNATIVE_MODEL_FOR_ENCODING = "gpt-4o"
|
16 |
|
17 |
|
18 |
class _GoogleGenerativeLLM(OpenAILLM):
|
|
|
28 |
|
29 |
def encode(self, text: str) -> list[int]:
|
30 |
print("Tokens are different from actual tokens because tiktoken for gpt is used.")
|
31 |
+
try:
|
32 |
+
return super().encode(text)
|
33 |
+
except KeyError:
|
34 |
+
# NOTE: generative-aiのモデルとtiktokenが対応していないため、実際とは異なるがgpt-4oで代用
|
35 |
+
return tiktoken.encoding_for_model(ALTERNATIVE_MODEL_FOR_ENCODING).encode(text)
|
36 |
|
37 |
def decode(self, encoded: list[int]) -> str:
|
38 |
print("Tokens are different from actual tokens because tiktoken for gpt is used.")
|
39 |
+
try:
|
40 |
+
return super().decode(encoded)
|
41 |
+
except KeyError:
|
42 |
+
# NOTE: generative-aiのモデルとtiktokenが対応していないため、実際とは異なるがgpt-4oで代用
|
43 |
+
return tiktoken.encoding_for_model(ALTERNATIVE_MODEL_FOR_ENCODING).decode(encoded)
|
44 |
|
45 |
def _convert_to_response(self, platform_response: OpenAIResponse) -> Response:
|
46 |
platform_response.id = str(platform_response.id) # IDがnullで出てくるため、strに変換
|
src/backend/movie_recommender/movie_recommender.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from typing import Final
|
3 |
+
|
4 |
+
from azure.core.credentials import AzureKeyCredential
|
5 |
+
from azure.search.documents import SearchClient
|
6 |
+
from azure.search.documents._paging import SearchItemPaged
|
7 |
+
from azure.search.documents.models import QueryAnswerType, QueryCaptionType, QueryType, VectorizedQuery
|
8 |
+
from openai import AzureOpenAI
|
9 |
+
|
10 |
+
SEMANTIC_CONFIG_NAME: Final[str] = "my-semantic-config"
|
11 |
+
MODEL: Final[str] = "text-embedding-3-small"
|
12 |
+
K_NEAREST_NEIGHBORS: Final[int] = 3
|
13 |
+
TOP: Final[int] = 2
|
14 |
+
|
15 |
+
# 検索で取得するフィールド
|
16 |
+
SEARCH_FIELDS: Final[list[str]] = [
|
17 |
+
"file_name",
|
18 |
+
"content",
|
19 |
+
]
|
20 |
+
|
21 |
+
|
22 |
+
class MovieRecommender:
|
23 |
+
def generate_embeddings(self, text: list[str]) -> list[float]:
|
24 |
+
azure_openai_client = AzureOpenAI(
|
25 |
+
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
26 |
+
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
|
27 |
+
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
28 |
+
)
|
29 |
+
response = azure_openai_client.embeddings.create(input=text, model=MODEL) # type: ignore
|
30 |
+
return response.data[0].embedding
|
31 |
+
|
32 |
+
def search(self, queries: dict[str, dict[str, list[str]]]) -> dict[str, SearchItemPaged[dict]]:
|
33 |
+
search_client = SearchClient(
|
34 |
+
endpoint=os.environ["AZURE_SEARCH_SERVICE_ENDPOINT"],
|
35 |
+
index_name=os.environ["AZURE_SEARCH_INDEX_NAME"],
|
36 |
+
credential=AzureKeyCredential(os.environ["AZURE_SEARCH_SERVICE_KEY"]),
|
37 |
+
)
|
38 |
+
search_results = {}
|
39 |
+
for view_point, query in queries.items():
|
40 |
+
search_text = query["keyword"]
|
41 |
+
search_results[view_point] = search_client.search(
|
42 |
+
search_text=str(search_text),
|
43 |
+
vector_queries=[
|
44 |
+
VectorizedQuery(
|
45 |
+
vector=self.generate_embeddings(search_text),
|
46 |
+
k_nearest_neighbors=K_NEAREST_NEIGHBORS,
|
47 |
+
fields="content_vector",
|
48 |
+
)
|
49 |
+
],
|
50 |
+
select=SEARCH_FIELDS,
|
51 |
+
top=TOP,
|
52 |
+
query_type=QueryType.SEMANTIC,
|
53 |
+
semantic_configuration_name=SEMANTIC_CONFIG_NAME,
|
54 |
+
query_caption=QueryCaptionType.EXTRACTIVE,
|
55 |
+
query_answer=QueryAnswerType.EXTRACTIVE,
|
56 |
+
)
|
57 |
+
return search_results
|
58 |
+
|
59 |
+
def format_search_results(self, search_results: dict[str, SearchItemPaged[dict]]) -> str:
|
60 |
+
return "\n".join(
|
61 |
+
[
|
62 |
+
f"## {key}\n### ファイル名:\n- {v['file_name']}\n### 内容:\n- {v['content']}\n"
|
63 |
+
for key, value in search_results.items()
|
64 |
+
for v in value
|
65 |
+
]
|
66 |
+
)
|
src/backend/movie_summarizer/movie_summarizer.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from neollm import MyLLM
|
2 |
+
from neollm.types import Messages, Response
|
3 |
+
|
4 |
+
SYSTEM_PROMPT_TAG = "system_prompt"
|
5 |
+
|
6 |
+
|
7 |
+
class MovieSummarizer(MyLLM):
|
8 |
+
def create_system_prompt(self, inputs: dict[str, str]) -> str:
|
9 |
+
prompt = (
|
10 |
+
"あなたは動画キャプションの要約器です。\n"
|
11 |
+
"デザインを学ぶ学生が作った「# 作品の評価」と作品の改善における「# おすすめの動画」が渡されるので、これらを使って文章生成を行なってください。\n"
|
12 |
+
"要約において、以下の「# 入力フォーマット」と「# 出力フォーマット」、また「# 制約条件」を参考にしてください。\n\n"
|
13 |
+
"# 入力フォーマット\n"
|
14 |
+
"## {世界観、モデリングなどのデザイン創作において重視される項目}\n"
|
15 |
+
"### ファイル名:\n"
|
16 |
+
"{おすすめ動画のファイル名}\n"
|
17 |
+
"### 内容:\n"
|
18 |
+
"{おすすめ動画の内容}\n\n"
|
19 |
+
"{他の項目に関しても同様のフォーマットで続いていく}\n\n"
|
20 |
+
"# 出力フォーマット\n"
|
21 |
+
"## {世界観、モデリングなどのデザイン創作において重視される項目}\n"
|
22 |
+
"### ファイル名:\n"
|
23 |
+
"- {おすすめ動画のファイル名。複数ある場合、ここに箇条書きで列挙してください}\n"
|
24 |
+
"### 動画の概要:\n"
|
25 |
+
"{おすすめ動画の内容を要約したもの。ここは小文字で出してください。絶対に「#」などで大文字にしないでください。}\n"
|
26 |
+
"---\n\n"
|
27 |
+
"{次の項目へ。他の項目に関しても同様のフォーマットで続いていく}\n\n"
|
28 |
+
"# 制約条件\n"
|
29 |
+
"- 文章の出力において、「## {世界観、モデリングなどのデザイン創作において重視される項目}」と「### ファイル名:」の部分は変更しないでください。\n"
|
30 |
+
"- 「### 動画の概要」のところでは、入力された「### 内容」を、「改善点を解決するためになぜこの動画がおすすめか」「どんなことが話されているか」「これを見れば何が学べるか」をわかりやすくようやくしてください。\n"
|
31 |
+
"- 「# おすすめの動画」では2つ動画が渡されますが、必ずしも2つおすすめする必要はございません。学生の作品改善に有用だと思われる場合は、複数個おすすめしてください。\n"
|
32 |
+
"- **複数個おすすめする際は、1つの項目で何個も「### ファイル名」「### 動画の概要」を作らず、まとめてファイル名を記述・内容を要約してください。**\n"
|
33 |
+
"- 「# 出力フォーマット」にもありますが、項目ごとに「---」を入れて区切ってください。\n\n"
|
34 |
+
"それでは、生成を開始してください。あなたならできます。\n\n"
|
35 |
+
"# 作品の評価\n"
|
36 |
+
f"{inputs['evaluation_text']}\n\n"
|
37 |
+
"# おすすめの動画\n"
|
38 |
+
f"{inputs['recommendation_text']}"
|
39 |
+
)
|
40 |
+
# タグの追加
|
41 |
+
return f"<{SYSTEM_PROMPT_TAG}>\n{prompt}\n</{SYSTEM_PROMPT_TAG}>"
|
42 |
+
|
43 |
+
def _preprocess(self, inputs: dict[str, str]) -> Messages:
|
44 |
+
return [
|
45 |
+
{
|
46 |
+
"role": "system",
|
47 |
+
"content": self.create_system_prompt(inputs),
|
48 |
+
},
|
49 |
+
{"role": "user", "content": "<system_prompt>に従い、動画教材の要約を行なってください。"},
|
50 |
+
]
|
51 |
+
|
52 |
+
def _postprocess(self, response: Response) -> str:
|
53 |
+
return response.choices[0].message.content
|
src/backend/query_generator/query_generator.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from neollm import MyLLM
|
2 |
+
from neollm.types import Messages, Response
|
3 |
+
|
4 |
+
SYSTEM_PROMPT_TAG = "system_prompt"
|
5 |
+
|
6 |
+
|
7 |
+
class QueryGenerator(MyLLM):
|
8 |
+
def create_system_prompt(
|
9 |
+
self,
|
10 |
+
evaluation_text: str,
|
11 |
+
) -> str:
|
12 |
+
# 基本部分・デジハリの説明
|
13 |
+
prompt = "あなたは美術大学の3DCGデザイナー専攻の作品評価AIです。\n"
|
14 |
+
prompt += (
|
15 |
+
"プロ視点で、生徒の作品をより良くするためのキーワードを生成してください。\n"
|
16 |
+
"キーワード生成にあたり、以下の「# 制約条件」は必ず守ってください。\n\n"
|
17 |
+
)
|
18 |
+
|
19 |
+
# 制約条件
|
20 |
+
prompt += (
|
21 |
+
"# 制約条件\n"
|
22 |
+
"- 美術大学の教授が作品を事前に評価した文章が、「# 評価テキスト」で渡されます。\n"
|
23 |
+
" - ここでは、項目ごとでの点数付けおよび、良い点・改善点の列挙が行われています。\n"
|
24 |
+
" - あなたは、「# 評価テキスト」の各項目の改善点をもとに、項目ごとにキーワード生成を行ってください。生成形式の詳細は、後に記述する「# 出力フォーマット」を確認してください。\n"
|
25 |
+
"- あなたが生成したキーワードは、教材検索システムの検索クエリとして使用されます。そのことを念頭に置いてキーワードを生成してください。\n"
|
26 |
+
"- 「# 出力フォーマット」や「# 出力例」を参考に、必ずJSON形式で出力してください。\n\n"
|
27 |
+
)
|
28 |
+
|
29 |
+
# 評価テキスト
|
30 |
+
prompt += "# 評価テキスト\n" f"{evaluation_text}\n\n"
|
31 |
+
|
32 |
+
# 出力フォーマット
|
33 |
+
prompt += (
|
34 |
+
"# 出力フォーマット\n"
|
35 |
+
" {"
|
36 |
+
" '{「# 評価テキスト」の項目1}': {'keyword': '{項目1の改善点をもとに、キーワードをlist[str]の形式で出力}'},"
|
37 |
+
" '{「# 評価テキスト」の項目2}': {'keyword': '{項目2の改善点をもとに、キーワードをlist[str]の形式で出力}'},"
|
38 |
+
" '{「# 評価テキスト」の項目3}': {'keyword': '{項目3の改善点をもとに、キーワードをlist[str]の形式で出力}'},"
|
39 |
+
" {項目4以降に関しても上記と同様のフォーマットで出力してください}"
|
40 |
+
" }"
|
41 |
+
)
|
42 |
+
|
43 |
+
# 出力フォーマット
|
44 |
+
prompt += (
|
45 |
+
"# 出力例\n"
|
46 |
+
" {"
|
47 |
+
" 'モデリング': {'keyword': ['モデリング', 'ディテール追加', '襖', '掛軸', '細部の精緻化']},"
|
48 |
+
" 'ライティング': {'keyword': ['ライティング', '陰影強調', '立体感', '照明設定']},"
|
49 |
+
" }"
|
50 |
+
)
|
51 |
+
|
52 |
+
# 講義に関するナレッジ
|
53 |
+
prompt += "それでは、生成を開始してください。あなたならできます。"
|
54 |
+
|
55 |
+
# タグの追加
|
56 |
+
return f"<{SYSTEM_PROMPT_TAG}>\n{prompt}\n</{SYSTEM_PROMPT_TAG}>"
|
57 |
+
|
58 |
+
def _preprocess(self, inputs: dict[str, str]) -> Messages:
|
59 |
+
return [
|
60 |
+
{
|
61 |
+
"role": "system",
|
62 |
+
"content": self.create_system_prompt(
|
63 |
+
inputs["evaluation_text"],
|
64 |
+
),
|
65 |
+
},
|
66 |
+
{
|
67 |
+
"role": "user",
|
68 |
+
"content": [
|
69 |
+
{
|
70 |
+
"type": "text",
|
71 |
+
"text": "<system_prompt>に従って、入力された3DCG作品をよりよくするためのキーワードを生成してください。",
|
72 |
+
},
|
73 |
+
{
|
74 |
+
"type": "image_url",
|
75 |
+
"image_url": {"url": f"data:image/png;base64,{inputs['image_data']}"},
|
76 |
+
},
|
77 |
+
],
|
78 |
+
},
|
79 |
+
]
|
80 |
+
|
81 |
+
def _postprocess(self, response: Response) -> str:
|
82 |
+
return response.choices[0].message.content
|
src/backend/tutor/cg/cg_image_tutor/cg_image_tutor.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from typing import Literal, cast
|
2 |
|
3 |
from neollm import MyLLM
|
@@ -43,3 +44,8 @@ class CGImageTutor(MyLLM):
|
|
43 |
|
44 |
def _postprocess(self, response: Response) -> str:
|
45 |
return response.choices[0].message.content
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from collections.abc import Generator
|
2 |
from typing import Literal, cast
|
3 |
|
4 |
from neollm import MyLLM
|
|
|
44 |
|
45 |
def _postprocess(self, response: Response) -> str:
|
46 |
return response.choices[0].message.content
|
47 |
+
|
48 |
+
def generate_response_stream(
|
49 |
+
self, inputs: dict[str, str | Literal["NORMAL", "HARD"]]
|
50 |
+
) -> Generator[str, None, None]:
|
51 |
+
yield from super().call_stream(inputs)
|
src/backend/tutor/cg/cg_image_tutor/prompt.py
CHANGED
@@ -82,12 +82,16 @@ def create_system_prompt(
|
|
82 |
" **{項目1}:{点数}**\n"
|
83 |
" - 良い点:{項目1に関する良い点}\n"
|
84 |
" - 改善点:{項目1に関する改善点}\n"
|
85 |
-
"
|
|
|
|
|
|
|
86 |
" - 良い点:{項目1に関する良い点}\n"
|
87 |
" - 改善点:{項目1に関する改善点}\n"
|
|
|
|
|
|
|
88 |
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
89 |
-
" ## 個人能力評価観点\n"
|
90 |
-
" {「## 個人能力評価観点」について、「## 課題評価観点」と同様のフォーマットで評価してください}\n\n"
|
91 |
" ## コメント\n"
|
92 |
" {フィードバックした内容を元に、作品の全体的な完成度や、さらにブラッシュアップするために何が重要か、コメントしてください。"
|
93 |
)
|
|
|
82 |
" **{項目1}:{点数}**\n"
|
83 |
" - 良い点:{項目1に関する良い点}\n"
|
84 |
" - 改善点:{項目1に関する改善点}\n"
|
85 |
+
" {基本的に項目は1つですが他にも与えられている場合は項目2以降も同様に評価してください}\n\n"
|
86 |
+
" ## 個人能力評価観点\n"
|
87 |
+
" {「## 個人能力評価観点」の各項目について評価してください}\n\n"
|
88 |
+
" **{項目1}:{点数}**\n"
|
89 |
" - 良い点:{項目1に関する良い点}\n"
|
90 |
" - 改善点:{項目1に関する改善点}\n"
|
91 |
+
" **{項目2}:{点数}**\n"
|
92 |
+
" - 良い点:{項目2に関する良い点}\n"
|
93 |
+
" - 改善点:{項目2に関する改善点}\n"
|
94 |
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
|
|
|
|
95 |
" ## コメント\n"
|
96 |
" {フィードバックした内容を元に、作品の全体的な完成度や、さらにブラッシュアップするために何が重要か、コメントしてください。"
|
97 |
)
|
src/backend/tutor/cg/cg_video_tutor/prompt.py
CHANGED
@@ -72,12 +72,16 @@ def create_system_prompt(
|
|
72 |
" **{項目1}:{点数}**\n"
|
73 |
" - 良い点:{項目1に関する良い点}\n"
|
74 |
" - 改善点:{項目1に関する改善点}\n"
|
75 |
-
"
|
|
|
|
|
|
|
76 |
" - 良い点:{項目1に関する良い点}\n"
|
77 |
" - 改善点:{項目1に関する改善点}\n"
|
|
|
|
|
|
|
78 |
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
79 |
-
" ## 個人能力評価観点\n"
|
80 |
-
" {「## 個人能力評価観点」について、「## 課題評価観点」と同様のフォーマットで評価してください}\n\n"
|
81 |
" ## コメント\n"
|
82 |
" {フィードバックした内容を元に、作品の全体的な完成度や、さらにブラッシュアップするために何が重要か、コメントしてください。"
|
83 |
)
|
|
|
72 |
" **{項目1}:{点数}**\n"
|
73 |
" - 良い点:{項目1に関する良い点}\n"
|
74 |
" - 改善点:{項目1に関する改善点}\n"
|
75 |
+
" {基本的に項目は1つですが他にも与えられている場合は項目2以降も同様に評価してください}\n\n"
|
76 |
+
" ## 個人能力評価観点\n"
|
77 |
+
" {「## 個人能力評価観点」の各項目について評価してください}\n\n"
|
78 |
+
" **{項目1}:{点数}**\n"
|
79 |
" - 良い点:{項目1に関する良い点}\n"
|
80 |
" - 改善点:{項目1に関する改善点}\n"
|
81 |
+
" **{項目2}:{点数}**\n"
|
82 |
+
" - 良い点:{項目2に関する良い点}\n"
|
83 |
+
" - 改善点:{項目2に関する改善点}\n"
|
84 |
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
|
|
|
|
85 |
" ## コメント\n"
|
86 |
" {フィードバックした内容を元に、作品の全体的な完成度や、さらにブラッシュアップするために何が重要か、コメントしてください。"
|
87 |
)
|
src/backend/tutor/graphic/idea_tutor/graphic_idea_tutor.py
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
from neollm import MyLLM
|
2 |
from neollm.types import Messages, Response
|
3 |
|
@@ -27,3 +29,6 @@ class GraphicIdeaTutor(MyLLM):
|
|
27 |
|
28 |
def _postprocess(self, response: Response) -> str:
|
29 |
return response.choices[0].message.content
|
|
|
|
|
|
|
|
1 |
+
from collections.abc import Generator
|
2 |
+
|
3 |
from neollm import MyLLM
|
4 |
from neollm.types import Messages, Response
|
5 |
|
|
|
29 |
|
30 |
def _postprocess(self, response: Response) -> str:
|
31 |
return response.choices[0].message.content
|
32 |
+
|
33 |
+
def generate_response_stream(self, inputs: dict[str, str | list[str]]) -> Generator[str, None, None]:
|
34 |
+
yield from super().call_stream(inputs)
|
src/backend/tutor/graphic/poster_tutor/graphic_poster_tutor.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from typing import Literal, cast
|
2 |
|
3 |
from neollm import MyLLM
|
@@ -183,3 +184,8 @@ class GraphicPosterTutor(MyLLM):
|
|
183 |
|
184 |
def _postprocess(self, response: Response) -> str:
|
185 |
return response.choices[0].message.content
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from collections.abc import Generator
|
2 |
from typing import Literal, cast
|
3 |
|
4 |
from neollm import MyLLM
|
|
|
184 |
|
185 |
def _postprocess(self, response: Response) -> str:
|
186 |
return response.choices[0].message.content
|
187 |
+
|
188 |
+
def generate_response_stream(
|
189 |
+
self, inputs: dict[str, str | Literal["NORMAL", "HARD"]]
|
190 |
+
) -> Generator[str, None, None]:
|
191 |
+
yield from super().call_stream(inputs)
|
src/backend/tutor/graphic/poster_tutor/prompt.py
CHANGED
@@ -86,7 +86,13 @@ def create_system_prompt(
|
|
86 |
" - 改善点:{項目2に関する改善点,500文字程度}\n"
|
87 |
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
88 |
" ## 技能評価\n"
|
89 |
-
" {
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
" ## コメント\n"
|
91 |
" {フィードバックした内容を元に、作品の全体的な完成度や、さらにブラッシュアップするために何が重要か、コメントしてください。"
|
92 |
)
|
|
|
86 |
" - 改善点:{項目2に関する改善点,500文字程度}\n"
|
87 |
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
88 |
" ## 技能評価\n"
|
89 |
+
" **{項目1}:{点数}**\n"
|
90 |
+
" - 良い点:{項目1に関する良い点}\n"
|
91 |
+
" - 改善点:{項目1に関する改善点,500文字程度}\n"
|
92 |
+
" **{項目2}:{点数}**\n"
|
93 |
+
" - 良い点:{項目2に関する良い点}\n"
|
94 |
+
" - 改善点:{項目2に関する改善点,500文字程度}\n"
|
95 |
+
" {項目3以降に関しても上記と同様のフォーマットで評価してください}\n\n"
|
96 |
" ## コメント\n"
|
97 |
" {フィードバックした内容を元に、作品の全体的な完成度や、さらにブラッシュアップするために何が重要か、コメントしてください。"
|
98 |
)
|
src/gradio_interface/cg/image.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import os
|
|
|
2 |
from typing import Literal
|
3 |
|
4 |
import numpy as np
|
@@ -7,8 +8,11 @@ from src.backend.tutor.cg.cg_image_tutor.cg_image_tutor import CGImageTutor
|
|
7 |
from src.backend.utils import encode_image
|
8 |
from src.gradio_interface.utils import create_evaluation_tab
|
9 |
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
12 |
BACKLOG_IMAGE_PATH_CG = "fig/backlog_CG静止画_動画_20241214.png"
|
13 |
|
14 |
ASSIGNMENT_DEFAULT = """## 内容: 『写実表現』Mayaモデリング静止画課題(3か月課題制作)
|
@@ -89,7 +93,7 @@ def analyze_cg_image(
|
|
89 |
knowledge: str,
|
90 |
mode_setting: Literal["NORMAL", "HARD"] = "NORMAL",
|
91 |
color_mode_setting: Literal["カラー", "モノクロ"] = "カラー",
|
92 |
-
) -> str:
|
93 |
"""3DCG静止画の評価を実行する関数
|
94 |
|
95 |
Parameters:
|
@@ -110,10 +114,12 @@ def analyze_cg_image(
|
|
110 |
"""
|
111 |
|
112 |
cg_image_tutor = CGImageTutor(
|
113 |
-
platform=PLATFORM,
|
|
|
|
|
114 |
)
|
115 |
|
116 |
-
|
117 |
inputs={
|
118 |
"image_data": encode_image(image),
|
119 |
"evaluation_criteria": evaluation_criteria,
|
@@ -124,3 +130,7 @@ def analyze_cg_image(
|
|
124 |
"color_mode_setting": color_mode_setting,
|
125 |
}
|
126 |
)
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
+
from collections.abc import Generator
|
3 |
from typing import Literal
|
4 |
|
5 |
import numpy as np
|
|
|
8 |
from src.backend.utils import encode_image
|
9 |
from src.gradio_interface.utils import create_evaluation_tab
|
10 |
|
11 |
+
# TODO: gemini-20-flash-exp のストリーム出力対応
|
12 |
+
# PLATFORM = "google-generativeai"
|
13 |
+
# MODEL = "gemini-20-flash-exp"
|
14 |
+
PLATFORM = "gcp"
|
15 |
+
MODEL = "gemini-1.5-pro-002"
|
16 |
BACKLOG_IMAGE_PATH_CG = "fig/backlog_CG静止画_動画_20241214.png"
|
17 |
|
18 |
ASSIGNMENT_DEFAULT = """## 内容: 『写実表現』Mayaモデリング静止画課題(3か月課題制作)
|
|
|
93 |
knowledge: str,
|
94 |
mode_setting: Literal["NORMAL", "HARD"] = "NORMAL",
|
95 |
color_mode_setting: Literal["カラー", "モノクロ"] = "カラー",
|
96 |
+
) -> Generator[str, None, None]:
|
97 |
"""3DCG静止画の評価を実行する関数
|
98 |
|
99 |
Parameters:
|
|
|
114 |
"""
|
115 |
|
116 |
cg_image_tutor = CGImageTutor(
|
117 |
+
platform=PLATFORM,
|
118 |
+
model=MODEL,
|
119 |
+
# client_settings={"api_key": os.getenv("GOOGLE_API_KEY")}
|
120 |
)
|
121 |
|
122 |
+
response_generator = cg_image_tutor.generate_response_stream(
|
123 |
inputs={
|
124 |
"image_data": encode_image(image),
|
125 |
"evaluation_criteria": evaluation_criteria,
|
|
|
130 |
"color_mode_setting": color_mode_setting,
|
131 |
}
|
132 |
)
|
133 |
+
response = ""
|
134 |
+
for delta_content in response_generator:
|
135 |
+
response += delta_content
|
136 |
+
yield response
|
src/gradio_interface/cg/image_with_movie_recommendation.py
ADDED
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
import re
|
4 |
+
from typing import Literal
|
5 |
+
|
6 |
+
import numpy as np
|
7 |
+
from neollm.utils.postprocess import json2dict
|
8 |
+
|
9 |
+
from src.backend.movie_recommender.movie_recommender import MovieRecommender
|
10 |
+
from src.backend.movie_summarizer.movie_summarizer import MovieSummarizer
|
11 |
+
from src.backend.query_generator.query_generator import QueryGenerator
|
12 |
+
from src.backend.tutor.cg.cg_image_tutor.cg_image_tutor import CGImageTutor
|
13 |
+
from src.backend.utils import encode_image
|
14 |
+
from src.gradio_interface.utils import create_evaluation_tab
|
15 |
+
|
16 |
+
# TODO: gemini-20-flash-exp に変更
|
17 |
+
PLATFORM = "gcp"
|
18 |
+
MODEL = "gemini-1.5-pro-002"
|
19 |
+
# PLATFORM = "google-generativeai"
|
20 |
+
# MODEL = "gemini-20-flash-exp"
|
21 |
+
# GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
22 |
+
BACKLOG_IMAGE_PATH_CG = "fig/backlog_CG静止画_動画_20241123.png"
|
23 |
+
|
24 |
+
ASSIGNMENT_DEFAULT = """## 内容: 『写実表現』Mayaモデリング静止画課題(3か月課題制作)
|
25 |
+
- モチーフは部屋です。部屋での生活感を考えて、そこに置かれている様々な物もレイアウトしてください。
|
26 |
+
- 全体的なスケールバランスや質感などもこだわって、写実的に表現しましょう。
|
27 |
+
- モチーフ対象とした資料と比較し、遜色の無い再現性を目指して制作にあたって下さい。
|
28 |
+
## 目的
|
29 |
+
- モデリング方法に慣れる
|
30 |
+
- Photoshopを使用したテクスチャ作成に慣れる
|
31 |
+
- オブジェクトに適したマテリアル設定やUVマッピングが行えるようになる
|
32 |
+
- ライティング&レンダリングの基本を理解する
|
33 |
+
- 1年間でプロを目指すために、スキルだけでなく、自身の作品(ポートフォリオ)充実の一環として。
|
34 |
+
## 作品規定
|
35 |
+
- サイズ=1280×720pix
|
36 |
+
- カメラアングルを変えた画像を2枚提出(構図を考えてカメラを配置し、カメラビューにてレンダリング)
|
37 |
+
"""
|
38 |
+
CRITERIA_DEFAULT = """## 課題評価観点:各項目5段階で評価される
|
39 |
+
- 世界観:登場する⼩道具から背景までが、魅⼒的で作品意図に合ったものであるか。
|
40 |
+
5. 舞台設定や背景が詳細かつ魅力的で、作品意図と完全に一致している。細部まで徹底的に構築され、世界観が生きている。
|
41 |
+
4. 魅力的で統一感のある世界観。わずかな不足はあるが、視聴者に十分伝わる。
|
42 |
+
3. 世界観は成立しているが、深みや説得力がやや不足している。
|
43 |
+
2. 世界観に矛盾が多く、魅力に欠ける。設定が浅く、改善が必要。
|
44 |
+
1. 設定が未完成か、ほとんど感じられない。
|
45 |
+
## 個人能力評価観点: 各項目5段階で評価される。
|
46 |
+
- モデリング: 5.モチーフの構造を理解し、整合性を正しく造型できる。4.アニメーションによる変形に対応したトポロジーで作成できる。3.リアル調かデフォルメのどちらかを造形する事ができる。2.物語を説明するために必要な形状を作成することができる。1.ツールを使うことができない。
|
47 |
+
- テクスチャリング: 5.モデルの置かれた状況や状態、これまでの経緯を正しく表現することができる。4.モデルに対して必要なディティールを描き加えることができる。3.テクスチャマップを理解して使い分けることができる。2.作品や周囲の環境に合わせたカラーテクスチャを貼ることができる。1.ツールを使うことができない。
|
48 |
+
- ライティング: 5.必要とする演出に合わせて、理論的にライティング&レンダリングを構築できる。4.映像作品のための演出的なライティング⼿法の知識がある。3.イメージベースドライティングを理解してライティングをすることができる。2.照明があてられた状態でレンダリング結果を得ることができている。1.ツールを使うことができない。
|
49 |
+
- レイアウト: 5.視線誘導を意識して画面内を構成できる。4.近景、中景、遠景を意識し奥行きを感じさせる表現ができる。3.三分割法を理解し実践できる。2.形の大小、色の強弱、要素の粗密などに注目しバランスが取れるようにフレーミングできる。1.レイアウトを理解していない。
|
50 |
+
- レンダリング: 5.計算時間をかけ過ぎずに適切な計算時間でレンダリングできる。4.レンダリングの計算精度の意味を理解し、拡散反射、鏡面反射、透明度、ボリューム、サブサーフェイススキャッタリングごとに画質の向上ができる。2.画質を向上しレンダリング時に発生するノイズの軽減ができる。1.画質の向上ができない
|
51 |
+
"""
|
52 |
+
ARTWORK_CONTENT_DEFAULT = "- テーマ(一文または短いフレーズ):\n- 作品の世界観の設定(いつ?どこで?などの状況):\n- 一番観てもらいたい点・自分の作品の一番のうり: "
|
53 |
+
KNOWLEDGE_DEFAULT = """生徒はこの課題の前までに、以下の「学習項目」の内容を学習しています。
|
54 |
+
# 学習項目
|
55 |
+
- 2Dグラフィックス
|
56 |
+
- 様々なモデリング手法
|
57 |
+
- マテリアル / UV / テクスチャ
|
58 |
+
- ライティング / カメラワーク
|
59 |
+
- レンダリング
|
60 |
+
3DCGデザイナー専攻の概要やカリキュラムは以下の通りです。
|
61 |
+
# 3DCGデザイナー専攻の概要
|
62 |
+
- モデリング・アニメーション・コンポジットなど、「3DCGデザイナー」として就職・転職に必要なスキルを1年間で習得する
|
63 |
+
- メインで学習するソフトはMayaで、基礎から応用までを網羅する。Mayaはゲーム、映画、CM、VFXスタジオで広く使用され、業界シェアNo.1のソフトウェア。3DCG業界の求人では、Mayaや3ds MAXでのモデリング・アニメーションが求められることが多い。実写映像との相性が良く、映像にエフェクトを加える際にも活用され、ハリウッドでも多く使われている。
|
64 |
+
## 想定される受講生の特徴
|
65 |
+
- 1.未経験から1年間でCG・映像業界へ就職・転職したい方
|
66 |
+
- 2.Mayaでの3DCG制作スキル・映像表現を身に付けたい方
|
67 |
+
- 3.基礎からCG・映像の制作スキルを習得したい方
|
68 |
+
- 4.大学とのWスクールや仕事との両立を目指す方
|
69 |
+
## カリキュラム
|
70 |
+
- 1〜3か月
|
71 |
+
- CG演習: CG基礎、CGモデリング、CGレンダリング
|
72 |
+
- 映像授業・演習: 映像編集演習、2Dグラフィックス演習、CG概論
|
73 |
+
- 3か月課題制作: 3DCG静止画課題
|
74 |
+
"""
|
75 |
+
|
76 |
+
|
77 |
+
def convert_json_to_dict(text):
|
78 |
+
"""
|
79 |
+
マークダウンのコードブロックからJSONを抽出し、Pythonの辞書に変換する関数
|
80 |
+
Args:
|
81 |
+
text (str): ```json で囲まれたJSON文字列を含むテキスト
|
82 |
+
Returns:
|
83 |
+
dict: 変換された辞書
|
84 |
+
Raises:
|
85 |
+
ValueError: JSONの抽出に失敗した場合
|
86 |
+
json.JSONDecodeError: JSON形式が不正な場合
|
87 |
+
"""
|
88 |
+
try:
|
89 |
+
# ```json と ``` の間のテキストを抽出
|
90 |
+
pattern = r"```json\s*([\s\S]*?)\s*```"
|
91 |
+
match = re.search(pattern, text)
|
92 |
+
|
93 |
+
if not match:
|
94 |
+
raise ValueError("JSONブロックが見つかりませんでした")
|
95 |
+
|
96 |
+
json_str = match.group(1)
|
97 |
+
|
98 |
+
# 文字列をPythonの辞書に変換
|
99 |
+
result = json.loads(json_str)
|
100 |
+
return result
|
101 |
+
|
102 |
+
except json.JSONDecodeError as e:
|
103 |
+
print(f"JSONの解析に失敗しました: {str(e)}")
|
104 |
+
raise
|
105 |
+
except ValueError as e:
|
106 |
+
print(f"エラー: {str(e)}")
|
107 |
+
raise
|
108 |
+
|
109 |
+
|
110 |
+
def create_cg_image_with_movie_recommendation_tab():
|
111 |
+
return create_evaluation_tab(
|
112 |
+
tab_name="CG/静止画(動画教材レコメンド)",
|
113 |
+
backlog_image_path=BACKLOG_IMAGE_PATH_CG,
|
114 |
+
analyze_fn=analyze_cg_image,
|
115 |
+
recommendation_fn=movie_recommendation,
|
116 |
+
assignment_default=ASSIGNMENT_DEFAULT,
|
117 |
+
criteria_default=CRITERIA_DEFAULT,
|
118 |
+
artwork_content_default=ARTWORK_CONTENT_DEFAULT,
|
119 |
+
knowledge_default=KNOWLEDGE_DEFAULT,
|
120 |
+
description="## 静止画の評価(併せておすすめの動画教材を提示します)",
|
121 |
+
type="image",
|
122 |
+
)
|
123 |
+
|
124 |
+
|
125 |
+
def analyze_cg_image(
|
126 |
+
image: np.ndarray,
|
127 |
+
evaluation_criteria: str,
|
128 |
+
artwork_content: str,
|
129 |
+
assignment: str,
|
130 |
+
knowledge: str,
|
131 |
+
mode_setting: Literal["NORMAL", "HARD"] = "NORMAL",
|
132 |
+
color_mode_setting: Literal["カラー", "モノクロ"] = "カラー",
|
133 |
+
):
|
134 |
+
"""3DCG静止画の評価を実行する関数
|
135 |
+
Parameters:
|
136 |
+
-----------
|
137 |
+
image : numpy.ndarray
|
138 |
+
評価する画像
|
139 |
+
evaluation_criteria : str
|
140 |
+
評価観点
|
141 |
+
artwork_content : str
|
142 |
+
作品概要
|
143 |
+
assignment : str
|
144 |
+
課題概要
|
145 |
+
Returns:
|
146 |
+
--------
|
147 |
+
str:
|
148 |
+
評価結果
|
149 |
+
"""
|
150 |
+
|
151 |
+
cg_image_tutor = CGImageTutor(
|
152 |
+
platform=PLATFORM,
|
153 |
+
model=MODEL,
|
154 |
+
client_settings={"api_key": GOOGLE_API_KEY},
|
155 |
+
)
|
156 |
+
|
157 |
+
image_data = encode_image(image)
|
158 |
+
|
159 |
+
evaluation_text = cg_image_tutor(
|
160 |
+
inputs={
|
161 |
+
"image_data": image_data,
|
162 |
+
"evaluation_criteria": evaluation_criteria,
|
163 |
+
"artwork_content": artwork_content,
|
164 |
+
"assignment": assignment,
|
165 |
+
"knowledge": knowledge,
|
166 |
+
"mode_setting": mode_setting,
|
167 |
+
"color_mode_setting": color_mode_setting,
|
168 |
+
}
|
169 |
+
)
|
170 |
+
|
171 |
+
return evaluation_text
|
172 |
+
|
173 |
+
|
174 |
+
def movie_recommendation(image: np.ndarray, evaluation_text: str):
|
175 |
+
image_data = encode_image(image)
|
176 |
+
|
177 |
+
# 検索クエリ生成
|
178 |
+
query_generator = QueryGenerator(
|
179 |
+
platform=PLATFORM,
|
180 |
+
model=MODEL,
|
181 |
+
# client_settings={"api_key": GOOGLE_API_KEY},
|
182 |
+
)
|
183 |
+
queries = query_generator(
|
184 |
+
inputs={
|
185 |
+
"evaluation_text": evaluation_text,
|
186 |
+
"image_data": image_data,
|
187 |
+
}
|
188 |
+
)
|
189 |
+
|
190 |
+
movie_recommender = MovieRecommender()
|
191 |
+
search_results = movie_recommender.search(queries=json2dict(queries))
|
192 |
+
recommendation_text = movie_recommender.format_search_results(search_results)
|
193 |
+
|
194 |
+
# 作品の改善点と合わせて動画教材を要約
|
195 |
+
movie_summarizer = MovieSummarizer(
|
196 |
+
platform=PLATFORM,
|
197 |
+
model=MODEL,
|
198 |
+
# client_settings={"api_key": GOOGLE_API_KEY},
|
199 |
+
)
|
200 |
+
movie_recommendation = movie_summarizer(
|
201 |
+
inputs={
|
202 |
+
"evaluation_text": evaluation_text,
|
203 |
+
"recommendation_text": recommendation_text,
|
204 |
+
}
|
205 |
+
)
|
206 |
+
|
207 |
+
return movie_recommendation
|
src/gradio_interface/graphic/idea.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1 |
import os
|
|
|
2 |
|
3 |
from src.backend.tutor.graphic.idea_tutor.graphic_idea_tutor import GraphicIdeaTutor
|
4 |
from src.gradio_interface.utils import create_chatbot_tab
|
5 |
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
8 |
BACKLOG_IMAGE_PATH_IDEA = "fig/backlog_コンセプト壁打ち_20241214.png"
|
9 |
|
10 |
|
@@ -16,7 +20,7 @@ def create_idea_tab():
|
|
16 |
)
|
17 |
|
18 |
|
19 |
-
def chat_graphic_idea(user_input: list[str], history: list[str]) -> str:
|
20 |
"""広告ポスターの壁打ちを実行する関数
|
21 |
|
22 |
Parameters:
|
@@ -32,12 +36,19 @@ def chat_graphic_idea(user_input: list[str], history: list[str]) -> str:
|
|
32 |
"""
|
33 |
|
34 |
graphic_idea_tutor = GraphicIdeaTutor(
|
35 |
-
platform=PLATFORM,
|
|
|
|
|
36 |
)
|
37 |
|
38 |
-
|
39 |
inputs={
|
40 |
"user_input": user_input,
|
41 |
"history": history,
|
42 |
}
|
43 |
)
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
+
from collections.abc import Generator
|
3 |
|
4 |
from src.backend.tutor.graphic.idea_tutor.graphic_idea_tutor import GraphicIdeaTutor
|
5 |
from src.gradio_interface.utils import create_chatbot_tab
|
6 |
|
7 |
+
# TODO: gemini-20-flash-exp のストリーム出力対応
|
8 |
+
# PLATFORM = "google-generativeai"
|
9 |
+
# MODEL = "gemini-20-flash-exp"
|
10 |
+
PLATFORM = "gcp"
|
11 |
+
MODEL = "gemini-1.5-pro-002"
|
12 |
BACKLOG_IMAGE_PATH_IDEA = "fig/backlog_コンセプト壁打ち_20241214.png"
|
13 |
|
14 |
|
|
|
20 |
)
|
21 |
|
22 |
|
23 |
+
def chat_graphic_idea(user_input: list[str], history: list[str]) -> Generator[str, None, None]:
|
24 |
"""広告ポスターの壁打ちを実行する関数
|
25 |
|
26 |
Parameters:
|
|
|
36 |
"""
|
37 |
|
38 |
graphic_idea_tutor = GraphicIdeaTutor(
|
39 |
+
platform=PLATFORM,
|
40 |
+
model=MODEL,
|
41 |
+
# client_settings={"api_key": os.getenv("GOOGLE_API_KEY")}
|
42 |
)
|
43 |
|
44 |
+
response_generator = graphic_idea_tutor.generate_response_stream(
|
45 |
inputs={
|
46 |
"user_input": user_input,
|
47 |
"history": history,
|
48 |
}
|
49 |
)
|
50 |
+
|
51 |
+
response = ""
|
52 |
+
for delta_content in response_generator:
|
53 |
+
response += delta_content
|
54 |
+
yield response
|
src/gradio_interface/graphic/poster.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import os
|
|
|
2 |
from typing import Literal
|
3 |
|
4 |
import numpy as np
|
@@ -8,8 +9,11 @@ from src.backend.tutor.graphic.poster_tutor.graphic_transcription import Graphic
|
|
8 |
from src.backend.utils import encode_image
|
9 |
from src.gradio_interface.utils import create_evaluation_tab
|
10 |
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
13 |
BACKLOG_IMAGE_PATH_GRAPHIC = "fig/backlog_グラフィック広告ポスター_20241214.png"
|
14 |
|
15 |
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
@@ -140,7 +144,7 @@ def analyze_graphic_poster(
|
|
140 |
assignment: str,
|
141 |
knowledge: str,
|
142 |
mode_setting: Literal["NORMAL", "HARD"] = "HARD",
|
143 |
-
) -> str:
|
144 |
"""広告ポスターの評価を実行する関数
|
145 |
|
146 |
Parameters:
|
@@ -160,16 +164,12 @@ def analyze_graphic_poster(
|
|
160 |
評価結果
|
161 |
"""
|
162 |
|
163 |
-
graphic_transcription = GraphicTranscription(
|
164 |
-
platform=PLATFORM, model=MODEL, client_settings={"api_key": GOOGLE_API_KEY}
|
165 |
-
)
|
166 |
transcription = graphic_transcription(inputs=encode_image(image))
|
167 |
|
168 |
-
graphic_poster_tutor = GraphicPosterTutor(
|
169 |
-
platform=PLATFORM, model=MODEL, client_settings={"api_key": GOOGLE_API_KEY}
|
170 |
-
)
|
171 |
|
172 |
-
|
173 |
inputs={
|
174 |
"transcription": transcription,
|
175 |
"image_data": encode_image(image),
|
@@ -180,3 +180,7 @@ def analyze_graphic_poster(
|
|
180 |
"mode_setting": mode_setting,
|
181 |
}
|
182 |
)
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
+
from collections.abc import Generator
|
3 |
from typing import Literal
|
4 |
|
5 |
import numpy as np
|
|
|
9 |
from src.backend.utils import encode_image
|
10 |
from src.gradio_interface.utils import create_evaluation_tab
|
11 |
|
12 |
+
# TODO: gemini-20-flash-exp のストリーム出力対応
|
13 |
+
# PLATFORM = "google-generativeai"
|
14 |
+
# MODEL = "gemini-20-flash-exp"
|
15 |
+
PLATFORM = "gcp"
|
16 |
+
MODEL = "gemini-1.5-pro-002"
|
17 |
BACKLOG_IMAGE_PATH_GRAPHIC = "fig/backlog_グラフィック広告ポスター_20241214.png"
|
18 |
|
19 |
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
|
|
144 |
assignment: str,
|
145 |
knowledge: str,
|
146 |
mode_setting: Literal["NORMAL", "HARD"] = "HARD",
|
147 |
+
) -> Generator[str, None, None]:
|
148 |
"""広告ポスターの評価を実行する関数
|
149 |
|
150 |
Parameters:
|
|
|
164 |
評価結果
|
165 |
"""
|
166 |
|
167 |
+
graphic_transcription = GraphicTranscription(platform=PLATFORM, model=MODEL)
|
|
|
|
|
168 |
transcription = graphic_transcription(inputs=encode_image(image))
|
169 |
|
170 |
+
graphic_poster_tutor = GraphicPosterTutor(platform=PLATFORM, model=MODEL)
|
|
|
|
|
171 |
|
172 |
+
response_generator = graphic_poster_tutor.generate_response_stream(
|
173 |
inputs={
|
174 |
"transcription": transcription,
|
175 |
"image_data": encode_image(image),
|
|
|
180 |
"mode_setting": mode_setting,
|
181 |
}
|
182 |
)
|
183 |
+
response = ""
|
184 |
+
for delta_content in response_generator:
|
185 |
+
response += delta_content
|
186 |
+
yield response
|
src/gradio_interface/utils.py
CHANGED
@@ -20,7 +20,7 @@ IDEA_TUTOR_INSTRUCTION = """## テンプレートを利用して、AI を活用
|
|
20 |
|
21 |
|
22 |
def create_evaluation_tab(
|
23 |
-
tab_name: Literal["グラフィック/広告ポスター", "CG/静止画", "CG/動画"],
|
24 |
backlog_image_path: str,
|
25 |
analyze_fn,
|
26 |
assignment_default: str,
|
@@ -28,6 +28,7 @@ def create_evaluation_tab(
|
|
28 |
artwork_content_default: str,
|
29 |
knowledge_default: str,
|
30 |
description: str,
|
|
|
31 |
type: Literal["image", "video"] = "image",
|
32 |
):
|
33 |
"""評価用タブを作成する共通関数
|
@@ -42,10 +43,14 @@ def create_evaluation_tab(
|
|
42 |
課題概要の初期値
|
43 |
criteria_default : str
|
44 |
評価観点の初期値
|
|
|
|
|
45 |
artwork_content_default : str
|
46 |
作品概要の初期値
|
47 |
description : str
|
48 |
タブの説明文
|
|
|
|
|
49 |
|
50 |
Returns:
|
51 |
--------
|
@@ -54,7 +59,7 @@ def create_evaluation_tab(
|
|
54 |
"""
|
55 |
with gr.TabItem(tab_name):
|
56 |
gr.Markdown(description + "\n### ※提出した作品はAIの再学習に利用されません\n### 作品をアップロード")
|
57 |
-
if tab_name == "CG/静止画":
|
58 |
color_mode_setting = gr.Radio(
|
59 |
["カラー", "モノクロ"],
|
60 |
label="作品の状態を選択してください",
|
@@ -112,12 +117,17 @@ def create_evaluation_tab(
|
|
112 |
artwork_content_input.value,
|
113 |
knowledge_input.value,
|
114 |
"",
|
|
|
115 |
]
|
116 |
|
|
|
117 |
output_text = gr.Markdown(label="評価結果")
|
118 |
|
119 |
-
if tab_name == "CG
|
120 |
-
|
|
|
|
|
|
|
121 |
analyze_fn,
|
122 |
inputs=[
|
123 |
artwork_input,
|
@@ -130,38 +140,94 @@ def create_evaluation_tab(
|
|
130 |
],
|
131 |
outputs=output_text,
|
132 |
)
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
artwork_input,
|
138 |
assignment_input,
|
139 |
criteria_input,
|
140 |
artwork_content_input,
|
141 |
knowledge_input,
|
142 |
-
|
|
|
143 |
],
|
144 |
-
outputs=output_text,
|
145 |
)
|
146 |
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
151 |
artwork_input,
|
152 |
assignment_input,
|
153 |
criteria_input,
|
154 |
artwork_content_input,
|
155 |
knowledge_input,
|
156 |
output_text,
|
157 |
-
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
|
167 |
def create_chatbot_tab(
|
|
|
20 |
|
21 |
|
22 |
def create_evaluation_tab(
|
23 |
+
tab_name: Literal["グラフィック/広告ポスター", "CG/静止画", "CG/動画", "CG/静止画(動画教材レコメンド)"],
|
24 |
backlog_image_path: str,
|
25 |
analyze_fn,
|
26 |
assignment_default: str,
|
|
|
28 |
artwork_content_default: str,
|
29 |
knowledge_default: str,
|
30 |
description: str,
|
31 |
+
recommendation_fn=None,
|
32 |
type: Literal["image", "video"] = "image",
|
33 |
):
|
34 |
"""評価用タブを作成する共通関数
|
|
|
43 |
課題概要の初期値
|
44 |
criteria_default : str
|
45 |
評価観点の初期値
|
46 |
+
knowledge_default : str
|
47 |
+
デザイン関連の知識の初期値
|
48 |
artwork_content_default : str
|
49 |
作品概要の初期値
|
50 |
description : str
|
51 |
タブの説明文
|
52 |
+
recommendation_fn : callable or None
|
53 |
+
おすすめ動画のレコメンド関数
|
54 |
|
55 |
Returns:
|
56 |
--------
|
|
|
59 |
"""
|
60 |
with gr.TabItem(tab_name):
|
61 |
gr.Markdown(description + "\n### ※提出した作品はAIの再学習に利用されません\n### 作品をアップロード")
|
62 |
+
if tab_name == "CG/静止画" or tab_name == "CG/静止画(動画教材レコメンド)":
|
63 |
color_mode_setting = gr.Radio(
|
64 |
["カラー", "モノクロ"],
|
65 |
label="作品の状態を選択してください",
|
|
|
117 |
artwork_content_input.value,
|
118 |
knowledge_input.value,
|
119 |
"",
|
120 |
+
"",
|
121 |
]
|
122 |
|
123 |
+
gr.Markdown("# 評価結果")
|
124 |
output_text = gr.Markdown(label="評価結果")
|
125 |
|
126 |
+
if tab_name == "CG/静止画(動画教材レコメンド)":
|
127 |
+
gr.Markdown("---")
|
128 |
+
gr.Markdown("# おすすめ動画")
|
129 |
+
output_recommendation_text = gr.Markdown(label="おすすめ動画")
|
130 |
+
analyze_event = submit_button.click(
|
131 |
analyze_fn,
|
132 |
inputs=[
|
133 |
artwork_input,
|
|
|
140 |
],
|
141 |
outputs=output_text,
|
142 |
)
|
143 |
+
|
144 |
+
# 作品評価が終わったら、おすすめ動画のレコメンド処理を実行
|
145 |
+
analyze_event.then(
|
146 |
+
recommendation_fn,
|
147 |
+
inputs=[artwork_input, output_text],
|
148 |
+
outputs=[output_recommendation_text],
|
149 |
+
)
|
150 |
+
|
151 |
+
cancel_button.click(
|
152 |
+
fn=clear_inputs,
|
153 |
+
inputs=[],
|
154 |
+
outputs=[
|
155 |
artwork_input,
|
156 |
assignment_input,
|
157 |
criteria_input,
|
158 |
artwork_content_input,
|
159 |
knowledge_input,
|
160 |
+
output_text,
|
161 |
+
output_recommendation_text,
|
162 |
],
|
|
|
163 |
)
|
164 |
|
165 |
+
gr.Markdown("---")
|
166 |
+
gr.Markdown("# 改善施策・改善履歴バックログ")
|
167 |
+
gr.Image(backlog_image_path)
|
168 |
+
|
169 |
+
return (
|
170 |
artwork_input,
|
171 |
assignment_input,
|
172 |
criteria_input,
|
173 |
artwork_content_input,
|
174 |
knowledge_input,
|
175 |
output_text,
|
176 |
+
output_recommendation_text,
|
177 |
+
)
|
178 |
+
else:
|
179 |
+
if tab_name == "CG/静止画":
|
180 |
+
submit_button.click(
|
181 |
+
analyze_fn,
|
182 |
+
inputs=[
|
183 |
+
artwork_input,
|
184 |
+
assignment_input,
|
185 |
+
criteria_input,
|
186 |
+
artwork_content_input,
|
187 |
+
knowledge_input,
|
188 |
+
mode_setting,
|
189 |
+
color_mode_setting,
|
190 |
+
],
|
191 |
+
outputs=output_text,
|
192 |
+
)
|
193 |
+
else:
|
194 |
+
submit_button.click(
|
195 |
+
analyze_fn,
|
196 |
+
inputs=[
|
197 |
+
artwork_input,
|
198 |
+
assignment_input,
|
199 |
+
criteria_input,
|
200 |
+
artwork_content_input,
|
201 |
+
knowledge_input,
|
202 |
+
mode_setting,
|
203 |
+
],
|
204 |
+
outputs=output_text,
|
205 |
+
)
|
206 |
+
cancel_button.click(
|
207 |
+
fn=clear_inputs,
|
208 |
+
inputs=[],
|
209 |
+
outputs=[
|
210 |
+
artwork_input,
|
211 |
+
assignment_input,
|
212 |
+
criteria_input,
|
213 |
+
artwork_content_input,
|
214 |
+
knowledge_input,
|
215 |
+
output_text,
|
216 |
+
],
|
217 |
+
)
|
218 |
|
219 |
+
gr.Markdown("---")
|
220 |
+
gr.Markdown("# 改善施策・改善履歴バックログ")
|
221 |
+
gr.Image(backlog_image_path)
|
222 |
|
223 |
+
return (
|
224 |
+
artwork_input,
|
225 |
+
assignment_input,
|
226 |
+
criteria_input,
|
227 |
+
artwork_content_input,
|
228 |
+
knowledge_input,
|
229 |
+
output_text,
|
230 |
+
)
|
231 |
|
232 |
|
233 |
def create_chatbot_tab(
|