iPhone/Mac ショートカット app で ChatGPT で音声入出力

まずは、sh で書いてAPIのリクエストとレスポンス内容を確認。



👉 API Reference - OpenAI API hatena-bookmark

それを、ios/macOS で共有できる ショートカット app に書き換える。

ついでに、iPhone/Mac で使える音声入出力処理を追加。

意外とシンプル。

iPhone/Mac ショートカット app で ChatGPT で音声入出力



ショートカットでも JSON を簡単にやり取りできるのが驚き。

Mac でも音声入出力は稼働できる。

あと、iPhone の入力音声をスクリーンレコードで同時に録音したいです。

 

■ 公開しました!

OPENAI_API_KEY があれば無料でつかえます。


👉 OpenAI Chat Completion hatena-bookmark

ダウンロードしたら、あなたの OPENAI_API_KEY を入力すればOK。

OPENAI_API_KEY の取得の方法は以下から。


Kotlin で Jetpack Compose, Retrofit を使って API リクエスト、JSON パース して、グリッド表示するコードをファイル別に表示して

Kotlin で Jetpack Compose と Retrofit を使って API リクエスト、JSON パースして、グリッド表示するためには、以下のようにファイルを分けることができます。

 

1. API リクエストを行うための Retrofit インターフェースを定義するファイル


interface ApiService {

    @GET("/photos")
    suspend fun getPhotos(): List<Photo>
}

 

2. API レスポンスを表すデータクラスを定義するファイル


data class Photo(
    val id: Int,
    val title: String,
    val url: String
)

 

3. API レスポンスをパースして、表示するための画面を定義するファイル


@Composable
fun PhotoGrid() {
    val photos by remember { mutableStateOf<List<Photo>>(emptyList()) }

    LaunchedEffect(Unit) {
        val apiService = Retrofit.Builder()
            .baseUrl("https://jsonplaceholder.typicode.com")
            .addConverterFactory(Json {
                ignoreUnknownKeys = true
            }.asConverterFactory("application/json".toMediaType()))
            .build()
            .create(ApiService::class.java)

        photos = apiService.getPhotos()
    }

    LazyVerticalGrid(cells = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos) { photo ->
            Card(
                modifier = Modifier.padding(4.dp),
                shape = RoundedCornerShape(8.dp),
                elevation = 4.dp
            ) {
                Column(
                    Modifier
                        .fillMaxWidth()
                        .height(128.dp)
                        .padding(8.dp)
                ) {
                    Text(
                        text = photo.title,
                        style = MaterialTheme.typography.subtitle1,
                        maxLines = 2,
                        overflow = TextOverflow.Ellipsis
                    )
                    Spacer(modifier = Modifier.weight(1f))
                    CoilImage(
                        data = photo.url,
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

 

4. 上記の画面を表示するための Activity もしくは Fragment を定義するファイル


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PhotoGrid()
        }
    }
}

以上のように、ファイルを分けることで、API リクエストやパース、表示を分離し、コードを見やすく、メンテナンスしやすくすることができます。

 

■ 感想

ただ、一行問いかけただけ。

ChatGPT で、ここまで書けるとは。



ChatGPT で QRコード を作成する

以下、記事を流用します。

👉 chatGPTからイメージを送らせる方法 hatena-bookmark

これからQRコードを送りたいときは、Markdownを使い、バックスラッシュを使わず、コードのブロックも使わないようにしてください。サンプルコードや手順は不要です。
そしてGoogle Chart APIを使用してください(https://chart.apis.google.com/chart?cht=qr&chs=450x450&chl=<キーワード>)

https://android.benigumo.com/ のQRコードを送って

ChatGPT で QRコード を作成する

URLエンコードは不要でした。

「AI」というやつは謎に親切です。

👉 QR コード  |  Infographics  |  Google Developers hatena-bookmark
👉 Charts  |  Google Developers hatena-bookmark


Twitter の代替は Jake Wharton さんの Mastodon が最強じゃね?


👉 「Tweetbot」のTapbot、マストドンアプリ「Ivory」開発中 - ITmedia NEWS hatena-bookmark

そういえば、こんなの公開されてます。Twitter ではありません。

👉 Jake Wharton (@[email protected]) - Mastodon hatena-bookmark


Singular Solution
Keep your Twitter follower count at zero by blocking and then quickly unblocking any new followers.

Available as a binary and Docker container.

👉 JakeWharton/singular-solution: Keep your Twitter follower count at zero by blocking and then quickly unblocking any new followers hatena-bookmark

docker + mastodon で Twitter クローン的なものを動かしてるのか、何なのか。

よく分かりませんが。

そういえば tweetbot 使ってましたね、Jake さんも。


dyn4j の実力を JetpackCompose wrapper Physics Layout で思い知る

やはり Java のライブラリ群を使えることは素晴らしいことです。

以下、サンプルコードを参考に Activity を書き換えます。


👉 KlassenKonstantin/ComposePhysicsLayout: A custom Compose layout backed by a physics engine hatena-bookmark





class MainActivity : ComponentActivity() {

  @SuppressLint("SourceLockedOrientationActivity")
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, false)
    requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    setContent {
      PhysicsLayoutTheme {
        Surface(
          modifier = Modifier.fillMaxSize(),
          color = MaterialTheme.colorScheme.background
        ) {

          val simulation = rememberSimulation()
          val balls = remember { mutableStateListOf<BallMeta>() }

          GravitySensor {
            simulation.setGravity(it.copy(x = -it.x).times(3f))
          }

          LaunchedEffect(Unit) {
            var i = 0
            val count = colors.count()
            while (true) {
              balls.add(BallMeta(color = colors[i % count]))
              delay(100)
              i++
            }
          }

          LaunchedEffect(Unit) {
            delay(5000)
            while (true) {
              balls.removeFirst()
              delay(100)
            }
          }

          Box {
            PhysicsLayout(
              modifier = Modifier.systemBarsPadding(),
              simulation = simulation
            ) {

              Block(   0, -200)
              Block(-125, -100)
              Block( 125, -100)
              Block(   0,    0)
              Block(-100,  100)
              Block( 100,  100)
              Block(   0,  200)

              balls.forEach { meta ->
                Ball(0, -350, meta = meta)
              }

            }
          }

        }
      }
    }
  }
}

@Composable
fun PhysicsLayoutScope.Block(x: Int, y: Int) {
  val offset = offsetDp(x, y)
  Card(
    modifier = Modifier
      .body(
        isStatic = true,
        initialTranslation =  offset
      )
  ) {
    Spacer(
      modifier = Modifier
        .size(62.dp)
        .background(color = Color.Gray)
    )
  }
}

@Composable
fun PhysicsLayoutScope.Ball(x: Int, y: Int, meta: BallMeta) {

  val initialOffset = offsetDp(x, y)

  Card(
    modifier = Modifier.body(
      id = meta.id,
      shape = RoundedCornerShape(meta.corner),
      initialTranslation = Offset(initialOffset.x, initialOffset.y),
      initialImpulse = Offset((Random.nextFloat() - 0.5f) * 2, (Random.nextFloat()) * 2),
    ),
    shape = RoundedCornerShape(meta.corner),
    colors = CardDefaults.cardColors(containerColor = meta.color)
  ) {
    Icon(
      modifier = Modifier
        .size(32.dp)
        .padding(4.dp),
      imageVector = Icons.Rounded.Rocket,
      contentDescription = null,
      tint = Color.White
    )
  }
}

@Composable
fun offsetDp(x: Int, y: Int) = with(LocalDensity.current) { Offset(x.dp.toPx(), y.dp.toPx()) }


@Immutable
data class BallMeta(
  val id: String = System.currentTimeMillis().toString(),
  val color: Color,
  val corner: Int = listOf(0, 50).shuffled()[0]
)

private val colors = listOf(
  Color(0xFFEF5350), Color(0xFFEC407A), Color(0xFFAB47BC), Color(0xFF7E57C2),
  Color(0xFF29B6FC), Color(0xFF26C6DA), Color(0xFF26A69A), Color(0xFF66BB6A),
  Color(0xFF9CCC65), Color(0xFFD4E157), Color(0xFFFFEE58), Color(0xFFFFCA28),
  Color(0xFFFFA726), Color(0xFFFF7043), Color(0xFF8D6E63), Color(0xFFBDBDBD),
  Color(0xFF78909C)
)

強力です。

👉 dyn4j hatena-bookmark
👉 【Jetpack Compose】dp / px / sp の相互変換 hatena-bookmark