'웹 개발/Problems'에 해당되는 글 11건

  1. 2024.06.04 AI와 통화기능 구현
  2. 2024.05.11 SSH 접속시 no matching host key type found. Their offer: ssh-rsa 에러
  3. 2024.01.16 Decimal calculation in Javascript 1
  4. 2021.07.02 AWS s3 404 Not Found 에러
  5. 2021.06.02 삽질: axios , 삼항연산자, 두개이상의 경우의 수
  6. 2021.06.02 React img 태그 src={require()}
  7. 2021.04.28 윈도우에서 chmod 등 리눅스 명령어가 필요할 때
  8. 2021.04.28 문자열 안에 html 태그가 있고 그걸 적용 시키기

AI와 통화기능 구현

웹 개발/Problems 2024. 6. 4. 17:37

AI와 통화기능을 구현하기 위해 다음과 같은 스텝들이 필요했다.

1. 나의 목소리를 텍스트로 변환.

2. 변환된 텍스트를 AI프롬프트에 입력 후 스트림 형식의 텍스트 데이터 받기.

3. 스트림 형식의 텍스트 데이터를 음성으로 변환 후 스트림 형식으로 클라이언트로 전달.

4. audio/mpeg 형식의 파일을 응답받은 클라이언트는 플레이 가능 즉시 재생.

5. 유저로 하여금 심심하지 않게 모든 오디오에 대해 비주얼라이져를 통해 소리를 시각적으로 표현.

각 스텝마다 문제점들이 발생했는데 이 문제점들을 어떻게 해결했는지 정리해보겠다.

1. 나의 목소리를 텍스트로 변환.

이부분은 web api에서 제공하는 SpeechRecognition 객체를 이용하여 구현하였다. 처음에는 매우 동작이 잘 되어 안심을 했지만, 모바일에서는 잘 되지 않는 문제가 발생하였는데, 이것은 나중에 비주얼라이저와 같이 실행할 경우에 안되는것을 알아냈다. 또한 특정 모바일에서 인식 간격이 너무 짧아 말을 천천히하면 중간에 speechend 리스터가 호출되어 버려서 프롬프트가 짤리는 현상이 발생했다. 이 문제는 추후에 버튼을 추가해 버튼을 누르기 전까지는 계속 인식을 하도록 변경하는 방향으로 했다. 당장 AI를 손 볼 시간은 없기 때문에..

2. 변환된 텍스트를 AI프롬프트에 입력 후 스트림 형식의 텍스트 데이터 받기. / 3. 스트림 형식의 텍스트 데이터를 음성으로 변환 후 스트림 형식으로 클라이언트로 전달

스트림 형식의 텍스트 데이터는 기존에는 라이브러리를 사용해서 처리하고 있었다. 하지만 라이브러리에서 토큰 마다 이벤트 함수가 호출되기는 하는데, 이것을 바로 바로 음성으로 변환을 해버리면 단어가 짤리는 경우도 있고, 무엇보다 요청을 너무 많이 해버리게 된다. 따라서 이걸 문장 단위로 짤라서 음성 변환 요청을 하려고 시도를 했는데, 구조적으로 라이브러리에서 제공하는 토큰 콜백함수만으로 이를 구현하기가 매우 힘들었다. 왜냐면 비동기적으로 스트림형태의 데이터를 받고 있는데 이를 문장단위로 짜른 변수를 음성으로 변환하고, 또 음성으로 변환되는 스트림형태의 오디오 데이터를 클라이언트에게 순차적으로 보내주어야 했기 때문이다. 이를 해결하기 위해 결국 ReadableStream의 pipeThrough 메서드를 사용해야 한다는 것을 깨달았고, 결국 라이브러리를 분석하여 스트림형태의 데이터를 텍스트로 변환하는 부분만 추출하여 3개의 트랜스포머를 만들어서 순차적으로 텍스트 변환, 문장 생성, 문장 음성 변환 단계를 거친 후 클라이언트에게 성공적으로 데이터를 전달 해줄 수 있었다.

4. audio/mpeg 형식의 파일을 응답받은 클라이언트는 플레이 가능 즉시 재생. / 5. 유저로 하여금 심심하지 않게 모든 오디오에 대해 비주얼라이져를 통해 소리를 시각적으로 표현.

이부분은 자바스크립트 객체인 Audio객체를 통해 손쉽게 구현할 수 있었다. 해당 객체가 URL을 통해 audio 형식의 데이터를 받으면 자동으로 실시간 스트리밍 기능을 제공하기 때문에 큰 어려움은 없었다. 문제는 비주얼라이저였는데, 처음 사용했던 라이브러리는 나의 마이크로 입력되는 소리는 손쉽게 비주얼라이징 했지만, 서버로 부터 응답받은 오디오를 비주얼라이징 객체에 넘겨주려고했는데 Audio 객체의 play 메서드와 충돌이 일어났다. 결국 해결방법을 찾지 못한 채 다른 라이브러리인 audiomotion-analyzer를 사용해보았는데, 일단 데스크톱에서는 매우 잘 동작했다. 하지만 모바일에서는 SpeechRecognition과 비주얼라이저를 같이 사용하면 작동을 안하는 현상이 있어서, 모바일 기기에서는 마이크 입력할 때는 비주얼라이저를 꺼주었다. 아이폰에서는 잘 동작을 했지만 안드로이드에서만 이러한 현상이 나타났는데, 이는 라이브러리가 어떻게 동작하는지와 안드로이드 환경의 SpeechRecognition이 동작하는 원리를 알아야 할거 같아서 일단 미래의 내가 해결하기로 했다..

이번 개발을 통해 stream형태의 데이터를 어떻게 다룰지 감을 잡은 거 같다.

:

SSH 접속시 no matching host key type found. Their offer: ssh-rsa 에러

웹 개발/Problems 2024. 5. 11. 18:58

SSH 연결 중에 나타난 "no matching host key type found" 에러는 서버가 제공하는 호스트 키 유형과 클라이언트(여기서는 맥북)가 지원하는 키 유형이 일치하지 않을 때 발생합니다. 최근 SSH 프로토콜에서는 보안이 강화되면서 일부 구형 키 유형(예: `ssh-rsa`)이 기본적으로 지원되지 않을 수 있습니다.

이 문제를 해결하기 위해 SSH 클라이언트 설정에서 `ssh-rsa` 키 유형을 명시적으로 허용하는 방법을 시도할 수 있습니다. 맥북에서는 다음 단계를 따라 설정할 수 있습니다:

1. 터미널을 열고 SSH 설정 파일을 편집: Terminal을 열고 아래 명령어로 SSH 설정 파일을 편집합니다.

sudo nano /etc/ssh/ssh_config

2. 호스트 키 알고리즘 추가: 파일의 적당한 위치에 다음 두 줄을 추가합니다.

HostkeyAlgorithms +ssh-rsa
PubkeyAcceptedKeyTypes +ssh-rsa

이렇게 하면 `ssh-rsa` 키 유형을 사용할 수 있습니다.

3. **파일 저장 및 종료**: 변경 사항을 저장하고 nano 편집기를 종료합니다(`Ctrl+O`, `Enter`, `Ctrl+X`).

4. **SSH 다시 시도**: 이제 다시 SSH로 연결을 시도해보세요.

ssh dlghwns0314@dlghwns0314.gabia.io

이 설정 변경 후에도 문제가 지속된다면, 서버 관리자에게 서버의 SSH 설정을 업데이트하여 보다 현대적인 키 유형을 지원하도록 요청하는 것도 고려할 수 있습니다.

:

Decimal calculation in Javascript

웹 개발/Problems 2024. 1. 16. 02:10

자바스크립트를 이용해 실수의 연산을 하다보면 소수점이 이상하게 나타나는 경우가 있다. 컴퓨터는 기본적으로 이진수를 이용하기 때문에 실수를 다루는 방식은 컴퓨팅 언어마다 다르다. Javascript는 모든 숫자를 IEEE 754 표준을 따르는 배정밀도 64 비트 부동 소수점 형식으로 표현하며, 이 형식은 실수를 근사치로 저장한다.

따라서 0.6 / 0.2 와 같은 연산을 실행 해보면 3이 아닌 2.999999999996 와 같은 예측에서 벗어난 값이 나온다. 이를 해결하기 위해 실수를 정수로 바꾼 후에 연산을 하고 다시 원래 실수로 돌려놓는 방법을 사용한다.

function findMostDecimal(array: number[]): number {
    let maxDecimal = 0;
    let numberWithMostDecimals = array[0];

    for (let num of array) {
        let decimalCount = countDecimalPlaces(num);
        if (decimalCount > maxDecimal) {
            maxDecimal = decimalCount;
            numberWithMostDecimals = num;
        }
    }

    return numberWithMostDecimals;
}

먼저 실수들 중에서 가장 소수점 자리가 큰 실수를 알아낸다.

export function countDecimalPlaces_10(value: number): number {
    if (!isFinite(value)) return 0; //무한대나 NaN인 경우, 0을 반환
    let text = value.toString();
    let index = text.indexOf('.');
    if (index === -1) return 1; //소수점이 없는 경우, 1을 반환. (정수이므로)
    let decimalPart = text.substring(index + 1);
    return Math.pow(10, decimalPart.length);
}

실수를 정수로 바꾸기 위해 지수를 구한다.

 

export function operateDecimals(
    value1: number,
    value2: number,
    operation: Tadjuster_method,
) {
    const mostDecimal = findMostDecimal([value1, value2]);
    const factor = countDecimalPlaces_10(mostDecimal);
    const newValue1 = Math.floor(value1 * factor);
    const newValue2 = Math.floor(value2 * factor);
    let result;
    switch (operation) {
        case 'add':
            result = (newValue1 + newValue2) / factor;
            break;
        case 'subtract':
            result = (newValue1 - newValue2) / factor;
            break;
        case 'multiply':
            result = (newValue1 * newValue2) / (factor * factor);
            break;
        case 'divide':
            result = newValue1 / newValue2; // 나눗셈의 경우, factor로 나눌 필요 없음
            break;
        default:
            throw new Error('Invalid operation');
    }
    return result;
}

이렇게 정수로 바꾼다음 연산을 진행한 뒤 지수를 다시 나눠주면 원하는 값을 구할 수 있다.

:

AWS s3 404 Not Found 에러

웹 개발/Problems 2021. 7. 2. 00:20

로컬 호스트에서는 잘 작동하지만 s3에 배포하고 나서 특정 페이지에 접근하자 404 Not Found에러 페이지가 렌딩 되었다. 하필 소셜 로그인 페이지에서 에러가 발생하여 소셜로그인 하려먼 s3에서 다른 특별한 설정을 해야 하나 하고 고심 하고 있던 찰나, 한 유튜브 영상으로 s3 배포하는 과정을 시청하던중 

이 부분을 설정하는 것을 보았다. 왠지 이것 때문에 그럴 수 도 있겠다 싶어서 바로 기본값인 error.html에서 index.html로 바꾸자 에러가 해결되었다... 항상 주의 해야겠다.

:

삽질: axios , 삼항연산자, 두개이상의 경우의 수

웹 개발/Problems 2021. 6. 2. 20:19
import React, { useState, useEffect } from 'react';
import img from 'react';
import { Container } from './UserInfoMatchingDetailStyled.jsx';
const axios = require('axios');
const MatchingDetail = ({ participant }) => {
	let [userData, setUserData] = useState(null);
	let [tier, setTier] = useState('');
	let [kda, setKda] = useState(0);
	let [rank, setRank] = useState('');
	let [isLoading, setIsLoading] = useState(null);
	console.log('participant before useEffect: ', participant);
	useEffect(() => {
		console.log('participant: ');
		console.log(participant);
		if (participant) {
			setIsLoading(true);
			console.log('participant name: ');
			console.log(participant.summonerName);
			axios
				.get(
					`${
						process.env.REACT_APP_SERVER_URL
					}/utils/history?name=${'미키주일'}`,
				)
				.then((response) => {
					let data = response.data;
					let tempKda = 0;
					console.log('data: ');
					console.log(data);
					setTier(
						data.league_solo === undefined ? 'UNRANKED' : data.league_solo.tier,
					);
					setRank(data.league_solo === undefined ? '' : data.league_solo.rank);
					if (data.matches) {
						data.matches.forEach((match) => {
							tempKda += match.kda;
						});
						tempKda = tempKda / data.matches.length;
						console.log('tempKda before Math.floor: ');
						console.log(tempKda);
						tempKda = Math.floor(tempKda * 100) / 100;
						console.log('tempKda after Math.floor: ', tempKda);
						setKda(tempKda);
					}
					setIsLoading(false);
					setUserData(data);
				})
				.catch((err) => {
					console.log(err);
					setIsLoading(false);
				});
		}
	}, [participant]);
	// useEffect(() => {
	// 	if (userData.league_solo) {
	// 		console.log(userData);
	// 		setTier(userData.league_solo.tier);
	// 	}
	// }, [userData]);
	switch (isLoading) {
		case null: {
			return '';
		}
		case true: {
			return (
				<Container>
					<fieldset>
						{participant !== null ? <div>{participant.summonerName}</div> : ''}
						{'Loading...'}
						<button
							onClick={() => {
								setIsLoading(null);
							}}
						>
							close
						</button>
					</fieldset>
				</Container>
			);
		}
		case false: {
			return (
				<Container>
					<fieldset>
						{participant !== null ? <div>{participant.summonerName}</div> : ''}
						<div>
							<img
								src={require(`../../Images/tierIcons/${tier}.png`).default}
								width={'100'}
								height={'100'}
							/>
							{tier}
							{'   '}
							{rank}
							<div>{kda !== 0 ? kda : ''}</div>
						</div>
						<button
							onClick={() => {
								setIsLoading(null);
							}}
						>
							close
						</button>
					</fieldset>
				</Container>
			);
		}
	}
};

export default MatchingDetail;

participant 변수가 들어오면 axios를 실행해 그 지연시간동안 loading...을 보여줘야했다.

로딩인지 아닌지 확인하기 위해 isLoading 상태를 설정해주었지만 participant 변수가 들어오는 순간에는 isLoading이 기본값인 false로 인식되서 reference 에러가 났다. 생각을 해보니 이것은 세가지의 경우의수가 있는 문제라서 삼항연산자로 해결을 하기 매우 어렵다고 판단해 swtich문을 써서 리턴을 각각 다르게 했다.

경우 1. participant가 안들어 온 경우

경우 2. participant는 들어왔지만 아직 로딩이 안된 경우

경우 3. participant도 들어 오고 로딩이 끝나 데이터가 들어온 경우

따라서 삼항연산자로는 힘들다. 왜냐하면 경우 2번에서 participant가 들어오는 순간에는 로딩 변수의 기본값이 false인데, axios 실행전 setIsLoading(true)를 실행하지만 그것을 실행하기 전에 이미 false로 인식이 되어 로딩이 끝난줄 알고 데이터를 뿌릴려고 했다. 리액트에서 경우가 많아질 때 절대로 삼항 연산자는 피해야 겠다.

:

React img 태그 src={require()}

웹 개발/Problems 2021. 6. 2. 20:08
<img
src={require(경로).default}
/>

require()함수 호출 후 .default 속성으로 불러 와야 이미지가 불러와진다.

구글링해서 검색해보면 사람들은 .default를 추가 안했던데 어떻게 한건진 모르겠다... 

:

윈도우에서 chmod 등 리눅스 명령어가 필요할 때

웹 개발/Problems 2021. 4. 28. 23:49

git bash를 사용하면 된다.

.ps git bash에서 paste기능은 키보드 insert키를 누르면 된다.

:

문자열 안에 html 태그가 있고 그걸 적용 시키기

웹 개발/Problems 2021. 4. 28. 22:43
let str = "안녕하세요 <br> 반갑습니다"

return (<div dangerouslySetInnerHTML={{ __html: str }}></div>)

리액트에서 지원하는 dangerouslySetInnerHTML props를 사용하되, {__html: 문자열} 이라는 객체 형태로 넣어 주면 된다.

: