Skip to content
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

ドキュメント更新とリファクタリング #164

Merged
merged 12 commits into from
Oct 9, 2024
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ NEXT_PUBLIC_VOICEVOX_INTONATION=""
NEXT_PUBLIC_KOEIROMAP_KEY=""

# Google TTS
GOOGLE_APPLICATION_CREDENTIALS="./credentials.json"
NEXT_PUBLIC_GOOGLE_TTS_TYPE=""

# StyleBertVits2
Expand Down
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ NEXT_PUBLIC_VOICEVOX_INTONATION=""
NEXT_PUBLIC_KOEIROMAP_KEY=""

# Google TTS
GOOGLE_APPLICATION_CREDENTIALS="./credentials.json"
NEXT_PUBLIC_GOOGLE_TTS_TYPE=""

# StyleBertVits2
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ npm run dev
<a href="https://github.com/gijigae" title="gijigae">
<img src="https://github.com/gijigae.png" width="40" height="40" alt="gijigae">
</a>
<a href="https://github.com/takm-reason" title="takm-reason">
<img src="https://github.com/takm-reason.png" width="40" height="40" alt="takm-reason">
</a>
</p>

他、プライベートスポンサー 複数名
Expand Down
3 changes: 3 additions & 0 deletions docs/README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ Your support will greatly contribute to the development and improvement of the A
<a href="https://github.com/gijigae" title="gijigae">
<img src="https://github.com/gijigae.png" width="40" height="40" alt="gijigae">
</a>
<a href="https://github.com/takm-reason" title="takm-reason">
<img src="https://github.com/takm-reason.png" width="40" height="40" alt="takm-reason">
</a>
</p>

Plus multiple private sponsors
Expand Down
3 changes: 3 additions & 0 deletions docs/README_ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ npm run dev
<a href="https://github.com/gijigae" title="gijigae">
<img src="https://github.com/gijigae.png" width="40" height="40" alt="gijigae">
</a>
<a href="https://github.com/takm-reason" title="takm-reason">
<img src="https://github.com/takm-reason.png" width="40" height="40" alt="takm-reason">
</a>
</p>

그 외, 다수의 비공개 스폰서
Expand Down
3 changes: 3 additions & 0 deletions docs/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ npm run dev
<a href="https://github.com/gijigae" title="gijigae">
<img src="https://github.com/gijigae.png" width="40" height="40" alt="gijigae">
</a>
<a href="https://github.com/takm-reason" title="takm-reason">
<img src="https://github.com/takm-reason.png" width="40" height="40" alt="takm-reason">
</a>
</p>

以及多位匿名贊助者
Expand Down
19 changes: 5 additions & 14 deletions src/components/messageReceiver.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState, useCallback } from 'react'
import { processReceivedMessage } from '@/features/chat/handlers'
import { speakMessageHandler } from '@/features/chat/handlers'
import settingsStore from '@/features/stores/settings'
import homeStore from '@/features/stores/home'

Expand All @@ -17,17 +17,8 @@ const MessageReceiver = () => {
const [lastTimestamp, setLastTimestamp] = useState(0)
const clientId = settingsStore((state) => state.clientId)

const updateChatLog = useCallback((messages: Message[]) => {
homeStore.setState((state) => ({
chatLog: [
...state.chatLog,
...messages.map((message) => ({
role: 'assistant',
content: message.message,
})),
],
}))
messages.forEach((message) => processReceivedMessage(message.message))
const speakMessage = useCallback((messages: Message[]) => {
messages.forEach((message) => speakMessageHandler(message.message))
}, [])

useEffect(() => {
Expand All @@ -40,7 +31,7 @@ const MessageReceiver = () => {
)
const data = await response.json()
if (data.messages && data.messages.length > 0) {
updateChatLog(data.messages)
speakMessage(data.messages)
const newLastTimestamp =
data.messages[data.messages.length - 1].timestamp
setLastTimestamp(newLastTimestamp)
Expand All @@ -53,7 +44,7 @@ const MessageReceiver = () => {
const intervalId = setInterval(fetchMessages, 1000) // 5秒ごとに変更

return () => clearInterval(intervalId)
}, [lastTimestamp, clientId, updateChatLog]) // chatLogを依存配列から除外し、updateChatLogを追加
}, [lastTimestamp, clientId, speakMessage])

return <></>
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/voice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
PRESET_D,
} from '@/features/constants/koeiroParam'
import { AIVoice } from '@/features/constants/settings'
import { testVoice } from '@/features/messages/speakCharacter'
import { testVoiceVox } from '@/features/messages/speakCharacter'
import settingsStore from '@/features/stores/settings'
import { Link } from '../link'
import { TextButton } from '../textButton'
Expand Down Expand Up @@ -208,7 +208,7 @@ const Voice = () => {
</option>
))}
</select>
<TextButton onClick={() => testVoice()} className="ml-16">
<TextButton onClick={() => testVoiceVox()} className="ml-16">
{t('TestVoice')}
</TextButton>
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/components/slides.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react'
import slideStore from '../features/stores/slide'
import homeStore from '../features/stores/home'
import { processReceivedMessage } from '../features/chat/handlers'
import slideStore from '@/features/stores/slide'
import homeStore from '@/features/stores/home'
import { speakMessageHandler } from '@/features/chat/handlers'
import SlideContent from './slideContent'
import SlideControls from './slideControls'

Expand Down Expand Up @@ -114,7 +114,7 @@ const Slides: React.FC<SlidesProps> = ({ markdown }) => {

const currentLines = getCurrentLines()
console.log(currentLines)
processReceivedMessage(currentLines)
speakMessageHandler(currentLines)
},
[selectedSlideDocs]
)
Expand Down
200 changes: 101 additions & 99 deletions src/features/chat/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,121 +9,123 @@ import slideStore from '@/features/stores/slide'
import { goToSlide } from '@/components/slides'

/**
* 文字列を処理する関数
* 受け取ったメッセージを処理し、AIの応答を生成して発話させる
* @param receivedMessage 処理する文字列
* @param sentences 返答を一文単位で格納する配列
* @param aiTextLog AIの返答ログ
* @param tag タグ
* @param isCodeBlock コードブロックのフラグ
* @param codeBlockText コードブロックのテキスト
*/
export const processReceivedMessage = async (
receivedMessage: string,
sentences: string[] = [],
aiTextLog: Message[] = [],
tag: string = '',
isCodeBlock: boolean = false,
codeBlockText: string = ''
) => {
export const speakMessageHandler = async (receivedMessage: string) => {
const ss = settingsStore.getState()
const hs = homeStore.getState()
const currentSlideMessages: string[] = []

Comment on lines 16 to 19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

型安全性の向上が必要です。

sshs変数の型がanyとして推論されています。型安全性を高めるために、これらの変数に明示的な型アノテーションを追加することを推奨します。

以下のように型を指定することを検討してください:

- const ss = settingsStore.getState()
- const hs = homeStore.getState()
+ const ss: SettingsState = settingsStore.getState()
+ const hs: HomeState = homeStore.getState()

SettingsStateHomeStateは適切なインターフェースまたは型エイリアスに置き換えてください。

Committable suggestion was skipped due to low confidence.

// 返答内容のタグ部分と返答部分を分離
const tagMatch = receivedMessage.match(/^\[(.*?)\]/)
if (tagMatch && tagMatch[0]) {
tag = tagMatch[0]
receivedMessage = receivedMessage.slice(tag.length)
}
let isCodeBlock: boolean = false
let codeBlockText: string = ''
let logText: string = ''
let assistantMessage: string[] = []
let remainingMessage = receivedMessage
let prevRemainingMessage: string = ''
const addedChatLog: Message[] = []
const delimiter = '```'

while (remainingMessage.length > 0 || isCodeBlock) {
let sentence = ''
prevRemainingMessage = remainingMessage

if (remainingMessage.includes(delimiter)) {
// コードブロックの分割
isCodeBlock = true
const [first, ...rest] = remainingMessage.split(delimiter)
;[remainingMessage, codeBlockText] = [
first,
rest.join(delimiter).replace(/^\n/, ''),
]
} else if (remainingMessage == '' && isCodeBlock) {
// コードブロックの分割
let code = ''
const [first, ...rest] = codeBlockText.split(delimiter)
;[code, remainingMessage] = [first, rest.join(delimiter)]
addedChatLog.push({
role: 'assistant',
content: logText,
})
addedChatLog.push({
role: 'code',
content: code,
})

codeBlockText = ''
logText = ''
isCodeBlock = false
}

// 返答内容のタグ部分と返答部分を分離
let tag: string = ''
const tagMatch = remainingMessage.match(/^\[(.*?)\]/)
if (tagMatch?.[0]) {
tag = tagMatch[0]
remainingMessage = remainingMessage.slice(tag.length)
}

// 返答を一文単位で切り出して処理する
while (receivedMessage.length > 0) {
const sentenceMatch = receivedMessage.match(
/^(.+?[。..!?!?\n]|.{20,}[、,])/
const sentenceMatch = remainingMessage.match(
/^(.{1,19}?[。..!?!?\n]|.{20,}?[、,])/
)
if (sentenceMatch?.[0]) {
let sentence = sentenceMatch[0]
// 区切った文字をsentencesに追加
sentences.push(sentence)
// 区切った文字の残りでreceivedMessageを更新
receivedMessage = receivedMessage.slice(sentence.length).trimStart()

// 発話不要/不可能な文字列だった場合はスキップ
if (
!sentence.includes('```') &&
!sentence.replace(
/^[\s\u3000\t\n\r\[\(\{「[(【『〈《〔{«‹〘〚〛〙›»〕》〉』】)]」\}\)\]'"''""・、。,.!?!?::;;\-_=+~~**@@##$$%%^^&&||\\\//``]+$/gu,
''
)
) {
continue
}

// タグと返答を結合(音声再生で使用される)
let aiText = `${tag} ${sentence}`
console.log('aiText', aiText)
sentence = sentenceMatch?.[0]
// 区切った文字の残りでremainingMessageを更新
remainingMessage = remainingMessage.slice(sentence.length).trimStart()
}

if (isCodeBlock && !sentence.includes('```')) {
codeBlockText += sentence
continue
}
if (remainingMessage != '' && remainingMessage == prevRemainingMessage) {
sentence = prevRemainingMessage
remainingMessage = ''
}

if (sentence.includes('```')) {
if (isCodeBlock) {
// コードブロックの終了処理
const [codeEnd, ...restOfSentence] = sentence.split('```')
aiTextLog.push({
role: 'code',
content: codeBlockText + codeEnd,
})
aiText += `${tag} ${restOfSentence.join('```') || ''}`
// 発話不要/不可能な文字列だった場合はスキップ
if (
sentence == '' ||
sentence.replace(
/^[\s\u3000\t\n\r\[\(\{「[(【『〈《〔{«‹〘〚〛〙›»〕》〉』】)]」\}\)\]'"''""・、。,.!?!?::;;\-_=+~~**@@##$$%%^^&&||\\\//``]+$/gu,
''
) == ''
) {
continue
}
Comment on lines +29 to +91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

メッセージ処理ロジックの改善

メッセージ処理のロジックが大幅に改善されていますが、いくつかの提案があります:

  1. この大きなwhile文は複雑で理解しにくい可能性があります。処理を小さな関数に分割することを検討してください。

  2. コードブロックの処理とタグの抽出のロジックを別々の関数に抽出することで、メインのループをよりシンプルにできます。

  3. 正規表現を使用している箇所(例:行68-69)では、正規表現をより読みやすくするために、正規表現パターンを定数として定義することを検討してください。

以下のようなリファクタリングを提案します:

const CODE_BLOCK_DELIMITER = '```';
const SENTENCE_REGEX = /^(.{1,19}?[。..!?!?\n]|.{20,}?[、,])/;

function extractCodeBlock(message: string): [string, string] {
  const [first, ...rest] = message.split(CODE_BLOCK_DELIMITER);
  return [first, rest.join(CODE_BLOCK_DELIMITER).replace(/^\n/, '')];
}

function extractTag(message: string): [string, string] {
  const tagMatch = message.match(/^\[(.*?)\]/);
  if (tagMatch?.[0]) {
    return [tagMatch[0], message.slice(tagMatch[0].length)];
  }
  return ['', message];
}

// メインのwhile文内で使用
const [remainingMessage, codeBlockText] = isCodeBlock
  ? extractCodeBlock(remainingMessage)
  : [remainingMessage, ''];

const [tag, messageWithoutTag] = extractTag(remainingMessage);

これらの変更により、メインのループがより読みやすくなり、各処理の意図がより明確になります。


// AssistantMessage欄の更新
homeStore.setState({ assistantMessage: sentences.join(' ') })
// 区切った文字をassistantMessageに追加
assistantMessage.push(sentence)
// タグと返答を結合(音声再生で使用される)
let aiText = tag ? `${tag} ${sentence}` : sentence

codeBlockText = ''
isCodeBlock = false
} else {
// コードブロックの開始処理
isCodeBlock = true
;[aiText, codeBlockText] = aiText.split('```')
}
const aiTalks = textsToScreenplay([aiText], ss.koeiroParam) // TODO
logText = logText + ' ' + sentence

sentence = sentence.replace(/```/g, '')
speakCharacter(
aiTalks[0],
() => {
homeStore.setState({
assistantMessage: assistantMessage.join(' '),
})
hs.incrementChatProcessingCount()
// スライド用のメッセージを更新
currentSlideMessages.push(sentence)
},
() => {
hs.decrementChatProcessingCount()
currentSlideMessages.shift()
homeStore.setState({
slideMessages: currentSlideMessages,
})
}
)
} // while loop end
Comment on lines +93 to +119
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

メッセージ処理と発話ロジックの改善

メッセージの処理と発話ロジックが適切に実装されていますが、いくつかの改善点があります:

  1. speakCharacter関数のコールバック内でステート更新が複数回行われています。これらをまとめることで、パフォーマンスが向上する可能性があります。

  2. currentSlideMessagesの更新ロジックが複雑です。この部分を別の関数に抽出することで、メインのロジックがよりクリーンになります。

以下のようなリファクタリングを提案します:

function updateHomeStore(sentence: string, assistantMessage: string[]) {
  homeStore.setState({
    assistantMessage: assistantMessage.join(' '),
    slideMessages: [...homeStore.getState().slideMessages, sentence],
  });
  homeStore.getState().incrementChatProcessingCount();
}

// speakCharacter関数内で使用
speakCharacter(
  aiTalks[0],
  () => updateHomeStore(sentence, assistantMessage),
  () => {
    homeStore.getState().decrementChatProcessingCount();
    homeStore.setState(state => ({
      slideMessages: state.slideMessages.slice(1),
    }));
  }
);

これらの変更により、ステート更新のロジックがより整理され、可読性が向上します。

Comment on lines +15 to +119
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

関数の分割とモジュール化を検討してください

speakMessageHandler関数は大幅にリファクタリングされ、可読性が向上しました。しかし、まだ長く複雑な関数となっています。以下の改善を提案します:

  1. メッセージの解析、コードブロックの処理、タグの抽出などの機能を別々の関数に分割することを検討してください。これにより、各部分の責任が明確になり、テストや保守が容易になります。

  2. 繰り返されているコードパターン(例:行68-69の正規表現マッチング)をヘルパー関数として抽出することを検討してください。これにより、コードの重複を減らし、変更が必要な場合に1か所で修正できるようになります。

  3. エッジケースのエラー処理を改善することを検討してください。特に、予期しない入力や状態変化に対してより堅牢になるよう、適切な例外処理やエラーメッセージを追加することをお勧めします。

以下のような関数分割を提案します:

function parseCodeBlock(message: string): [string, string] {
  // コードブロックの解析ロジック
}

function extractTag(message: string): [string, string] {
  // タグ抽出ロジック
}

function parseSentence(message: string): [string, string] {
  // 文の解析ロジック
}

// メイン関数内で使用
const [remainingMessage, codeBlock] = parseCodeBlock(receivedMessage);
const [tag, messageWithoutTag] = extractTag(remainingMessage);
const [sentence, newRemainingMessage] = parseSentence(messageWithoutTag);

これらの変更により、speakMessageHandler関数の複雑さが軽減され、各部分の責任が明確になります。


const aiTalks = textsToScreenplay([aiText], ss.koeiroParam)
aiTextLog.push({ role: 'assistant', content: sentence })

// 文ごとに音声を生成 & 再生、返答を表示
const currentAssistantMessage = sentences.join(' ')

speakCharacter(
aiTalks[0],
() => {
homeStore.setState({
assistantMessage: currentAssistantMessage,
})
hs.incrementChatProcessingCount()
// スライド用のメッセージを更新
currentSlideMessages.push(sentence)
homeStore.setState({
slideMessages: currentSlideMessages,
})
},
() => {
hs.decrementChatProcessingCount()
currentSlideMessages.shift()
homeStore.setState({
slideMessages: currentSlideMessages,
})
}
)
} else {
// マッチする文がない場合、ループを抜ける
break
}
}
addedChatLog.push({
role: 'assistant',
content: logText,
})
homeStore.setState({
slideMessages: currentSlideMessages,
chatLog: [...hs.chatLog, ...addedChatLog],
})
}

/**
Expand Down
21 changes: 0 additions & 21 deletions src/features/googletts/googletts.ts

This file was deleted.

Loading
Loading