PromptTuner: Building a HarmonyOS 6 Prompt Assistant App - Technical Practices
This article shares the complete development journey of PromptTuner, a HarmonyOS 6 native app for prompt discovery, optimization, and management, covering architecture, ArkUI declarative UI patterns, state management, API abstraction, local analytics, privacy compliance, and performance optimization techniques with concrete code examples and metrics.
Project Background
With the proliferation of large language models, AI interaction has become integral to daily work. However, crafting high-quality prompts remains a challenge for ordinary users. PromptTuner was created to address the difficulty of discovering, optimizing, managing, and reusing prompts, turning them from disposable inputs into reusable creative assets.
Technology Selection: Why HarmonyOS 6
Declarative UI Advantage: ArkUI declarative UI combined with ArkTS strong typing significantly improves maintainability and development efficiency for complex interactions compared to imperative UI.
Enhanced System Capabilities: HarmonyOS 6 improvements in system components, SymbolGlyph icons, dark mode, performance, and storage align well with long-term evolution of tool-type apps.
Privacy and Security: System-level permissions and local Preferences sandbox enable a "local-only, no external exposure" data policy, meeting core user privacy demands.
Core Features and Architecture
Five Core Modules
Prompt Square: Categorized/tagged browsing, search, like, collect, report, one-click copy; multi-dimensional filtering by popularity, recency, category.
Prompt Optimization & Diagnosis: Input raw prompt; supports clarification, style enhancement, disambiguation; provides multi-dimensional scoring (completeness, clarity, effectiveness, professionalism) with improvement suggestions via AI.
Prompt Templates: Slot-based parameterized generation; supports local saving of personal templates for quick customized prompt creation.
Learning Center: Tutorial articles and practical tips; supports collection and navigation to related features.
Profile: Unified management of collections, history, personal templates, and learning content; all data stored locally for privacy.
Three-Layer Architecture
UI Layer: ArkTS + ArkUI multi-page app split into Square, Tools, Learning, Profile pages; each manages own state, communicates via routing and parameters.
Service Layer: Includes public UI capabilities (via @xbl/内部基础库 components like TabBar, XPrivacyDialog), network access (PromptApi.ets, ArticleApi.ets, CategoryApi.ets with unified doPost, response format compatibility, error handling), local data and analytics (AnalyticsUtil.ets, StorageUtil.ets based on preferences).
Data Layer: DataModels.ets defines Prompt, Template, Article models; combines rawfile seed data with online APIs for complete data system.
Development Challenges and Solutions
1. Data Security and Privacy Compliance
Challenge: Prompt history, personal templates, learning records are sensitive creative assets. Need local control, transparent permissions, and privacy compliance without complex account system.
Solution:
Adopt HarmonyOS Preferences storage; all user data stays in app sandbox.
Use XPrivacyDialog component at first launch to show privacy agreement, clearly informing data usage.
Design extensible storage structure reserving interfaces for future multi-device sync while keeping MVP fully local.
2. Performance and Stability
Challenge: Square list, search, optimization require frequent network interaction; need to control first-screen load and scroll performance. Analytics and local stats must not block main thread.
Solution:
Implement pagination and lazy loading to limit per-render data volume.
Analytics use in-memory cache + batch write via eventCache; flush when threshold reached, reducing frequent I/O.
Unified API timeout (60s) and robust error handling to prevent page freezes.
3. User Experience Improvements
Challenge: Tool app should minimize operation paths; find/optimize/copy/save within 3 steps. Smooth navigation between Square, Optimization, Learning Center without "lost" feeling.
Solution:
Route parameter passing enables one-click jump from Square detail/Learning article to Tools page with auto-filled prompt content.
Integrate "Create + Optimize + Diagnose" into single CreateAndOptimizePage with tab switching for tool reuse.
Clear feedback at key operations (copy, collect, optimize).
4. Data Sync and Security Considerations
Challenge: MVP uses local data + light backend; plan for future multi-device, multi-account sync evolution. Local storage structure must ensure extensibility.
Solution:
Reserve extension fields in core entity models (Prompt, Template, Article) for future iterations.
Local storage uses JSON serialization for easy migration to cloud or incremental sync.
Analytics data structure designed for cloud statistics compatibility with export interfaces reserved.
5. Engineering Standards and Team Collaboration
Challenge: Ensure consistent code style, avoid "fat pages" and duplicate business logic, improve maintainability.
Solution:
Strict Type Constraints: Ban any / unknown / Record, for...in, optional chaining; enforce interfaces and utility functions for unified logic, boosting type safety.
Directory Layering: Pages/components/utils/models separated by directory; pages only handle UI rendering, business logic pushed to Service layer.
Unified Design System: Centralized Constants/DesignTokens manage resources and styles, ensuring multi-page consistency and easy theming.
HarmonyOS 6 New Features Empowering PromptTuner
1. ArkUI Declarative UI and Multi-Tab App Skeleton
Problem Solved: Traditional imperative UI complicates cross-page state sync and leads to verbose navigation code.
Practice:
Organize pages with @Entry + @Component; MainPage as main container.
Configure four main tabs (Square, Tools, Learning, Profile) via TabBar + TabItem[] using system Symbol icons for unified visual language.
Implement launch-page privacy dialog in Index.ets using ArkUI overlay capability and custom callbacks.
Key Code Implementation:
@Entry
@Component
struct MainPage {
@State selectedTab: string = 'square'
@State optimizePromptParam: string = ''
@State optimizeModeParam: string = ''
private tabs: TabItem[] = [
{ key: 'square', title: '广场', icon: $r('sys.symbol.square_grid_2x2'), selectedIcon: $r('sys.symbol.square_fill_grid_2x2') },
{ key: 'create', title: '工具', icon: $r('sys.symbol.wand_and_stars'), selectedIcon: $r('sys.symbol.wand_and_stars_fill') },
{ key: 'learning', title: '学习', icon: $r('sys.symbol.book_pages'), selectedIcon: $r('sys.symbol.book_pages_fill') },
{ key: 'profile', title: '我的', icon: $r('sys.symbol.person'), selectedIcon: $r('sys.symbol.person_fill') }
]
build() {
Column() {
Stack() {
this.renderContent()
}.layoutWeight(1)
TabBar({
tabs: this.tabs,
selectedTab: this.selectedTab,
selectedColor: $r('app.color.color_primary'),
onTabChange: (key: string) => {
this.selectedTab = key
console.info('切换到标签:', key)
}
})
}.width('100%').height('100%').backgroundColor($r('app.color.surface_background'))
}
}Index Launch Page Privacy Dialog: In aboutToAppear lifecycle, check privacy agreement status via preferences; show dialog if not agreed, navigate to main page after agreement.
aboutToAppear(): void {
this.initializeApp()
}
private async initializeApp(): Promise<void> {
await this.checkPrivacyAgreementStatus()
if (this.hasAgreedPrivacy) {
this.navigateToMain()
}
}
private async checkPrivacyAgreementStatus(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext
const store = await preferences.getPreferences(context, 'aiprompt_privacy_store')
const agreedRaw: Object = await store.get('privacy_agreed', false)
const agreedValue: boolean = (agreedRaw as boolean) === true
this.hasAgreedPrivacy = agreedValue
if (!this.hasAgreedPrivacy) {
this.showPrivacyDialog = true
console.info('隐私协议未同意,显示隐私弹窗')
} else {
console.info('隐私协议已同意')
}
} catch (error) {
console.error('检查隐私协议状态失败:', JSON.stringify(error))
this.hasAgreedPrivacy = false
this.showPrivacyDialog = true
}
}
private navigateToMain(): void {
router.replaceUrl({ url: RouteConstants.MAIN_PAGE }).catch((error: BusinessError) => {
console.error(`跳转失败: ${error.code}, ${error.message}`)
})
}Result: Clear page structure, smooth tab switching, pluggable startup flow (ready for A/B testing, onboarding).
2. State Management and Cross-Page Parameter Passing
Problem Solved: Parameter loss during tab switching/back navigation; low reuse of optimization tool.
Practice:
Use @State for selectedTab, optimizePromptParam, optimizeModeParam.
Parse route parameters via RouterHelper.getParams() in aboutToAppear and onPageShow for directed tab jumps and parameter backfill when returning from draft list or diagnosis result.
Consolidate "Create + Optimize" into CreateAndOptimizePage as reusable tool container.
Key Code:
aboutToAppear(): void {
const params: ESObject | null = RouterHelper.getParams(this.getUIContext()) as ESObject | null
if (params) {
if (params.selectedTab) this.selectedTab = params.selectedTab as string
if (params.optimizePrompt) this.optimizePromptParam = params.optimizePrompt as string
if (params.optimizeMode) this.optimizeModeParam = params.optimizeMode as string
}
}
onPageShow(): void {
const params: ESObject | null = RouterHelper.getParams(this.getUIContext()) as ESObject | null
if (params) {
if (params.selectedTab) this.selectedTab = params.selectedTab as string
if (params.optimizePrompt) this.optimizePromptParam = params.optimizePrompt as string
if (params.optimizeMode) this.optimizeModeParam = params.optimizeMode as string
}
}
@Builder
renderContent() {
if (this.selectedTab === 'square') SquareListPage()
else if (this.selectedTab === 'create') CreateAndOptimizePage({ optimizePrompt: this.optimizePromptParam, optimizeMode: this.optimizeModeParam })
else if (this.selectedTab === 'learning') LearningCenterPage()
else if (this.selectedTab === 'profile') ProfilePage()
}Result: One-click jump from Square/Learning to Tools tab with prompt and mode parameters, significantly reducing repetitive input.
3. API Capability Abstraction and Prompt Optimization Flow
Problem Solved: Unstable backend response formats (sometimes {code, data}, sometimes direct data); parsing logic scattered across pages.
Practice:
Unified encapsulation of generatePrompt, diagnosePrompt, optimizePrompt, likePrompt in PromptApi.ets.
Detect and compat different response formats (check for code / data / message fields); normalize errors and format at tool layer.
Convert diagnosis dimension objects ( completeness / clarity / effectiveness / professionalism) to array structure for unified UI rendering of score bars or radar charts.
Response Format Compatibility Handling:
static async generatePrompt(params: GeneratePromptParams): Promise<GeneratePromptResponse> {
try {
const response: ESObject = await doPost<ESObject>({
host: ApiConstants.HOST_URL,
url: `${ApiConstants.API_PREFIX}/generate`,
data: { requirement: params.requirement },
timeout: 60000
})
let responseData: ESObject = response
const hasCode = (responseData as ESObject).code !== undefined
const hasData = (responseData as ESObject).data !== undefined
const hasMessage = (responseData as ESObject).message !== undefined
if (hasCode && hasData && hasMessage) {
const apiResponse = responseData as ApiResponse<GeneratePromptResponse>
if (apiResponse.code !== 0 || !apiResponse.data) throw new Error(apiResponse.message || '生成失败')
return apiResponse.data
}
const hasPrompt = (responseData as ESObject).prompt !== undefined
const hasTitle = (responseData as ESObject).title !== undefined
if (hasPrompt && hasTitle) return responseData as GeneratePromptResponse
throw new Error('响应格式不正确:缺少必要字段')
} catch (error) {
console.error('generatePrompt error:', JSON.stringify(error))
if (error instanceof Error) throw error
throw new Error('生成提示词失败,请稍后重试')
}
}Diagnosis Result Data Transformation:
const dimensions: DiagnoseDimension[] = [
{ name: 'completeness', score: data.dimensions.completeness, maxScore: 25, label: '完整性' },
{ name: 'clarity', score: data.dimensions.clarity, maxScore: 25, label: '清晰度' },
{ name: 'effectiveness', score: data.dimensions.effectiveness, maxScore: 25, label: '有效性' },
{ name: 'professionalism', score: data.dimensions.professionalism, maxScore: 25, label: '专业性' }
]Result: Page layer focuses only on business logic and UI; duplicate error handling and data conversion code greatly reduced; future API adjustments lower cost.
4. Local Analytics and User Behavior Analysis
Problem Solved: Need quick MVP validation without heavy analytics SDK.
Practice:
Define EventType, EventRecord, AnalyticsData in AnalyticsUtil.ets; store event list locally via preferences.
Control batch writes with eventCache + MAX_CACHE_SIZE to reduce frequent I/O; set MAX_EVENTS for local rate limiting.
Provide high-level wrappers ( trackPageView, trackSearch, trackCopy, trackFavorite, trackLike, trackOptimize, trackTemplateGenerate, trackReport) for easy PRD-driven instrumentation.
Event Recording and Cache Mechanism:
static async trackEvent(
eventType: EventType,
eventName: string,
targetId: string,
targetType: string,
properties?: ESObject
): Promise<void> {
if (!AnalyticsUtil.dataPreferences) { console.warn('AnalyticsUtil not initialized'); return }
const event: EventRecord = {
eventType, eventName, targetId, targetType,
properties: properties ? JSON.stringify(properties) : '{}',
timestamp: new Date().toISOString()
}
AnalyticsUtil.eventCache.push(event)
if (AnalyticsUtil.eventCache.length >= AnalyticsUtil.MAX_CACHE_SIZE) {
await AnalyticsUtil.flushEvents()
}
}Batch Flush Mechanism:
static async flushEvents(): Promise<void> {
if (!AnalyticsUtil.dataPreferences || AnalyticsUtil.eventCache.length === 0) return
try {
const existingEvents = await AnalyticsUtil.getEvents()
let index = 0
const cacheLength = AnalyticsUtil.eventCache.length
while (index < cacheLength) {
existingEvents.push(AnalyticsUtil.eventCache[index])
index++
}
let finalEvents = existingEvents
if (existingEvents.length > AnalyticsUtil.MAX_EVENTS) {
const startIndex = existingEvents.length - AnalyticsUtil.MAX_EVENTS
finalEvents = []
let i = startIndex
while (i < existingEvents.length) { finalEvents.push(existingEvents[i]); i++ }
}
await AnalyticsUtil.dataPreferences.put('events', JSON.stringify(finalEvents))
await AnalyticsUtil.dataPreferences.flush()
AnalyticsUtil.eventCache = []
console.info(`Flushed ${cacheLength} events to storage`)
} catch (error) { console.error('Failed to flush events:', JSON.stringify(error)) }
}Result: Complete user behavior recording even offline/weak network; exportable for analysis, providing quantitative basis for iteration.
5. Security and Privacy: Privacy Dialog and Fine-Grained Permission Management
Problem Solved: Users sensitive to privacy terms on first use; harsh experience causes churn.
Practice:
Use XPrivacyDialog component for consistent privacy dialog copy and buttons, matching system style.
In Index.ets, read privacy_agreed flag via preferences.getPreferences; fault-tolerant (show dialog on read failure).
On "View Agreement/Privacy Policy" click, generate dark-mode-adapted web link via getUrlWithDarkMode and navigate to common CommonWebPage via router.pushUrl.
Privacy Dialog Display and Interaction:
if (this.showPrivacyDialog) {
XPrivacyDialog({
appName: this.appName,
detailMode: XPrivacyDetailMode.CUSTOM,
onOpenDetail: (type: XPrivacyOpenType) => {
if (type === XPrivacyOpenType.USER) {
const userAgreementUrl = getUrlWithDarkMode(getContext(this) as common.UIAbilityContext, PrivacyConstants.USER_AGREEMENT_URL)
router.pushUrl({ url: RouteConstants.COMMON_WEB, params: { url: userAgreementUrl, title: '用户协议' } })
} else {
const privacyPolicyUrl = getUrlWithDarkMode(getContext(this) as common.UIAbilityContext, PrivacyConstants.PRIVACY_POLICY_URL)
router.pushUrl({ url: RouteConstants.COMMON_WEB, params: { url: privacyPolicyUrl, title: '隐私政策' } })
}
},
onAgree: async () => {
try {
const context = getContext(this) as common.UIAbilityContext
const store = await preferences.getPreferences(context, 'aiprompt_privacy_store')
await store.put('privacy_agreed', true)
await store.flush()
this.showPrivacyDialog = false
this.hasAgreedPrivacy = true
console.info('隐私协议已同意,继续应用初始化')
this.navigateToMain()
} catch (error) { console.error('保存隐私协议状态失败:', JSON.stringify(error)) }
},
onDecline: () => { console.info('用户拒绝隐私协议'); /* show prompt but not force exit */ }
})
}Result: Improved privacy agreement acceptance rate; clearer user awareness of data usage; foundation for future advanced permissions (network, clipboard).
6. Design and Theming: Consistent Design Tokens and Dark Mode Adaptation
Problem Solved: Visual fragmentation across multi-page, multi-component (colors, spacing, radii).
Practice:
Centralize Spacing, Radius, color resources in DesignTokens.ets and resource files; reference via $r('app.color.*') with dark mode support.
Common components (Card, Button, Tag) uniformly use these tokens, ensuring consistency across Square, Tools, Learning, Profile pages.
DesignTokens Definition:
export class Spacing {
static readonly XS: number = 4; static readonly SM: number = 8; static readonly MD: number = 16;
static readonly LG: number = 24; static readonly XL: number = 32; static readonly XXL: number = 48;
static readonly xs: number = 4; static readonly sm: number = 8; static readonly md: number = 16;
static readonly lg: number = 24; static readonly xl: number = 32; static readonly xxl: number = 48;
}
export class Radius {
static readonly SM: number = 8; static readonly MD: number = 12; static readonly LG: number = 16; static readonly XL: number = 20;
static readonly sm: number = 8; static readonly md: number = 12; static readonly lg: number = 16; static readonly xl: number = 20;
}Common Component Using DesignTokens:
build() {
Column() {
if (this.content) this.content()
}.width('100%').padding(Spacing.MD).backgroundColor($r('app.color.card_background')).borderRadius(Radius.MD)
.onClick(() => { if (this.onCardClick) this.onCardClick() })
}Result: Semantic layer between design and implementation; global theming or visual tweaks via token adjustments only.
7. Performance Optimization Strategies (List and Network Scenarios)
Typical Scenarios: Square list/search (large-scale prompt card scrolling); Learning Center article list/detail (long list + rich text).
Practice:
Pagination + lazy loading to reduce one-time render pressure.
Unified reasonable timeout and error handling at API layer to avoid "stuck in loading".
Decouple analytics writes from business requests; batch flush via cache to lower frequent I/O.
Result: Smooth scrolling and stable response on mid-to-low-end devices; performance headroom reserved for future AI capabilities.
Results Review: Development Efficiency and Performance Gains
1. Development Efficiency and Architecture Benefits
Component Library Reuse: Leveraging HarmonyOS 6 ArkUI and @xbl/内部基础库, core capabilities (home skeleton, tab structure, privacy dialog) built rapidly, saving ~40% development time vs. from-scratch. Unified API encapsulation ( PromptApi, ArticleApi) and Analytics utils raised code reuse to 70%+.
Architecture Maintainability: Three-layer design clarifies responsibilities; new features only touch relevant layer, reducing coupling. Centralized error handling and data conversion in API layer cuts maintenance cost ~50%.
2. Performance and Experience Optimization Outcomes
First-Screen Load: Lazy loading, pagination, local caching keep Square list first-screen under 1.5s (meets PRD target). Launch privacy check async, no impact on startup speed.
Interaction Experience: Tab switching state-driven, response <100ms. Jump from Learning/Square to Tools reduced from 5 steps to 2 via parameter passing. Core operations (copy/collect/optimize) within 2-3 steps, aligning with tool-app best practices.
Stability: Analytics batch write cuts I/O frequency 80%, no main-thread impact under high-frequency ops. List scrolling maintains 60fps on mid-to-low-end devices after optimization.
3. User Feedback (Planned)
Collect feedback on integrated "find + optimize + template" experience.
Survey user perception of local storage, privacy, data control.
Track core feature usage and retention (post-launch).
Lessons Learned: Reflections and Takeaways
1. HarmonyOS 6 Development Experience Changes
Strong Type System Benefits: ArkTS strong typing and interface constraints (explicit structures in PromptApi, DataModels) drastically reduced type errors; ~60% potential bugs caught at compile time. Clear interfaces improved readability, cutting new-member onboarding ~30%.
Declarative UI Advantages: ArkUI declarative UI with lifecycle hooks ( aboutToAppear, onPageShow) clarifies unidirectional "data → UI" flow, simplifying state management. Declarative approach reduces ~40% boilerplate vs. imperative UI.
2. Declarative UI Development Best Practices
State Management: @State / @Builder combo makes tab content switching logic clear and testable; state-driven UI updates avoid manual DOM complexity. Abstract common UI capabilities into components ( CommonButton, CommonCard, CommonTag) for unified style and reduced style drift; component reuse >80%.
Code Organization: Keep build methods in key pages (Index, MainPage) pure UI description; side effects (data loading, state init) in lifecycle or utils. Leads to cleaner page code, easier testing, lower maintenance.
3. Cross-Device Considerations for Local-First Tool Apps
Current Architecture Extensibility: Phone-first but designed for multi-device. HarmonyOS multi-device traits enable planning tablet/PC multi-column layouts and richer editing. Decoupled Data and Service layers allow UI adaptation per form factor while keeping business logic unchanged.
Future Expansion: Multi-device collaboration for "phone collect, tablet deep edit" via distributed soft bus for seamless data/task flow. Explore large-screen multi-window (e.g., left prompt list + right optimization tool side-by-side).
4. Expectations for HarmonyOS Ecosystem
System-Level AI Capabilities: Deeper integration with system clipboard, notes, browser for PromptTuner to fit user workflows. More advanced capabilities within permission boundaries to expand developer imagination.
Ecosystem Building: More general component libraries (like @xbl/内部基础库) to lower tool-app scaffolding barrier, letting developers focus on business innovation. More technical sharing and best practices for a virtuous developer community.
Future Roadmap: PromptTuner Evolution
1. Feature Iteration Plan
Continuously improve prompt search (intent understanding, semantic similarity retrieval), expand to multi-language and multi-dimensional optimization.
Strengthen template management and collaboration (multi-level categorization, team sharing, permission control) for enterprise and personal efficiency.
2. HarmonyOS New Feature Tracking
Monitor new ArkUI components, adapt more terminal forms (large screen, wearable, PC) with flexible layouts and responsive design for multi-window, multi-device collaboration.
Deeply explore cross-device task flow and distributed soft bus for seamless prompt content and creation task flow across phone, tablet, PC, even vehicle infotainment.
3. Ecosystem Integration and Open Cooperation
Proactively connect with HarmonyOS ecosystem learning, creation, office, notes, input method apps for cross-app content invocation and convenient linkage (e.g., one-click optimize notes/browser content into high-quality prompts).
Explore deep integration with system clipboard, quick apps, voice assistant for more native entry points and intelligent distribution.
Strengthen integration with cloud LLM services, enterprise knowledge bases, open platforms to foster knowledge sharing and model capability complement, providing richer innovation tools for individuals and enterprises.
Conclusion: Technology Empowering Efficient Creation
PromptTuner originated from the question of how to make AI better serve users. Through HarmonyOS 6 capabilities, abstract prompt engineering concepts were transformed into tangible, usable product features, boosting AI interaction efficiency.
PromptTuner is more than a tool; it's a bridge between users and AI. Via HarmonyOS native experience, it provides a one-stop space for "find prompts, use prompts, learn prompts" where users can discover quality templates, continuously improve prompt quality via diagnosis/optimization, systematically master prompt engineering, and turn excellent prompts into personal reusable assets. This closed loop converts prompts from disposable consumables into reusable creative assets.
Technology is a means, not an end. PromptTuner's value lies in leveraging HarmonyOS 6 to ground abstract prompt engineering into perceivable functions and interactions. Good technology should "stay behind the scenes," letting users focus on creation, not technical details. Throughout development, user experience remained central; declarative UI, state management, performance optimization crafted a smooth, stable, easy-to-use product. This "technology serves creation" philosophy guides PromptTuner's continuous iteration.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
51CTO HarmonyOS Developer Community
The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
