柿崎透真 commited on
Commit
66a50c8
·
1 Parent(s): 1b0b08e

feat: 動画教材レコメンド機能 & ストリーム出力

Browse files
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
- return super().encode(text)
 
 
 
 
30
 
31
  def decode(self, encoded: list[int]) -> str:
32
  print("Tokens are different from actual tokens because tiktoken for gpt is used.")
33
- return super().decode(encoded)
 
 
 
 
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
- " **{項目2}:{点数}**\n"
 
 
 
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
- " **{項目2}:{点数}**\n"
 
 
 
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
- " {広告物としての評価と同様のフォーマットで各項目について評価}\n\n"
 
 
 
 
 
 
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
- PLATFORM = "google-generativeai"
11
- MODEL = "gemini-20-flash-exp"
 
 
 
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, model=MODEL, client_settings={"api_key": os.getenv("GOOGLE_API_KEY")}
 
 
114
  )
115
 
116
- return cg_image_tutor(
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
- PLATFORM = "google-generativeai"
7
- MODEL = "gemini-20-flash-exp"
 
 
 
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, model=MODEL, client_settings={"api_key": os.getenv("GOOGLE_API_KEY")}
 
 
36
  )
37
 
38
- return graphic_idea_tutor(
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
- PLATFORM = "google-generativeai"
12
- MODEL = "gemini-20-flash-exp"
 
 
 
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
- return graphic_poster_tutor(
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
- submit_button.click(
 
 
 
121
  analyze_fn,
122
  inputs=[
123
  artwork_input,
@@ -130,38 +140,94 @@ def create_evaluation_tab(
130
  ],
131
  outputs=output_text,
132
  )
133
- else:
134
- submit_button.click(
135
- analyze_fn,
136
- inputs=[
 
 
 
 
 
 
 
 
137
  artwork_input,
138
  assignment_input,
139
  criteria_input,
140
  artwork_content_input,
141
  knowledge_input,
142
- mode_setting,
 
143
  ],
144
- outputs=output_text,
145
  )
146
 
147
- cancel_button.click(
148
- fn=clear_inputs,
149
- inputs=[],
150
- outputs=[
 
151
  artwork_input,
152
  assignment_input,
153
  criteria_input,
154
  artwork_content_input,
155
  knowledge_input,
156
  output_text,
157
- ],
158
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- gr.Markdown("---")
161
- gr.Markdown("# 改善施策・改善履歴バックログ")
162
- gr.Image(backlog_image_path)
163
 
164
- return artwork_input, assignment_input, criteria_input, artwork_content_input, knowledge_input, output_text
 
 
 
 
 
 
 
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(