OpenHarmony开发者论坛

标题: [OpenHarmony学习计划] 亲戚称谓计算器 + LLM [打印本页]

作者: lishengzxc    时间: 2024-6-23 21:19
标题: [OpenHarmony学习计划] 亲戚称谓计算器 + LLM
[md]![0030086000741113711.20240623203749.20672203875181820224293013290081.png](https://forums-obs.openharmony.c ... r7qgrgdgradrfzp.png "0030086000741113711.20240623203749.20672203875181820224293013290081.png")

# 概述

本项目是是学习 [https://developer.huawei.com/con ... /101718800110527001](https://developer.huawei.com/con ... /101718800110527001) 后的一份练习 demo

实现了以下特性:

* 自定义组件:[https://developer.huawei.com/con ... rmonyos-guides-V5/3\_3\_u81ea\_u5b9a\_u4e49\_u7ec4\_u4ef6-V5](https://developer.huawei.com/con ... 4e49_u7ec4_u4ef6-V5)
* 状态管理:[https://developer.huawei.com/con ... state-management-V5](https://developer.huawei.com/con ... state-management-V5)
* 应用架构设计基础——MVVM模式:[https://developer.huawei.com/con ... rddetails/tutorials\_Next-BasicArchitectureDesignPart1](https://developer.huawei.com/con ... itectureDesignPart1)
* 应用架构设计基础——三层架构:[https://developer.huawei.com/con ... rddetails/tutorials\_Next-BasicArchitectureDesignPart2](https://developer.huawei.com/con ... itectureDesignPart2)
* 网络请求:[https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faxios](https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faxios)

接下来简要描述下开发流程,源码可以直接查看:[https://gitee.com/lishengzxc/ohms-next-relationship](https://gitee.com/lishengzxc/ohms-next-relationship)

# 开发流程

## 参考三层架构准备项目目录
![0030086000741113711.20240623204240.44787044389701801371695480030290.png](https://forums-obs.openharmony.c ... igi3n7l9744aa2k.png "0030086000741113711.20240623204240.44787044389701801371695480030290.png")


## 准备 UI

拆分 UI,实现计算显示部分组件 Display.ets 和 键盘部分 Ctrl.ets

### Display.ets

result 和 calcString 类似于 react 的 props 接受外部传入的值

```
@Component
export struct Display {
  @Prop result: string = '';
  @Prop calcString: string = '';

  build() {
    Column() {
      Column() {
        Text(this.result)
          .fontSize(25)
          .padding({ left: 16, right: 16 })
          .width('100%')
          .height('40%')
          .textAlign(TextAlign.End)
      }

      Column() {
        Text(this.calcString)
          .fontSize(35)
          .width('100%')
          .height('60%')
          .padding({ left: 16, right: 16 })
          .textAlign(TextAlign.End)
          .borderWidth({ bottom: 1 })
          .borderColor('#ccc')

      }

    }.width('100%').height('25%')
  }
}
```

### Ctrl.ets

使用 Grid 组件布局:[https://developer.huawei.com/con ... ment-create-grid-V5](https://developer.huawei.com/con ... ment-create-grid-V5)

需要注意 = 按钮需要占用 2 行;

按钮点击的时候执行父组件传入的方法,用来接受用户点了什么按钮

```
@Component
export struct Ctrl {
  @Prop loading: boolean = false;
  btns: string[][] = [
    ['Item', '父', '父亲'],
    ['Item', '母', '母亲'],
    ['Function', '回退', 'back', 'gray'],
    ['Function', '清空', 'clear', 'black'],
    ['Item', '兄', '哥哥'],
    ['Item', '姐', '姐姐'],
    ['Item', '夫', '老公'],
    ['Item', '妻', '老婆'],
    ['Item', '弟', '弟弟'],
    ['Item', '妹', '妹妹'],
    ['Function', '互换', 'change', 'blue'],
    ['Function', '=', '=', 'red'],
    ['Item', '子', '儿子'],
    ['Item', '女', '女儿'],
    ['Function', '?', '?']
  ];
  onBtnClick?: (data: string[]) => void
  layoutOptions: GridLayoutOptions = {
    regularSize: [1, 1],
    onGetRectByIndex: (index: number) => {
      if (index === 11) {
        // 控制 = 按钮占有 2 行
        return [2, 3, 2, 1]
      }

      return [index / 4 - 1, index / 4 - 1, 1, 1];
    }
  }

  build() {
    Grid(undefined, this.layoutOptions) {
      ForEach(this.btns, (data: string[]) => {
        GridItem() {

          if (data[1] === '=') {
            Button(data[1])
              .type(ButtonType.Normal)
              .onClick(() => {
                if (this.loading) return;
                this.onBtnClick?.(data)
              })
              .width('100%')
              .height('100%')
              .borderRadius(20)
              .opacity(this.loading ? 0.3 : 1)
              .backgroundColor(data[3])

          } else {
            Button(data[1])
              .type(ButtonType.Normal)
              .onClick(() => {
                if (this.loading) return;
                this.onBtnClick?.(data)
              })
              .width('100%')
              .height('100%')
              .borderRadius(20)
              .backgroundColor(data[3])
          }
        }
      }, (data: string) => data[2])
    }
    .columnsGap(10)
    .rowsGap(10)
    .rowsTemplate('1fr 1fr 1fr 1fr')
    .padding(10)
    .columnsTemplate('1fr 1fr 1fr 1fr')
    .width('100%')
    .height('75%')
  }
}
```

### 组合他们

<pre><br class="Apple-interchange-newline"/></pre>

```
import { Display } from '../view/Display';
import { Ctrl } from '../view/Ctrl';
import { llm } from '@ohos/commons';

@Preview
@Component
export struct Calculator {
  @State result: string = '';
  @State calc: string[] = [];
  @State loading: boolean = false;
  onBtnClick: (data: string[]) => string | void = (data: string[]) => {
    const type = data[0];
    const value = data[2];

    if (type === 'Function') {
      if (value === 'back') {
        this.calc.pop();
      }
      if (value === '=') {
        this.onCalc();
      }
      if (value === 'clear') {
        this.calc = [];
        this.result = '';
      }

    }

    if (type === 'Item') {
      this.calc.push(value);
    }
  }

  onCalc() {
    if (!this.calc.length) return;
    if (this.calc.length === 1) {
      this.result = this.calc[0]
    }

    this.askLLM();
  }

  askLLM() {
    this.loading = true;

    llm({
      system: '你是一个亲戚称谓计算器,请根据我的提问回复我 应该称呼对方为什么,比如爸爸的爸爸,请回复我 {\"data\":\"爷爷\"}',
      query: this.calc.join('的'),
      appKey: 'sk-xxx', // 请使用真实的大模型 appkey
    }).then((res) => {
      console.log('llm', res);

      this.calc = [JSON.parse(res as string).data]
      this.result = this.calc[0];
      this.loading = false;
    })

  }

  build() {
    Column() {
      Display({ result: this.result, calcString: this.calc.join('的') })
      Ctrl({ onBtnClick: this.onBtnClick, loading: this.loading })
    }.width('100%').height('100%')
  }
}
```

## 如何计算

> 我没有使用以下常见的本地称谓树的方式来表达称谓的数据结构
>
> ```
> {
>   "爸爸": {
>     "爸爸": "爷爷",
>     "妈妈": "奶奶",
>     "哥哥": "伯父",
>     "弟弟": "叔叔",
>     "姐姐": "姑妈",
>     "妹妹": "姑妈",
>     "丈夫": "未知",
>     "妻子": "妈妈",
>     "儿子": { "older": "哥哥", "middle": "我", "younger": "弟弟" },
>     "女儿": { "older": "姐姐", "middle": "我", "younger": "妹妹" }
>   }
> }
> ```
>
> 而是为了实践网络请求的场景,直接问大模型拿到结果

### 准备大模型账号

我使用的是通义千问,这个不特别展开如何申请账号,可以自己搜索阿里云-百炼自己摸索下

## 封装请求方法

```
import axios, { AxiosError, AxiosResponse } from '@ohos/axios'

interface iParams {
  system: string,
  query: string,
  appKey: string,

}

interface iMsg {
  role: string,
  content: string
}

interface iInput {
  messages: iMsg[]
}

interface iData {
  model: string,
  input: iInput
}

interface iOutput {
  finish_reason: string,
  text: string
}

interface iRes {
  output: iOutput
}

export default (params: iParams) => {

  return axios.post<string, AxiosResponse<iRes>, iData>('https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation', {
    "model": "qwen-max",
    "input": {
      "messages": [
        {
          "role": "system",
          "content": params.system
        },
        {
          "role": "user",
          "content": params.query
        }
      ]
    },
  }, {
    headers: {
      'Authorization': `Bearer ${params.appKey}`,
      'Content-Type': 'application/json'
    }
  })
    .then((response: AxiosResponse<iRes>) => {
      console.info('axios success', JSON.stringify(response));
      return response.data.output.text;
    })
    .catch((error: AxiosError) => {
      console.info('axios error', JSON.stringify(error));
    });
}
```

## 请求大模型

```
llm({
      system: '你是一个亲戚称谓计算器,请根据我的提问回复我 应该称呼对方为什么,比如爸爸的爸爸,请回复我 {\"data\":\"爷爷\"}',
      query: this.calc.join('的'),
      appKey: 'sk-xxx'
    })
```

# 构建运行



![0030086000741113711.20240623210338.41016412977744940621048475927143.gif](https://forums-obs.openharmony.c ... mh1hwzzamgxv0p2.gif "0030086000741113711.20240623210338.41016412977744940621048475927143.gif")


[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5