This commit is contained in:
Thomas Marchand
2025-12-17 14:18:38 +00:00
parent bf15c67c96
commit 932f463f0d
4 changed files with 87 additions and 167 deletions

View File

@@ -16,11 +16,13 @@ struct ControlView: View {
@State private var isLoading = true
@State private var streamTask: Task<Void, Never>?
@State private var showMissionMenu = false
@State private var shouldScrollToBottom = false
@FocusState private var isInputFocused: Bool
private let api = APIService.shared
private let nav = NavigationState.shared
private let bottomAnchorId = "bottom-anchor"
var body: some View {
ZStack {
@@ -169,6 +171,11 @@ struct ControlView: View {
.id(message.id)
}
}
// Bottom anchor for scrolling past last message
Color.clear
.frame(height: 1)
.id(bottomAnchorId)
}
.padding()
}
@@ -177,15 +184,23 @@ struct ControlView: View {
isInputFocused = false
}
.onChange(of: messages.count) { _, _ in
if let lastMessage = messages.last {
withAnimation {
proxy.scrollTo(lastMessage.id, anchor: .bottom)
}
scrollToBottom(proxy: proxy)
}
.onChange(of: shouldScrollToBottom) { _, shouldScroll in
if shouldScroll {
scrollToBottom(proxy: proxy)
shouldScrollToBottom = false
}
}
}
}
private func scrollToBottom(proxy: ScrollViewProxy) {
withAnimation {
proxy.scrollTo(bottomAnchorId, anchor: .bottom)
}
}
private var emptyStateView: some View {
VStack(spacing: 32) {
Spacer()
@@ -313,7 +328,7 @@ struct ControlView: View {
private func loadCurrentMission() async {
isLoading = true
defer { isLoading = false }
do {
if let mission = try await api.getCurrentMission() {
currentMission = mission
@@ -324,6 +339,11 @@ struct ControlView: View {
content: entry.content
)
}
// Scroll to bottom after loading
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
shouldScrollToBottom = true
}
}
} catch {
print("Failed to load mission: \(error)")
@@ -333,7 +353,7 @@ struct ControlView: View {
private func loadMission(id: String) async {
isLoading = true
defer { isLoading = false }
do {
let missions = try await api.listMissions()
if let mission = missions.first(where: { $0.id == id }) {
@@ -346,6 +366,11 @@ struct ControlView: View {
)
}
HapticService.success()
// Scroll to bottom after loading
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
shouldScrollToBottom = true
}
}
} catch {
print("Failed to load mission: \(error)")

View File

@@ -213,6 +213,7 @@ struct FilesView: View {
isEditingPath = false
} label: {
Image(systemName: "xmark.circle.fill")
.font(.title3)
.foregroundStyle(Theme.textMuted)
}
@@ -221,18 +222,20 @@ struct FilesView: View {
isEditingPath = false
} label: {
Image(systemName: "arrow.right.circle.fill")
.font(.title3)
.foregroundStyle(Theme.accent)
}
}
.padding(.horizontal, 16)
} else {
// Breadcrumb path
// Breadcrumb path using / separators
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 2) {
HStack(spacing: 0) {
ForEach(Array(breadcrumbs.enumerated()), id: \.offset) { index, crumb in
if index > 0 {
Image(systemName: "chevron.right")
.font(.caption2.weight(.semibold))
// Add / separator after first element (which is "/"), not before
if index > 1 {
Text("/")
.font(.subheadline.weight(.medium))
.foregroundStyle(Theme.textMuted)
}
@@ -242,30 +245,31 @@ struct FilesView: View {
Text(crumb.name)
.font(.subheadline.weight(index == breadcrumbs.count - 1 ? .semibold : .medium))
.foregroundStyle(index == breadcrumbs.count - 1 ? Theme.textPrimary : Theme.textTertiary)
.padding(.horizontal, 8)
.padding(.horizontal, 4)
.padding(.vertical, 6)
.background(index == breadcrumbs.count - 1 ? Theme.backgroundSecondary : .clear)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
}
}
// Edit button
Button {
editedPath = currentPath
isEditingPath = true
isPathFieldFocused = true
} label: {
Image(systemName: "pencil")
.font(.caption)
.foregroundStyle(Theme.textMuted)
.padding(6)
}
}
.padding(.trailing, 16)
.padding(.trailing, 8)
}
// Edit button - larger tap target
Button {
editedPath = currentPath
isEditingPath = true
isPathFieldFocused = true
HapticService.selectionChanged()
} label: {
Image(systemName: "square.and.pencil")
.font(.body)
.foregroundStyle(Theme.accent)
.frame(width: 44, height: 44)
}
}
}
.padding(.leading, currentPath == "/" && !isEditingPath ? 16 : 0)
.padding(.leading, currentPath == "/" && !isEditingPath ? 12 : 0)
.frame(height: 44)
.background(.thinMaterial)
}

View File

@@ -84,21 +84,37 @@ async function generateIcon(size, outputPath) {
}
}
async function generateIconsForApp(iconDir, filenameMap, sizes) {
if (!fs.existsSync(iconDir)) {
console.log(`⚠ Icon directory not found: ${iconDir}`);
return;
}
console.log(`\nGenerating icons in: ${iconDir}`);
// Generate all icon sizes
for (const size of sizes) {
const filename = filenameMap[size];
if (!filename) {
console.log(`Warning: No filename mapping for size ${size}`);
continue;
}
const outputPath = path.join(iconDir, filename);
await generateIcon(size, outputPath);
}
}
async function main() {
const scriptDir = __dirname;
const projectRoot = path.resolve(scriptDir, '..');
const iconDir = path.join(projectRoot, 'ios_dashboard', 'OpenAgentDashboard', 'Assets.xcassets', 'AppIcon.appiconset');
if (!fs.existsSync(iconDir)) {
console.error(`Error: Icon directory not found: ${iconDir}`);
process.exit(1);
}
console.log(`Generating iOS app icons in: ${iconDir}`);
console.log(`Generating iOS app icons`);
console.log(`Using indigo color: ${INDIGO_COLOR}\n`);
// Map sizes to filenames
const filenameMap = {
// OpenAgentDashboard icons
const dashboardIconDir = path.join(projectRoot, 'ios_dashboard', 'OpenAgentDashboard', 'Assets.xcassets', 'AppIcon.appiconset');
const dashboardFilenameMap = {
20: 'icon-20.png',
29: 'icon-29.png',
40: 'icon-40.png',
@@ -113,18 +129,14 @@ async function main() {
180: 'icon-180.png',
1024: 'icon-1024.png',
};
await generateIconsForApp(dashboardIconDir, dashboardFilenameMap, ICON_SIZES);
// Generate all icon sizes
for (const size of ICON_SIZES) {
const filename = filenameMap[size];
if (!filename) {
console.log(`Warning: No filename mapping for size ${size}`);
continue;
}
const outputPath = path.join(iconDir, filename);
await generateIcon(size, outputPath);
}
// Calorily icons (only 1024x1024)
const calorilyIconDir = path.join(projectRoot, 'calorily', 'Calorily', 'Assets.xcassets', 'AppIcon.appiconset');
const calorilyFilenameMap = {
1024: 'icon_1024.png',
};
await generateIconsForApp(calorilyIconDir, calorilyFilenameMap, [1024]);
console.log('\n✓ All icons generated successfully!');
}

File diff suppressed because one or more lines are too long