@liujitcn/kratos-taro-app-system 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/build.mjs +40 -0
- package/package.json +36 -0
- package/src/api/base/ai_message.ts +377 -0
- package/src/api/base/ai_session.ts +85 -0
- package/src/api/base/ai_tool.ts +23 -0
- package/src/build.ts +13 -0
- package/src/index.ts +14 -0
- package/src/pages.ts +26 -0
- package/src/rpc/base/v1/ai_message.ts +98 -0
- package/src/rpc/base/v1/ai_session.ts +214 -0
- package/src/rpc/base/v1/ai_tool.ts +74 -0
- package/src/rpc/common/v1/enum.ts +71 -0
- package/src/rpc/google/protobuf/timestamp.ts +115 -0
- package/src/types/assets.d.ts +4 -0
- package/src/views/pages/my/my.scss +134 -0
- package/src/views/pages/my/my.tsx +82 -0
- package/src/views/pagesMember/ai/components/Composer.tsx +71 -0
- package/src/views/pagesMember/ai/components/SessionDrawer.tsx +100 -0
- package/src/views/pagesMember/ai/components/WelcomePanel.tsx +75 -0
- package/src/views/pagesMember/ai/components/composer.scss +114 -0
- package/src/views/pagesMember/ai/components/session-drawer.scss +135 -0
- package/src/views/pagesMember/ai/components/welcome-panel.scss +284 -0
- package/src/views/pagesMember/ai/index.scss +130 -0
- package/src/views/pagesMember/ai/index.tsx +945 -0
- package/src/views/pagesMember/ai/stream.ts +152 -0
- package/src/views/pagesMember/profile/profile.scss +135 -0
- package/src/views/pagesMember/profile/profile.tsx +159 -0
- package/src/views/pagesMember/settings/settings.scss +56 -0
- package/src/views/pagesMember/settings/settings.tsx +60 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Image, ScrollView, Text, View } from '@tarojs/components'
|
|
2
|
+
import {
|
|
3
|
+
formatSrc,
|
|
4
|
+
navigateAppRoute,
|
|
5
|
+
navigateToLogin,
|
|
6
|
+
resolveBundledAsset,
|
|
7
|
+
useUserStore,
|
|
8
|
+
} from '@liujitcn/kratos-taro-app-core'
|
|
9
|
+
import './my.scss'
|
|
10
|
+
|
|
11
|
+
const defaultAvatar = resolveBundledAsset('static/images/avatar.png')
|
|
12
|
+
const centerBackground = resolveBundledAsset('static/images/center_bg.png')
|
|
13
|
+
|
|
14
|
+
/** 个人中心首页。 */
|
|
15
|
+
export default function MyPage() {
|
|
16
|
+
const profile = useUserStore((state) => state.userInfo)
|
|
17
|
+
const isLoggedIn = useUserStore((state) => state.isAuthenticated())
|
|
18
|
+
const ensureAuthenticated = useUserStore((state) => state.ensureAuthenticated)
|
|
19
|
+
|
|
20
|
+
const openAuthenticatedPage = (route: string) => {
|
|
21
|
+
if (!ensureAuthenticated()) {
|
|
22
|
+
navigateToLogin()
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
navigateAppRoute(route)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ScrollView
|
|
30
|
+
enableBackToTop
|
|
31
|
+
scrollY
|
|
32
|
+
className='my-viewport'
|
|
33
|
+
style={{ backgroundImage: `url(${centerBackground})` }}
|
|
34
|
+
>
|
|
35
|
+
<View className={`my-profile${process.env.TARO_ENV === 'weapp' ? ' my-profile--weapp' : ''}`}>
|
|
36
|
+
{isLoggedIn && profile ? (
|
|
37
|
+
<View className='my-overview'>
|
|
38
|
+
<View onClick={() => navigateAppRoute('app/profile')}>
|
|
39
|
+
<Image
|
|
40
|
+
className='my-avatar'
|
|
41
|
+
src={profile.avatar ? formatSrc(profile.avatar) : defaultAvatar}
|
|
42
|
+
mode='aspectFill'
|
|
43
|
+
/>
|
|
44
|
+
</View>
|
|
45
|
+
<View className='my-meta'>
|
|
46
|
+
<View className='my-nickname'>{profile.nick_name}</View>
|
|
47
|
+
<View className='my-extra' onClick={() => navigateAppRoute('app/profile')}>
|
|
48
|
+
<Text className='my-update'>更新头像昵称</Text>
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
</View>
|
|
52
|
+
) : (
|
|
53
|
+
<View className='my-overview'>
|
|
54
|
+
<View onClick={() => navigateToLogin()}>
|
|
55
|
+
<Image className='my-avatar my-avatar--gray' src={defaultAvatar} mode='aspectFill' />
|
|
56
|
+
</View>
|
|
57
|
+
<View className='my-meta'>
|
|
58
|
+
<View className='my-nickname' onClick={() => navigateToLogin()}>
|
|
59
|
+
未登录
|
|
60
|
+
</View>
|
|
61
|
+
<View className='my-extra'>
|
|
62
|
+
<Text className='my-tips'>点击登录账号</Text>
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
</View>
|
|
66
|
+
)}
|
|
67
|
+
<View className='my-settings' onClick={() => openAuthenticatedPage('app/settings')}>
|
|
68
|
+
设置
|
|
69
|
+
</View>
|
|
70
|
+
</View>
|
|
71
|
+
|
|
72
|
+
<View className='my-ai-entry' onClick={() => openAuthenticatedPage('app/ai')}>
|
|
73
|
+
<View className='my-ai-entry__icon'>AI</View>
|
|
74
|
+
<View className='my-ai-entry__content'>
|
|
75
|
+
<View className='my-ai-entry__title'>智能助手</View>
|
|
76
|
+
<View className='my-ai-entry__desc'>帮你整理信息、回答问题并处理日常任务</View>
|
|
77
|
+
</View>
|
|
78
|
+
<View className='my-ai-entry__action'>去提问</View>
|
|
79
|
+
</View>
|
|
80
|
+
</ScrollView>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Button, Text, Textarea, View } from '@tarojs/components'
|
|
2
|
+
import { Microphone, Plus, Share } from '@liujitcn/kratos-taro-app-ui'
|
|
3
|
+
import type { AiAttachment } from '../../../../rpc/base/v1/ai_session'
|
|
4
|
+
import './composer.scss'
|
|
5
|
+
|
|
6
|
+
type ComposerProps = {
|
|
7
|
+
value: string
|
|
8
|
+
attachments: AiAttachment[]
|
|
9
|
+
placeholder: string
|
|
10
|
+
bottom: string
|
|
11
|
+
recording: boolean
|
|
12
|
+
sending: boolean
|
|
13
|
+
disabled: boolean
|
|
14
|
+
onChange: (value: string) => void
|
|
15
|
+
onAttach: () => void
|
|
16
|
+
onRecord: () => void
|
|
17
|
+
onSend: () => void
|
|
18
|
+
onRemoveAttachment: (attachment: AiAttachment) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** AI 助手消息输入器。 */
|
|
22
|
+
export default function Composer(props: ComposerProps) {
|
|
23
|
+
return (
|
|
24
|
+
<View className='composer' style={{ paddingBottom: props.bottom }}>
|
|
25
|
+
<View className='composer-main'>
|
|
26
|
+
<Button className='attach-button' hoverClass='none' onClick={props.onAttach}>
|
|
27
|
+
<Plus size={30} color='#111' />
|
|
28
|
+
</Button>
|
|
29
|
+
<View className='composer-card'>
|
|
30
|
+
{props.attachments.length ? (
|
|
31
|
+
<View className='composer-attachments'>
|
|
32
|
+
{props.attachments.map((attachment) => (
|
|
33
|
+
<View
|
|
34
|
+
key={attachment.id || attachment.url || attachment.name}
|
|
35
|
+
className='composer-attachment'
|
|
36
|
+
onClick={() => props.onRemoveAttachment(attachment)}
|
|
37
|
+
>
|
|
38
|
+
<Text>{attachment.name} ×</Text>
|
|
39
|
+
</View>
|
|
40
|
+
))}
|
|
41
|
+
</View>
|
|
42
|
+
) : null}
|
|
43
|
+
<Textarea
|
|
44
|
+
className='composer-input'
|
|
45
|
+
autoHeight
|
|
46
|
+
maxlength={500}
|
|
47
|
+
value={props.value}
|
|
48
|
+
placeholder={props.placeholder}
|
|
49
|
+
placeholderClass='composer-placeholder'
|
|
50
|
+
onInput={(event) => props.onChange(event.detail.value)}
|
|
51
|
+
/>
|
|
52
|
+
<Button
|
|
53
|
+
className={`voice-button${props.recording ? ' active' : ''}`}
|
|
54
|
+
hoverClass='none'
|
|
55
|
+
onClick={props.onRecord}
|
|
56
|
+
>
|
|
57
|
+
<Microphone size={28} color={props.recording ? '#00a96b' : '#111'} />
|
|
58
|
+
</Button>
|
|
59
|
+
</View>
|
|
60
|
+
<Button
|
|
61
|
+
className={`send-button${props.disabled ? ' is-disabled' : ''}${props.sending ? ' is-sending' : ''}`}
|
|
62
|
+
disabled={props.disabled}
|
|
63
|
+
hoverClass='none'
|
|
64
|
+
onClick={props.onSend}
|
|
65
|
+
>
|
|
66
|
+
<Share size={28} color={props.disabled ? '#111' : '#00a96b'} />
|
|
67
|
+
</Button>
|
|
68
|
+
</View>
|
|
69
|
+
</View>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Button, Input, ScrollView, Text, View } from '@tarojs/components'
|
|
2
|
+
import { More, Plus, Search } from '@liujitcn/kratos-taro-app-ui'
|
|
3
|
+
import type { AiSession } from '../../../../rpc/base/v1/ai_session'
|
|
4
|
+
import './session-drawer.scss'
|
|
5
|
+
|
|
6
|
+
type SessionDrawerProps = {
|
|
7
|
+
open: boolean
|
|
8
|
+
topPadding: string
|
|
9
|
+
keyword: string
|
|
10
|
+
loading: boolean
|
|
11
|
+
sessions: AiSession[]
|
|
12
|
+
activeSessionId: string
|
|
13
|
+
onClose: () => void
|
|
14
|
+
onCreate: () => void
|
|
15
|
+
onSelect: (sessionID: string) => void
|
|
16
|
+
onAction: (session: AiSession) => void
|
|
17
|
+
onKeywordChange: (value: string) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatSessionTime(session: AiSession) {
|
|
21
|
+
const seconds = Number(session.updated_at?.seconds ?? 0)
|
|
22
|
+
const nanos = Number(session.updated_at?.nanos ?? 0)
|
|
23
|
+
const timestamp = seconds * 1000 + Math.floor(nanos / 1_000_000)
|
|
24
|
+
if (!timestamp) return ''
|
|
25
|
+
const date = new Date(timestamp)
|
|
26
|
+
const now = new Date()
|
|
27
|
+
const isToday =
|
|
28
|
+
date.getFullYear() === now.getFullYear() &&
|
|
29
|
+
date.getMonth() === now.getMonth() &&
|
|
30
|
+
date.getDate() === now.getDate()
|
|
31
|
+
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
|
32
|
+
const day = `${date.getDate()}`.padStart(2, '0')
|
|
33
|
+
const hour = `${date.getHours()}`.padStart(2, '0')
|
|
34
|
+
const minute = `${date.getMinutes()}`.padStart(2, '0')
|
|
35
|
+
return isToday ? `${hour}:${minute}` : `${month}-${day}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** AI 助手历史会话抽屉。 */
|
|
39
|
+
export default function SessionDrawer(props: SessionDrawerProps) {
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
{props.open ? <View className='session-mask' onClick={props.onClose} /> : null}
|
|
43
|
+
<View
|
|
44
|
+
className={`session-drawer${props.open ? ' is-open' : ''}`}
|
|
45
|
+
style={{ paddingTop: props.topPadding }}
|
|
46
|
+
>
|
|
47
|
+
<View className='session-drawer__head'>
|
|
48
|
+
<View className='session-drawer__title'>历史会话</View>
|
|
49
|
+
<Button className='session-create' hoverClass='none' onClick={props.onCreate}>
|
|
50
|
+
<Plus size={16} color='#27ba9b' />
|
|
51
|
+
<Text>新建</Text>
|
|
52
|
+
</Button>
|
|
53
|
+
</View>
|
|
54
|
+
<View className='session-search'>
|
|
55
|
+
<Search size={16} color='#898b94' />
|
|
56
|
+
<Input
|
|
57
|
+
className='session-search-input'
|
|
58
|
+
confirmType='search'
|
|
59
|
+
placeholder='搜索会话'
|
|
60
|
+
placeholderClass='session-search-placeholder'
|
|
61
|
+
value={props.keyword}
|
|
62
|
+
onInput={(event) => props.onKeywordChange(event.detail.value)}
|
|
63
|
+
/>
|
|
64
|
+
</View>
|
|
65
|
+
<ScrollView className='session-list' scrollY showScrollbar={false}>
|
|
66
|
+
{props.loading ? <View className='session-empty'>正在加载会话...</View> : null}
|
|
67
|
+
{props.sessions.map((session) => (
|
|
68
|
+
<View
|
|
69
|
+
key={session.id}
|
|
70
|
+
className={`session-item${session.id === props.activeSessionId ? ' is-active' : ''}`}
|
|
71
|
+
onClick={() => props.onSelect(session.id)}
|
|
72
|
+
onLongPress={() => props.onAction(session)}
|
|
73
|
+
>
|
|
74
|
+
<View className='session-content'>
|
|
75
|
+
<View className='session-row'>
|
|
76
|
+
<View className='session-title'>{session.title}</View>
|
|
77
|
+
<View className='session-time'>{formatSessionTime(session)}</View>
|
|
78
|
+
</View>
|
|
79
|
+
<View className='session-summary'>{session.summary || '暂无摘要'}</View>
|
|
80
|
+
</View>
|
|
81
|
+
<Button
|
|
82
|
+
className='session-more'
|
|
83
|
+
hoverClass='none'
|
|
84
|
+
onClick={(event) => {
|
|
85
|
+
event.stopPropagation()
|
|
86
|
+
props.onAction(session)
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<More size={22} color='#9ca3af' />
|
|
90
|
+
</Button>
|
|
91
|
+
</View>
|
|
92
|
+
))}
|
|
93
|
+
{!props.loading && !props.sessions.length ? (
|
|
94
|
+
<View className='session-empty'>没有匹配的会话</View>
|
|
95
|
+
) : null}
|
|
96
|
+
</ScrollView>
|
|
97
|
+
</View>
|
|
98
|
+
</>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Button, Text, View } from '@tarojs/components'
|
|
2
|
+
import { ArrowRight, Refresh } from '@liujitcn/kratos-taro-app-ui'
|
|
3
|
+
import type { AiShortcut } from '../../../../rpc/base/v1/ai_tool'
|
|
4
|
+
import './welcome-panel.scss'
|
|
5
|
+
|
|
6
|
+
type WelcomePanelProps = {
|
|
7
|
+
greetingMessage: string
|
|
8
|
+
loading: boolean
|
|
9
|
+
shortcuts: AiShortcut[]
|
|
10
|
+
canRefresh: boolean
|
|
11
|
+
onRefresh: () => void
|
|
12
|
+
onShortcutTap: (shortcut: AiShortcut) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** AI 助手空会话欢迎面板。 */
|
|
16
|
+
export default function WelcomePanel(props: WelcomePanelProps) {
|
|
17
|
+
return (
|
|
18
|
+
<View className='welcome-panel'>
|
|
19
|
+
<View className='welcome-row is-hello'>
|
|
20
|
+
<View className='ai-avatar'>
|
|
21
|
+
<View className='ai-avatar__halo' />
|
|
22
|
+
<View className='ai-avatar__hair-back' />
|
|
23
|
+
<View className='ai-avatar__face'>
|
|
24
|
+
<View className='ai-avatar__bang' />
|
|
25
|
+
<View className='ai-avatar__eyes'><View /><View /></View>
|
|
26
|
+
<View className='ai-avatar__blush is-left' />
|
|
27
|
+
<View className='ai-avatar__blush is-right' />
|
|
28
|
+
<View className='ai-avatar__smile' />
|
|
29
|
+
</View>
|
|
30
|
+
<View className='ai-avatar__hair-side is-left' />
|
|
31
|
+
<View className='ai-avatar__hair-side is-right' />
|
|
32
|
+
<View className='ai-avatar__body' />
|
|
33
|
+
<View className='ai-avatar__bow' />
|
|
34
|
+
<View className='ai-avatar__spark' />
|
|
35
|
+
</View>
|
|
36
|
+
<View className='welcome-bubble is-hello'>您好,AI助手为您服务!</View>
|
|
37
|
+
</View>
|
|
38
|
+
<View className='welcome-bubble is-intro'>{props.greetingMessage}</View>
|
|
39
|
+
<View className='prompt-card'>
|
|
40
|
+
<View className='prompt-card__head'>
|
|
41
|
+
<View>
|
|
42
|
+
<View className='prompt-card__eyebrow'>快捷操作</View>
|
|
43
|
+
<View className='prompt-card__title'>您可以这样问</View>
|
|
44
|
+
</View>
|
|
45
|
+
{props.canRefresh ? (
|
|
46
|
+
<Button className='prompt-refresh' hoverClass='none' onClick={props.onRefresh}>
|
|
47
|
+
<Text>换一换</Text><Refresh size={25} color='#00a96b' />
|
|
48
|
+
</Button>
|
|
49
|
+
) : null}
|
|
50
|
+
</View>
|
|
51
|
+
{props.loading ? <View className='prompt-loading'>正在加载...</View> : null}
|
|
52
|
+
{!props.loading && !props.shortcuts.length ? (
|
|
53
|
+
<View className='prompt-loading'>暂无可用快捷助手</View>
|
|
54
|
+
) : null}
|
|
55
|
+
{!props.loading
|
|
56
|
+
? props.shortcuts.map((shortcut, index) => (
|
|
57
|
+
<Button
|
|
58
|
+
key={shortcut.key || shortcut.title}
|
|
59
|
+
className='prompt-item'
|
|
60
|
+
hoverClass='none'
|
|
61
|
+
onClick={() => props.onShortcutTap(shortcut)}
|
|
62
|
+
>
|
|
63
|
+
<Text className='prompt-index'>{index + 1}</Text>
|
|
64
|
+
<View className='prompt-content'>
|
|
65
|
+
<Text className='prompt-text'>{shortcut.title}</Text>
|
|
66
|
+
<Text className='prompt-meta'>{shortcut.group || '通用助手'}</Text>
|
|
67
|
+
</View>
|
|
68
|
+
<ArrowRight size={20} color='#9aa0aa' />
|
|
69
|
+
</Button>
|
|
70
|
+
))
|
|
71
|
+
: null}
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
.attach-button,
|
|
2
|
+
.voice-button,
|
|
3
|
+
.send-button {
|
|
4
|
+
padding: 0;
|
|
5
|
+
margin: 0;
|
|
6
|
+
border-radius: 0;
|
|
7
|
+
background: transparent;
|
|
8
|
+
line-height: normal;
|
|
9
|
+
|
|
10
|
+
&::after { border: none; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.composer {
|
|
14
|
+
flex-shrink: 0;
|
|
15
|
+
width: 100%;
|
|
16
|
+
padding: 14px 24px 18px;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
background-color: transparent;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.composer-main {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 16px;
|
|
26
|
+
min-height: 124px;
|
|
27
|
+
padding: 22px 18px;
|
|
28
|
+
border-radius: 24px;
|
|
29
|
+
background-color: #fff;
|
|
30
|
+
box-shadow: 0 12px 34px rgba(15, 23, 42, 0.08);
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.attach-button {
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
width: 72px;
|
|
40
|
+
height: 72px;
|
|
41
|
+
border: 2px solid #d6dae2;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
background-color: #fff;
|
|
44
|
+
box-shadow: none;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.composer-card {
|
|
49
|
+
flex: 1;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: 10px;
|
|
55
|
+
min-height: 72px;
|
|
56
|
+
padding: 0 12px 0 28px;
|
|
57
|
+
border: 2px solid #d6dae2;
|
|
58
|
+
border-radius: 38px;
|
|
59
|
+
background-color: #fff;
|
|
60
|
+
box-shadow: none;
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.composer-attachments {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
gap: 8px;
|
|
68
|
+
width: 100%;
|
|
69
|
+
padding-top: 4px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.composer-attachment {
|
|
73
|
+
max-width: 240px;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
text-overflow: ellipsis;
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
padding: 6px 12px;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
color: #16806d;
|
|
80
|
+
font-size: 20px;
|
|
81
|
+
line-height: 28px;
|
|
82
|
+
background-color: #e8f8f4;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.composer-input {
|
|
86
|
+
flex: 1;
|
|
87
|
+
min-width: 0;
|
|
88
|
+
min-height: 42px;
|
|
89
|
+
max-height: 126px;
|
|
90
|
+
padding: 14px 0;
|
|
91
|
+
box-sizing: border-box;
|
|
92
|
+
color: #333;
|
|
93
|
+
font-size: 29px;
|
|
94
|
+
line-height: 42px;
|
|
95
|
+
overflow-y: auto;
|
|
96
|
+
background-color: transparent;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.composer-placeholder { color: #8d929c; font-size: 29px; }
|
|
100
|
+
|
|
101
|
+
.voice-button,
|
|
102
|
+
.send-button {
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
border-radius: 50%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.voice-button { width: 60px; height: 60px; background-color: transparent; }
|
|
111
|
+
.voice-button.active { background-color: #e8f8f4; }
|
|
112
|
+
.send-button { width: 74px; height: 74px; background-color: #f2f2f2; }
|
|
113
|
+
.send-button.is-disabled { background-color: #f2f2f2; }
|
|
114
|
+
.send-button:not(.is-disabled) { background-color: #e7f7f2; }
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
.session-create,
|
|
2
|
+
.session-more {
|
|
3
|
+
padding: 0;
|
|
4
|
+
margin: 0;
|
|
5
|
+
border-radius: 0;
|
|
6
|
+
background: transparent;
|
|
7
|
+
line-height: normal;
|
|
8
|
+
&::after { border: none; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.session-mask {
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: 0;
|
|
14
|
+
right: 0;
|
|
15
|
+
bottom: 0;
|
|
16
|
+
left: 560px;
|
|
17
|
+
z-index: 20;
|
|
18
|
+
background-color: rgba(0, 0, 0, 0.18);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.session-drawer {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
position: absolute;
|
|
25
|
+
top: 0;
|
|
26
|
+
left: 0;
|
|
27
|
+
bottom: 0;
|
|
28
|
+
z-index: 21;
|
|
29
|
+
width: 560px;
|
|
30
|
+
padding: 0 24px 36px;
|
|
31
|
+
background-color: #fff;
|
|
32
|
+
box-shadow: none;
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
transform: translateX(-100%);
|
|
35
|
+
transition: transform 0.2s ease;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.session-drawer.is-open {
|
|
39
|
+
box-shadow: 24px 0 60px rgba(0, 0, 0, 0.12);
|
|
40
|
+
transform: translateX(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.session-drawer__head {
|
|
44
|
+
flex-shrink: 0;
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
min-height: 76px;
|
|
49
|
+
margin-bottom: 18px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.session-drawer__title { color: #333; font-size: 32px; font-weight: 700; line-height: 40px; }
|
|
53
|
+
|
|
54
|
+
.session-create {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
gap: 4px;
|
|
59
|
+
width: 112px;
|
|
60
|
+
height: 52px;
|
|
61
|
+
border-radius: 8px;
|
|
62
|
+
color: #27ba9b;
|
|
63
|
+
font-size: 24px;
|
|
64
|
+
line-height: 52px;
|
|
65
|
+
background-color: #e8f8f4;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.session-search {
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 10px;
|
|
73
|
+
height: 64px;
|
|
74
|
+
padding: 0 18px;
|
|
75
|
+
margin-bottom: 18px;
|
|
76
|
+
border-radius: 10px;
|
|
77
|
+
background-color: #f6f7f9;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.session-search-input { flex: 1; min-width: 0; color: #333; font-size: 24px; }
|
|
82
|
+
.session-search-placeholder { color: #b8bcc5; }
|
|
83
|
+
.session-list { flex: 1; min-height: 0; }
|
|
84
|
+
|
|
85
|
+
.session-item {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 12px;
|
|
89
|
+
padding: 22px 16px 22px 22px;
|
|
90
|
+
border: 1px solid #eee;
|
|
91
|
+
border-radius: 10px;
|
|
92
|
+
background-color: #fff;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.session-item + .session-item { margin-top: 18px; }
|
|
96
|
+
.session-item.is-active { border-color: #c7eee4; background-color: #e8f8f4; }
|
|
97
|
+
.session-content { flex: 1; min-width: 0; }
|
|
98
|
+
.session-row { display: flex; align-items: center; gap: 14px; }
|
|
99
|
+
|
|
100
|
+
.session-title {
|
|
101
|
+
flex: 1;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
overflow: hidden;
|
|
104
|
+
text-overflow: ellipsis;
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
color: #333;
|
|
107
|
+
font-size: 26px;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
line-height: 34px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.session-time { flex-shrink: 0; color: #a6abb3; font-size: 20px; line-height: 30px; }
|
|
113
|
+
|
|
114
|
+
.session-summary {
|
|
115
|
+
margin-top: 10px;
|
|
116
|
+
overflow: hidden;
|
|
117
|
+
text-overflow: ellipsis;
|
|
118
|
+
white-space: nowrap;
|
|
119
|
+
color: #777;
|
|
120
|
+
font-size: 22px;
|
|
121
|
+
line-height: 32px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.session-more {
|
|
125
|
+
flex-shrink: 0;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
width: 56px;
|
|
130
|
+
height: 56px;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
background-color: transparent;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.session-empty { padding: 60px 0; color: #999; font-size: 24px; text-align: center; }
|